diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /cui/source/customize | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'cui/source/customize')
-rw-r--r-- | cui/source/customize/CommandCategoryListBox.cxx | 607 | ||||
-rw-r--r-- | cui/source/customize/CustomNotebookbarGenerator.cxx | 282 | ||||
-rw-r--r-- | cui/source/customize/SvxConfigPageHelper.cxx | 424 | ||||
-rw-r--r-- | cui/source/customize/SvxMenuConfigPage.cxx | 594 | ||||
-rw-r--r-- | cui/source/customize/SvxNotebookbarConfigPage.cxx | 568 | ||||
-rw-r--r-- | cui/source/customize/SvxToolbarConfigPage.cxx | 928 | ||||
-rw-r--r-- | cui/source/customize/acccfg.cxx | 1633 | ||||
-rw-r--r-- | cui/source/customize/cfg.cxx | 3249 | ||||
-rw-r--r-- | cui/source/customize/cfgutil.cxx | 1393 | ||||
-rw-r--r-- | cui/source/customize/eventdlg.cxx | 160 | ||||
-rw-r--r-- | cui/source/customize/eventdlg.hxx | 52 | ||||
-rw-r--r-- | cui/source/customize/macropg.cxx | 717 | ||||
-rw-r--r-- | cui/source/customize/macropg_impl.hxx | 56 |
13 files changed, 10663 insertions, 0 deletions
diff --git a/cui/source/customize/CommandCategoryListBox.cxx b/cui/source/customize/CommandCategoryListBox.cxx new file mode 100644 index 0000000000..f7f3295a92 --- /dev/null +++ b/cui/source/customize/CommandCategoryListBox.cxx @@ -0,0 +1,607 @@ +/* -*- 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/. + * + * 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 <CommandCategoryListBox.hxx> + +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XDispatchInformationProvider.hpp> +#include <com/sun/star/frame/theUICommandDescription.hpp> +#include <com/sun/star/ui/theUICategoryDescription.hpp> +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <com/sun/star/script/browse/theBrowseNodeFactory.hpp> +#include <com/sun/star/script/browse/BrowseNodeFactoryViewTypes.hpp> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +// include search util +#include <com/sun/star/util/SearchFlags.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/textsearch.hxx> + +#include <dialmgr.hxx> +#include <strings.hrc> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/SetFlagContextHelper.hxx> +#include <comphelper/string.hxx> +#include <officecfg/Office/Common.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/searchopt.hxx> +#include <sal/log.hxx> + +#include <cfg.hxx> //for SaveInData + +CommandCategoryListBox::CommandCategoryListBox(std::unique_ptr<weld::ComboBox> xControl) + : pStylesInfo(nullptr) + , m_xControl(std::move(xControl)) +{ + //Initialize search util + m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE; + m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE; + m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE + | css::util::SearchFlags::REG_NOT_ENDOFLINE); +} + +CommandCategoryListBox::~CommandCategoryListBox() { ClearAll(); } + +void CommandCategoryListBox::ClearAll() +{ + // Clear objects from m_aGroupInfo vector to avoid memory leak + for (const auto& It : m_aGroupInfo) + { + if (It->nKind == SfxCfgKind::GROUP_STYLES && It->pObject) + { + SfxStyleInfo_Impl* pStyle = static_cast<SfxStyleInfo_Impl*>(It->pObject); + delete pStyle; + } + else if (It->nKind == SfxCfgKind::FUNCTION_SCRIPT && It->pObject) + { + OUString* pScriptURI = static_cast<OUString*>(It->pObject); + delete pScriptURI; + } + else if (It->nKind == SfxCfgKind::GROUP_SCRIPTCONTAINER && It->pObject) + { + css::uno::XInterface* xi = static_cast<css::uno::XInterface*>(It->pObject); + if (xi != nullptr) + { + xi->release(); + } + } + } + + m_aGroupInfo.clear(); + m_xControl->clear(); +} + +void CommandCategoryListBox::Init(const css::uno::Reference<css::uno::XComponentContext>& xContext, + const css::uno::Reference<css::frame::XFrame>& xFrame, + const OUString& sModuleLongName) +{ + // User will not see incomplete UI + m_xControl->freeze(); + ClearAll(); + + m_xContext = xContext; + m_xFrame = xFrame; + + m_sModuleLongName = sModuleLongName; + m_xGlobalCategoryInfo = css::ui::theUICategoryDescription::get(m_xContext); + m_xModuleCategoryInfo.set(m_xGlobalCategoryInfo->getByName(m_sModuleLongName), + css::uno::UNO_QUERY_THROW); + m_xUICmdDescription = css::frame::theUICommandDescription::get(m_xContext); + + // Support style commands + css::uno::Reference<css::frame::XController> xController; + css::uno::Reference<css::frame::XModel> xModel; + if (xFrame.is()) + xController = xFrame->getController(); + if (xController.is()) + xModel = xController->getModel(); + + m_aStylesInfo.init(sModuleLongName, xModel); + SetStylesInfo(&m_aStylesInfo); + + try + { + css::uno::Reference<css::frame::XDispatchInformationProvider> xProvider( + m_xFrame, css::uno::UNO_QUERY_THROW); + css::uno::Sequence<sal_Int16> lGroups = xProvider->getSupportedCommandGroups(); + + sal_Int32 nGroupsLength = lGroups.getLength(); + + if (nGroupsLength > 0) + { + // Add the category of "All commands" + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_ALLFUNCTIONS, 0)); + m_xControl->append(weld::toId(m_aGroupInfo.back().get()), + CuiResId(RID_CUISTR_ALLFUNCTIONS)); + } + + // Separate the "All commands"category from the actual categories + m_xControl->append_separator(""); + + typedef std::pair<OUString, sal_Int16> str_id; + std::vector<str_id> aCategories; + + // Add the actual categories + for (sal_Int32 i = 0; i < nGroupsLength; ++i) + { + sal_Int16 nGroupID = lGroups[i]; + OUString sGroupID = OUString::number(nGroupID); + OUString sGroupName; + + try + { + m_xModuleCategoryInfo->getByName(sGroupID) >>= sGroupName; + if (sGroupName.isEmpty()) + continue; + } + catch (const css::container::NoSuchElementException&) + { + continue; + } + aCategories.emplace_back(std::make_pair(sGroupName, nGroupID)); + } + + auto const sort = comphelper::string::NaturalStringSorter( + comphelper::getProcessComponentContext(), + Application::GetSettings().GetUILanguageTag().getLocale()); + + std::sort(aCategories.begin(), aCategories.end(), + [&sort](const str_id& a, const str_id& b) { + return sort.compare(a.first, b.first) < 0; + }); + + // Add the actual categories + for (const auto& a : aCategories) + { + const OUString& rGroupName = a.first; + sal_Int16 nGroupID = a.second; + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_FUNCTION, nGroupID)); + m_xControl->append(weld::toId(m_aGroupInfo.back().get()), rGroupName); + } + + // Separate regular commands from styles and macros + m_xControl->append_separator(""); + + // Add macros category + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, nullptr)); + m_xControl->append(weld::toId(m_aGroupInfo.back().get()), CuiResId(RID_CUISTR_MACROS)); + + // Add styles category + //TODO: last param should contain user data? + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_STYLES, 0, nullptr)); + m_xControl->append(weld::toId(m_aGroupInfo.back().get()), + CuiResId(RID_CUISTR_GROUP_STYLES)); + } + catch (const css::uno::RuntimeException&) + { + throw; + } + catch (const css::uno::Exception&) + { + } + + // Reveal the updated UI to user + m_xControl->thaw(); + m_xControl->set_active(0); +} + +void CommandCategoryListBox::FillFunctionsList( + const css::uno::Sequence<css::frame::DispatchInformation>& xCommands, + CuiConfigFunctionListBox* pFunctionListBox, const OUString& filterTerm, + SaveInData* pCurrentSaveInData) +{ + // Setup search filter parameters + m_searchOptions.searchString = filterTerm; + utl::TextSearch textSearch(m_searchOptions); + const bool bInExperimentalMode = officecfg::Office::Common::Misc::ExperimentalMode::get(); + + for (const auto& rInfo : xCommands) + { + auto aProperties + = vcl::CommandInfoProvider::GetCommandProperties(rInfo.Command, m_sModuleLongName); + + OUString sUIName = getCommandName(rInfo.Command); + OUString sLabel = vcl::CommandInfoProvider::GetLabelForCommand(aProperties); + OUString sTooltipLabel + = vcl::CommandInfoProvider::GetTooltipForCommand(rInfo.Command, aProperties, m_xFrame); + OUString sPopupLabel = (vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties)) + .replaceFirst("~", ""); + bool bIsExperimental + = vcl::CommandInfoProvider::IsExperimental(rInfo.Command, m_sModuleLongName); + + // Hide experimental commands when not in experimental mode + bool bHideExperimental = bIsExperimental && !bInExperimentalMode; + + // Apply the search filter + if (bHideExperimental + || (!filterTerm.isEmpty() && !textSearch.searchForward(sUIName) + && !textSearch.searchForward(sLabel) && !textSearch.searchForward(sTooltipLabel) + && !textSearch.searchForward(sPopupLabel))) + { + continue; + } + + css::uno::Reference<css::graphic::XGraphic> xImage; + if (pCurrentSaveInData) + xImage = pCurrentSaveInData->GetImage(rInfo.Command); + + m_aGroupInfo.push_back(std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::FUNCTION_SLOT, 0)); + SfxGroupInfo_Impl* pGrpInfo = m_aGroupInfo.back().get(); + pGrpInfo->sCommand = rInfo.Command; + pGrpInfo->sLabel = sUIName; + pGrpInfo->sTooltip = sTooltipLabel; + pFunctionListBox->append(weld::toId(m_aGroupInfo.back().get()), sUIName, xImage); + } +} + +OUString CommandCategoryListBox::getCommandName(const OUString& sCommand) +{ + OUString sUIName; + try + { + css::uno::Reference<css::container::XNameAccess> xModuleConf; + m_xUICmdDescription->getByName(m_sModuleLongName) >>= xModuleConf; + if (xModuleConf.is()) + { + ::comphelper::SequenceAsHashMap lProps(xModuleConf->getByName(sCommand)); + sUIName = lProps.getUnpackedValueOrDefault("Name", OUString()); + } + } + catch (const css::uno::RuntimeException&) + { + throw; + } + catch (css::uno::Exception&) + { + sUIName.clear(); + } + + // fallback for missing UINames !? + if (sUIName.isEmpty()) + { + sUIName = sCommand; + } + + return sUIName; +} + +void CommandCategoryListBox::categorySelected(CuiConfigFunctionListBox* pFunctionListBox, + const OUString& filterTerm, + SaveInData* pCurrentSaveInData) +{ + SfxGroupInfo_Impl* pInfo = weld::fromId<SfxGroupInfo_Impl*>(m_xControl->get_active_id()); + std::vector<std::unique_ptr<weld::TreeIter>> aNodesToExpand; + pFunctionListBox->freeze(); + pFunctionListBox->ClearAll(); + + switch (pInfo->nKind) + { + case SfxCfgKind::GROUP_ALLFUNCTIONS: + { + css::uno::Reference<css::frame::XDispatchInformationProvider> xProvider( + m_xFrame, css::uno::UNO_QUERY); + sal_Int32 nEntryCount = m_xControl->get_count(); + + for (sal_Int32 nCurPos = 0; nCurPos < nEntryCount; ++nCurPos) + { + SfxGroupInfo_Impl* pCurrentInfo + = weld::fromId<SfxGroupInfo_Impl*>(m_xControl->get_id(nCurPos)); + + if (!pCurrentInfo) //separator + continue; + + if (pCurrentInfo->nKind == SfxCfgKind::GROUP_FUNCTION) + { + css::uno::Sequence<css::frame::DispatchInformation> lCommands; + try + { + lCommands = xProvider->getConfigurableDispatchInformation( + pCurrentInfo->nUniqueID); + FillFunctionsList(lCommands, pFunctionListBox, filterTerm, + pCurrentSaveInData); + } + catch (css::container::NoSuchElementException&) + { + } + } + } + + break; + } + case SfxCfgKind::GROUP_FUNCTION: + { + sal_uInt16 nGroup = pInfo->nUniqueID; + css::uno::Reference<css::frame::XDispatchInformationProvider> xProvider( + m_xFrame, css::uno::UNO_QUERY_THROW); + css::uno::Sequence<css::frame::DispatchInformation> lCommands + = xProvider->getConfigurableDispatchInformation(nGroup); + FillFunctionsList(lCommands, pFunctionListBox, filterTerm, pCurrentSaveInData); + break; + } + case SfxCfgKind::GROUP_SCRIPTCONTAINER: //Macros + { + SAL_INFO("cui.customize", "** ** About to initialise SF Scripts"); + // Add Scripting Framework entries + css::uno::Reference<css::script::browse::XBrowseNode> rootNode; + try + { + css::uno::Reference<css::script::browse::XBrowseNodeFactory> xFac + = css::script::browse::theBrowseNodeFactory::get(m_xContext); + rootNode.set(xFac->createView( + css::script::browse::BrowseNodeFactoryViewTypes::MACROSELECTOR)); + } + catch (css::uno::Exception const&) + { + TOOLS_WARN_EXCEPTION( + "cui.customize", + "Caught some exception whilst retrieving browse nodes from factory"); + // TODO exception handling + } + + if (rootNode.is() && rootNode->hasChildNodes()) + { + //We call acquire on the XBrowseNode so that it does not + //get autodestructed and become invalid when accessed later. + rootNode->acquire(); + + m_aGroupInfo.push_back(std::make_unique<SfxGroupInfo_Impl>( + SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, static_cast<void*>(rootNode.get()))); + + // Add main macro groups + const css::uno::Sequence<css::uno::Reference<css::script::browse::XBrowseNode>> + aChildNodes = rootNode->getChildNodes(); + for (auto const& childGroup : aChildNodes) + { + childGroup->acquire(); + + if (childGroup->hasChildNodes()) + { + OUString sUIName; + if (childGroup->getName() == "user") + { + sUIName = CuiResId(RID_CUISTR_MYMACROS); + } + else if (childGroup->getName() == "share") + { + sUIName = CuiResId(RID_CUISTR_PRODMACROS); + } + else + { + sUIName = childGroup->getName(); + } + + if (sUIName.isEmpty()) + { + continue; + } + + m_aGroupInfo.push_back(std::make_unique<SfxGroupInfo_Impl>( + SfxCfgKind::GROUP_SCRIPTCONTAINER, 0)); + std::unique_ptr<weld::TreeIter> xMacroGroup(pFunctionListBox->tree_append( + weld::toId(m_aGroupInfo.back().get()), sUIName)); + + { + // tdf#128010: Do not nag user asking to enable JRE: if it's disabled, + // simply don't show relevant entries (user chose to not use JRE) + css::uno::ContextLayer layer( + comphelper::NoEnableJavaInteractionContext()); + //Add the children and the grand children + addChildren(xMacroGroup.get(), childGroup, pFunctionListBox, filterTerm, + pCurrentSaveInData, aNodesToExpand); + } + + // Remove the main group if empty + if (!pFunctionListBox->iter_has_child(*xMacroGroup)) + { + pFunctionListBox->remove(*xMacroGroup); + } + else if (!filterTerm.isEmpty()) + { + aNodesToExpand.emplace_back(std::move(xMacroGroup)); + } + } + } + } + + break; + } + case SfxCfgKind::GROUP_STYLES: + { + const std::vector<SfxStyleInfo_Impl> lStyleFamilies = pStylesInfo->getStyleFamilies(); + + for (const auto& pIt : lStyleFamilies) + { + if (pIt.sLabel.isEmpty()) + { + continue; + } + + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_STYLES, 0)); + // pIt.sLabel is Name of the style family + std::unique_ptr<weld::TreeIter> xFuncEntry(pFunctionListBox->tree_append( + weld::toId(m_aGroupInfo.back().get()), pIt.sLabel)); + + const std::vector<SfxStyleInfo_Impl> lStyles = pStylesInfo->getStyles(pIt.sFamily); + + // Setup search filter parameters + m_searchOptions.searchString = filterTerm; + utl::TextSearch textSearch(m_searchOptions); + + // Insert children (styles) + for (const auto& pStyleIt : lStyles) + { + OUString sUIName = pStyleIt.sLabel; + sal_Int32 aStartPos = 0; + sal_Int32 aEndPos = sUIName.getLength(); + + // Apply the search filter + if (!filterTerm.isEmpty() + && !textSearch.SearchForward(sUIName, &aStartPos, &aEndPos)) + { + continue; + } + + SfxStyleInfo_Impl* pStyle = new SfxStyleInfo_Impl(pStyleIt); + + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_STYLES, 0, pStyle)); + + m_aGroupInfo.back()->sCommand = pStyle->sCommand; + m_aGroupInfo.back()->sLabel = pStyle->sLabel; + + pFunctionListBox->append(weld::toId(m_aGroupInfo.back().get()), sUIName, + xFuncEntry.get()); + } + + // Remove the style group from the list if no children + if (!pFunctionListBox->iter_has_child(*xFuncEntry)) + { + pFunctionListBox->remove(*xFuncEntry); + } + else if (!filterTerm.isEmpty()) + { + aNodesToExpand.emplace_back(std::move(xFuncEntry)); + } + } + + break; + } + default: + // Do nothing, the list box will stay empty + SAL_INFO("cui.customize", + "Ignoring unexpected SfxCfgKind: " << static_cast<int>(pInfo->nKind)); + break; + } + + pFunctionListBox->thaw(); + + if (pFunctionListBox->n_children()) + pFunctionListBox->select(0); + + //post freeze + for (const auto& it : aNodesToExpand) + pFunctionListBox->expand_row(*it); +} + +void CommandCategoryListBox::SetStylesInfo(SfxStylesInfo_Impl* pStyles) { pStylesInfo = pStyles; } + +void CommandCategoryListBox::addChildren( + const weld::TreeIter* parentEntry, + const css::uno::Reference<css::script::browse::XBrowseNode>& parentNode, + CuiConfigFunctionListBox* pFunctionListBox, const OUString& filterTerm, + SaveInData* pCurrentSaveInData, std::vector<std::unique_ptr<weld::TreeIter>>& rNodesToExpand) +{ + // Setup search filter parameters + m_searchOptions.searchString = filterTerm; + utl::TextSearch textSearch(m_searchOptions); + + const css::uno::Sequence<css::uno::Reference<css::script::browse::XBrowseNode>> aChildNodes + = parentNode->getChildNodes(); + for (auto const& child : aChildNodes) + { + // Acquire to prevent auto-destruction + child->acquire(); + + if (child->hasChildNodes()) + { + OUString sUIName = child->getName(); + + m_aGroupInfo.push_back(std::make_unique<SfxGroupInfo_Impl>( + SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, static_cast<void*>(child.get()))); + std::unique_ptr<weld::TreeIter> xNewEntry(pFunctionListBox->tree_append( + weld::toId(m_aGroupInfo.back().get()), sUIName, parentEntry)); + + addChildren(xNewEntry.get(), child, pFunctionListBox, filterTerm, pCurrentSaveInData, + rNodesToExpand); + + // Remove the group if empty + if (!pFunctionListBox->iter_has_child(*xNewEntry)) + pFunctionListBox->remove(*xNewEntry); + else + rNodesToExpand.emplace_back(std::move(xNewEntry)); + } + else if (child->getType() == css::script::browse::BrowseNodeTypes::SCRIPT) + { + // Prepare for filtering + OUString sUIName = child->getName(); + sal_Int32 aStartPos = 0; + sal_Int32 aEndPos = sUIName.getLength(); + + // Apply the search filter + if (!filterTerm.isEmpty() && !textSearch.SearchForward(sUIName, &aStartPos, &aEndPos)) + { + continue; + } + + OUString uri, description; + + css::uno::Reference<css::beans::XPropertySet> xPropSet(child, css::uno::UNO_QUERY); + + if (!xPropSet.is()) + { + continue; + } + + css::uno::Any value = xPropSet->getPropertyValue("URI"); + value >>= uri; + + try + { + value = xPropSet->getPropertyValue("Description"); + value >>= description; + } + catch (css::uno::Exception&) + { + // do nothing, the description will be empty + } + + if (description.isEmpty()) + { + description = CuiResId(RID_CUISTR_NOMACRODESC); + } + + OUString* pScriptURI = new OUString(uri); + + css::uno::Reference<css::graphic::XGraphic> xImage; + if (pCurrentSaveInData) + xImage = pCurrentSaveInData->GetImage(uri); + + m_aGroupInfo.push_back( + std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::FUNCTION_SCRIPT, 0, pScriptURI)); + m_aGroupInfo.back()->sCommand = uri; + m_aGroupInfo.back()->sLabel = sUIName; + m_aGroupInfo.back()->sHelpText = description; + pFunctionListBox->append(weld::toId(m_aGroupInfo.back().get()), sUIName, xImage, + parentEntry); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/customize/CustomNotebookbarGenerator.cxx b/cui/source/customize/CustomNotebookbarGenerator.cxx new file mode 100644 index 0000000000..ba5d881822 --- /dev/null +++ b/cui/source/customize/CustomNotebookbarGenerator.cxx @@ -0,0 +1,282 @@ +/* -*- 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/. + * + * 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 <rtl/bootstrap.hxx> +#include <config_folders.h> +#include <CustomNotebookbarGenerator.hxx> +#include <osl/file.hxx> +#include <osl/thread.h> +#include <vcl/EnumContext.hxx> +#include <vcl/settings.hxx> +#include <sfx2/viewfrm.hxx> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <unotools/confignode.hxx> +#include <libxml/parser.h> +#include <o3tl/string_view.hxx> + +#define aUIPropertiesCount 3 + +using namespace css; + +CustomNotebookbarGenerator::CustomNotebookbarGenerator() {} + +static OUString lcl_activeAppName(vcl::EnumContext::Application eApp) +{ + switch (eApp) + { + case vcl::EnumContext::Application::Writer: + return "ActiveWriter"; + case vcl::EnumContext::Application::Calc: + return "ActiveCalc"; + case vcl::EnumContext::Application::Impress: + return "ActiveImpress"; + case vcl::EnumContext::Application::Draw: + return "ActiveDraw"; + default: + return OUString(); + } +} + +static OUString lcl_getAppName(vcl::EnumContext::Application eApp) +{ + switch (eApp) + { + case vcl::EnumContext::Application::Writer: + return "Writer"; + case vcl::EnumContext::Application::Calc: + return "Calc"; + case vcl::EnumContext::Application::Impress: + return "Impress"; + case vcl::EnumContext::Application::Draw: + return "Draw"; + default: + return OUString(); + } +} + +static OUString getAppNameRegistryPath() +{ + vcl::EnumContext::Application eApp = vcl::EnumContext::Application::Any; + + if (SfxViewFrame* pViewFrame = SfxViewFrame::Current()) + { + const Reference<frame::XFrame>& xFrame = pViewFrame->GetFrame().GetFrameInterface(); + const Reference<frame::XModuleManager> xModuleManager + = frame::ModuleManager::create(::comphelper::getProcessComponentContext()); + eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xFrame)); + } + + OUString sAppName(lcl_getAppName(eApp)); + return "org.openoffice.Office.UI.ToolbarMode/Applications/" + sAppName; +} + +static OUString customizedUIPathBuffer() +{ + OUString sDirPath("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( + "bootstrap") ":UserInstallation}/user/config/soffice.cfg/"); + rtl::Bootstrap::expandMacros(sDirPath); + return sDirPath; +} + +OUString CustomNotebookbarGenerator::getCustomizedUIPath() +{ + OUString sAppName, sNotebookbarUIFileName; + CustomNotebookbarGenerator::getFileNameAndAppName(sAppName, sNotebookbarUIFileName); + return customizedUIPathBuffer() + "modules/s" + sAppName.toAsciiLowerCase() + "/ui/" + + sNotebookbarUIFileName; +} + +OUString CustomNotebookbarGenerator::getOriginalUIPath() +{ + OUString sAppName, sNotebookbarUIFileName; + CustomNotebookbarGenerator::getFileNameAndAppName(sAppName, sNotebookbarUIFileName); + return AllSettings::GetUIRootDir() + "modules/s" + sAppName.toAsciiLowerCase() + "/ui/" + + sNotebookbarUIFileName; +} + +static OUString getUIDirPath() +{ + OUString sAppName, sNotebookbarUIFileName; + CustomNotebookbarGenerator::getFileNameAndAppName(sAppName, sNotebookbarUIFileName); + OUString sUIDirPath + = customizedUIPathBuffer() + "modules/s" + sAppName.toAsciiLowerCase() + "/ui/"; + return sUIDirPath; +} + +OString CustomNotebookbarGenerator::getSystemPath(OUString const& sURL) +{ + if (sURL.isEmpty()) + return OString(); + OUString sSystemPathSettings; + if (osl_getSystemPathFromFileURL(sURL.pData, &sSystemPathSettings.pData) != osl_File_E_None) + { + SAL_WARN("cui.customnotebookbar", "Cannot get system path for :" << sURL); + return OString(); + } + OString osSystemPathSettings + = OUStringToOString(sSystemPathSettings, osl_getThreadTextEncoding()); + return osSystemPathSettings; +} + +static void changeNodeValue(xmlNode* pNodePtr, const char* pProperty, const char* pValue) +{ + pNodePtr = pNodePtr->xmlChildrenNode; + while (pNodePtr) + { + if (!(xmlStrcmp(pNodePtr->name, reinterpret_cast<const xmlChar*>("property")))) + { + xmlChar* UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("name")); + if (!(xmlStrcmp(UriValue, reinterpret_cast<const xmlChar*>(pProperty)))) + xmlNodeSetContent(pNodePtr, reinterpret_cast<const xmlChar*>(pValue)); + xmlFree(UriValue); + break; + } + pNodePtr = pNodePtr->next; + } +} + +static void searchNodeAndAttribute(xmlNode* pNodePtr, const char* pUIItemID, const char* pProperty, + const char* pValue) +{ + pNodePtr = pNodePtr->xmlChildrenNode; + while (pNodePtr) + { + if (pNodePtr->type == XML_ELEMENT_NODE) + { + if (!(xmlStrcmp(pNodePtr->name, reinterpret_cast<const xmlChar*>("object")))) + { + xmlChar* UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("id")); + if (!(xmlStrcmp(UriValue, reinterpret_cast<const xmlChar*>(pUIItemID)))) + changeNodeValue(pNodePtr, pProperty, pValue); + xmlFree(UriValue); + } + searchNodeAndAttribute(pNodePtr, pUIItemID, pProperty, pValue); + } + pNodePtr = pNodePtr->next; + } +} + +static xmlDocPtr notebookbarXMLParser(const OString& rDocName, const OString& rUIItemID, + const OString& rProperty, const OString& rValue) +{ + xmlDocPtr pDocPtr = xmlParseFile(rDocName.getStr()); + xmlNodePtr pNodePtr = xmlDocGetRootElement(pDocPtr); + searchNodeAndAttribute(pNodePtr, rUIItemID.getStr(), rProperty.getStr(), rValue.getStr()); + return pDocPtr; +} + +void CustomNotebookbarGenerator::modifyCustomizedUIFile(const Sequence<OUString>& sUIItemProperties) +{ + const OUString sUIPath = getCustomizedUIPath(); + if (osl::File(sUIPath).open(osl_File_OpenFlag_Read) != osl::FileBase::E_None) + createCustomizedUIFile(); + + const OString sCustomizedUIPath = getSystemPath(sUIPath); + for (auto const& aValue : sUIItemProperties) + { + std::vector<OString> aProperties(aUIPropertiesCount); + for (sal_Int32 aIndex = 0; aIndex < aUIPropertiesCount; aIndex++) + { + sal_Int32 nPos = aIndex; + std::u16string_view sToken = o3tl::getToken(aValue, nPos, ',', nPos); + aProperties[aIndex] = OUStringToOString(sToken, RTL_TEXTENCODING_UTF8); + } + xmlDocPtr doc = notebookbarXMLParser(sCustomizedUIPath, aProperties[0], aProperties[1], + aProperties[2]); + + if (doc != nullptr) + { + xmlSaveFormatFile(sCustomizedUIPath.getStr(), doc, 1); + xmlFreeDoc(doc); + } + } +} + +void CustomNotebookbarGenerator::getFileNameAndAppName(OUString& sAppName, + OUString& sNotebookbarUIFileName) +{ + SfxViewFrame* pFrame = SfxViewFrame::Current(); + if (!pFrame) + return; + + const auto xContext = comphelper::getProcessComponentContext(); + utl::OConfigurationTreeRoot aRoot(xContext, "org.openoffice.Office.UI.ToolbarMode/", false); + const Reference<frame::XFrame>& xFrame = pFrame->GetFrame().GetFrameInterface(); + const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create(xContext); + + vcl::EnumContext::Application eApp + = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xFrame)); + OUString sActiveAppName(lcl_activeAppName(eApp)); + sAppName = lcl_getAppName(eApp); + const Any aValue = aRoot.getNodeValue(sActiveAppName); + aValue >>= sNotebookbarUIFileName; +} + +void CustomNotebookbarGenerator::createCustomizedUIFile() +{ + OUString sUserUIDir = getUIDirPath(); + OUString sOriginalUIPath = getOriginalUIPath(); + OUString sCustomizedUIPath = getCustomizedUIPath(); + + sal_uInt32 nflag = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write; + osl::Directory aDirectory(sUserUIDir); + if (aDirectory.open() != osl::FileBase::E_None) + osl::Directory::create(sUserUIDir, nflag); + else + SAL_WARN("cui.customnotebookbar", + "Cannot create the directory or directory was present :" << sUserUIDir); + + osl::File aFile(sCustomizedUIPath); + if (aFile.open(nflag) != osl::FileBase::E_None) + osl::File::copy(sOriginalUIPath, sCustomizedUIPath); + else + SAL_WARN("cui.customnotebookbar", + "Cannot copy the file or file was present :" << sCustomizedUIPath); +} + +Sequence<OUString> CustomNotebookbarGenerator::getCustomizedUIItem(OUString sNotebookbarConfigType) +{ + OUString aPath = getAppNameRegistryPath(); + const utl::OConfigurationTreeRoot aAppNode(::comphelper::getProcessComponentContext(), aPath, + false); + + const utl::OConfigurationNode aModesNode = aAppNode.openNode("Modes"); + const utl::OConfigurationNode aModeNode(aModesNode.openNode(sNotebookbarConfigType)); + const Any aValue = aModeNode.getNodeValue("UIItemProperties"); + Sequence<OUString> aValues; + aValue >>= aValues; + return aValues; +} + +void CustomNotebookbarGenerator::setCustomizedUIItem(Sequence<OUString> sUIItemProperties, + OUString sNotebookbarConfigType) +{ + OUString aPath = getAppNameRegistryPath(); + const utl::OConfigurationTreeRoot aAppNode(::comphelper::getProcessComponentContext(), aPath, + true); + const utl::OConfigurationNode aModesNode = aAppNode.openNode("Modes"); + const utl::OConfigurationNode aModeNode(aModesNode.openNode(sNotebookbarConfigType)); + + css::uno::Any aUIItemProperties(sUIItemProperties); + aModeNode.setNodeValue("UIItemProperties", aUIItemProperties); + aAppNode.commit(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/SvxConfigPageHelper.cxx b/cui/source/customize/SvxConfigPageHelper.cxx new file mode 100644 index 0000000000..c14cb3560c --- /dev/null +++ b/cui/source/customize/SvxConfigPageHelper.cxx @@ -0,0 +1,424 @@ +/* -*- 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 <SvxConfigPageHelper.hxx> + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/ui/ImageType.hpp> +#include <com/sun/star/ui/ItemType.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <comphelper/random.hxx> +#include <svtools/imgdef.hxx> +#include <svtools/miscopt.hxx> + +static sal_Int16 theImageType = css::ui::ImageType::COLOR_NORMAL | css::ui::ImageType::SIZE_DEFAULT; + +void SvxConfigPageHelper::RemoveEntry(SvxEntries* pEntries, SvxConfigEntry const* pChildEntry) +{ + SvxEntries::iterator iter = pEntries->begin(); + + while (iter != pEntries->end()) + { + if (pChildEntry == *iter) + { + pEntries->erase(iter); + break; + } + ++iter; + } +} + +OUString SvxConfigPageHelper::replaceSaveInName(const OUString& rMessage, + std::u16string_view rSaveInName) +{ + OUString name = rMessage.replaceFirst("%SAVE IN SELECTION%", rSaveInName); + + return name; +} + +OUString SvxConfigPageHelper::stripHotKey(const OUString& str) { return str.replaceFirst("~", ""); } + +OUString SvxConfigPageHelper::replaceSixteen(const OUString& str, sal_Int32 nReplacement) +{ + return str.replaceAll(OUString::number(16), OUString::number(nReplacement)); +} + +sal_Int16 SvxConfigPageHelper::GetImageType() { return theImageType; } + +void SvxConfigPageHelper::InitImageType() +{ + theImageType = css::ui::ImageType::COLOR_NORMAL | css::ui::ImageType::SIZE_DEFAULT; + + if (SvtMiscOptions::GetCurrentSymbolsSize() == SFX_SYMBOLS_SIZE_LARGE) + { + theImageType |= css::ui::ImageType::SIZE_LARGE; + } + else if (SvtMiscOptions::GetCurrentSymbolsSize() == SFX_SYMBOLS_SIZE_32) + { + theImageType |= css::ui::ImageType::SIZE_32; + } +} + +css::uno::Reference<css::graphic::XGraphic> +SvxConfigPageHelper::GetGraphic(const css::uno::Reference<css::ui::XImageManager>& xImageManager, + const OUString& rCommandURL) +{ + css::uno::Reference<css::graphic::XGraphic> result; + + if (xImageManager.is()) + { + // TODO handle large graphics + css::uno::Sequence<css::uno::Reference<css::graphic::XGraphic>> aGraphicSeq; + + css::uno::Sequence<OUString> aImageCmdSeq{ rCommandURL }; + + try + { + aGraphicSeq = xImageManager->getImages(GetImageType(), aImageCmdSeq); + + if (aGraphicSeq.hasElements()) + { + result = aGraphicSeq[0]; + } + } + catch (css::uno::Exception&) + { + // will return empty XGraphic + } + } + + return result; +} + +OUString SvxConfigPageHelper::generateCustomName(const OUString& prefix, SvxEntries* entries, + sal_Int32 suffix /*= 1*/) +{ + OUString name; + sal_Int32 pos = 0; + + // find and replace the %n placeholder in the prefix string + name = prefix.replaceFirst("%n", OUString::number(suffix), &pos); + + if (pos == -1) + { + // no placeholder found so just append the suffix + name += OUString::number(suffix); + } + + if (!entries) + return name; + + // now check if there is an already existing entry with this name + bool bFoundEntry = false; + for (auto const& entry : *entries) + { + if (name.equals(entry->GetName())) + { + bFoundEntry = true; + break; + } + } + + if (bFoundEntry) + { + // name already exists so try the next number up + return generateCustomName(prefix, entries, ++suffix); + } + + return name; +} + +OUString SvxConfigPageHelper::generateCustomMenuURL(SvxEntries* entries, sal_Int32 suffix /*= 1*/) +{ + OUString url = "vnd.openoffice.org:CustomMenu" + OUString::number(suffix); + if (!entries) + return url; + + // now check is there is an already existing entry with this url + bool bFoundEntry = false; + for (auto const& entry : *entries) + { + if (url.equals(entry->GetCommand())) + { + bFoundEntry = true; + break; + } + } + + if (bFoundEntry) + { + // url already exists so try the next number up + return generateCustomMenuURL(entries, ++suffix); + } + + return url; +} + +sal_uInt32 SvxConfigPageHelper::generateRandomValue() +{ + return comphelper::rng::uniform_uint_distribution(0, std::numeric_limits<unsigned int>::max()); +} + +OUString SvxConfigPageHelper::generateCustomURL(SvxEntries* entries) +{ + OUString url = OUString::Concat(ITEM_TOOLBAR_URL) + CUSTOM_TOOLBAR_STR + + // use a random number to minimize possible clash with existing custom toolbars + OUString::number(generateRandomValue(), 16); + + // now check is there is an already existing entry with this url + bool bFoundEntry = false; + for (auto const& entry : *entries) + { + if (url.equals(entry->GetCommand())) + { + bFoundEntry = true; + break; + } + } + + if (bFoundEntry) + { + // url already exists so try the next number up + return generateCustomURL(entries); + } + + return url; +} + +OUString SvxConfigPageHelper::GetModuleName(std::u16string_view aModuleId) +{ + if (aModuleId == u"com.sun.star.text.TextDocument" + || aModuleId == u"com.sun.star.text.GlobalDocument") + return "Writer"; + else if (aModuleId == u"com.sun.star.text.WebDocument") + return "Writer/Web"; + else if (aModuleId == u"com.sun.star.drawing.DrawingDocument") + return "Draw"; + else if (aModuleId == u"com.sun.star.presentation.PresentationDocument") + return "Impress"; + else if (aModuleId == u"com.sun.star.sheet.SpreadsheetDocument") + return "Calc"; + else if (aModuleId == u"com.sun.star.script.BasicIDE") + return "Basic"; + else if (aModuleId == u"com.sun.star.formula.FormulaProperties") + return "Math"; + else if (aModuleId == u"com.sun.star.sdb.RelationDesign") + return "Relation Design"; + else if (aModuleId == u"com.sun.star.sdb.QueryDesign") + return "Query Design"; + else if (aModuleId == u"com.sun.star.sdb.TableDesign") + return "Table Design"; + else if (aModuleId == u"com.sun.star.sdb.DataSourceBrowser") + return "Data Source Browser"; + else if (aModuleId == u"com.sun.star.sdb.DatabaseDocument") + return "Database"; + + return OUString(); +} + +OUString SvxConfigPageHelper::GetUIModuleName( + const OUString& aModuleId, + const css::uno::Reference<css::frame::XModuleManager2>& rModuleManager) +{ + assert(rModuleManager.is()); + + OUString aModuleUIName; + + try + { + css::uno::Any a = rModuleManager->getByName(aModuleId); + css::uno::Sequence<css::beans::PropertyValue> aSeq; + + if (a >>= aSeq) + { + for (css::beans::PropertyValue const& rProp : std::as_const(aSeq)) + { + if (rProp.Name == "ooSetupFactoryUIName") + { + rProp.Value >>= aModuleUIName; + break; + } + } + } + } + catch (css::uno::RuntimeException&) + { + throw; + } + catch (css::uno::Exception&) + { + } + + if (aModuleUIName.isEmpty()) + aModuleUIName = GetModuleName(aModuleId); + + return aModuleUIName; +} + +bool SvxConfigPageHelper::GetMenuItemData( + const css::uno::Reference<css::container::XIndexAccess>& rItemContainer, sal_Int32 nIndex, + OUString& rCommandURL, OUString& rLabel, sal_uInt16& rType, sal_Int32& rStyle, + css::uno::Reference<css::container::XIndexAccess>& rSubMenu) +{ + try + { + css::uno::Sequence<css::beans::PropertyValue> aProps; + if (rItemContainer->getByIndex(nIndex) >>= aProps) + { + for (css::beans::PropertyValue const& rProp : std::as_const(aProps)) + { + if (rProp.Name == ITEM_DESCRIPTOR_COMMANDURL) + { + rProp.Value >>= rCommandURL; + } + else if (rProp.Name == ITEM_DESCRIPTOR_CONTAINER) + { + rProp.Value >>= rSubMenu; + } + else if (rProp.Name == ITEM_DESCRIPTOR_STYLE) + { + rProp.Value >>= rStyle; + } + else if (rProp.Name == ITEM_DESCRIPTOR_LABEL) + { + rProp.Value >>= rLabel; + } + else if (rProp.Name == ITEM_DESCRIPTOR_TYPE) + { + rProp.Value >>= rType; + } + } + + return true; + } + } + catch (css::lang::IndexOutOfBoundsException&) + { + } + + return false; +} + +bool SvxConfigPageHelper::GetToolbarItemData( + const css::uno::Reference<css::container::XIndexAccess>& rItemContainer, sal_Int32 nIndex, + OUString& rCommandURL, OUString& rLabel, sal_uInt16& rType, bool& rIsVisible, sal_Int32& rStyle) +{ + try + { + css::uno::Sequence<css::beans::PropertyValue> aProps; + if (rItemContainer->getByIndex(nIndex) >>= aProps) + { + for (css::beans::PropertyValue const& rProp : std::as_const(aProps)) + { + if (rProp.Name == ITEM_DESCRIPTOR_COMMANDURL) + { + rProp.Value >>= rCommandURL; + } + else if (rProp.Name == ITEM_DESCRIPTOR_STYLE) + { + rProp.Value >>= rStyle; + } + else if (rProp.Name == ITEM_DESCRIPTOR_LABEL) + { + rProp.Value >>= rLabel; + } + else if (rProp.Name == ITEM_DESCRIPTOR_TYPE) + { + rProp.Value >>= rType; + } + else if (rProp.Name == ITEM_DESCRIPTOR_ISVISIBLE) + { + rProp.Value >>= rIsVisible; + } + } + + return true; + } + } + catch (css::lang::IndexOutOfBoundsException&) + { + } + + return false; +} + +css::uno::Sequence<css::beans::PropertyValue> +SvxConfigPageHelper::ConvertSvxConfigEntry(const SvxConfigEntry* pEntry) +{ + // If the name has not been changed, then the label can be stored + // as an empty string. + // It will be initialised again later using the command to label map. + OUString sLabel; + if (pEntry->HasChangedName() || pEntry->GetCommand().isEmpty()) + sLabel = pEntry->GetName(); + + css::uno::Sequence<css::beans::PropertyValue> aPropSeq{ + comphelper::makePropertyValue(ITEM_DESCRIPTOR_COMMANDURL, pEntry->GetCommand()), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_TYPE, css::ui::ItemType::DEFAULT), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_LABEL, sLabel), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_STYLE, + static_cast<sal_Int16>(pEntry->GetStyle())) + }; + + return aPropSeq; +} + +css::uno::Sequence<css::beans::PropertyValue> +SvxConfigPageHelper::ConvertToolbarEntry(const SvxConfigEntry* pEntry) +{ + // If the name has not been changed, then the label can be stored + // as an empty string. + // It will be initialised again later using the command to label map. + OUString sLabel; + if (pEntry->HasChangedName() || pEntry->GetCommand().isEmpty()) + sLabel = pEntry->GetName(); + + css::uno::Sequence<css::beans::PropertyValue> aPropSeq{ + comphelper::makePropertyValue(ITEM_DESCRIPTOR_COMMANDURL, pEntry->GetCommand()), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_TYPE, css::ui::ItemType::DEFAULT), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_LABEL, sLabel), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_ISVISIBLE, pEntry->IsVisible()), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_STYLE, + static_cast<sal_Int16>(pEntry->GetStyle())) + }; + + return aPropSeq; +} + +bool SvxConfigPageHelper::EntrySort(SvxConfigEntry const* a, SvxConfigEntry const* b) +{ + return a->GetName().compareTo(b->GetName()) < 0; +} + +bool SvxConfigPageHelper::SvxConfigEntryModified(SvxConfigEntry const* pEntry) +{ + SvxEntries* pEntries = pEntry->GetEntries(); + if (!pEntries) + return false; + + for (const auto& entry : *pEntries) + { + if (entry->IsModified() || SvxConfigEntryModified(entry)) + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/SvxMenuConfigPage.cxx b/cui/source/customize/SvxMenuConfigPage.cxx new file mode 100644 index 0000000000..4ee5ce9be0 --- /dev/null +++ b/cui/source/customize/SvxMenuConfigPage.cxx @@ -0,0 +1,594 @@ +/* -*- 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 <dlgname.hxx> +#include <vcl/weld.hxx> +#include <vcl/svapp.hxx> +#include <vcl/commandevent.hxx> + +#include <strings.hrc> +#include <helpids.h> + +#include <cfg.hxx> +#include <SvxMenuConfigPage.hxx> +#include <SvxConfigPageHelper.hxx> +#include <dialmgr.hxx> + +#include <comphelper/processfactory.hxx> + +SvxMenuConfigPage::SvxMenuConfigPage(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet& rSet, bool bIsMenuBar) + : SvxConfigPage(pPage, pController, rSet) + , m_bIsMenuBar(bIsMenuBar) +{ + m_xGearBtn = m_xBuilder->weld_menu_button("menugearbtn"); + m_xGearBtn->show(); + m_xContentsListBox.reset( + new SvxMenuEntriesListBox(m_xBuilder->weld_tree_view("menucontents"), this)); + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + m_xDropTargetHelper.reset(new SvxConfigPageFunctionDropTarget(*this, rTreeView)); + rTreeView.connect_size_allocate(LINK(this, SvxMenuConfigPage, MenuEntriesSizeAllocHdl)); + Size aSize(m_xFunctions->get_size_request()); + rTreeView.set_size_request(aSize.Width(), aSize.Height()); + MenuEntriesSizeAllocHdl(aSize); + rTreeView.set_hexpand(true); + rTreeView.set_vexpand(true); + rTreeView.show(); + + rTreeView.connect_changed(LINK(this, SvxMenuConfigPage, SelectMenuEntry)); + rTreeView.connect_popup_menu(LINK(this, SvxMenuConfigPage, ContentContextMenuHdl)); + + m_xFunctions->get_widget().connect_popup_menu( + LINK(this, SvxMenuConfigPage, FunctionContextMenuHdl)); + + m_xGearBtn->connect_selected(LINK(this, SvxMenuConfigPage, GearHdl)); + + m_xCommandCategoryListBox->connect_changed(LINK(this, SvxMenuConfigPage, SelectCategory)); + + m_xMoveUpButton->connect_clicked(LINK(this, SvxConfigPage, MoveHdl)); + m_xMoveDownButton->connect_clicked(LINK(this, SvxConfigPage, MoveHdl)); + + m_xAddCommandButton->connect_clicked(LINK(this, SvxMenuConfigPage, AddCommandHdl)); + m_xRemoveCommandButton->connect_clicked(LINK(this, SvxMenuConfigPage, RemoveCommandHdl)); + + m_xInsertBtn->connect_selected(LINK(this, SvxMenuConfigPage, InsertHdl)); + m_xModifyBtn->connect_selected(LINK(this, SvxMenuConfigPage, ModifyItemHdl)); + m_xResetBtn->connect_clicked(LINK(this, SvxMenuConfigPage, ResetMenuHdl)); + + // These operations are not possible on menus/context menus yet + m_xModifyBtn->remove_item("changeIcon"); + m_xModifyBtn->remove_item("resetIcon"); + m_xModifyBtn->remove_item("restoreItem"); + + if (!bIsMenuBar) + { + //TODO: Remove this when the gear button is implemented for context menus + m_xGearBtn->set_sensitive(false); + m_xGearBtn->hide(); + } + else + { + // TODO: Remove this when it is possible to reset menubar menus individually + m_xResetBtn->set_sensitive(false); + } +} + +void SvxMenuConfigPage::ListModified() +{ + // regenerate with the current ordering within the list + SvxEntries* pEntries = GetTopLevelSelection()->GetEntries(); + pEntries->clear(); + + for (int i = 0; i < m_xContentsListBox->n_children(); ++i) + pEntries->push_back(weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(i))); + + GetSaveInData()->SetModified(); + GetTopLevelSelection()->SetModified(); + UpdateButtonStates(); +} + +IMPL_LINK(SvxMenuConfigPage, MenuEntriesSizeAllocHdl, const Size&, rSize, void) +{ + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + std::vector<int> aWidths; + + int nStandardImageColWidth = rTreeView.get_checkbox_column_width(); + int nMargin = 16; + + aWidths.push_back(rSize.Width() - (nMargin + nStandardImageColWidth)); + rTreeView.set_column_fixed_widths(aWidths); +} + +SvxMenuConfigPage::~SvxMenuConfigPage() +{ + for (int i = 0, nCount = m_xSaveInListBox->get_count(); i < nCount; ++i) + delete weld::fromId<SaveInData*>(m_xSaveInListBox->get_id(i)); + m_xSaveInListBox->clear(); +} + +// Populates the Menu combo box +void SvxMenuConfigPage::Init() +{ + // ensure that the UI is cleared before populating it + m_xTopLevelListBox->clear(); + m_xContentsListBox->clear(); + + ReloadTopLevelListBox(); + + m_xTopLevelListBox->set_active(m_xTopLevelListBox->get_count() ? 0 : -1); + SelectElement(); + + m_xCommandCategoryListBox->Init(comphelper::getProcessComponentContext(), m_xFrame, + m_aModuleId); + m_xCommandCategoryListBox->categorySelected(m_xFunctions.get(), OUString(), GetSaveInData()); + SelectFunctionHdl(m_xFunctions->get_widget()); +} + +IMPL_LINK_NOARG(SvxMenuConfigPage, SelectMenuEntry, weld::TreeView&, void) { UpdateButtonStates(); } + +void SvxMenuConfigPage::UpdateButtonStates() +{ + // Disable Up and Down buttons depending on current selection + int selection = m_xContentsListBox->get_selected_index(); + + bool bIsSeparator + = selection != -1 + && weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(selection))->IsSeparator(); + bool bIsValidSelection = (m_xContentsListBox->n_children() != 0 && selection != -1); + + m_xMoveUpButton->set_sensitive(bIsValidSelection && selection != 0); + m_xMoveDownButton->set_sensitive(bIsValidSelection + && selection != m_xContentsListBox->n_children() - 1); + + m_xRemoveCommandButton->set_sensitive(bIsValidSelection); + + m_xModifyBtn->set_sensitive(bIsValidSelection && !bIsSeparator); + + // If there is no top level selection (menu), then everything working on the right box + // which contains the functions of the selected menu/toolbar needs to be disabled + SvxConfigEntry* pMenuData = GetTopLevelSelection(); + + m_xInsertBtn->set_sensitive(pMenuData != nullptr); + + SvxConfigEntry* selectedCmd = CreateCommandFromSelection(GetScriptURL()); + + m_xAddCommandButton->set_sensitive( + pMenuData != nullptr && !IsCommandInMenuList(selectedCmd, pMenuData->GetEntries())); + + delete selectedCmd; + + if (bIsValidSelection) + { + m_xRemoveCommandButton->set_sensitive(pMenuData != nullptr); + } + + //Handle the gear button + if (pMenuData && m_bIsMenuBar) + { + // Add option (gear_add) will always be enabled + m_xGearBtn->set_item_sensitive("menu_gear_delete", pMenuData->IsDeletable()); + m_xGearBtn->set_item_sensitive("menu_gear_rename", pMenuData->IsRenamable()); + m_xGearBtn->set_item_sensitive("menu_gear_move", pMenuData->IsMovable()); + } +} + +void SvxMenuConfigPage::DeleteSelectedTopLevel() +{ + SvxConfigEntry* pMenuData = GetTopLevelSelection(); + + SvxEntries* pParentEntries = FindParentForChild(GetSaveInData()->GetEntries(), pMenuData); + + SvxConfigPageHelper::RemoveEntry(pParentEntries, pMenuData); + delete pMenuData; + + ReloadTopLevelListBox(); + + GetSaveInData()->SetModified(); +} + +void SvxMenuConfigPage::DeleteSelectedContent() +{ + int nActEntry = m_xContentsListBox->get_selected_index(); + + if (nActEntry == -1) + return; + + // get currently selected menu entry + SvxConfigEntry* pMenuEntry + = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + // get currently selected menu + SvxConfigEntry* pMenu = GetTopLevelSelection(); + + // remove menu entry from the list for this menu + SvxConfigPageHelper::RemoveEntry(pMenu->GetEntries(), pMenuEntry); + + // remove menu entry from UI + m_xContentsListBox->remove(nActEntry); + + // if this is a submenu entry, redraw the menus list box + if (pMenuEntry->IsPopup()) + { + ReloadTopLevelListBox(); + } + + // delete data for menu entry + delete pMenuEntry; + + GetSaveInData()->SetModified(); + pMenu->SetModified(); +} + +short SvxMenuConfigPage::QueryReset() +{ + OUString msg = CuiResId(RID_CUISTR_CONFIRM_MENU_RESET); + + OUString saveInName = m_xSaveInListBox->get_active_text(); + + OUString label = SvxConfigPageHelper::replaceSaveInName(msg, saveInName); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog( + GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, label)); + return xQueryBox->run(); +} + +void SvxMenuConfigPage::SelectElement() +{ + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + + SvxConfigEntry* pMenuData = GetTopLevelSelection(); + if (!pMenuData) + rTreeView.clear(); + else + { + SvxEntries* pEntries = pMenuData->GetEntries(); + + rTreeView.bulk_insert_for_each( + pEntries->size(), [this, &rTreeView, pEntries](weld::TreeIter& rIter, int nIdx) { + auto const& entry = (*pEntries)[nIdx]; + OUString sId(weld::toId(entry)); + rTreeView.set_id(rIter, sId); + InsertEntryIntoUI(entry, rTreeView, rIter, true); + }); + } + + UpdateButtonStates(); +} + +IMPL_LINK(SvxMenuConfigPage, GearHdl, const OUString&, rIdent, void) +{ + if (rIdent == "menu_gear_add") + { + SvxMainMenuOrganizerDialog aDialog(GetFrameWeld(), GetSaveInData()->GetEntries(), nullptr, + true); + + if (aDialog.run() == RET_OK) + { + GetSaveInData()->SetEntries(aDialog.ReleaseEntries()); + ReloadTopLevelListBox(aDialog.GetSelectedEntry()); + GetSaveInData()->SetModified(); + } + } + else if (rIdent == "menu_gear_delete") + { + DeleteSelectedTopLevel(); + } + else if (rIdent == "menu_gear_rename") + { + SvxConfigEntry* pMenuData = GetTopLevelSelection(); + + OUString sCurrentName(SvxConfigPageHelper::stripHotKey(pMenuData->GetName())); + OUString sDesc = CuiResId(RID_CUISTR_LABEL_NEW_NAME); + + SvxNameDialog aNameDialog(GetFrameWeld(), sCurrentName, sDesc); + aNameDialog.set_help_id(HID_SVX_CONFIG_RENAME_MENU); + aNameDialog.set_title(CuiResId(RID_CUISTR_RENAME_MENU)); + + if (aNameDialog.run() == RET_OK) + { + OUString sNewName = aNameDialog.GetName(); + + if (sCurrentName == sNewName) + return; + + pMenuData->SetName(sNewName); + + ReloadTopLevelListBox(); + + GetSaveInData()->SetModified(); + } + } + else if (rIdent == "menu_gear_move") + { + SvxConfigEntry* pMenuData = GetTopLevelSelection(); + + SvxMainMenuOrganizerDialog aDialog(GetFrameWeld(), GetSaveInData()->GetEntries(), pMenuData, + false); + if (aDialog.run() == RET_OK) + { + GetSaveInData()->SetEntries(aDialog.ReleaseEntries()); + + ReloadTopLevelListBox(); + + GetSaveInData()->SetModified(); + } + } + else + { + //This block should never be reached + SAL_WARN("cui.customize", "Unknown gear menu option: " << rIdent); + return; + } + + UpdateButtonStates(); +} + +IMPL_LINK_NOARG(SvxMenuConfigPage, SelectCategory, weld::ComboBox&, void) +{ + OUString aSearchTerm(m_xSearchEdit->get_text()); + + m_xCommandCategoryListBox->categorySelected(m_xFunctions.get(), aSearchTerm, GetSaveInData()); + + SelectFunctionHdl(m_xFunctions->get_widget()); +} + +IMPL_LINK_NOARG(SvxMenuConfigPage, AddCommandHdl, weld::Button&, void) +{ + int nPos = AddFunction(-1, /*bAllowDuplicates*/ false); + if (nPos == -1) + return; + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + SvxConfigEntry* pEntry = weld::fromId<SvxConfigEntry*>(rTreeView.get_id(nPos)); + InsertEntryIntoUI(pEntry, rTreeView, nPos, true); +} + +IMPL_LINK_NOARG(SvxMenuConfigPage, RemoveCommandHdl, weld::Button&, void) +{ + DeleteSelectedContent(); + if (GetSaveInData()->IsModified()) + { + UpdateButtonStates(); + } +} + +IMPL_LINK(SvxMenuConfigPage, InsertHdl, const OUString&, rIdent, void) +{ + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + if (rIdent == "insertseparator") + { + SvxConfigEntry* pNewEntryData = new SvxConfigEntry; + pNewEntryData->SetUserDefined(); + int nPos = AppendEntry(pNewEntryData, -1); + InsertEntryIntoUI(pNewEntryData, rTreeView, nPos, true); + } + else if (rIdent == "insertsubmenu") + { + OUString aNewName; + OUString aDesc = CuiResId(RID_CUISTR_SUBMENU_NAME); + + SvxNameDialog aNameDialog(GetFrameWeld(), aNewName, aDesc); + aNameDialog.set_help_id(HID_SVX_CONFIG_NAME_SUBMENU); + aNameDialog.set_title(CuiResId(RID_CUISTR_ADD_SUBMENU)); + + if (aNameDialog.run() == RET_OK) + { + aNewName = aNameDialog.GetName(); + + SvxConfigEntry* pNewEntryData + = new SvxConfigEntry(aNewName, aNewName, true, /*bParentData*/ false); + pNewEntryData->SetName(aNewName); + pNewEntryData->SetUserDefined(); + + int nPos = AppendEntry(pNewEntryData, -1); + InsertEntryIntoUI(pNewEntryData, rTreeView, nPos, true); + + ReloadTopLevelListBox(); + + m_xContentsListBox->scroll_to_row(nPos); + m_xContentsListBox->select(nPos); + + GetSaveInData()->SetModified(); + } + } + else + { + //This block should never be reached + SAL_WARN("cui.customize", "Unknown insert option: " << rIdent); + return; + } + + if (GetSaveInData()->IsModified()) + { + UpdateButtonStates(); + } +} + +IMPL_LINK(SvxMenuConfigPage, ModifyItemHdl, const OUString&, rIdent, void) +{ + if (rIdent == "renameItem") + { + int nActEntry = m_xContentsListBox->get_selected_index(); + SvxConfigEntry* pEntry + = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + OUString aNewName(SvxConfigPageHelper::stripHotKey(pEntry->GetName())); + OUString aDesc = CuiResId(RID_CUISTR_LABEL_NEW_NAME); + + SvxNameDialog aNameDialog(GetFrameWeld(), aNewName, aDesc); + aNameDialog.set_help_id(HID_SVX_CONFIG_RENAME_MENU_ITEM); + aNameDialog.set_title(CuiResId(RID_CUISTR_RENAME_MENU)); + + if (aNameDialog.run() == RET_OK) + { + aNewName = aNameDialog.GetName(); + + pEntry->SetName(aNewName); + m_xContentsListBox->set_text(nActEntry, aNewName, 0); + + GetSaveInData()->SetModified(); + GetTopLevelSelection()->SetModified(); + } + } + else + { + //This block should never be reached + SAL_WARN("cui.customize", "Unknown insert option: " << rIdent); + return; + } + + if (GetSaveInData()->IsModified()) + { + UpdateButtonStates(); + } +} + +IMPL_LINK_NOARG(SvxMenuConfigPage, ResetMenuHdl, weld::Button&, void) +{ + SvxConfigEntry* pMenuData = GetTopLevelSelection(); + + if (pMenuData == nullptr) + { + SAL_WARN("cui.customize", + "RHB top level selection is null. A menu must be selected to reset!"); + return; + } + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog( + GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, + CuiResId(RID_CUISTR_CONFIRM_RESTORE_DEFAULT_MENU))); + + // Resetting individual top-level menus is not possible at the moment. + // So we are resetting only if it is a context menu + if (m_bIsMenuBar || xQueryBox->run() != RET_YES) + return; + + sal_Int32 nPos = m_xTopLevelListBox->get_active(); + ContextMenuSaveInData* pSaveInData = static_cast<ContextMenuSaveInData*>(GetSaveInData()); + + pSaveInData->ResetContextMenu(pMenuData); + + // ensure that the UI is cleared before populating it + m_xTopLevelListBox->clear(); + m_xContentsListBox->clear(); + + ReloadTopLevelListBox(); + + // Reselect the reset menu + m_xTopLevelListBox->set_active(nPos); + SelectElement(); +} + +SaveInData* SvxMenuConfigPage::CreateSaveInData( + const css::uno::Reference<css::ui::XUIConfigurationManager>& xCfgMgr, + const css::uno::Reference<css::ui::XUIConfigurationManager>& xParentCfgMgr, + const OUString& aModuleId, bool bDocConfig) +{ + if (!m_bIsMenuBar) + return static_cast<SaveInData*>( + new ContextMenuSaveInData(xCfgMgr, xParentCfgMgr, aModuleId, bDocConfig)); + + return static_cast<SaveInData*>( + new MenuSaveInData(xCfgMgr, xParentCfgMgr, aModuleId, bDocConfig)); +} + +IMPL_LINK(SvxMenuConfigPage, ContentContextMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + + // Select clicked entry + std::unique_ptr<weld::TreeIter> xIter(rTreeView.make_iterator()); + if (!rTreeView.get_dest_row_at_pos(rCEvt.GetMousePosPixel(), xIter.get(), false)) + return false; + rTreeView.select(*xIter); + SelectMenuEntry(rTreeView); + + int nSelectIndex = m_xContentsListBox->get_selected_index(); + + bool bIsSeparator + = nSelectIndex != -1 + && weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nSelectIndex))->IsSeparator(); + bool bIsValidSelection = (m_xContentsListBox->n_children() != 0 && nSelectIndex != -1); + + std::unique_ptr<weld::Builder> xBuilder( + Application::CreateBuilder(&rTreeView, "cui/ui/entrycontextmenu.ui")); + auto xContextMenu = xBuilder->weld_menu("menu"); + xContextMenu->set_visible("add", false); + xContextMenu->set_visible("remove", bIsValidSelection); + xContextMenu->set_visible("rename", bIsValidSelection && !bIsSeparator); + xContextMenu->set_visible("changeIcon", false); + xContextMenu->set_visible("resetIcon", false); + xContextMenu->set_visible("restoreDefault", false); + OUString sCommand(xContextMenu->popup_at_rect( + &rTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)))); + + if (sCommand == "remove") + { + RemoveCommandHdl(*m_xRemoveCommandButton); + } + else if (sCommand == "rename") + { + ModifyItemHdl("renameItem"); + } + else if (!sCommand.isEmpty()) + SAL_WARN("cui.customize", "Unknown context menu action: " << sCommand); + return true; +} + +IMPL_LINK(SvxMenuConfigPage, FunctionContextMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + weld::TreeView& rTreeView = m_xFunctions->get_widget(); + + // Select clicked entry + std::unique_ptr<weld::TreeIter> xIter(rTreeView.make_iterator()); + if (!rTreeView.get_dest_row_at_pos(rCEvt.GetMousePosPixel(), xIter.get(), false)) + return false; + rTreeView.select(*xIter); + SelectFunctionHdl(rTreeView); + + std::unique_ptr<weld::Builder> xBuilder( + Application::CreateBuilder(&rTreeView, "cui/ui/entrycontextmenu.ui")); + auto xContextMenu = xBuilder->weld_menu("menu"); + xContextMenu->set_visible("add", true); + xContextMenu->set_visible("remove", false); + xContextMenu->set_visible("rename", false); + xContextMenu->set_visible("changeIcon", false); + xContextMenu->set_visible("resetIcon", false); + xContextMenu->set_visible("restoreDefault", false); + OUString sCommand(xContextMenu->popup_at_rect( + &rTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)))); + + if (sCommand == "add") + { + AddCommandHdl(*m_xAddCommandButton); + } + else if (!sCommand.isEmpty()) + SAL_WARN("cui.customize", "Unknown context menu action: " << sCommand); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/SvxNotebookbarConfigPage.cxx b/cui/source/customize/SvxNotebookbarConfigPage.cxx new file mode 100644 index 0000000000..f44bc70d3d --- /dev/null +++ b/cui/source/customize/SvxNotebookbarConfigPage.cxx @@ -0,0 +1,568 @@ +/* -*- 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 <vcl/commandinfoprovider.hxx> +#include <vcl/event.hxx> +#include <vcl/weld.hxx> +#include <vcl/svapp.hxx> + +#include <algorithm> +#include <cstddef> + +#include <helpids.h> +#include <strings.hrc> + +#include <cfg.hxx> +#include <SvxNotebookbarConfigPage.hxx> +#include <SvxConfigPageHelper.hxx> +#include <dialmgr.hxx> +#include <libxml/parser.h> +#include <osl/file.hxx> +#include <CustomNotebookbarGenerator.hxx> +#include <sfx2/notebookbar/SfxNotebookBar.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/frame/theUICommandDescription.hpp> + +namespace uno = com::sun::star::uno; +namespace frame = com::sun::star::frame; +namespace lang = com::sun::star::lang; +namespace container = com::sun::star::container; +namespace beans = com::sun::star::beans; +namespace graphic = com::sun::star::graphic; + +static bool isCategoryAvailable(std::u16string_view sClassId, std::u16string_view sUIItemId, + std::u16string_view sActiveCategory, bool& isCategory) +{ + if (sUIItemId == sActiveCategory) + return true; + else if ((sClassId == u"GtkMenu" || sClassId == u"GtkGrid") && sUIItemId != sActiveCategory) + { + isCategory = false; + return false; + } + return false; +} + +static OUString charToString(const char* cString) +{ + return OUString(cString, strlen(cString), RTL_TEXTENCODING_UTF8); +} + +static OUString getFileName(std::u16string_view aFileName) +{ + if (aFileName == u"notebookbar.ui") + return CuiResId(RID_CUISTR_TABBED); + else if (aFileName == u"notebookbar_compact.ui") + return CuiResId(RID_CUISTR_TABBED_COMPACT); + else if (aFileName == u"notebookbar_groupedbar_full.ui") + return CuiResId(RID_CUISTR_GROUPEDBAR); + else if (aFileName == u"notebookbar_groupedbar_compact.ui") + return CuiResId(RID_CUISTR_GROUPEDBAR_COMPACT); + else + return "None"; +} + +static OUString getModuleId(std::u16string_view sModuleName) +{ + if (sModuleName == u"Writer") + return "com.sun.star.text.TextDocument"; + else if (sModuleName == u"Draw") + return "com.sun.star.drawing.DrawingDocument"; + else if (sModuleName == u"Impress") + return "com.sun.star.presentation.PresentationDocument"; + else if (sModuleName == u"Calc") + return "com.sun.star.sheet.SpreadsheetDocument"; + else + return "None"; +} + +SvxNotebookbarConfigPage::SvxNotebookbarConfigPage(weld::Container* pPage, + weld::DialogController* pController, + const SfxItemSet& rSet) + : SvxConfigPage(pPage, pController, rSet) +{ + m_xCommandCategoryListBox->set_visible(false); + m_xDescriptionFieldLb->set_visible(false); + m_xSearchEdit->set_visible(false); + m_xDescriptionField->set_visible(false); + m_xMoveUpButton->set_visible(false); + m_xMoveDownButton->set_visible(false); + m_xCommandButtons->set_visible(false); + m_xLeftFunctionLabel->set_visible(false); + m_xSearchLabel->set_visible(false); + m_xCategoryLabel->set_visible(false); + m_xCustomizeBox->set_visible(false); + m_xCustomizeLabel->set_visible(false); + + weld::TreeView& rCommandCategoryBox = m_xFunctions->get_widget(); + rCommandCategoryBox.hide(); + + m_xContentsListBox.reset( + new SvxNotebookbarEntriesListBox(m_xBuilder->weld_tree_view("toolcontents"), this)); + m_xDropTargetHelper.reset( + new SvxConfigPageFunctionDropTarget(*this, m_xContentsListBox->get_widget())); + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + Size aSize(m_xFunctions->get_size_request()); + rTreeView.set_size_request(aSize.Width(), aSize.Height()); + + rTreeView.set_hexpand(true); + rTreeView.set_vexpand(true); + rTreeView.set_help_id(HID_SVX_CONFIG_NOTEBOOKBAR_CONTENTS); + rTreeView.show(); +} + +SvxNotebookbarConfigPage::~SvxNotebookbarConfigPage() {} + +void SvxNotebookbarConfigPage::DeleteSelectedTopLevel() {} + +void SvxNotebookbarConfigPage::DeleteSelectedContent() {} + +void SvxNotebookbarConfigPage::Init() +{ + m_xTopLevelListBox->clear(); + m_xContentsListBox->clear(); + m_xSaveInListBox->clear(); + OUString sNotebookbarInterface = getFileName(m_sFileName); + + OUString sScopeName + = utl::ConfigManager::getProductName() + " " + m_sAppName + " - " + sNotebookbarInterface; + OUString sSaveInListBoxID = notebookbarTabScope; + + m_xSaveInListBox->append(sSaveInListBoxID, sScopeName); + m_xSaveInListBox->set_active_id(sSaveInListBoxID); + + m_xTopLevelListBox->append("NotebookBar", CuiResId(RID_CUISTR_ALL_COMMANDS)); + m_xTopLevelListBox->set_active_id("NotebookBar"); + SelectElement(); +} + +SaveInData* SvxNotebookbarConfigPage::CreateSaveInData( + const css::uno::Reference<css::ui::XUIConfigurationManager>& xCfgMgr, + const css::uno::Reference<css::ui::XUIConfigurationManager>& xParentCfgMgr, + const OUString& aModuleId, bool bDocConfig) +{ + return static_cast<SaveInData*>( + new ToolbarSaveInData(xCfgMgr, xParentCfgMgr, aModuleId, bDocConfig)); +} + +void SvxNotebookbarConfigPage::UpdateButtonStates() {} + +short SvxNotebookbarConfigPage::QueryReset() +{ + OUString msg = CuiResId(RID_CUISTR_CONFIRM_TOOLBAR_RESET); + + OUString saveInName = m_xSaveInListBox->get_active_text(); + + OUString label = SvxConfigPageHelper::replaceSaveInName(msg, saveInName); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog( + GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, label)); + int nValue = xQueryBox->run(); + if (nValue == RET_YES) + { + osl::File::remove(CustomNotebookbarGenerator::getCustomizedUIPath()); + + OUString sNotebookbarInterface = getFileName(m_sFileName); + Sequence<OUString> sSequenceEntries; + CustomNotebookbarGenerator::setCustomizedUIItem(sSequenceEntries, sNotebookbarInterface); + OUString sUIPath = "modules/s" + m_sAppName.toAsciiLowerCase() + "/ui/"; + sfx2::SfxNotebookBar::ReloadNotebookBar(sUIPath); + } + return nValue; +} + +void SvxConfigPage::InsertEntryIntoNotebookbarTabUI(std::u16string_view sClassId, + const OUString& sUIItemId, + const OUString& sUIItemCommand, + weld::TreeView& rTreeView, + const weld::TreeIter& rIter) +{ + css::uno::Reference<css::container::XNameAccess> m_xCommandToLabelMap; + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + uno::Reference<container::XNameAccess> xNameAccess( + css::frame::theUICommandDescription::get(xContext)); + + uno::Sequence<beans::PropertyValue> aPropSeq; + + xNameAccess->getByName(getModuleId(m_sAppName)) >>= m_xCommandToLabelMap; + + try + { + uno::Any aModuleVal = m_xCommandToLabelMap->getByName(sUIItemCommand); + + aModuleVal >>= aPropSeq; + } + catch (container::NoSuchElementException&) + { + } + + OUString aLabel; + for (auto const& prop : std::as_const(aPropSeq)) + if (prop.Name == "Name") + prop.Value >>= aLabel; + + OUString aName = SvxConfigPageHelper::stripHotKey(aLabel); + + if (sClassId == u"GtkSeparatorMenuItem" || sClassId == u"GtkSeparator") + { + rTreeView.set_text(rIter, "--------------------------------------------", 0); + } + else + { + if (aName.isEmpty()) + aName = sUIItemId; + auto xImage = GetSaveInData()->GetImage(sUIItemCommand); + if (xImage.is()) + rTreeView.set_image(rIter, xImage, -1); + rTreeView.set_text(rIter, aName, 0); + rTreeView.set_id(rIter, sUIItemId); + } +} + +void SvxNotebookbarConfigPage::getNodeValue(xmlNode* pNodePtr, NotebookbarEntries& aNodeEntries) +{ + pNodePtr = pNodePtr->xmlChildrenNode; + while (pNodePtr) + { + if (!(xmlStrcmp(pNodePtr->name, reinterpret_cast<const xmlChar*>("property")))) + { + xmlChar* UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("name")); + if (!(xmlStrcmp(UriValue, reinterpret_cast<const xmlChar*>("visible")))) + { + xmlChar* aValue = xmlNodeGetContent(pNodePtr); + const char* cVisibleValue = reinterpret_cast<const char*>(aValue); + aNodeEntries.sVisibleValue = charToString(cVisibleValue); + xmlFree(aValue); + } + if (!(xmlStrcmp(UriValue, reinterpret_cast<const xmlChar*>("action_name")))) + { + xmlChar* aValue = xmlNodeGetContent(pNodePtr); + const char* cActionName = reinterpret_cast<const char*>(aValue); + aNodeEntries.sActionName = charToString(cActionName); + xmlFree(aValue); + } + xmlFree(UriValue); + } + pNodePtr = pNodePtr->next; + } +} + +void SvxNotebookbarConfigPage::searchNodeandAttribute(std::vector<NotebookbarEntries>& aEntries, + std::vector<CategoriesEntries>& aCategoryList, + OUString& sActiveCategory, + CategoriesEntries& aCurItemEntry, + xmlNode* pNodePtr, bool isCategory) +{ + pNodePtr = pNodePtr->xmlChildrenNode; + while (pNodePtr) + { + if (pNodePtr->type == XML_ELEMENT_NODE) + { + const char* cNodeName = reinterpret_cast<const char*>(pNodePtr->name); + if (strcmp(cNodeName, "object") == 0) + { + OUString sSecondVal; + + xmlChar* UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("id")); + const char* cUIItemID = reinterpret_cast<const char*>(UriValue); + OUString sUIItemId = charToString(cUIItemID); + xmlFree(UriValue); + + UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("class")); + const char* cClassId = reinterpret_cast<const char*>(UriValue); + OUString sClassId = charToString(cClassId); + xmlFree(UriValue); + + CategoriesEntries aCategoryEntry; + if (sClassId == "sfxlo-PriorityHBox") + { + aCategoryEntry.sDisplayName = sUIItemId; + aCategoryEntry.sUIItemId = sUIItemId; + aCategoryEntry.sClassType = sClassId; + aCategoryList.push_back(aCategoryEntry); + + aCurItemEntry = aCategoryEntry; + } + else if (sClassId == "sfxlo-PriorityMergedHBox") + { + aCategoryEntry.sDisplayName = aCurItemEntry.sDisplayName + " | " + sUIItemId; + aCategoryEntry.sUIItemId = sUIItemId; + aCategoryEntry.sClassType = sClassId; + + if (aCurItemEntry.sClassType == sClassId) + { + sal_Int32 rPos = 0; + aCategoryEntry.sDisplayName + = OUString::Concat( + o3tl::getToken(aCurItemEntry.sDisplayName, rPos, ' ', rPos)) + + " | " + sUIItemId; + } + aCategoryList.push_back(aCategoryEntry); + aCurItemEntry = aCategoryEntry; + } + else if (sClassId == "svtlo-ManagedMenuButton") + { + sal_Int32 rPos = 1; + sSecondVal = sUIItemId.getToken(rPos, ':', rPos); + if (!sSecondVal.isEmpty()) + { + aCategoryEntry.sDisplayName + = aCurItemEntry.sDisplayName + " | " + sSecondVal; + aCategoryEntry.sUIItemId = sSecondVal; + aCategoryList.push_back(aCategoryEntry); + } + } + + NotebookbarEntries nodeEntries; + if (isCategoryAvailable(sClassId, sUIItemId, sActiveCategory, isCategory) + || isCategory) + { + isCategory = true; + if (sClassId == "GtkMenuItem" || sClassId == "GtkToolButton" + || sClassId == "GtkMenuToolButton" + || (sClassId == "svtlo-ManagedMenuButton" && sSecondVal.isEmpty())) + { + nodeEntries.sClassId = sClassId; + nodeEntries.sUIItemId = sUIItemId; + nodeEntries.sDisplayName = sUIItemId; + + getNodeValue(pNodePtr, nodeEntries); + aEntries.push_back(nodeEntries); + } + else if (sClassId == "GtkSeparatorMenuItem" || sClassId == "GtkSeparator") + { + nodeEntries.sClassId = sClassId; + nodeEntries.sUIItemId = sUIItemId; + nodeEntries.sDisplayName = "Null"; + nodeEntries.sVisibleValue = "Null"; + nodeEntries.sActionName = "Null"; + aEntries.push_back(nodeEntries); + } + else if (sClassId == "sfxlo-PriorityHBox" + || sClassId == "sfxlo-PriorityMergedHBox" + || sClassId == "svtlo-ManagedMenuButton") + { + nodeEntries.sClassId = sClassId; + nodeEntries.sUIItemId = sUIItemId; + nodeEntries.sDisplayName + = aCategoryList[aCategoryList.size() - 1].sDisplayName; + nodeEntries.sVisibleValue = "Null"; + nodeEntries.sActionName = "Null"; + aEntries.push_back(nodeEntries); + } + } + } + searchNodeandAttribute(aEntries, aCategoryList, sActiveCategory, aCurItemEntry, + pNodePtr, isCategory); + } + pNodePtr = pNodePtr->next; + } +} + +void SvxNotebookbarConfigPage::FillFunctionsList(xmlNodePtr pRootNodePtr, + std::vector<NotebookbarEntries>& aEntries, + std::vector<CategoriesEntries>& aCategoryList, + OUString& sActiveCategory) +{ + CategoriesEntries aCurItemEntry; + searchNodeandAttribute(aEntries, aCategoryList, sActiveCategory, aCurItemEntry, pRootNodePtr, + false); +} + +void SvxNotebookbarConfigPage::SelectElement() +{ + OString sUIFileUIPath = CustomNotebookbarGenerator::getSystemPath( + CustomNotebookbarGenerator::getCustomizedUIPath()); + xmlDocPtr pDoc = xmlParseFile(sUIFileUIPath.getStr()); + if (!pDoc) + { + sUIFileUIPath = CustomNotebookbarGenerator::getSystemPath( + CustomNotebookbarGenerator::getOriginalUIPath()); + pDoc = xmlParseFile(sUIFileUIPath.getStr()); + } + + if (!pDoc) + return; + xmlNodePtr pNodePtr = xmlDocGetRootElement(pDoc); + + std::vector<NotebookbarEntries> aEntries; + std::vector<CategoriesEntries> aCategoryList; + OUString sActiveCategory = m_xTopLevelListBox->get_active_id(); + FillFunctionsList(pNodePtr, aEntries, aCategoryList, sActiveCategory); + + if (m_xTopLevelListBox->get_count() == 1) + { + for (const auto& rCategory : aCategoryList) + m_xTopLevelListBox->append(rCategory.sUIItemId, rCategory.sDisplayName); + } + tools::ULong nStart = 0; + if (aEntries[nStart].sClassId == "sfxlo-PriorityHBox" + || aEntries[nStart].sClassId == "sfxlo-PriorityMergedHBox") + nStart = 1; + + std::vector<NotebookbarEntries> aTempEntries; + for (std::size_t nIdx = nStart; nIdx < aEntries.size(); nIdx++) + { + if (aEntries[nIdx].sClassId == "svtlo-ManagedMenuButton") + { + aTempEntries.push_back(aEntries[nIdx]); + sal_Int32 rPos = 1; + sActiveCategory = aEntries[nIdx].sUIItemId.getToken(rPos, ':', rPos); + FillFunctionsList(pNodePtr, aTempEntries, aCategoryList, sActiveCategory); + } + else + aTempEntries.push_back(aEntries[nIdx]); + } + + aEntries = std::move(aTempEntries); + + static_cast<SvxNotebookbarEntriesListBox*>(m_xContentsListBox.get())->GetTooltipMap().clear(); + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + rTreeView.bulk_insert_for_each( + aEntries.size(), [this, &rTreeView, &aEntries](weld::TreeIter& rIter, int nIdx) { + if (aEntries[nIdx].sActionName != "Null") + { + if (aEntries[nIdx].sVisibleValue == "True") + { + rTreeView.set_toggle(rIter, TRISTATE_TRUE); + } + else + { + rTreeView.set_toggle(rIter, TRISTATE_FALSE); + } + } + InsertEntryIntoNotebookbarTabUI(aEntries[nIdx].sClassId, aEntries[nIdx].sDisplayName, + aEntries[nIdx].sActionName, rTreeView, rIter); + if (aEntries[nIdx].sClassId != u"GtkSeparatorMenuItem" + && aEntries[nIdx].sClassId != u"GtkSeparator") + { + static_cast<SvxNotebookbarEntriesListBox*>(m_xContentsListBox.get()) + ->GetTooltipMap()[aEntries[nIdx].sDisplayName] + = aEntries[nIdx].sActionName; + } + }); + + aEntries.clear(); + + xmlFreeDoc(pDoc); +} + +SvxNotebookbarEntriesListBox::SvxNotebookbarEntriesListBox(std::unique_ptr<weld::TreeView> xParent, + SvxConfigPage* pPg) + : SvxMenuEntriesListBox(std::move(xParent), pPg) +{ + m_xControl->connect_toggled(LINK(this, SvxNotebookbarEntriesListBox, CheckButtonHdl)); + m_xControl->connect_key_press(Link<const KeyEvent&, bool>()); + m_xControl->connect_key_press(LINK(this, SvxNotebookbarEntriesListBox, KeyInputHdl)); + // remove the inherited connect_query_tooltip then add the new one + m_xControl->connect_query_tooltip(Link<const weld::TreeIter&, OUString>()); + m_xControl->connect_query_tooltip(LINK(this, SvxNotebookbarEntriesListBox, QueryTooltip)); +} + +SvxNotebookbarEntriesListBox::~SvxNotebookbarEntriesListBox() {} + +static void EditRegistryFile(std::u16string_view sUIItemId, const OUString& sSetEntry, + const OUString& sNotebookbarInterface) +{ + int nFlag = 0; + Sequence<OUString> aOldEntries + = CustomNotebookbarGenerator::getCustomizedUIItem(sNotebookbarInterface); + Sequence<OUString> aNewEntries(aOldEntries.getLength() + 1); + auto pNewEntries = aNewEntries.getArray(); + for (int nIdx = 0; nIdx < aOldEntries.getLength(); nIdx++) + { + sal_Int32 rPos = 0; + std::u16string_view sFirstValue = o3tl::getToken(aOldEntries[nIdx], rPos, ',', rPos); + if (sFirstValue == sUIItemId) + { + aOldEntries.getArray()[nIdx] = sSetEntry; + nFlag = 1; + break; + } + pNewEntries[nIdx] = aOldEntries[nIdx]; + } + + if (nFlag == 0) + { + pNewEntries[aOldEntries.getLength()] = sSetEntry; + CustomNotebookbarGenerator::setCustomizedUIItem(aNewEntries, sNotebookbarInterface); + } + else + { + CustomNotebookbarGenerator::setCustomizedUIItem(aOldEntries, sNotebookbarInterface); + } +} + +void SvxNotebookbarEntriesListBox::ChangedVisibility(int nRow) +{ + OUString sUIItemId = m_xControl->get_selected_id(); + OUString sNotebookbarInterface = getFileName(m_pPage->GetFileName()); + + OUString sVisible; + if (m_xControl->get_toggle(nRow) == TRISTATE_TRUE) + sVisible = "True"; + else + sVisible = "False"; + OUString sSetEntries = sUIItemId + ",visible," + sVisible; + Sequence<OUString> sSeqOfEntries{ sSetEntries }; + EditRegistryFile(sUIItemId, sSetEntries, sNotebookbarInterface); + CustomNotebookbarGenerator::modifyCustomizedUIFile(sSeqOfEntries); + OUString sUIPath = "modules/s" + m_pPage->GetAppName().toAsciiLowerCase() + "/ui/"; + sfx2::SfxNotebookBar::ReloadNotebookBar(sUIPath); +} + +IMPL_LINK(SvxNotebookbarEntriesListBox, CheckButtonHdl, const weld::TreeView::iter_col&, rRowCol, + void) +{ + ChangedVisibility(m_xControl->get_iter_index_in_parent(rRowCol.first)); +} + +IMPL_LINK(SvxNotebookbarEntriesListBox, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + if (rKeyEvent.GetKeyCode() == KEY_SPACE) + { + int nRow = m_xControl->get_selected_index(); + m_xControl->set_toggle(nRow, m_xControl->get_toggle(nRow) == TRISTATE_TRUE ? TRISTATE_FALSE + : TRISTATE_TRUE); + ChangedVisibility(nRow); + return true; + } + return SvxMenuEntriesListBox::KeyInputHdl(rKeyEvent); +} + +IMPL_LINK(SvxNotebookbarEntriesListBox, QueryTooltip, const weld::TreeIter&, rIter, OUString) +{ + const OUString& rsCommand = m_aTooltipMap[m_xControl->get_id(rIter)]; + if (rsCommand.isEmpty()) + return OUString(); + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(m_pPage->GetFrame())); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rsCommand, aModuleName); + OUString sTooltipLabel = vcl::CommandInfoProvider::GetTooltipForCommand(rsCommand, aProperties, + m_pPage->GetFrame()); + return CuiResId(RID_CUISTR_COMMANDLABEL) + ": " + + m_xControl->get_text(rIter).replaceFirst("~", "") + "\n" + + CuiResId(RID_CUISTR_COMMANDNAME) + ": " + rsCommand + "\n" + + CuiResId(RID_CUISTR_COMMANDTIP) + ": " + sTooltipLabel.replaceFirst("~", ""); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/SvxToolbarConfigPage.cxx b/cui/source/customize/SvxToolbarConfigPage.cxx new file mode 100644 index 0000000000..035059ea8e --- /dev/null +++ b/cui/source/customize/SvxToolbarConfigPage.cxx @@ -0,0 +1,928 @@ +/* -*- 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 <vcl/event.hxx> +#include <vcl/weld.hxx> +#include <vcl/svapp.hxx> +#include <vcl/commandevent.hxx> + +#include <sfx2/sfxsids.hrc> +#include <svl/stritem.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <algorithm> +#include <helpids.h> +#include <strings.hrc> + +#include <cfg.hxx> +#include <SvxToolbarConfigPage.hxx> +#include <SvxConfigPageHelper.hxx> +#include <dialmgr.hxx> + +#include <dlgname.hxx> +#include <comphelper/processfactory.hxx> + +SvxToolbarConfigPage::SvxToolbarConfigPage(weld::Container* pPage, + weld::DialogController* pController, + const SfxItemSet& rSet) + : SvxConfigPage(pPage, pController, rSet) +{ + m_xGearBtn = m_xBuilder->weld_menu_button("toolbargearbtn"); + m_xGearBtn->show(); + m_xContainer->set_help_id(HID_SVX_CONFIG_TOOLBAR); + + m_xContentsListBox.reset( + new SvxToolbarEntriesListBox(m_xBuilder->weld_tree_view("toolcontents"), this)); + m_xDropTargetHelper.reset( + new SvxConfigPageFunctionDropTarget(*this, m_xContentsListBox->get_widget())); + + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + Size aSize(m_xFunctions->get_size_request()); + rTreeView.set_size_request(aSize.Width(), aSize.Height()); + + rTreeView.set_hexpand(true); + rTreeView.set_vexpand(true); + rTreeView.set_help_id(HID_SVX_CONFIG_TOOLBAR_CONTENTS); + rTreeView.show(); + + rTreeView.connect_changed(LINK(this, SvxToolbarConfigPage, SelectToolbarEntry)); + rTreeView.connect_popup_menu(LINK(this, SvxToolbarConfigPage, ContentContextMenuHdl)); + + m_xFunctions->get_widget().connect_popup_menu( + LINK(this, SvxToolbarConfigPage, FunctionContextMenuHdl)); + + m_xTopLevelListBox->set_help_id(HID_SVX_TOPLEVELLISTBOX); + m_xSaveInListBox->set_help_id(HID_SVX_SAVE_IN); + m_xMoveUpButton->set_help_id(HID_SVX_UP_TOOLBAR_ITEM); + m_xMoveDownButton->set_help_id(HID_SVX_DOWN_TOOLBAR_ITEM); + m_xDescriptionField->set_help_id(HID_SVX_DESCFIELD); + + m_xCommandCategoryListBox->connect_changed(LINK(this, SvxToolbarConfigPage, SelectCategory)); + + m_xGearBtn->connect_selected(LINK(this, SvxToolbarConfigPage, GearHdl)); + + m_xMoveUpButton->connect_clicked(LINK(this, SvxToolbarConfigPage, MoveHdl)); + m_xMoveDownButton->connect_clicked(LINK(this, SvxToolbarConfigPage, MoveHdl)); + // Always enable Up and Down buttons + // added for issue i53677 by shizhoubo + m_xMoveDownButton->set_sensitive(true); + m_xMoveUpButton->set_sensitive(true); + + m_xAddCommandButton->connect_clicked(LINK(this, SvxToolbarConfigPage, AddCommandHdl)); + m_xRemoveCommandButton->connect_clicked(LINK(this, SvxToolbarConfigPage, RemoveCommandHdl)); + + m_xInsertBtn->connect_selected(LINK(this, SvxToolbarConfigPage, InsertHdl)); + m_xModifyBtn->connect_selected(LINK(this, SvxToolbarConfigPage, ModifyItemHdl)); + m_xResetBtn->connect_clicked(LINK(this, SvxToolbarConfigPage, ResetToolbarHdl)); + + // "Insert Submenu" is irrelevant to the toolbars + m_xInsertBtn->remove_item("insertsubmenu"); + + // Gear menu's "Move" action is irrelevant to the toolbars + m_xGearBtn->set_item_sensitive("toolbar_gear_move", false); + + // default toolbar to select is standardbar unless a different one + // has been passed in + m_aURLToSelect = ITEM_TOOLBAR_URL; + m_aURLToSelect += "standardbar"; + + const SfxPoolItem* pItem = rSet.GetItem(SID_CONFIG); + + if (pItem) + { + OUString text = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if (text.startsWith(ITEM_TOOLBAR_URL)) + { + m_aURLToSelect = text.copy(0); + } + } +} + +void SvxToolbarConfigPage::ListModified() +{ + // regenerate with the current ordering within the list + SvxEntries* pEntries = GetTopLevelSelection()->GetEntries(); + pEntries->clear(); + + for (int i = 0; i < m_xContentsListBox->n_children(); ++i) + pEntries->push_back(weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(i))); + + GetSaveInData()->SetModified(); + GetTopLevelSelection()->SetModified(); + + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + if (pToolbar) + static_cast<ToolbarSaveInData*>(GetSaveInData())->ApplyToolbar(pToolbar); +} + +SvxToolbarConfigPage::~SvxToolbarConfigPage() +{ + for (int i = 0, nCount = m_xSaveInListBox->get_count(); i < nCount; ++i) + { + ToolbarSaveInData* pData = weld::fromId<ToolbarSaveInData*>(m_xSaveInListBox->get_id(i)); + delete pData; + } + m_xSaveInListBox->clear(); +} + +void SvxToolbarConfigPage::DeleteSelectedTopLevel() +{ + const sal_Int32 nSelectionPos = m_xTopLevelListBox->get_active(); + ToolbarSaveInData* pSaveInData = static_cast<ToolbarSaveInData*>(GetSaveInData()); + pSaveInData->RemoveToolbar(GetTopLevelSelection()); + + int nCount = m_xTopLevelListBox->get_count(); + if (nCount > 1) + { + // select next entry after the one being deleted + // selection position is indexed from 0 so need to + // subtract one from the entry count + if (nSelectionPos != nCount - 1) + { + m_xTopLevelListBox->set_active(nSelectionPos + 1); + } + else + { + m_xTopLevelListBox->set_active(nSelectionPos - 1); + } + SelectElement(); + + // and now remove the entry + m_xTopLevelListBox->remove(nSelectionPos); + } + else + { + ReloadTopLevelListBox(); + } +} + +void SvxToolbarConfigPage::DeleteSelectedContent() +{ + int nActEntry = m_xContentsListBox->get_selected_index(); + + if (nActEntry == -1) + return; + + // get currently selected entry + SvxConfigEntry* pEntry = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + + // remove entry from the list for this toolbar + SvxConfigPageHelper::RemoveEntry(pToolbar->GetEntries(), pEntry); + + // remove toolbar entry from UI + m_xContentsListBox->remove(nActEntry); + + // delete data for toolbar entry + delete pEntry; + + static_cast<ToolbarSaveInData*>(GetSaveInData())->ApplyToolbar(pToolbar); + UpdateButtonStates(); + + // if this is the last entry in the toolbar and it is a user + // defined toolbar pop up a dialog asking the user if they + // want to delete the toolbar + if (m_xContentsListBox->n_children() == 0 && GetTopLevelSelection()->IsDeletable()) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog( + GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, + CuiResId(RID_SXVSTR_CONFIRM_DELETE_TOOLBAR))); + if (xQueryBox->run() == RET_YES) + { + DeleteSelectedTopLevel(); + } + } +} + +IMPL_LINK(SvxToolbarConfigPage, MoveHdl, weld::Button&, rButton, void) +{ + MoveEntry(&rButton == m_xMoveUpButton.get()); +} + +void SvxToolbarConfigPage::MoveEntry(bool bMoveUp) +{ + SvxConfigPage::MoveEntry(bMoveUp); + + // Apply change to currently selected toolbar + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + if (pToolbar) + static_cast<ToolbarSaveInData*>(GetSaveInData())->ApplyToolbar(pToolbar); + else + { + SAL_WARN("cui.customize", "SvxToolbarConfigPage::MoveEntry(): no entry"); + UpdateButtonStates(); + } +} + +void SvxToolbarConfigPage::Init() +{ + // ensure that the UI is cleared before populating it + m_xTopLevelListBox->clear(); + m_xContentsListBox->clear(); + + ReloadTopLevelListBox(); + + sal_Int32 nCount = m_xTopLevelListBox->get_count(); + sal_Int32 nPos = nCount > 0 ? 0 : -1; + + if (!m_aURLToSelect.isEmpty()) + { + for (sal_Int32 i = 0; i < nCount; ++i) + { + SvxConfigEntry* pData = weld::fromId<SvxConfigEntry*>(m_xTopLevelListBox->get_id(i)); + + if (pData->GetCommand().equals(m_aURLToSelect)) + { + nPos = i; + break; + } + } + + // in future select the default toolbar: Standard + m_aURLToSelect = ITEM_TOOLBAR_URL; + m_aURLToSelect += "standardbar"; + } + + m_xTopLevelListBox->set_active(nPos); + SelectElement(); + + m_xCommandCategoryListBox->Init(comphelper::getProcessComponentContext(), m_xFrame, + m_aModuleId); + m_xCommandCategoryListBox->categorySelected(m_xFunctions.get(), OUString(), GetSaveInData()); + SelectFunctionHdl(m_xFunctions->get_widget()); +} + +SaveInData* SvxToolbarConfigPage::CreateSaveInData( + const css::uno::Reference<css::ui::XUIConfigurationManager>& xCfgMgr, + const css::uno::Reference<css::ui::XUIConfigurationManager>& xParentCfgMgr, + const OUString& aModuleId, bool bDocConfig) +{ + return static_cast<SaveInData*>( + new ToolbarSaveInData(xCfgMgr, xParentCfgMgr, aModuleId, bDocConfig)); +} + +IMPL_LINK_NOARG(SvxToolbarConfigPage, SelectToolbarEntry, weld::TreeView&, void) +{ + UpdateButtonStates(); +} + +IMPL_LINK(SvxToolbarConfigPage, GearHdl, const OUString&, rIdent, void) +{ + SvxConfigEntry* pCurrentToolbar = GetTopLevelSelection(); + + if (rIdent == "toolbar_gear_add") + { + OUString prefix = CuiResId(RID_CUISTR_NEW_TOOLBAR); + + OUString aNewName + = SvxConfigPageHelper::generateCustomName(prefix, GetSaveInData()->GetEntries()); + + OUString aNewURL = SvxConfigPageHelper::generateCustomURL(GetSaveInData()->GetEntries()); + + SvxNewToolbarDialog aNameDialog(GetFrameWeld(), aNewName); + + // Reflect the actual m_xSaveInListBox into the new toolbar dialog + for (int i = 0, nCount = m_xSaveInListBox->get_count(); i < nCount; ++i) + aNameDialog.m_xSaveInListBox->append_text(m_xSaveInListBox->get_text(i)); + + aNameDialog.m_xSaveInListBox->set_active(m_xSaveInListBox->get_active()); + + if (aNameDialog.run() == RET_OK) + { + aNewName = aNameDialog.GetName(); + + // Where to save the new toolbar? (i.e. Modulewise or documentwise) + int nInsertPos = aNameDialog.m_xSaveInListBox->get_active(); + + ToolbarSaveInData* pData + = weld::fromId<ToolbarSaveInData*>(m_xSaveInListBox->get_id(nInsertPos)); + + if (GetSaveInData() != pData) + { + m_xSaveInListBox->set_active(nInsertPos); + SelectSaveInLocation(*m_xSaveInListBox); + } + + SvxConfigEntry* pToolbar = new SvxConfigEntry(aNewName, aNewURL, true, false); + + pToolbar->SetUserDefined(); + pToolbar->SetMain(); + + pData->CreateToolbar(pToolbar); + + OUString sId(weld::toId(pToolbar)); + m_xTopLevelListBox->append(sId, pToolbar->GetName()); + m_xTopLevelListBox->set_active_id(sId); + SelectElement(); + + pData->SetModified(); + } + } + else if (rIdent == "toolbar_gear_delete") + { + if (pCurrentToolbar && pCurrentToolbar->IsDeletable()) + { + DeleteSelectedTopLevel(); + UpdateButtonStates(); + } + } + else if (rIdent == "toolbar_gear_rename") + { + sal_Int32 nSelectionPos = m_xTopLevelListBox->get_active(); + SvxConfigEntry* pToolbar + = weld::fromId<SvxConfigEntry*>(m_xTopLevelListBox->get_id(nSelectionPos)); + ToolbarSaveInData* pSaveInData = static_cast<ToolbarSaveInData*>(GetSaveInData()); + + //Rename the toolbar + OUString sCurrentName(SvxConfigPageHelper::stripHotKey(pToolbar->GetName())); + OUString sDesc = CuiResId(RID_CUISTR_LABEL_NEW_NAME); + + SvxNameDialog aNameDialog(GetFrameWeld(), sCurrentName, sDesc); + aNameDialog.set_help_id(HID_SVX_CONFIG_RENAME_TOOLBAR); + aNameDialog.set_title(CuiResId(RID_CUISTR_RENAME_TOOLBAR)); + + if (aNameDialog.run() == RET_OK) + { + OUString sNewName = aNameDialog.GetName(); + + if (sCurrentName == sNewName) + return; + + pToolbar->SetName(sNewName); + pSaveInData->ApplyToolbar(pToolbar); + + // have to use remove and insert to change the name + m_xTopLevelListBox->remove(nSelectionPos); + OUString sId(weld::toId(pToolbar)); + m_xTopLevelListBox->insert(nSelectionPos, sNewName, &sId, nullptr, nullptr); + m_xTopLevelListBox->set_active_id(sId); + } + } + else if (rIdent == "toolbar_gear_iconOnly" || rIdent == "toolbar_gear_textOnly" + || rIdent == "toolbar_gear_iconAndText") + { + ToolbarSaveInData* pSaveInData = static_cast<ToolbarSaveInData*>(GetSaveInData()); + + if (pCurrentToolbar == nullptr || pSaveInData == nullptr) + { + SAL_WARN("cui.customize", "NULL toolbar or savein data"); + return; + } + + sal_Int32 nStyle = 0; + if (rIdent == "toolbar_gear_iconOnly") + nStyle = 0; + else if (rIdent == "toolbar_gear_textOnly") + nStyle = 1; + else if (rIdent == "toolbar_gear_iconAndText") + nStyle = 2; + + pCurrentToolbar->SetStyle(nStyle); + pSaveInData->SetSystemStyle(m_xFrame, pCurrentToolbar->GetCommand(), nStyle); + + SelectElement(); + } + else + { + //This block should never be reached + SAL_WARN("cui.customize", "Unknown gear menu option: " << rIdent); + return; + } +} + +IMPL_LINK_NOARG(SvxToolbarConfigPage, SelectCategory, weld::ComboBox&, void) +{ + OUString aSearchTerm(m_xSearchEdit->get_text()); + + m_xCommandCategoryListBox->categorySelected(m_xFunctions.get(), aSearchTerm, GetSaveInData()); + + SelectFunctionHdl(m_xFunctions->get_widget()); +} + +IMPL_LINK_NOARG(SvxToolbarConfigPage, AddCommandHdl, weld::Button&, void) { AddFunction(); } + +IMPL_LINK_NOARG(SvxToolbarConfigPage, RemoveCommandHdl, weld::Button&, void) +{ + DeleteSelectedContent(); +} + +IMPL_LINK(SvxToolbarConfigPage, InsertHdl, const OUString&, rIdent, void) +{ + if (rIdent == "insertseparator") + { + // Get the currently selected toolbar + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + + SvxConfigEntry* pNewEntryData = new SvxConfigEntry; + pNewEntryData->SetUserDefined(); + + int nPos = AppendEntry(pNewEntryData, -1); + InsertEntryIntoUI(pNewEntryData, m_xContentsListBox->get_widget(), nPos); + + static_cast<ToolbarSaveInData*>(GetSaveInData())->ApplyToolbar(pToolbar); + + UpdateButtonStates(); + } + else + { + //This block should never be reached + SAL_WARN("cui.customize", "Unknown insert option: " << rIdent); + return; + } +} + +IMPL_LINK(SvxToolbarConfigPage, ModifyItemHdl, const OUString&, rIdent, void) +{ + bool bNeedsApply = false; + + // get currently selected toolbar + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + + if (rIdent.isEmpty() || pToolbar == nullptr) + { + SAL_WARN("cui.customize", "No toolbar selected, or empty rIdent!"); + return; + } + + if (rIdent == "renameItem") + { + int nActEntry = m_xContentsListBox->get_selected_index(); + SvxConfigEntry* pEntry + = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + OUString aNewName(SvxConfigPageHelper::stripHotKey(pEntry->GetName())); + OUString aDesc = CuiResId(RID_CUISTR_LABEL_NEW_NAME); + + SvxNameDialog aNameDialog(GetFrameWeld(), aNewName, aDesc); + aNameDialog.set_help_id(HID_SVX_CONFIG_RENAME_TOOLBAR_ITEM); + aNameDialog.set_title(CuiResId(RID_CUISTR_RENAME_TOOLBAR)); + + if (aNameDialog.run() == RET_OK) + { + aNewName = aNameDialog.GetName(); + + if (aNewName.isEmpty()) // tdf#80758 - Accelerator character ("~") is passed as + pEntry->SetName("~"); // the button name in case of empty values. + else + pEntry->SetName(aNewName); + + m_xContentsListBox->set_text(nActEntry, aNewName, 0); + bNeedsApply = true; + } + } + else if (rIdent == "changeIcon") + { + int nActEntry = m_xContentsListBox->get_selected_index(); + SvxConfigEntry* pEntry + = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + SvxIconSelectorDialog aIconDialog(GetFrameWeld(), GetSaveInData()->GetImageManager(), + GetSaveInData()->GetParentImageManager()); + + if (aIconDialog.run() == RET_OK) + { + css::uno::Reference<css::graphic::XGraphic> newgraphic = aIconDialog.GetSelectedIcon(); + + if (newgraphic.is()) + { + css::uno::Sequence<OUString> aURLSeq{ pEntry->GetCommand() }; + + if (!pEntry->GetBackupGraphic().is()) + { + css::uno::Reference<css::graphic::XGraphic> backup + = SvxConfigPageHelper::GetGraphic(GetSaveInData()->GetImageManager(), + aURLSeq[0]); + + if (backup.is()) + { + pEntry->SetBackupGraphic(backup); + } + } + + css::uno::Sequence<css::uno::Reference<css::graphic::XGraphic>> aGraphicSeq{ + newgraphic + }; + try + { + GetSaveInData()->GetImageManager()->replaceImages( + SvxConfigPageHelper::GetImageType(), aURLSeq, aGraphicSeq); + + m_xContentsListBox->remove(nActEntry); + + OUString sId(weld::toId(pEntry)); + m_xContentsListBox->insert(nActEntry, sId); + m_xContentsListBox->set_toggle(nActEntry, pEntry->IsVisible() ? TRISTATE_TRUE + : TRISTATE_FALSE); + InsertEntryIntoUI(pEntry, m_xContentsListBox->get_widget(), nActEntry); + + m_xContentsListBox->select(nActEntry); + m_xContentsListBox->scroll_to_row(nActEntry); + + GetSaveInData()->PersistChanges(GetSaveInData()->GetImageManager()); + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Error replacing image"); + } + } + } + } + else if (rIdent == "resetIcon") + { + int nActEntry = m_xContentsListBox->get_selected_index(); + SvxConfigEntry* pEntry + = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + css::uno::Reference<css::graphic::XGraphic> backup = pEntry->GetBackupGraphic(); + + css::uno::Sequence<css::uno::Reference<css::graphic::XGraphic>> aGraphicSeq{ backup }; + + css::uno::Sequence<OUString> aURLSeq{ pEntry->GetCommand() }; + + try + { + GetSaveInData()->GetImageManager()->replaceImages(SvxConfigPageHelper::GetImageType(), + aURLSeq, aGraphicSeq); + + m_xContentsListBox->remove(nActEntry); + + OUString sId(weld::toId(pEntry)); + m_xContentsListBox->insert(nActEntry, sId); + m_xContentsListBox->set_toggle(nActEntry, + pEntry->IsVisible() ? TRISTATE_TRUE : TRISTATE_FALSE); + InsertEntryIntoUI(pEntry, m_xContentsListBox->get_widget(), nActEntry); + + m_xContentsListBox->select(nActEntry); + m_xContentsListBox->scroll_to_row(nActEntry); + + // reset backup in entry + pEntry->SetBackupGraphic(css::uno::Reference<css::graphic::XGraphic>()); + + GetSaveInData()->PersistChanges(GetSaveInData()->GetImageManager()); + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Error resetting image"); + } + } + else if (rIdent == "restoreItem") + { + int nActEntry = m_xContentsListBox->get_selected_index(); + SvxConfigEntry* pEntry + = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nActEntry)); + + ToolbarSaveInData* pSaveInData = static_cast<ToolbarSaveInData*>(GetSaveInData()); + + OUString aSystemName = pSaveInData->GetSystemUIName(pEntry->GetCommand()); + + if (!pEntry->GetName().equals(aSystemName)) + { + pEntry->SetName(aSystemName); + m_xContentsListBox->set_text(nActEntry, SvxConfigPageHelper::stripHotKey(aSystemName), + 0); + bNeedsApply = true; + } + + css::uno::Sequence<OUString> aURLSeq{ pEntry->GetCommand() }; + + try + { + GetSaveInData()->GetImageManager()->removeImages(SvxConfigPageHelper::GetImageType(), + aURLSeq); + + // reset backup in entry + pEntry->SetBackupGraphic(css::uno::Reference<css::graphic::XGraphic>()); + + GetSaveInData()->PersistChanges(GetSaveInData()->GetImageManager()); + + m_xContentsListBox->remove(nActEntry); + + OUString sId(weld::toId(pEntry)); + m_xContentsListBox->insert(nActEntry, sId); + m_xContentsListBox->set_toggle(nActEntry, + pEntry->IsVisible() ? TRISTATE_TRUE : TRISTATE_FALSE); + InsertEntryIntoUI(pEntry, m_xContentsListBox->get_widget(), nActEntry); + + m_xContentsListBox->select(nActEntry); + m_xContentsListBox->scroll_to_row(nActEntry); + + bNeedsApply = true; + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Error restoring image"); + } + } + else + { + //This block should never be reached + SAL_WARN("cui.customize", "Unknown insert option: " << rIdent); + return; + } + + if (bNeedsApply) + { + static_cast<ToolbarSaveInData*>(GetSaveInData())->ApplyToolbar(pToolbar); + UpdateButtonStates(); + } +} + +IMPL_LINK_NOARG(SvxToolbarConfigPage, ResetToolbarHdl, weld::Button&, void) +{ + sal_Int32 nSelectionPos = m_xTopLevelListBox->get_active(); + + SvxConfigEntry* pToolbar + = weld::fromId<SvxConfigEntry*>(m_xTopLevelListBox->get_id(nSelectionPos)); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog( + GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, + CuiResId(RID_CUISTR_CONFIRM_RESTORE_DEFAULT))); + if (xQueryBox->run() == RET_YES) + { + ToolbarSaveInData* pSaveInData = static_cast<ToolbarSaveInData*>(GetSaveInData()); + + pSaveInData->RestoreToolbar(pToolbar); + + SelectElement(); + } +} + +void SvxToolbarConfigPage::UpdateButtonStates() +{ + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + int selection = m_xContentsListBox->get_selected_index(); + + bool bIsSeparator + = selection != -1 + && weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(selection))->IsSeparator(); + bool bIsValidSelection = (m_xContentsListBox->n_children() != 0 && selection != -1); + + m_xMoveUpButton->set_sensitive(bIsValidSelection); + m_xMoveDownButton->set_sensitive(bIsValidSelection); + + m_xRemoveCommandButton->set_sensitive(bIsValidSelection); + + m_xModifyBtn->set_sensitive(bIsValidSelection && !bIsSeparator); + + // Handle the gear button + // "toolbar_gear_add" option is always enabled + m_xGearBtn->set_item_sensitive("toolbar_gear_delete", pToolbar && pToolbar->IsDeletable()); + m_xGearBtn->set_item_sensitive("toolbar_gear_rename", pToolbar && pToolbar->IsRenamable()); +} + +short SvxToolbarConfigPage::QueryReset() +{ + OUString msg = CuiResId(RID_CUISTR_CONFIRM_TOOLBAR_RESET); + + OUString saveInName = m_xSaveInListBox->get_active_text(); + + OUString label = SvxConfigPageHelper::replaceSaveInName(msg, saveInName); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog( + GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, label)); + return xQueryBox->run(); +} + +void SvxToolbarConfigPage::SelectElement() +{ + m_xContentsListBox->clear(); + + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + if (pToolbar == nullptr) + { + //TODO: Disable related buttons + m_xInsertBtn->set_sensitive(false); + m_xResetBtn->set_sensitive(false); + m_xGearBtn->set_sensitive(false); + + return; + } + else + { + m_xInsertBtn->set_sensitive(true); + m_xResetBtn->set_sensitive(true); + m_xGearBtn->set_sensitive(true); + } + + switch (pToolbar->GetStyle()) + { + case 0: + { + m_xGearBtn->set_item_active("toolbar_gear_iconOnly", true); + break; + } + case 1: + { + m_xGearBtn->set_item_active("toolbar_gear_textOnly", true); + break; + } + case 2: + { + m_xGearBtn->set_item_active("toolbar_gear_iconAndText", true); + break; + } + } + + int i = 0; + SvxEntries* pEntries = pToolbar->GetEntries(); + for (auto const& entry : *pEntries) + { + OUString sId(weld::toId(entry)); + m_xContentsListBox->insert(i, sId); + if (entry->IsBinding() && !entry->IsSeparator()) + m_xContentsListBox->set_toggle(i, entry->IsVisible() ? TRISTATE_TRUE : TRISTATE_FALSE); + InsertEntryIntoUI(entry, m_xContentsListBox->get_widget(), i); + ++i; + } + + UpdateButtonStates(); +} + +void SvxToolbarConfigPage::AddFunction(int nTarget) +{ + SvxConfigEntry* pToolbar = GetTopLevelSelection(); + + if (pToolbar == nullptr) + return; + + // Add the command to the contents listbox of the selected toolbar + int nNewLBEntry = SvxConfigPage::AddFunction(nTarget, true /*bAllowDuplicates*/); + + if (nNewLBEntry == -1) + return; + + SvxConfigEntry* pEntry = weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nNewLBEntry)); + + if (pEntry->IsBinding()) //TODO sep ? + { + pEntry->SetVisible(true); + m_xContentsListBox->set_toggle(nNewLBEntry, TRISTATE_TRUE); + } + + InsertEntryIntoUI(pEntry, m_xContentsListBox->get_widget(), nNewLBEntry); + + // Changes are not visible on the toolbar until this point + // TODO: Figure out a way to show the changes on the toolbar, but revert if + // the dialog is closed by pressing "Cancel" + // get currently selected toolbar and apply change + if (pToolbar != nullptr) + { + static_cast<ToolbarSaveInData*>(GetSaveInData())->ApplyToolbar(pToolbar); + } +} + +SvxToolbarEntriesListBox::SvxToolbarEntriesListBox(std::unique_ptr<weld::TreeView> xParent, + SvxToolbarConfigPage* pPg) + : SvxMenuEntriesListBox(std::move(xParent), pPg) +{ + m_xControl->connect_toggled(LINK(this, SvxToolbarEntriesListBox, CheckButtonHdl)); + m_xControl->connect_key_press( + Link<const KeyEvent&, bool>()); //acknowledge we first remove the old one + m_xControl->connect_key_press( + LINK(this, SvxToolbarEntriesListBox, KeyInputHdl)); // then add the new one +} + +SvxToolbarEntriesListBox::~SvxToolbarEntriesListBox() {} + +void SvxToolbarEntriesListBox::ChangedVisibility(int nRow) +{ + SvxConfigEntry* pEntryData = weld::fromId<SvxConfigEntry*>(m_xControl->get_id(nRow)); + + if (pEntryData->IsBinding()) + { + pEntryData->SetVisible(m_xControl->get_toggle(nRow) == TRISTATE_TRUE); + + SvxConfigEntry* pToolbar = m_pPage->GetTopLevelSelection(); + + ToolbarSaveInData* pToolbarSaveInData + = static_cast<ToolbarSaveInData*>(m_pPage->GetSaveInData()); + + pToolbarSaveInData->ApplyToolbar(pToolbar); + } +} + +IMPL_LINK(SvxToolbarEntriesListBox, CheckButtonHdl, const weld::TreeView::iter_col&, rRowCol, void) +{ + ChangedVisibility(m_xControl->get_iter_index_in_parent(rRowCol.first)); +} + +IMPL_LINK(SvxToolbarEntriesListBox, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + // space key will change visibility of toolbar items + if (rKeyEvent.GetKeyCode() == KEY_SPACE) + { + int nRow = m_xControl->get_selected_index(); + SvxConfigEntry* pEntryData = weld::fromId<SvxConfigEntry*>(m_xControl->get_id(nRow)); + if (pEntryData->IsBinding() && !pEntryData->IsSeparator()) + { + m_xControl->set_toggle(nRow, m_xControl->get_toggle(nRow) == TRISTATE_TRUE + ? TRISTATE_FALSE + : TRISTATE_TRUE); + ChangedVisibility(nRow); + } + return true; + } + return SvxMenuEntriesListBox::KeyInputHdl(rKeyEvent); +} + +IMPL_LINK(SvxToolbarConfigPage, ContentContextMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + + // Select clicked entry + std::unique_ptr<weld::TreeIter> xIter(rTreeView.make_iterator()); + if (!rTreeView.get_dest_row_at_pos(rCEvt.GetMousePosPixel(), xIter.get(), false)) + return false; + rTreeView.select(*xIter); + SelectToolbarEntry(rTreeView); + + int nSelectIndex = m_xContentsListBox->get_selected_index(); + + bool bIsSeparator + = nSelectIndex != -1 + && weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nSelectIndex))->IsSeparator(); + bool bIsValidSelection = (m_xContentsListBox->n_children() != 0 && nSelectIndex != -1); + + std::unique_ptr<weld::Builder> xBuilder( + Application::CreateBuilder(&rTreeView, "cui/ui/entrycontextmenu.ui")); + auto xContextMenu = xBuilder->weld_menu("menu"); + xContextMenu->set_visible("add", false); + xContextMenu->set_visible("remove", bIsValidSelection); + xContextMenu->set_visible("rename", bIsValidSelection && !bIsSeparator); + xContextMenu->set_visible("changeIcon", bIsValidSelection && !bIsSeparator); + xContextMenu->set_visible("resetIcon", bIsValidSelection && !bIsSeparator); + xContextMenu->set_visible("restoreDefault", bIsValidSelection && !bIsSeparator); + OUString sCommand(xContextMenu->popup_at_rect( + &rTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)))); + + if (sCommand == "remove") + RemoveCommandHdl(*m_xRemoveCommandButton); + else if (sCommand == "rename") + ModifyItemHdl("renameItem"); + else if (sCommand == "changeIcon") + ModifyItemHdl("changeIcon"); + else if (sCommand == "resetIcon") + ModifyItemHdl("resetIcon"); + else if (sCommand == "restoreDefault") + ModifyItemHdl("restoreItem"); + else if (!sCommand.isEmpty()) + SAL_WARN("cui.customize", "Unknown context menu action: " << sCommand); + return true; +} + +IMPL_LINK(SvxToolbarConfigPage, FunctionContextMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + weld::TreeView& rTreeView = m_xFunctions->get_widget(); + + // Select clicked entry + std::unique_ptr<weld::TreeIter> xIter(rTreeView.make_iterator()); + if (!rTreeView.get_dest_row_at_pos(rCEvt.GetMousePosPixel(), xIter.get(), false)) + return false; + rTreeView.select(*xIter); + SelectFunctionHdl(rTreeView); + std::unique_ptr<weld::Builder> xBuilder( + Application::CreateBuilder(&rTreeView, "cui/ui/entrycontextmenu.ui")); + auto xContextMenu = xBuilder->weld_menu("menu"); + xContextMenu->set_visible("add", true); + xContextMenu->set_visible("remove", false); + xContextMenu->set_visible("rename", false); + xContextMenu->set_visible("changeIcon", false); + xContextMenu->set_visible("resetIcon", false); + xContextMenu->set_visible("restoreDefault", false); + OUString sCommand(xContextMenu->popup_at_rect( + &rTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)))); + + if (sCommand == "add") + AddCommandHdl(*m_xAddCommandButton); + else if (!sCommand.isEmpty()) + SAL_WARN("cui.customize", "Unknown context menu action: " << sCommand); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/acccfg.cxx b/cui/source/customize/acccfg.cxx new file mode 100644 index 0000000000..027ac72968 --- /dev/null +++ b/cui/source/customize/acccfg.cxx @@ -0,0 +1,1633 @@ +/* -*- 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 files + +#include <acccfg.hxx> +#include <cfgutil.hxx> +#include <dialmgr.hxx> + +#include <sfx2/filedlghelper.hxx> +#include <sfx2/minfitem.hxx> +#include <sfx2/sfxresid.hxx> + +#include <sal/macros.h> +#include <vcl/event.hxx> + +#include <strings.hrc> +#include <sfx2/strings.hrc> +#include <svx/svxids.hrc> + +// include interface declarations +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/StorageFactory.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/form/XReset.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/theUICommandDescription.hpp> +#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/UIConfigurationManager.hpp> +#include <com/sun/star/ui/XUIConfigurationManager.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> + +// include search util +#include <com/sun/star/util/SearchFlags.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <unotools/textsearch.hxx> + +// include other projects +#include <comphelper/processfactory.hxx> +#include <svtools/acceleratorexecute.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <config_features.h> + +#include <com/sun/star/frame/LayoutManager.hpp> + +// namespaces + +using namespace css; + +constexpr OUString FOLDERNAME_UICONFIG = u"Configurations2"_ustr; + +constexpr OUString MEDIATYPE_PROPNAME = u"MediaType"_ustr; + +const sal_uInt16 KEYCODE_ARRAY[] = { KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_F11, + KEY_F12, + KEY_F13, + KEY_F14, + KEY_F15, + KEY_F16, + + KEY_DOWN, + KEY_UP, + KEY_LEFT, + KEY_RIGHT, + KEY_HOME, + KEY_END, + KEY_PAGEUP, + KEY_PAGEDOWN, + KEY_RETURN, + KEY_ESCAPE, + KEY_BACKSPACE, + KEY_INSERT, + KEY_DELETE, + + KEY_OPEN, + KEY_CUT, + KEY_COPY, + KEY_PASTE, + KEY_UNDO, + KEY_REPEAT, + KEY_FIND, + KEY_PROPERTIES, + KEY_FRONT, + KEY_CONTEXTMENU, + KEY_MENU, + KEY_HELP, + + KEY_SHIFT | KEY_F1, + KEY_SHIFT | KEY_F2, + KEY_SHIFT | KEY_F3, + KEY_SHIFT | KEY_F4, + KEY_SHIFT | KEY_F5, + KEY_SHIFT | KEY_F6, + KEY_SHIFT | KEY_F7, + KEY_SHIFT | KEY_F8, + KEY_SHIFT | KEY_F9, + KEY_SHIFT | KEY_F10, + KEY_SHIFT | KEY_F11, + KEY_SHIFT | KEY_F12, + KEY_SHIFT | KEY_F13, + KEY_SHIFT | KEY_F14, + KEY_SHIFT | KEY_F15, + KEY_SHIFT | KEY_F16, + + KEY_SHIFT | KEY_DOWN, + KEY_SHIFT | KEY_UP, + KEY_SHIFT | KEY_LEFT, + KEY_SHIFT | KEY_RIGHT, + KEY_SHIFT | KEY_HOME, + KEY_SHIFT | KEY_END, + KEY_SHIFT | KEY_PAGEUP, + KEY_SHIFT | KEY_PAGEDOWN, + KEY_SHIFT | KEY_RETURN, + KEY_SHIFT | KEY_SPACE, + KEY_SHIFT | KEY_ESCAPE, + KEY_SHIFT | KEY_BACKSPACE, + KEY_SHIFT | KEY_INSERT, + KEY_SHIFT | KEY_DELETE, + KEY_SHIFT | KEY_EQUAL, + + KEY_MOD1 | KEY_0, + KEY_MOD1 | KEY_1, + KEY_MOD1 | KEY_2, + KEY_MOD1 | KEY_3, + KEY_MOD1 | KEY_4, + KEY_MOD1 | KEY_5, + KEY_MOD1 | KEY_6, + KEY_MOD1 | KEY_7, + KEY_MOD1 | KEY_8, + KEY_MOD1 | KEY_9, + KEY_MOD1 | KEY_A, + KEY_MOD1 | KEY_B, + KEY_MOD1 | KEY_C, + KEY_MOD1 | KEY_D, + KEY_MOD1 | KEY_E, + KEY_MOD1 | KEY_F, + KEY_MOD1 | KEY_G, + KEY_MOD1 | KEY_H, + KEY_MOD1 | KEY_I, + KEY_MOD1 | KEY_J, + KEY_MOD1 | KEY_K, + KEY_MOD1 | KEY_L, + KEY_MOD1 | KEY_M, + KEY_MOD1 | KEY_N, + KEY_MOD1 | KEY_O, + KEY_MOD1 | KEY_P, + KEY_MOD1 | KEY_Q, + KEY_MOD1 | KEY_R, + KEY_MOD1 | KEY_S, + KEY_MOD1 | KEY_T, + KEY_MOD1 | KEY_U, + KEY_MOD1 | KEY_V, + KEY_MOD1 | KEY_W, + KEY_MOD1 | KEY_X, + KEY_MOD1 | KEY_Y, + KEY_MOD1 | KEY_Z, + KEY_MOD1 | KEY_NUMBERSIGN, + KEY_MOD1 | KEY_COLON, + KEY_MOD1 | KEY_SEMICOLON, + KEY_MOD1 | KEY_QUOTELEFT, + KEY_MOD1 | KEY_QUOTERIGHT, + KEY_MOD1 | KEY_BRACKETLEFT, + KEY_MOD1 | KEY_BRACKETRIGHT, + KEY_MOD1 | KEY_RIGHTCURLYBRACKET, + KEY_MOD1 | KEY_POINT, + KEY_MOD1 | KEY_COMMA, + KEY_MOD1 | KEY_TILDE, + KEY_MOD1 | KEY_TAB, + + KEY_MOD1 | KEY_F1, + KEY_MOD1 | KEY_F2, + KEY_MOD1 | KEY_F3, + KEY_MOD1 | KEY_F4, + KEY_MOD1 | KEY_F5, + KEY_MOD1 | KEY_F6, + KEY_MOD1 | KEY_F7, + KEY_MOD1 | KEY_F8, + KEY_MOD1 | KEY_F9, + KEY_MOD1 | KEY_F10, + KEY_MOD1 | KEY_F11, + KEY_MOD1 | KEY_F12, + KEY_MOD1 | KEY_F13, + KEY_MOD1 | KEY_F14, + KEY_MOD1 | KEY_F15, + KEY_MOD1 | KEY_F16, + + KEY_MOD1 | KEY_DOWN, + KEY_MOD1 | KEY_UP, + KEY_MOD1 | KEY_LEFT, + KEY_MOD1 | KEY_RIGHT, + KEY_MOD1 | KEY_HOME, + KEY_MOD1 | KEY_END, + KEY_MOD1 | KEY_PAGEUP, + KEY_MOD1 | KEY_PAGEDOWN, + KEY_MOD1 | KEY_RETURN, + KEY_MOD1 | KEY_SPACE, + KEY_MOD1 | KEY_BACKSPACE, + KEY_MOD1 | KEY_INSERT, + KEY_MOD1 | KEY_DELETE, + + KEY_MOD1 | KEY_ADD, + KEY_MOD1 | KEY_SUBTRACT, + KEY_MOD1 | KEY_MULTIPLY, + KEY_MOD1 | KEY_DIVIDE, + KEY_MOD1 | KEY_EQUAL, + + KEY_SHIFT | KEY_MOD1 | KEY_0, + KEY_SHIFT | KEY_MOD1 | KEY_1, + KEY_SHIFT | KEY_MOD1 | KEY_2, + KEY_SHIFT | KEY_MOD1 | KEY_3, + KEY_SHIFT | KEY_MOD1 | KEY_4, + KEY_SHIFT | KEY_MOD1 | KEY_5, + KEY_SHIFT | KEY_MOD1 | KEY_6, + KEY_SHIFT | KEY_MOD1 | KEY_7, + KEY_SHIFT | KEY_MOD1 | KEY_8, + KEY_SHIFT | KEY_MOD1 | KEY_9, + KEY_SHIFT | KEY_MOD1 | KEY_A, + KEY_SHIFT | KEY_MOD1 | KEY_B, + KEY_SHIFT | KEY_MOD1 | KEY_C, + KEY_SHIFT | KEY_MOD1 | KEY_D, + KEY_SHIFT | KEY_MOD1 | KEY_E, + KEY_SHIFT | KEY_MOD1 | KEY_F, + KEY_SHIFT | KEY_MOD1 | KEY_G, + KEY_SHIFT | KEY_MOD1 | KEY_H, + KEY_SHIFT | KEY_MOD1 | KEY_I, + KEY_SHIFT | KEY_MOD1 | KEY_J, + KEY_SHIFT | KEY_MOD1 | KEY_K, + KEY_SHIFT | KEY_MOD1 | KEY_L, + KEY_SHIFT | KEY_MOD1 | KEY_M, + KEY_SHIFT | KEY_MOD1 | KEY_N, + KEY_SHIFT | KEY_MOD1 | KEY_O, + KEY_SHIFT | KEY_MOD1 | KEY_P, + KEY_SHIFT | KEY_MOD1 | KEY_Q, + KEY_SHIFT | KEY_MOD1 | KEY_R, + KEY_SHIFT | KEY_MOD1 | KEY_S, + KEY_SHIFT | KEY_MOD1 | KEY_T, + KEY_SHIFT | KEY_MOD1 | KEY_U, + KEY_SHIFT | KEY_MOD1 | KEY_V, + KEY_SHIFT | KEY_MOD1 | KEY_W, + KEY_SHIFT | KEY_MOD1 | KEY_X, + KEY_SHIFT | KEY_MOD1 | KEY_Y, + KEY_SHIFT | KEY_MOD1 | KEY_Z, + KEY_SHIFT | KEY_MOD1 | KEY_NUMBERSIGN, + KEY_SHIFT | KEY_MOD1 | KEY_COLON, + KEY_SHIFT | KEY_MOD1 | KEY_SEMICOLON, + KEY_SHIFT | KEY_MOD1 | KEY_QUOTELEFT, + KEY_SHIFT | KEY_MOD1 | KEY_QUOTERIGHT, + KEY_SHIFT | KEY_MOD1 | KEY_BRACKETLEFT, + KEY_SHIFT | KEY_MOD1 | KEY_BRACKETRIGHT, + KEY_SHIFT | KEY_MOD1 | KEY_RIGHTCURLYBRACKET, + KEY_SHIFT | KEY_MOD1 | KEY_POINT, + KEY_SHIFT | KEY_MOD1 | KEY_COMMA, + KEY_SHIFT | KEY_MOD1 | KEY_TILDE, + KEY_SHIFT | KEY_MOD1 | KEY_TAB, + + KEY_SHIFT | KEY_MOD1 | KEY_F1, + KEY_SHIFT | KEY_MOD1 | KEY_F2, + KEY_SHIFT | KEY_MOD1 | KEY_F3, + KEY_SHIFT | KEY_MOD1 | KEY_F4, + KEY_SHIFT | KEY_MOD1 | KEY_F5, + KEY_SHIFT | KEY_MOD1 | KEY_F6, + KEY_SHIFT | KEY_MOD1 | KEY_F7, + KEY_SHIFT | KEY_MOD1 | KEY_F8, + KEY_SHIFT | KEY_MOD1 | KEY_F9, + KEY_SHIFT | KEY_MOD1 | KEY_F10, + KEY_SHIFT | KEY_MOD1 | KEY_F11, + KEY_SHIFT | KEY_MOD1 | KEY_F12, + KEY_SHIFT | KEY_MOD1 | KEY_F13, + KEY_SHIFT | KEY_MOD1 | KEY_F14, + KEY_SHIFT | KEY_MOD1 | KEY_F15, + KEY_SHIFT | KEY_MOD1 | KEY_F16, + + KEY_SHIFT | KEY_MOD1 | KEY_DOWN, + KEY_SHIFT | KEY_MOD1 | KEY_UP, + KEY_SHIFT | KEY_MOD1 | KEY_LEFT, + KEY_SHIFT | KEY_MOD1 | KEY_RIGHT, + KEY_SHIFT | KEY_MOD1 | KEY_HOME, + KEY_SHIFT | KEY_MOD1 | KEY_END, + KEY_SHIFT | KEY_MOD1 | KEY_PAGEUP, + KEY_SHIFT | KEY_MOD1 | KEY_PAGEDOWN, + KEY_SHIFT | KEY_MOD1 | KEY_RETURN, + KEY_SHIFT | KEY_MOD1 | KEY_ESCAPE, + KEY_SHIFT | KEY_MOD1 | KEY_SPACE, + KEY_SHIFT | KEY_MOD1 | KEY_BACKSPACE, + KEY_SHIFT | KEY_MOD1 | KEY_INSERT, + KEY_SHIFT | KEY_MOD1 | KEY_DELETE, + KEY_SHIFT | KEY_MOD1 | KEY_EQUAL, + + KEY_MOD2 | KEY_0, + KEY_MOD2 | KEY_1, + KEY_MOD2 | KEY_2, + KEY_MOD2 | KEY_3, + KEY_MOD2 | KEY_4, + KEY_MOD2 | KEY_5, + KEY_MOD2 | KEY_6, + KEY_MOD2 | KEY_7, + KEY_MOD2 | KEY_8, + KEY_MOD2 | KEY_9, + KEY_MOD2 | KEY_A, + KEY_MOD2 | KEY_B, + KEY_MOD2 | KEY_C, + KEY_MOD2 | KEY_D, + KEY_MOD2 | KEY_E, + KEY_MOD2 | KEY_F, + KEY_MOD2 | KEY_G, + KEY_MOD2 | KEY_H, + KEY_MOD2 | KEY_I, + KEY_MOD2 | KEY_J, + KEY_MOD2 | KEY_K, + KEY_MOD2 | KEY_L, + KEY_MOD2 | KEY_M, + KEY_MOD2 | KEY_N, + KEY_MOD2 | KEY_O, + KEY_MOD2 | KEY_P, + KEY_MOD2 | KEY_Q, + KEY_MOD2 | KEY_R, + KEY_MOD2 | KEY_S, + KEY_MOD2 | KEY_T, + KEY_MOD2 | KEY_U, + KEY_MOD2 | KEY_V, + KEY_MOD2 | KEY_W, + KEY_MOD2 | KEY_X, + KEY_MOD2 | KEY_Y, + KEY_MOD2 | KEY_Z, + KEY_MOD2 | KEY_NUMBERSIGN, + KEY_MOD2 | KEY_COLON, + KEY_MOD2 | KEY_SEMICOLON, + KEY_MOD2 | KEY_QUOTELEFT, + KEY_MOD2 | KEY_QUOTERIGHT, + KEY_MOD2 | KEY_BRACKETLEFT, + KEY_MOD2 | KEY_BRACKETRIGHT, + KEY_MOD2 | KEY_RIGHTCURLYBRACKET, + KEY_MOD2 | KEY_POINT, + KEY_MOD2 | KEY_COMMA, + KEY_MOD2 | KEY_TILDE, + + KEY_MOD2 | KEY_F1, + KEY_MOD2 | KEY_F2, + KEY_MOD2 | KEY_F3, + KEY_MOD2 | KEY_F4, + KEY_MOD2 | KEY_F5, + KEY_MOD2 | KEY_F6, + KEY_MOD2 | KEY_F7, + KEY_MOD2 | KEY_F8, + KEY_MOD2 | KEY_F9, + KEY_MOD2 | KEY_F10, + KEY_MOD2 | KEY_F11, + KEY_MOD2 | KEY_F12, + KEY_MOD2 | KEY_F13, + KEY_MOD2 | KEY_F14, + KEY_MOD2 | KEY_F15, + KEY_MOD2 | KEY_F16, + + KEY_MOD2 | KEY_DOWN, + KEY_MOD2 | KEY_UP, + KEY_MOD2 | KEY_LEFT, + KEY_MOD2 | KEY_RIGHT, + KEY_MOD2 | KEY_HOME, + KEY_MOD2 | KEY_END, + KEY_MOD2 | KEY_PAGEUP, + KEY_MOD2 | KEY_PAGEDOWN, + KEY_MOD2 | KEY_RETURN, + KEY_MOD2 | KEY_SPACE, + KEY_MOD2 | KEY_BACKSPACE, + KEY_MOD2 | KEY_INSERT, + KEY_MOD2 | KEY_DELETE, + KEY_MOD2 | KEY_EQUAL, + + KEY_SHIFT | KEY_MOD2 | KEY_0, + KEY_SHIFT | KEY_MOD2 | KEY_1, + KEY_SHIFT | KEY_MOD2 | KEY_2, + KEY_SHIFT | KEY_MOD2 | KEY_3, + KEY_SHIFT | KEY_MOD2 | KEY_4, + KEY_SHIFT | KEY_MOD2 | KEY_5, + KEY_SHIFT | KEY_MOD2 | KEY_6, + KEY_SHIFT | KEY_MOD2 | KEY_7, + KEY_SHIFT | KEY_MOD2 | KEY_8, + KEY_SHIFT | KEY_MOD2 | KEY_9, + KEY_SHIFT | KEY_MOD2 | KEY_A, + KEY_SHIFT | KEY_MOD2 | KEY_B, + KEY_SHIFT | KEY_MOD2 | KEY_C, + KEY_SHIFT | KEY_MOD2 | KEY_D, + KEY_SHIFT | KEY_MOD2 | KEY_E, + KEY_SHIFT | KEY_MOD2 | KEY_F, + KEY_SHIFT | KEY_MOD2 | KEY_G, + KEY_SHIFT | KEY_MOD2 | KEY_H, + KEY_SHIFT | KEY_MOD2 | KEY_I, + KEY_SHIFT | KEY_MOD2 | KEY_J, + KEY_SHIFT | KEY_MOD2 | KEY_K, + KEY_SHIFT | KEY_MOD2 | KEY_L, + KEY_SHIFT | KEY_MOD2 | KEY_M, + KEY_SHIFT | KEY_MOD2 | KEY_N, + KEY_SHIFT | KEY_MOD2 | KEY_O, + KEY_SHIFT | KEY_MOD2 | KEY_P, + KEY_SHIFT | KEY_MOD2 | KEY_Q, + KEY_SHIFT | KEY_MOD2 | KEY_R, + KEY_SHIFT | KEY_MOD2 | KEY_S, + KEY_SHIFT | KEY_MOD2 | KEY_T, + KEY_SHIFT | KEY_MOD2 | KEY_U, + KEY_SHIFT | KEY_MOD2 | KEY_V, + KEY_SHIFT | KEY_MOD2 | KEY_W, + KEY_SHIFT | KEY_MOD2 | KEY_X, + KEY_SHIFT | KEY_MOD2 | KEY_Y, + KEY_SHIFT | KEY_MOD2 | KEY_Z, + KEY_SHIFT | KEY_MOD2 | KEY_NUMBERSIGN, + KEY_SHIFT | KEY_MOD2 | KEY_COLON, + KEY_SHIFT | KEY_MOD2 | KEY_SEMICOLON, + KEY_SHIFT | KEY_MOD2 | KEY_QUOTELEFT, + KEY_SHIFT | KEY_MOD2 | KEY_QUOTERIGHT, + KEY_SHIFT | KEY_MOD2 | KEY_BRACKETLEFT, + KEY_SHIFT | KEY_MOD2 | KEY_BRACKETRIGHT, + KEY_SHIFT | KEY_MOD2 | KEY_RIGHTCURLYBRACKET, + KEY_SHIFT | KEY_MOD2 | KEY_POINT, + KEY_SHIFT | KEY_MOD2 | KEY_COMMA, + KEY_SHIFT | KEY_MOD2 | KEY_TILDE, + + KEY_SHIFT | KEY_MOD2 | KEY_F1, + KEY_SHIFT | KEY_MOD2 | KEY_F2, + KEY_SHIFT | KEY_MOD2 | KEY_F3, + KEY_SHIFT | KEY_MOD2 | KEY_F4, + KEY_SHIFT | KEY_MOD2 | KEY_F5, + KEY_SHIFT | KEY_MOD2 | KEY_F6, + KEY_SHIFT | KEY_MOD2 | KEY_F7, + KEY_SHIFT | KEY_MOD2 | KEY_F8, + KEY_SHIFT | KEY_MOD2 | KEY_F9, + KEY_SHIFT | KEY_MOD2 | KEY_F10, + KEY_SHIFT | KEY_MOD2 | KEY_F11, + KEY_SHIFT | KEY_MOD2 | KEY_F12, + KEY_SHIFT | KEY_MOD2 | KEY_F13, + KEY_SHIFT | KEY_MOD2 | KEY_F14, + KEY_SHIFT | KEY_MOD2 | KEY_F15, + KEY_SHIFT | KEY_MOD2 | KEY_F16, + + KEY_SHIFT | KEY_MOD2 | KEY_DOWN, + KEY_SHIFT | KEY_MOD2 | KEY_UP, + KEY_SHIFT | KEY_MOD2 | KEY_LEFT, + KEY_SHIFT | KEY_MOD2 | KEY_RIGHT, + KEY_SHIFT | KEY_MOD2 | KEY_HOME, + KEY_SHIFT | KEY_MOD2 | KEY_END, + KEY_SHIFT | KEY_MOD2 | KEY_PAGEUP, + KEY_SHIFT | KEY_MOD2 | KEY_PAGEDOWN, + KEY_SHIFT | KEY_MOD2 | KEY_RETURN, + KEY_SHIFT | KEY_MOD2 | KEY_ESCAPE, + KEY_SHIFT | KEY_MOD2 | KEY_SPACE, + KEY_SHIFT | KEY_MOD2 | KEY_BACKSPACE, + KEY_SHIFT | KEY_MOD2 | KEY_INSERT, + KEY_SHIFT | KEY_MOD2 | KEY_DELETE, + KEY_SHIFT | KEY_MOD2 | KEY_EQUAL, + + KEY_MOD1 | KEY_MOD2 | KEY_0, + KEY_MOD1 | KEY_MOD2 | KEY_1, + KEY_MOD1 | KEY_MOD2 | KEY_2, + KEY_MOD1 | KEY_MOD2 | KEY_3, + KEY_MOD1 | KEY_MOD2 | KEY_4, + KEY_MOD1 | KEY_MOD2 | KEY_5, + KEY_MOD1 | KEY_MOD2 | KEY_6, + KEY_MOD1 | KEY_MOD2 | KEY_7, + KEY_MOD1 | KEY_MOD2 | KEY_8, + KEY_MOD1 | KEY_MOD2 | KEY_9, + KEY_MOD1 | KEY_MOD2 | KEY_A, + KEY_MOD1 | KEY_MOD2 | KEY_B, + KEY_MOD1 | KEY_MOD2 | KEY_C, + KEY_MOD1 | KEY_MOD2 | KEY_D, + KEY_MOD1 | KEY_MOD2 | KEY_E, + KEY_MOD1 | KEY_MOD2 | KEY_F, + KEY_MOD1 | KEY_MOD2 | KEY_G, + KEY_MOD1 | KEY_MOD2 | KEY_H, + KEY_MOD1 | KEY_MOD2 | KEY_I, + KEY_MOD1 | KEY_MOD2 | KEY_J, + KEY_MOD1 | KEY_MOD2 | KEY_K, + KEY_MOD1 | KEY_MOD2 | KEY_L, + KEY_MOD1 | KEY_MOD2 | KEY_M, + KEY_MOD1 | KEY_MOD2 | KEY_N, + KEY_MOD1 | KEY_MOD2 | KEY_O, + KEY_MOD1 | KEY_MOD2 | KEY_P, + KEY_MOD1 | KEY_MOD2 | KEY_Q, + KEY_MOD1 | KEY_MOD2 | KEY_R, + KEY_MOD1 | KEY_MOD2 | KEY_S, + KEY_MOD1 | KEY_MOD2 | KEY_T, + KEY_MOD1 | KEY_MOD2 | KEY_U, + KEY_MOD1 | KEY_MOD2 | KEY_V, + KEY_MOD1 | KEY_MOD2 | KEY_W, + KEY_MOD1 | KEY_MOD2 | KEY_X, + KEY_MOD1 | KEY_MOD2 | KEY_Y, + KEY_MOD1 | KEY_MOD2 | KEY_Z, + KEY_MOD1 | KEY_MOD2 | KEY_NUMBERSIGN, + KEY_MOD1 | KEY_MOD2 | KEY_COLON, + KEY_MOD1 | KEY_MOD2 | KEY_SEMICOLON, + KEY_MOD1 | KEY_MOD2 | KEY_QUOTELEFT, + KEY_MOD1 | KEY_MOD2 | KEY_QUOTERIGHT, + KEY_MOD1 | KEY_MOD2 | KEY_BRACKETLEFT, + KEY_MOD1 | KEY_MOD2 | KEY_BRACKETRIGHT, + KEY_MOD1 | KEY_MOD2 | KEY_RIGHTCURLYBRACKET, + KEY_MOD1 | KEY_MOD2 | KEY_POINT, + KEY_MOD1 | KEY_MOD2 | KEY_COMMA, + KEY_MOD1 | KEY_MOD2 | KEY_TILDE, + KEY_MOD1 | KEY_MOD2 | KEY_EQUAL, + + KEY_MOD1 | KEY_MOD2 | KEY_F1, + KEY_MOD1 | KEY_MOD2 | KEY_F2, + KEY_MOD1 | KEY_MOD2 | KEY_F3, + KEY_MOD1 | KEY_MOD2 | KEY_F4, + KEY_MOD1 | KEY_MOD2 | KEY_F5, + KEY_MOD1 | KEY_MOD2 | KEY_F6, + KEY_MOD1 | KEY_MOD2 | KEY_F7, + KEY_MOD1 | KEY_MOD2 | KEY_F8, + KEY_MOD1 | KEY_MOD2 | KEY_F9, + KEY_MOD1 | KEY_MOD2 | KEY_F10, + KEY_MOD1 | KEY_MOD2 | KEY_F11, + KEY_MOD1 | KEY_MOD2 | KEY_F12, + KEY_MOD1 | KEY_MOD2 | KEY_F13, + KEY_MOD1 | KEY_MOD2 | KEY_F14, + KEY_MOD1 | KEY_MOD2 | KEY_F15, + KEY_MOD1 | KEY_MOD2 | KEY_F16, + + KEY_MOD1 | KEY_MOD2 | KEY_DOWN, + KEY_MOD1 | KEY_MOD2 | KEY_UP, + KEY_MOD1 | KEY_MOD2 | KEY_LEFT, + KEY_MOD1 | KEY_MOD2 | KEY_RIGHT, + KEY_MOD1 | KEY_MOD2 | KEY_HOME, + KEY_MOD1 | KEY_MOD2 | KEY_END, + KEY_MOD1 | KEY_MOD2 | KEY_PAGEUP, + KEY_MOD1 | KEY_MOD2 | KEY_PAGEDOWN, + KEY_MOD1 | KEY_MOD2 | KEY_RETURN, + KEY_MOD1 | KEY_MOD2 | KEY_SPACE, + KEY_MOD1 | KEY_MOD2 | KEY_BACKSPACE, + KEY_MOD1 | KEY_MOD2 | KEY_INSERT, + KEY_MOD1 | KEY_MOD2 | KEY_DELETE, + + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_0, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_1, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_2, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_3, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_4, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_5, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_6, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_7, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_8, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_9, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_A, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_B, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_C, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_D, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_E, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_G, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_H, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_I, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_J, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_K, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_L, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_M, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_N, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_O, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_P, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_Q, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_R, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_S, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_T, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_U, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_V, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_W, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_X, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_Y, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_Z, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_NUMBERSIGN, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_COLON, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_SEMICOLON, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_QUOTELEFT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_QUOTERIGHT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_BRACKETLEFT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_BRACKETRIGHT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_RIGHTCURLYBRACKET, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_POINT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_COMMA, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_TILDE, + + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F1, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F2, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F3, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F4, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F5, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F6, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F7, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F8, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F9, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F10, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F11, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F12, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F13, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F14, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F15, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_F16, + + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_DOWN, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_UP, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_LEFT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_RIGHT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_HOME, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_END, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_PAGEUP, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_PAGEDOWN, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_RETURN, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_SPACE, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_BACKSPACE, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_INSERT, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_DELETE, + KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_EQUAL + +#ifdef __APPLE__ + , + KEY_MOD3 | KEY_0, + KEY_MOD3 | KEY_1, + KEY_MOD3 | KEY_2, + KEY_MOD3 | KEY_3, + KEY_MOD3 | KEY_4, + KEY_MOD3 | KEY_5, + KEY_MOD3 | KEY_6, + KEY_MOD3 | KEY_7, + KEY_MOD3 | KEY_8, + KEY_MOD3 | KEY_9, + KEY_MOD3 | KEY_A, + KEY_MOD3 | KEY_B, + KEY_MOD3 | KEY_C, + KEY_MOD3 | KEY_D, + KEY_MOD3 | KEY_E, + KEY_MOD3 | KEY_F, + KEY_MOD3 | KEY_G, + KEY_MOD3 | KEY_H, + KEY_MOD3 | KEY_I, + KEY_MOD3 | KEY_J, + KEY_MOD3 | KEY_K, + KEY_MOD3 | KEY_L, + KEY_MOD3 | KEY_M, + KEY_MOD3 | KEY_N, + KEY_MOD3 | KEY_O, + KEY_MOD3 | KEY_P, + KEY_MOD3 | KEY_Q, + KEY_MOD3 | KEY_R, + KEY_MOD3 | KEY_S, + KEY_MOD3 | KEY_T, + KEY_MOD3 | KEY_U, + KEY_MOD3 | KEY_V, + KEY_MOD3 | KEY_W, + KEY_MOD3 | KEY_X, + KEY_MOD3 | KEY_Y, + KEY_MOD3 | KEY_Z, + KEY_MOD2 | KEY_NUMBERSIGN, + KEY_MOD3 | KEY_COLON, + KEY_MOD3 | KEY_SEMICOLON, + KEY_MOD3 | KEY_QUOTELEFT, + KEY_MOD3 | KEY_QUOTERIGHT, + KEY_MOD3 | KEY_BRACKETLEFT, + KEY_MOD3 | KEY_BRACKETRIGHT, + KEY_MOD3 | KEY_RIGHTCURLYBRACKET, + KEY_MOD3 | KEY_POINT, + KEY_MOD3 | KEY_COMMA, + KEY_MOD3 | KEY_TILDE, + KEY_MOD3 | KEY_TAB, + + KEY_MOD3 | KEY_F1, + KEY_MOD3 | KEY_F2, + KEY_MOD3 | KEY_F3, + KEY_MOD3 | KEY_F4, + KEY_MOD3 | KEY_F5, + KEY_MOD3 | KEY_F6, + KEY_MOD3 | KEY_F7, + KEY_MOD3 | KEY_F8, + KEY_MOD3 | KEY_F9, + KEY_MOD3 | KEY_F10, + KEY_MOD3 | KEY_F11, + KEY_MOD3 | KEY_F12, + KEY_MOD3 | KEY_F13, + KEY_MOD3 | KEY_F14, + KEY_MOD3 | KEY_F15, + KEY_MOD3 | KEY_F16, + + KEY_MOD3 | KEY_DOWN, + KEY_MOD3 | KEY_UP, + KEY_MOD3 | KEY_LEFT, + KEY_MOD3 | KEY_RIGHT, + KEY_MOD3 | KEY_HOME, + KEY_MOD3 | KEY_END, + KEY_MOD3 | KEY_PAGEUP, + KEY_MOD3 | KEY_PAGEDOWN, + KEY_MOD3 | KEY_RETURN, + KEY_MOD3 | KEY_SPACE, + KEY_MOD3 | KEY_BACKSPACE, + KEY_MOD3 | KEY_INSERT, + KEY_MOD3 | KEY_DELETE, + + KEY_MOD3 | KEY_ADD, + KEY_MOD3 | KEY_SUBTRACT, + KEY_MOD3 | KEY_MULTIPLY, + KEY_MOD3 | KEY_DIVIDE, + KEY_MOD3 | KEY_EQUAL, + + KEY_SHIFT | KEY_MOD3 | KEY_0, + KEY_SHIFT | KEY_MOD3 | KEY_1, + KEY_SHIFT | KEY_MOD3 | KEY_2, + KEY_SHIFT | KEY_MOD3 | KEY_3, + KEY_SHIFT | KEY_MOD3 | KEY_4, + KEY_SHIFT | KEY_MOD3 | KEY_5, + KEY_SHIFT | KEY_MOD3 | KEY_6, + KEY_SHIFT | KEY_MOD3 | KEY_7, + KEY_SHIFT | KEY_MOD3 | KEY_8, + KEY_SHIFT | KEY_MOD3 | KEY_9, + KEY_SHIFT | KEY_MOD3 | KEY_A, + KEY_SHIFT | KEY_MOD3 | KEY_B, + KEY_SHIFT | KEY_MOD3 | KEY_C, + KEY_SHIFT | KEY_MOD3 | KEY_D, + KEY_SHIFT | KEY_MOD3 | KEY_E, + KEY_SHIFT | KEY_MOD3 | KEY_F, + KEY_SHIFT | KEY_MOD3 | KEY_G, + KEY_SHIFT | KEY_MOD3 | KEY_H, + KEY_SHIFT | KEY_MOD3 | KEY_I, + KEY_SHIFT | KEY_MOD3 | KEY_J, + KEY_SHIFT | KEY_MOD3 | KEY_K, + KEY_SHIFT | KEY_MOD3 | KEY_L, + KEY_SHIFT | KEY_MOD3 | KEY_M, + KEY_SHIFT | KEY_MOD3 | KEY_N, + KEY_SHIFT | KEY_MOD3 | KEY_O, + KEY_SHIFT | KEY_MOD3 | KEY_P, + KEY_SHIFT | KEY_MOD3 | KEY_Q, + KEY_SHIFT | KEY_MOD3 | KEY_R, + KEY_SHIFT | KEY_MOD3 | KEY_S, + KEY_SHIFT | KEY_MOD3 | KEY_T, + KEY_SHIFT | KEY_MOD3 | KEY_U, + KEY_SHIFT | KEY_MOD3 | KEY_V, + KEY_SHIFT | KEY_MOD3 | KEY_W, + KEY_SHIFT | KEY_MOD3 | KEY_X, + KEY_SHIFT | KEY_MOD3 | KEY_Y, + KEY_SHIFT | KEY_MOD3 | KEY_Z, + KEY_SHIFT | KEY_MOD3 | KEY_NUMBERSIGN, + KEY_SHIFT | KEY_MOD3 | KEY_COLON, + KEY_SHIFT | KEY_MOD3 | KEY_SEMICOLON, + KEY_SHIFT | KEY_MOD3 | KEY_QUOTELEFT, + KEY_SHIFT | KEY_MOD3 | KEY_QUOTERIGHT, + KEY_SHIFT | KEY_MOD3 | KEY_BRACKETLEFT, + KEY_SHIFT | KEY_MOD3 | KEY_BRACKETRIGHT, + KEY_SHIFT | KEY_MOD3 | KEY_RIGHTCURLYBRACKET, + KEY_SHIFT | KEY_MOD3 | KEY_POINT, + KEY_SHIFT | KEY_MOD3 | KEY_COMMA, + KEY_SHIFT | KEY_MOD3 | KEY_TILDE, + KEY_SHIFT | KEY_MOD3 | KEY_TAB, + + KEY_SHIFT | KEY_MOD3 | KEY_F1, + KEY_SHIFT | KEY_MOD3 | KEY_F2, + KEY_SHIFT | KEY_MOD3 | KEY_F3, + KEY_SHIFT | KEY_MOD3 | KEY_F4, + KEY_SHIFT | KEY_MOD3 | KEY_F5, + KEY_SHIFT | KEY_MOD3 | KEY_F6, + KEY_SHIFT | KEY_MOD3 | KEY_F7, + KEY_SHIFT | KEY_MOD3 | KEY_F8, + KEY_SHIFT | KEY_MOD3 | KEY_F9, + KEY_SHIFT | KEY_MOD3 | KEY_F10, + KEY_SHIFT | KEY_MOD3 | KEY_F11, + KEY_SHIFT | KEY_MOD3 | KEY_F12, + KEY_SHIFT | KEY_MOD3 | KEY_F13, + KEY_SHIFT | KEY_MOD3 | KEY_F14, + KEY_SHIFT | KEY_MOD3 | KEY_F15, + KEY_SHIFT | KEY_MOD3 | KEY_F16, + + KEY_SHIFT | KEY_MOD3 | KEY_DOWN, + KEY_SHIFT | KEY_MOD3 | KEY_UP, + KEY_SHIFT | KEY_MOD3 | KEY_LEFT, + KEY_SHIFT | KEY_MOD3 | KEY_RIGHT, + KEY_SHIFT | KEY_MOD3 | KEY_HOME, + KEY_SHIFT | KEY_MOD3 | KEY_END, + KEY_SHIFT | KEY_MOD3 | KEY_PAGEUP, + KEY_SHIFT | KEY_MOD3 | KEY_PAGEDOWN, + KEY_SHIFT | KEY_MOD3 | KEY_RETURN, + KEY_SHIFT | KEY_MOD3 | KEY_ESCAPE, + KEY_SHIFT | KEY_MOD3 | KEY_SPACE, + KEY_SHIFT | KEY_MOD3 | KEY_BACKSPACE, + KEY_SHIFT | KEY_MOD3 | KEY_INSERT, + KEY_SHIFT | KEY_MOD3 | KEY_DELETE, + KEY_SHIFT | KEY_MOD3 | KEY_EQUAL +#endif +}; + +const sal_uInt16 KEYCODE_ARRAY_SIZE = std::size(KEYCODE_ARRAY); + +/** select the entry, which match the current key input ... excepting + keys, which are used for the dialog itself. + */ +IMPL_LINK(SfxAcceleratorConfigPage, KeyInputHdl, const KeyEvent&, rKey, bool) +{ + vcl::KeyCode aCode1 = rKey.GetKeyCode(); + sal_uInt16 nCode1 = aCode1.GetCode(); + sal_uInt16 nMod1 = aCode1.GetModifier(); + + // is it related to our list box ? + if ((nCode1 == KEY_DOWN) || (nCode1 == KEY_UP) || (nCode1 == KEY_LEFT) || (nCode1 == KEY_RIGHT) + || (nCode1 == KEY_PAGEUP) || (nCode1 == KEY_PAGEDOWN)) + // no - handle it as normal dialog input + return false; + + for (int i = 0, nCount = m_xEntriesBox->n_children(); i < nCount; ++i) + { + TAccInfo* pUserData = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(i)); + if (pUserData) + { + sal_uInt16 nCode2 = pUserData->m_aKey.GetCode(); + sal_uInt16 nMod2 = pUserData->m_aKey.GetModifier(); + + if (nCode1 == nCode2 && nMod1 == nMod2) + { + m_xEntriesBox->select(i); + m_xEntriesBox->scroll_to_row(i); + return true; + } + } + } + + // no - handle it as normal dialog input + return false; +} + +SfxAcceleratorConfigPage::SfxAcceleratorConfigPage(weld::Container* pPage, + weld::DialogController* pController, + const SfxItemSet& aSet) + : SfxTabPage(pPage, pController, "cui/ui/accelconfigpage.ui", "AccelConfigPage", &aSet) +#if HAVE_FEATURE_SCRIPTING + , m_pMacroInfoItem() +#endif + , aLoadAccelConfigStr(CuiResId(RID_CUISTR_LOADACCELCONFIG)) + , aSaveAccelConfigStr(CuiResId(RID_CUISTR_SAVEACCELCONFIG)) + , aFilterAllStr(SfxResId(STR_SFX_FILTERNAME_ALL)) + , aFilterCfgStr(CuiResId(RID_CUISTR_FILTERNAME_CFG)) + , m_bStylesInfoInitialized(false) + , m_aUpdateDataTimer("SfxAcceleratorConfigPage UpdateDataTimer") + , m_aFillGroupIdle("SfxAcceleratorConfigPage m_aFillGroupIdle") + , m_xEntriesBox(m_xBuilder->weld_tree_view("shortcuts")) + , m_xOfficeButton(m_xBuilder->weld_radio_button("office")) + , m_xModuleButton(m_xBuilder->weld_radio_button("module")) + , m_xChangeButton(m_xBuilder->weld_button("change")) + , m_xRemoveButton(m_xBuilder->weld_button("delete")) + , m_xGroupLBox(new CuiConfigGroupListBox(m_xBuilder->weld_tree_view("category"))) + , m_xFunctionBox(new CuiConfigFunctionListBox(m_xBuilder->weld_tree_view("function"))) + , m_xKeyBox(m_xBuilder->weld_tree_view("keys")) + , m_xSearchEdit(m_xBuilder->weld_entry("searchEntry")) + , m_xLoadButton(m_xBuilder->weld_button("load")) + , m_xSaveButton(m_xBuilder->weld_button("save")) + , m_xResetButton(m_xBuilder->weld_button("reset")) +{ + Size aSize(m_xEntriesBox->get_approximate_digit_width() * 40, + m_xEntriesBox->get_height_rows(10)); + m_xEntriesBox->set_size_request(aSize.Width(), aSize.Height()); + aSize = Size(m_xEntriesBox->get_approximate_digit_width() * 19, + m_xEntriesBox->get_height_rows(9)); + m_xGroupLBox->set_size_request(aSize.Width(), aSize.Height()); + aSize = Size(m_xEntriesBox->get_approximate_digit_width() * 21, + m_xEntriesBox->get_height_rows(9)); + m_xFunctionBox->set_size_request(aSize.Width(), aSize.Height()); + aSize = Size(m_xEntriesBox->get_approximate_digit_width() * 20, + m_xEntriesBox->get_height_rows(9)); + m_xKeyBox->set_size_request(aSize.Width(), aSize.Height()); + + // install handler functions + m_xChangeButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, ChangeHdl)); + m_xRemoveButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, RemoveHdl)); + m_xEntriesBox->connect_changed(LINK(this, SfxAcceleratorConfigPage, SelectHdl)); + m_xEntriesBox->connect_key_press(LINK(this, SfxAcceleratorConfigPage, KeyInputHdl)); + m_xGroupLBox->connect_changed(LINK(this, SfxAcceleratorConfigPage, SelectHdl)); + m_xFunctionBox->connect_changed(LINK(this, SfxAcceleratorConfigPage, SelectHdl)); + m_xKeyBox->connect_changed(LINK(this, SfxAcceleratorConfigPage, SelectHdl)); + m_xLoadButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, Load)); + m_xSaveButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, Save)); + m_xResetButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, Default)); + m_xOfficeButton->connect_toggled(LINK(this, SfxAcceleratorConfigPage, RadioHdl)); + m_xSearchEdit->connect_changed(LINK(this, SfxAcceleratorConfigPage, SearchUpdateHdl)); + m_xSearchEdit->connect_focus_out(LINK(this, SfxAcceleratorConfigPage, FocusOut_Impl)); + + // detect max keyname width + int nMaxWidth = 0; + for (unsigned short i : KEYCODE_ARRAY) + { + int nTmp = m_xEntriesBox->get_pixel_size(vcl::KeyCode(i).GetName()).Width(); + if (nTmp > nMaxWidth) + nMaxWidth = nTmp; + } + // recalc second tab + auto nNewTab = nMaxWidth + 5; // additional space + + // initialize Entriesbox + std::vector<int> aWidths{ nNewTab }; + m_xEntriesBox->set_column_fixed_widths(aWidths); + + //Initialize search util + m_options.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE; + m_options.transliterateFlags |= TransliterationFlags::IGNORE_CASE; + m_options.searchFlag + |= (util::SearchFlags::REG_NOT_BEGINOFLINE | util::SearchFlags::REG_NOT_ENDOFLINE); + // initialize GroupBox + m_xGroupLBox->SetFunctionListBox(m_xFunctionBox.get()); + + // initialize KeyBox + m_xKeyBox->make_sorted(); + + m_aUpdateDataTimer.SetInvokeHandler(LINK(this, SfxAcceleratorConfigPage, ImplUpdateDataHdl)); + m_aUpdateDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT); + + m_aFillGroupIdle.SetInvokeHandler(LINK(this, SfxAcceleratorConfigPage, TimeOut_Impl)); + m_aFillGroupIdle.SetPriority(TaskPriority::HIGHEST); +} + +SfxAcceleratorConfigPage::~SfxAcceleratorConfigPage() +{ + m_aFillGroupIdle.Stop(); + + // free memory - remove all dynamic user data + for (int i = 0, nCount = m_xEntriesBox->n_children(); i < nCount; ++i) + { + TAccInfo* pUserData = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(i)); + delete pUserData; + } +} + +void SfxAcceleratorConfigPage::InitAccCfg() +{ + // already initialized ? + if (m_xContext.is()) + return; // yes -> do nothing + + try + { + // no - initialize this instance + m_xContext = comphelper::getProcessComponentContext(); + + m_xUICmdDescription = frame::theUICommandDescription::get(m_xContext); + + // get the current active frame, which should be our "parent" + // for this session + m_xFrame = GetFrame(); + if (!m_xFrame.is()) + { + uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(m_xContext); + m_xFrame = xDesktop->getActiveFrame(); + } + + // identify module + uno::Reference<frame::XModuleManager2> xModuleManager + = frame::ModuleManager::create(m_xContext); + m_sModuleLongName = xModuleManager->identify(m_xFrame); + comphelper::SequenceAsHashMap lModuleProps(xModuleManager->getByName(m_sModuleLongName)); + m_sModuleUIName + = lModuleProps.getUnpackedValueOrDefault("ooSetupFactoryUIName", OUString()); + + // get global accelerator configuration + m_xGlobal = css::ui::GlobalAcceleratorConfiguration::create(m_xContext); + + // get module accelerator configuration + + uno::Reference<ui::XModuleUIConfigurationManagerSupplier> xModuleCfgSupplier( + ui::theModuleUIConfigurationManagerSupplier::get(m_xContext)); + uno::Reference<ui::XUIConfigurationManager> xUICfgManager + = xModuleCfgSupplier->getUIConfigurationManager(m_sModuleLongName); + m_xModule = xUICfgManager->getShortCutManager(); + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + m_xContext.clear(); + } +} + +void SfxAcceleratorConfigPage::Init(const uno::Reference<ui::XAcceleratorConfiguration>& xAccMgr) +{ + if (!xAccMgr.is()) + return; + + if (!m_bStylesInfoInitialized) + { + uno::Reference<frame::XController> xController; + uno::Reference<frame::XModel> xModel; + if (m_xFrame.is()) + xController = m_xFrame->getController(); + if (xController.is()) + xModel = xController->getModel(); + + m_aStylesInfo.init(m_sModuleLongName, xModel); + m_xGroupLBox->SetStylesInfo(&m_aStylesInfo); + m_bStylesInfoInitialized = true; + } + + // Insert all editable accelerators into list box. It is possible + // that some accelerators are not mapped on the current system/keyboard + // but we don't want to lose these mappings. + for (sal_Int32 i1 = 0; i1 < KEYCODE_ARRAY_SIZE; ++i1) + { + vcl::KeyCode aKey = KEYCODE_ARRAY[i1]; + OUString sKey = aKey.GetName(); + if (sKey.isEmpty()) + continue; + TAccInfo* pEntry = new TAccInfo(i1, 0 /*nListPos*/, aKey); + m_xEntriesBox->append(weld::toId(pEntry), sKey); + int nPos = m_xEntriesBox->n_children() - 1; + m_xEntriesBox->set_text(nPos, OUString(), 1); + m_xEntriesBox->set_sensitive(nPos, true); + } + + // Assign all commands to its shortcuts - reading the accelerator config. + uno::Sequence<awt::KeyEvent> lKeys = xAccMgr->getAllKeyEvents(); + sal_Int32 c2 = lKeys.getLength(); + sal_Int32 i2 = 0; + + for (i2 = 0; i2 < c2; ++i2) + { + const awt::KeyEvent& aAWTKey = lKeys[i2]; + OUString sCommand = xAccMgr->getCommandByKeyEvent(aAWTKey); + OUString sLabel = GetLabel4Command(sCommand); + vcl::KeyCode aKeyCode = svt::AcceleratorExecute::st_AWTKey2VCLKey(aAWTKey); + sal_Int32 nPos = MapKeyCodeToPos(aKeyCode); + + if (nPos == -1) + continue; + + m_xEntriesBox->set_text(nPos, sLabel, 1); + + TAccInfo* pEntry = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(nPos)); + pEntry->m_bIsConfigurable = true; + + pEntry->m_sCommand = sCommand; + } + + // Map the VCL hardcoded key codes and mark them as not changeable + size_t c3 = Application::GetReservedKeyCodeCount(); + size_t i3 = 0; + for (i3 = 0; i3 < c3; ++i3) + { + const vcl::KeyCode* pKeyCode = Application::GetReservedKeyCode(i3); + sal_Int32 nPos = MapKeyCodeToPos(*pKeyCode); + + if (nPos == -1) + continue; + + // Hardcoded function mapped so no ID possible and mark entry as not changeable + TAccInfo* pEntry = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(nPos)); + pEntry->m_bIsConfigurable = false; + + m_xEntriesBox->set_sensitive(nPos, false); + } +} + +void SfxAcceleratorConfigPage::Apply(const uno::Reference<ui::XAcceleratorConfiguration>& xAccMgr) +{ + if (!xAccMgr.is()) + return; + + // Go through the list from the bottom to the top ... + // because logical accelerator must be preferred instead of + // physical ones! + for (int i = 0, nCount = m_xEntriesBox->n_children(); i < nCount; ++i) + { + TAccInfo* pUserData = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(i)); + OUString sCommand; + awt::KeyEvent aAWTKey; + + if (pUserData) + { + sCommand = pUserData->m_sCommand; + aAWTKey = svt::AcceleratorExecute::st_VCLKey2AWTKey(pUserData->m_aKey); + } + + try + { + if (!sCommand.isEmpty()) + xAccMgr->setKeyEvent(aAWTKey, sCommand); + else + xAccMgr->removeKeyEvent(aAWTKey); + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } + } +} + +void SfxAcceleratorConfigPage::ResetConfig() { m_xEntriesBox->clear(); } + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, ImplUpdateDataHdl, Timer*, void) +{ + SelectHdl(m_xGroupLBox->get_widget()); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, SearchUpdateHdl, weld::Entry&, void) +{ + m_aUpdateDataTimer.Start(); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, FocusOut_Impl, weld::Widget&, void) +{ + if (m_aUpdateDataTimer.IsActive()) + { + m_aUpdateDataTimer.Stop(); + m_aUpdateDataTimer.Invoke(); + } +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, Load, weld::Button&, void) +{ + // ask for filename, where we should load the new config data from + StartFileDialog(StartFileDialogType::Open, aLoadAccelConfigStr); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, Save, weld::Button&, void) +{ + StartFileDialog(StartFileDialogType::SaveAs, aSaveAccelConfigStr); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, Default, weld::Button&, void) +{ + uno::Reference<form::XReset> xReset(m_xAct, uno::UNO_QUERY); + if (xReset.is()) + xReset->reset(); + + m_xEntriesBox->freeze(); + ResetConfig(); + Init(m_xAct); + m_xEntriesBox->thaw(); + m_xEntriesBox->select(0); + SelectHdl(*m_xEntriesBox); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, ChangeHdl, weld::Button&, void) +{ + int nPos = m_xEntriesBox->get_selected_index(); + if (nPos == -1) + return; + + TAccInfo* pEntry = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(nPos)); + OUString sNewCommand = m_xFunctionBox->GetCurCommand(); + OUString sLabel = m_xFunctionBox->GetCurLabel(); + if (sLabel.isEmpty()) + sLabel = GetLabel4Command(sNewCommand); + + pEntry->m_sCommand = sNewCommand; + m_xEntriesBox->set_text(nPos, sLabel, 1); + + SelectHdl(m_xFunctionBox->get_widget()); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, RemoveHdl, weld::Button&, void) +{ + // get selected entry + int nPos = m_xEntriesBox->get_selected_index(); + if (nPos == -1) + return; + + TAccInfo* pEntry = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(nPos)); + + // remove function name from selected entry + m_xEntriesBox->set_text(nPos, OUString(), 1); + pEntry->m_sCommand.clear(); + + SelectHdl(m_xFunctionBox->get_widget()); +} + +IMPL_LINK(SfxAcceleratorConfigPage, SelectHdl, weld::TreeView&, rListBox, void) +{ + if (&rListBox == m_xEntriesBox.get()) + { + TAccInfo* pEntry = weld::fromId<TAccInfo*>(m_xEntriesBox->get_selected_id()); + + OUString sPossibleNewCommand = m_xFunctionBox->GetCurCommand(); + + m_xRemoveButton->set_sensitive(false); + m_xChangeButton->set_sensitive(false); + + if (pEntry && pEntry->m_bIsConfigurable) + { + if (pEntry->isConfigured()) + m_xRemoveButton->set_sensitive(true); + m_xChangeButton->set_sensitive(pEntry->m_sCommand != sPossibleNewCommand); + } + } + else if (&rListBox == &m_xGroupLBox->get_widget()) + { + m_xGroupLBox->GroupSelected(); + + // Pause redraw (Do not redraw at each removal) + m_xFunctionBox->freeze(); + // Apply the search filter to the functions list + OUString aSearchTerm(m_xSearchEdit->get_text()); + int nMatchFound = applySearchFilter(aSearchTerm); + // Resume redraw + m_xFunctionBox->thaw(); + if (nMatchFound != -1) + { + m_xFunctionBox->select(nMatchFound); + SelectHdl(m_xFunctionBox->get_widget()); + } + else + { + m_xKeyBox->clear(); + m_xChangeButton->set_sensitive(false); + } + } + else if (&rListBox == &m_xFunctionBox->get_widget()) + { + m_xRemoveButton->set_sensitive(false); + m_xChangeButton->set_sensitive(false); + + // #i36994 First selected can return null! + TAccInfo* pEntry = weld::fromId<TAccInfo*>(m_xEntriesBox->get_selected_id()); + if (pEntry) + { + OUString sPossibleNewCommand = m_xFunctionBox->GetCurCommand(); + + if (pEntry->m_bIsConfigurable) + { + if (pEntry->isConfigured()) + m_xRemoveButton->set_sensitive(true); + m_xChangeButton->set_sensitive(pEntry->m_sCommand != sPossibleNewCommand + && !sPossibleNewCommand.isEmpty()); + } + + // update key box + m_xKeyBox->clear(); + if (!sPossibleNewCommand.isEmpty()) + { + for (int i = 0, nCount = m_xEntriesBox->n_children(); i < nCount; ++i) + { + TAccInfo* pUserData = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(i)); + if (pUserData && pUserData->m_sCommand == sPossibleNewCommand) + { + m_xKeyBox->append(weld::toId(pUserData), pUserData->m_aKey.GetName()); + } + } + } + } + } + else + { + // goto selected "key" entry of the key box + int nP2 = -1; + TAccInfo* pU2 = weld::fromId<TAccInfo*>(m_xKeyBox->get_selected_id()); + if (pU2) + nP2 = MapKeyCodeToPos(pU2->m_aKey); + if (nP2 != -1) + { + m_xEntriesBox->select(nP2); + m_xEntriesBox->scroll_to_row(nP2); + SelectHdl(*m_xEntriesBox); + } + } +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, RadioHdl, weld::Toggleable&, void) +{ + uno::Reference<ui::XAcceleratorConfiguration> xOld = m_xAct; + + if (m_xOfficeButton->get_active()) + m_xAct = m_xGlobal; + else if (m_xModuleButton->get_active()) + m_xAct = m_xModule; + + // nothing changed? => do nothing! + if (m_xAct.is() && (xOld == m_xAct)) + return; + + m_xEntriesBox->freeze(); + ResetConfig(); + Init(m_xAct); + m_xEntriesBox->thaw(); + + m_xGroupLBox->Init(m_xContext, m_xFrame, m_sModuleLongName, true); + + // pb: #133213# do not select NULL entries + if (m_xEntriesBox->n_children()) + m_xEntriesBox->select(0); + + m_aFillGroupIdle.Start(); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, TimeOut_Impl, Timer*, void) +{ + // activating the selection, typically "all commands", can take a long time + // -> show wait cursor and disable input + weld::WaitObject aWaitObject(GetFrameWeld()); + + weld::TreeView& rTreeView = m_xGroupLBox->get_widget(); + SelectHdl(rTreeView); +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, LoadHdl, sfx2::FileDialogHelper*, void) +{ + assert(m_pFileDlg); + + OUString sCfgName; + if (ERRCODE_NONE == m_pFileDlg->GetError()) + sCfgName = m_pFileDlg->GetPath(); + + if (sCfgName.isEmpty()) + return; + + weld::WaitObject aWaitObject(GetFrameWeld()); + + uno::Reference<ui::XUIConfigurationManager> xCfgMgr; + uno::Reference<embed::XStorage> + xRootStorage; // we must hold the root storage alive, if xCfgMgr is used! + + try + { + // don't forget to release the storage afterwards! + uno::Reference<lang::XSingleServiceFactory> xStorageFactory( + embed::StorageFactory::create(m_xContext)); + uno::Sequence<uno::Any> lArgs{ uno::Any(sCfgName), + uno::Any(css::embed::ElementModes::READ) }; + + xRootStorage.set(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY_THROW); + uno::Reference<embed::XStorage> xUIConfig + = xRootStorage->openStorageElement(FOLDERNAME_UICONFIG, embed::ElementModes::READ); + if (xUIConfig.is()) + { + uno::Reference<ui::XUIConfigurationManager2> xCfgMgr2 + = ui::UIConfigurationManager::create(m_xContext); + xCfgMgr2->setStorage(xUIConfig); + xCfgMgr.set(xCfgMgr2, uno::UNO_QUERY_THROW); + } + + if (xCfgMgr.is()) + { + // open the configuration and update our UI + uno::Reference<ui::XAcceleratorConfiguration> xTempAccMgr(xCfgMgr->getShortCutManager(), + uno::UNO_SET_THROW); + + m_xEntriesBox->freeze(); + ResetConfig(); + Init(xTempAccMgr); + m_xEntriesBox->thaw(); + if (m_xEntriesBox->n_children()) + { + m_xEntriesBox->select(0); + SelectHdl(m_xFunctionBox->get_widget()); + } + } + + // don't forget to close the new opened storage! + // We are the owner of it. + if (xRootStorage.is()) + { + uno::Reference<lang::XComponent> xComponent; + xComponent.set(xCfgMgr, uno::UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + xRootStorage->dispose(); + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } +} + +IMPL_LINK_NOARG(SfxAcceleratorConfigPage, SaveHdl, sfx2::FileDialogHelper*, void) +{ + assert(m_pFileDlg); + + OUString sCfgName; + if (ERRCODE_NONE == m_pFileDlg->GetError()) + sCfgName = m_pFileDlg->GetPath(); + + if (sCfgName.isEmpty()) + return; + + weld::WaitObject aWaitObject(GetFrameWeld()); + + uno::Reference<embed::XStorage> xRootStorage; + + try + { + uno::Reference<lang::XSingleServiceFactory> xStorageFactory( + embed::StorageFactory::create(m_xContext)); + uno::Sequence<uno::Any> lArgs{ uno::Any(sCfgName), uno::Any(embed::ElementModes::WRITE) }; + + xRootStorage.set(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY_THROW); + + uno::Reference<embed::XStorage> xUIConfig( + xRootStorage->openStorageElement(FOLDERNAME_UICONFIG, embed::ElementModes::WRITE), + uno::UNO_SET_THROW); + uno::Reference<beans::XPropertySet> xUIConfigProps(xUIConfig, uno::UNO_QUERY_THROW); + + // set the correct media type if the storage was new created + OUString sMediaType; + xUIConfigProps->getPropertyValue(MEDIATYPE_PROPNAME) >>= sMediaType; + if (sMediaType.isEmpty()) + xUIConfigProps->setPropertyValue( + MEDIATYPE_PROPNAME, uno::Any(OUString("application/vnd.sun.xml.ui.configuration"))); + + uno::Reference<ui::XUIConfigurationManager2> xCfgMgr + = ui::UIConfigurationManager::create(m_xContext); + xCfgMgr->setStorage(xUIConfig); + + // get the target configuration access and update with all shortcuts + // which are set currently at the UI! + // Don't copy the m_xAct content to it... because m_xAct will be updated + // from the UI on pressing the button "OK" only. And inbetween it's not up to date! + uno::Reference<ui::XAcceleratorConfiguration> xTargetAccMgr(xCfgMgr->getShortCutManager(), + uno::UNO_SET_THROW); + Apply(xTargetAccMgr); + + // commit (order is important!) + uno::Reference<ui::XUIConfigurationPersistence> xCommit1(xTargetAccMgr, + uno::UNO_QUERY_THROW); + uno::Reference<ui::XUIConfigurationPersistence> xCommit2(xCfgMgr, uno::UNO_QUERY_THROW); + xCommit1->store(); + xCommit2->store(); + + if (xRootStorage.is()) + { + // Commit root storage + uno::Reference<embed::XTransactedObject> xCommit3(xRootStorage, uno::UNO_QUERY_THROW); + xCommit3->commit(); + } + + if (xRootStorage.is()) + { + if (xCfgMgr.is()) + xCfgMgr->dispose(); + xRootStorage->dispose(); + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } +} + +void SfxAcceleratorConfigPage::StartFileDialog(StartFileDialogType nType, const OUString& rTitle) +{ + bool bSave = nType == StartFileDialogType::SaveAs; + short nDialogType = bSave ? ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION + : ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE; + m_pFileDlg.reset( + new sfx2::FileDialogHelper(nDialogType, FileDialogFlags::NONE, GetFrameWeld())); + + m_pFileDlg->SetTitle(rTitle); + m_pFileDlg->AddFilter(aFilterAllStr, FILEDIALOG_FILTER_ALL); + m_pFileDlg->AddFilter(aFilterCfgStr, "*.cfg"); + m_pFileDlg->SetCurrentFilter(aFilterCfgStr); + m_pFileDlg->SetContext(sfx2::FileDialogHelper::AcceleratorConfig); + + Link<sfx2::FileDialogHelper*, void> aDlgClosedLink + = bSave ? LINK(this, SfxAcceleratorConfigPage, SaveHdl) + : LINK(this, SfxAcceleratorConfigPage, LoadHdl); + m_pFileDlg->StartExecuteModal(aDlgClosedLink); +} + +bool SfxAcceleratorConfigPage::FillItemSet(SfxItemSet*) +{ + Apply(m_xAct); + try + { + m_xAct->store(); + css::uno::Reference<css::beans::XPropertySet> xFrameProps(m_xFrame, + css::uno::UNO_QUERY_THROW); + css::uno::Reference<css::frame::XLayoutManager> xLayoutManager; + xFrameProps->getPropertyValue("LayoutManager") >>= xLayoutManager; + css::uno::Reference<css::beans::XPropertySet> xLayoutProps(xLayoutManager, + css::uno::UNO_QUERY_THROW); + xLayoutProps->setPropertyValue("RefreshContextToolbarToolTip", css::uno::Any(true)); + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + return false; + } + + return true; +} + +void SfxAcceleratorConfigPage::Reset(const SfxItemSet* rSet) +{ + // open accelerator configs + // Note: It initialize some other members too, which are needed here ... + // e.g. m_sModuleUIName! + InitAccCfg(); + + // change the description of the radio button, which switch to the module + // dependent accelerator configuration + OUString sButtonText = m_xModuleButton->get_label(); + sButtonText + = m_xModuleButton->strip_mnemonic(sButtonText).replaceFirst("$(MODULE)", m_sModuleUIName); + m_xModuleButton->set_label(sButtonText); + + if (m_xModule.is()) + m_xModuleButton->set_active(true); + else + { + m_xModuleButton->hide(); + m_xOfficeButton->set_active(true); + } + + RadioHdl(*m_xOfficeButton); + +#if HAVE_FEATURE_SCRIPTING + if (const SfxMacroInfoItem* pMacroItem = rSet->GetItemIfSet(SID_MACROINFO)) + { + m_pMacroInfoItem = pMacroItem; + m_xGroupLBox->SelectMacro(m_pMacroInfoItem); + } +#else + (void)rSet; +#endif +} + +sal_Int32 SfxAcceleratorConfigPage::MapKeyCodeToPos(const vcl::KeyCode& aKey) const +{ + sal_uInt16 nCode1 = aKey.GetCode() + aKey.GetModifier(); + for (int i = 0, nCount = m_xEntriesBox->n_children(); i < nCount; ++i) + { + TAccInfo* pUserData = weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(i)); + if (pUserData) + { + sal_uInt16 nCode2 = pUserData->m_aKey.GetCode() + pUserData->m_aKey.GetModifier(); + if (nCode1 == nCode2) + return i; + } + } + + return -1; +} + +OUString SfxAcceleratorConfigPage::GetLabel4Command(const OUString& sCommand) +{ + try + { + // check global command configuration first + uno::Reference<container::XNameAccess> xModuleConf; + m_xUICmdDescription->getByName(m_sModuleLongName) >>= xModuleConf; + if (xModuleConf.is()) + { + ::comphelper::SequenceAsHashMap lProps(xModuleConf->getByName(sCommand)); + OUString sLabel = lProps.getUnpackedValueOrDefault("Name", OUString()); + if (!sLabel.isEmpty()) + return sLabel; + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } + + // may be it's a style URL .. they must be handled special + SfxStyleInfo_Impl aStyle; + aStyle.sCommand = sCommand; + if (SfxStylesInfo_Impl::parseStyleCommand(aStyle)) + { + m_aStylesInfo.getLabel4Style(aStyle); + return aStyle.sLabel; + } + + return sCommand; +} + +/* + * Remove entries which doesn't contain the search term + */ +int SfxAcceleratorConfigPage::applySearchFilter(OUString const& rSearchTerm) +{ + if (rSearchTerm.isEmpty()) + return -1; + + m_options.searchString = rSearchTerm; + utl::TextSearch textSearch(m_options); + + for (int i = m_xFunctionBox->n_children(); i > 0; --i) + { + int nEntry = i - 1; + OUString aStr = m_xFunctionBox->get_text(nEntry); + sal_Int32 aStartPos = 0; + sal_Int32 aEndPos = aStr.getLength(); + + if (!textSearch.SearchForward(aStr, &aStartPos, &aEndPos)) + m_xFunctionBox->remove(nEntry); + } + + return m_xFunctionBox->n_children() ? 0 : -1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/cfg.cxx b/cui/source/customize/cfg.cxx new file mode 100644 index 0000000000..d41012850b --- /dev/null +++ b/cui/source/customize/cfg.cxx @@ -0,0 +1,3249 @@ +/* -*- 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 <cassert> +#include <stdlib.h> +#include <typeinfo> + +#include <utility> +#include <vcl/stdtext.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/event.hxx> +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/weld.hxx> +#include <vcl/decoview.hxx> +#include <vcl/virdev.hxx> + +#include <sfx2/minfitem.hxx> +#include <sfx2/sfxhelp.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/stritem.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <algorithm> +#include <strings.hrc> + +#include <acccfg.hxx> +#include <cfg.hxx> +#include <CustomNotebookbarGenerator.hxx> +#include <SvxMenuConfigPage.hxx> +#include <SvxToolbarConfigPage.hxx> +#include <SvxNotebookbarConfigPage.hxx> +#include <SvxConfigPageHelper.hxx> +#include "eventdlg.hxx" +#include <dialmgr.hxx> + +#include <unotools/configmgr.hxx> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/FileSystemStorageFactory.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XFrames.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/theUICommandDescription.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/ui/ImageManager.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationPersistence.hpp> +#include <com/sun/star/ui/XUIElement.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/ui/ImageType.hpp> +#include <com/sun/star/ui/theWindowStateConfiguration.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/util/thePathSettings.hpp> +#include <comphelper/documentinfo.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/processfactory.hxx> +#include <config_features.h> + +namespace uno = com::sun::star::uno; +namespace frame = com::sun::star::frame; +namespace lang = com::sun::star::lang; +namespace container = com::sun::star::container; +namespace beans = com::sun::star::beans; +namespace graphic = com::sun::star::graphic; + +#if OSL_DEBUG_LEVEL > 1 + +void printPropertySet( + const OUString& prefix, + const uno::Reference< beans::XPropertySet >& xPropSet ) +{ + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = + xPropSet->getPropertySetInfo(); + + const uno::Sequence< beans::Property >& aPropDetails = + xPropSetInfo->getProperties(); + + SAL_WARN("cui", "printPropertySet: " << aPropDetails.getLength() << " properties" ); + + for ( beans::Property const & aPropDetail : aPropDetails ) + { + OUString tmp; + sal_Int32 ival; + + uno::Any a = xPropSet->getPropertyValue( aPropDetail.Name ); + + if ( a >>= tmp ) + { + SAL_WARN("cui", prefix << ": Got property: " << aPropDetail.Name << tmp); + } + else if ( ( a >>= ival ) ) + { + SAL_WARN("cui", prefix << ": Got property: " << aPropDetail.Name << " = " << ival); + } + else + { + SAL_WARN("cui", prefix << ": Got property: " << aPropDetail.Name << " of type " << a.getValueTypeName()); + } + } +} + +void printProperties( + const OUString& prefix, + const uno::Sequence< beans::PropertyValue >& aProp ) +{ + for (beans::PropertyValue const & aPropVal : aProp) + { + OUString tmp; + + aPropVal.Value >>= tmp; + + SAL_WARN("cui", prefix << ": Got property: " << aPropVal.Name << " = " << tmp); + } +} + +void printEntries(SvxEntries* entries) +{ + for (auto const& entry : *entries) + { + SAL_WARN("cui", "printEntries: " << entry->GetName()); + } +} + +#endif + +bool +SvxConfigPage::CanConfig( std::u16string_view aModuleId ) +{ + return aModuleId != u"com.sun.star.script.BasicIDE" && aModuleId != u"com.sun.star.frame.Bibliography"; +} + +static std::unique_ptr<SfxTabPage> CreateSvxMenuConfigPage( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet ) +{ + return std::make_unique<SvxMenuConfigPage>(pPage, pController, *rSet); +} + +static std::unique_ptr<SfxTabPage> CreateSvxContextMenuConfigPage( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet ) +{ + return std::make_unique<SvxMenuConfigPage>(pPage, pController, *rSet, false); +} + +static std::unique_ptr<SfxTabPage> CreateKeyboardConfigPage( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet ) +{ + return std::make_unique<SfxAcceleratorConfigPage>(pPage, pController, *rSet); +} + +static std::unique_ptr<SfxTabPage> CreateSvxNotebookbarConfigPage(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rSet) +{ + return std::make_unique<SvxNotebookbarConfigPage>(pPage, pController, *rSet); +} + +static std::unique_ptr<SfxTabPage> CreateSvxToolbarConfigPage( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet ) +{ + return std::make_unique<SvxToolbarConfigPage>(pPage, pController, *rSet); +} + +static std::unique_ptr<SfxTabPage> CreateSvxEventConfigPage( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet ) +{ + return std::make_unique<SvxEventConfigPage>(pPage, pController, *rSet, SvxEventConfigPage::EarlyInit()); +} + +/****************************************************************************** + * + * SvxConfigDialog is the configuration dialog which is brought up from the + * Tools menu. It includes tabs for customizing menus, toolbars, events and + * key bindings. + * + *****************************************************************************/ +SvxConfigDialog::SvxConfigDialog(weld::Window * pParent, const SfxItemSet* pInSet) + : SfxTabDialogController(pParent, "cui/ui/customizedialog.ui", "CustomizeDialog", pInSet) +{ + SvxConfigPageHelper::InitImageType(); + + AddTabPage("menus", CreateSvxMenuConfigPage, nullptr); + AddTabPage("toolbars", CreateSvxToolbarConfigPage, nullptr); + AddTabPage("notebookbar", CreateSvxNotebookbarConfigPage, nullptr); + AddTabPage("contextmenus", CreateSvxContextMenuConfigPage, nullptr); + AddTabPage("keyboard", CreateKeyboardConfigPage, nullptr); + AddTabPage("events", CreateSvxEventConfigPage, nullptr); + + if (const SfxPoolItem* pItem = pInSet->GetItem(SID_CONFIG)) + { + OUString text = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if (text.startsWith( ITEM_TOOLBAR_URL ) ) + SetCurPageId("toolbars"); + else if (text.startsWith( ITEM_EVENT_URL) ) + SetCurPageId("events"); + } +#if HAVE_FEATURE_SCRIPTING + else if (pInSet->GetItemIfSet(SID_MACROINFO)) + { + // for the "assign" button in the Basic Macros chooser automatically switch + // to the keyboard tab in which this macro will be pre-selected for assigning + // to a keystroke + SetCurPageId("keyboard"); + } +#endif +} + +void SvxConfigDialog::ActivatePage(const OUString& rPage) +{ + SfxTabDialogController::ActivatePage(rPage); + GetResetButton()->set_visible(rPage != "keyboard"); +} + +void SvxConfigDialog::SetFrame(const css::uno::Reference<css::frame::XFrame>& xFrame) +{ + m_xFrame = xFrame; + OUString aModuleId = SvxConfigPage::GetFrameWithDefaultAndIdentify(m_xFrame); + + if (aModuleId != "com.sun.star.text.TextDocument" && + aModuleId != "com.sun.star.sheet.SpreadsheetDocument" && + aModuleId != "com.sun.star.presentation.PresentationDocument" && + aModuleId != "com.sun.star.drawing.DrawingDocument") + RemoveTabPage("notebookbar"); + + if (aModuleId == "com.sun.star.frame.StartModule") + RemoveTabPage("keyboard"); +} + +void SvxConfigDialog::PageCreated(const OUString &rId, SfxTabPage& rPage) +{ + if (rId == "menus" || rId == "keyboard" || rId == "notebookbar" + || rId == "toolbars" || rId == "contextmenus") + { + rPage.SetFrame(m_xFrame); + } + else if (rId == "events") + { + dynamic_cast< SvxEventConfigPage& >( rPage ).LateInit( m_xFrame ); + } +} + +/****************************************************************************** + * + * The SaveInData class is used to hold data for entries in the Save In + * ListBox controls in the menu and toolbar tabs + * + ******************************************************************************/ + +// Initialize static variable which holds default XImageManager +uno::Reference< css::ui::XImageManager>* SaveInData::xDefaultImgMgr = nullptr; + +SaveInData::SaveInData( + uno::Reference< css::ui::XUIConfigurationManager > xCfgMgr, + uno::Reference< css::ui::XUIConfigurationManager > xParentCfgMgr, + const OUString& aModuleId, + bool isDocConfig ) + : + bModified( false ), + bDocConfig( isDocConfig ), + bReadOnly( false ), + m_xCfgMgr(std::move( xCfgMgr )), + m_xParentCfgMgr(std::move( xParentCfgMgr )), + m_aSeparatorSeq{ comphelper::makePropertyValue(ITEM_DESCRIPTOR_TYPE, + css::ui::ItemType::SEPARATOR_LINE) } +{ + if ( bDocConfig ) + { + uno::Reference< css::ui::XUIConfigurationPersistence > + xDocPersistence( GetConfigManager(), uno::UNO_QUERY ); + + bReadOnly = xDocPersistence->isReadOnly(); + } + + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + + uno::Reference< container::XNameAccess > xNameAccess( + css::frame::theUICommandDescription::get(xContext) ); + + xNameAccess->getByName( aModuleId ) >>= m_xCommandToLabelMap; + + if ( !m_xImgMgr.is() ) + { + m_xImgMgr.set( GetConfigManager()->getImageManager(), uno::UNO_QUERY ); + } + + if ( !IsDocConfig() ) + { + // If this is not a document configuration then it is the settings + // for the module (writer, calc, impress etc.) Use this as the default + // XImageManager instance + xDefaultImgMgr = &m_xImgMgr; + } + else + { + // If this is a document configuration then use the module image manager + // as default. + if ( m_xParentCfgMgr.is() ) + { + m_xParentImgMgr.set( m_xParentCfgMgr->getImageManager(), uno::UNO_QUERY ); + xDefaultImgMgr = &m_xParentImgMgr; + } + } +} + +uno::Reference<graphic::XGraphic> SaveInData::GetImage(const OUString& rCommandURL) +{ + uno::Reference< graphic::XGraphic > xGraphic = + SvxConfigPageHelper::GetGraphic( m_xImgMgr, rCommandURL ); + + if (!xGraphic.is() && xDefaultImgMgr != nullptr && (*xDefaultImgMgr).is()) + { + xGraphic = SvxConfigPageHelper::GetGraphic( (*xDefaultImgMgr), rCommandURL ); + } + + return xGraphic; +} + +bool SaveInData::PersistChanges( + const uno::Reference< uno::XInterface >& xManager ) +{ + bool result = true; + + try + { + if ( xManager.is() && !IsReadOnly() ) + { + uno::Reference< css::ui::XUIConfigurationPersistence > + xConfigPersistence( xManager, uno::UNO_QUERY ); + + if ( xConfigPersistence->isModified() ) + { + xConfigPersistence->store(); + } + } + } + catch ( css::io::IOException& ) + { + result = false; + } + + return result; +} + +/****************************************************************************** + * + * The MenuSaveInData class extends SaveInData and provides menu specific + * load and store functionality. + * + ******************************************************************************/ + +// Initialize static variable which holds default Menu data +MenuSaveInData* MenuSaveInData::pDefaultData = nullptr; + +MenuSaveInData::MenuSaveInData( + const uno::Reference< css::ui::XUIConfigurationManager >& cfgmgr, + const uno::Reference< css::ui::XUIConfigurationManager >& xParentCfgMgr, + const OUString& aModuleId, + bool isDocConfig ) + : + SaveInData( cfgmgr, xParentCfgMgr, aModuleId, isDocConfig ), + m_aMenuResourceURL( + ITEM_MENUBAR_URL ), + m_aDescriptorContainer( + ITEM_DESCRIPTOR_CONTAINER ) +{ + try + { + m_xMenuSettings = GetConfigManager()->getSettings( ITEM_MENUBAR_URL, false ); + } + catch ( container::NoSuchElementException& ) + { + // will use menu settings for the module + } + + // If this is not a document configuration then it is the settings + // for the module (writer, calc, impress etc.). These settings should + // be set as the default to be used for SaveIn locations that do not + // have custom settings + if ( !IsDocConfig() ) + { + SetDefaultData( this ); + } +} + +MenuSaveInData::~MenuSaveInData() +{ +} + +SvxEntries* +MenuSaveInData::GetEntries() +{ + if ( pRootEntry == nullptr ) + { + pRootEntry.reset( new SvxConfigEntry( "MainMenus", OUString(), true, /*bParentData*/false) ); + + if ( m_xMenuSettings.is() ) + { + LoadSubMenus( m_xMenuSettings, OUString(), pRootEntry.get(), false ); + } + else if ( GetDefaultData() != nullptr ) + { + // If the doc has no config settings use module config settings + LoadSubMenus( GetDefaultData()->m_xMenuSettings, OUString(), pRootEntry.get(), false ); + } + } + + return pRootEntry->GetEntries(); +} + +void +MenuSaveInData::SetEntries( std::unique_ptr<SvxEntries> pNewEntries ) +{ + pRootEntry->SetEntries( std::move(pNewEntries) ); +} + +void SaveInData::LoadSubMenus( const uno::Reference< container::XIndexAccess >& xMenuSettings, + const OUString& rBaseTitle, SvxConfigEntry const * pParentData, bool bContextMenu ) +{ + SvxEntries* pEntries = pParentData->GetEntries(); + + // Don't access non existing menu configuration! + if ( !xMenuSettings.is() ) + return; + + for ( sal_Int32 nIndex = 0; nIndex < xMenuSettings->getCount(); ++nIndex ) + { + uno::Reference< container::XIndexAccess > xSubMenu; + OUString aCommandURL; + OUString aLabel; + + sal_uInt16 nType( css::ui::ItemType::DEFAULT ); + sal_Int32 nStyle(0); + + bool bItem = SvxConfigPageHelper::GetMenuItemData( xMenuSettings, nIndex, + aCommandURL, aLabel, nType, nStyle, xSubMenu ); + + if ( bItem ) + { + bool bIsUserDefined = true; + + if ( nType == css::ui::ItemType::DEFAULT ) + { + uno::Any a; + try + { + a = m_xCommandToLabelMap->getByName( aCommandURL ); + bIsUserDefined = false; + } + catch ( container::NoSuchElementException& ) + { + bIsUserDefined = true; + } + + bool bUseDefaultLabel = false; + // If custom label not set retrieve it from the command + // to info service + if ( aLabel.isEmpty() ) + { + bUseDefaultLabel = true; + uno::Sequence< beans::PropertyValue > aPropSeq; + if ( a >>= aPropSeq ) + { + OUString aMenuLabel; + for ( const beans::PropertyValue& prop : std::as_const(aPropSeq) ) + { + if ( bContextMenu ) + { + if ( prop.Name == "PopupLabel" ) + { + prop.Value >>= aLabel; + break; + } + else if ( prop.Name == "Label" ) + { + prop.Value >>= aMenuLabel; + } + } + else if ( prop.Name == "Label" ) + { + prop.Value >>= aLabel; + break; + } + } + if ( aLabel.isEmpty() ) + aLabel = aMenuLabel; + } + } + + SvxConfigEntry* pEntry = new SvxConfigEntry( + aLabel, aCommandURL, xSubMenu.is(), /*bParentData*/false ); + + pEntry->SetStyle( nStyle ); + pEntry->SetUserDefined( bIsUserDefined ); + if ( !bUseDefaultLabel ) + pEntry->SetName( aLabel ); + + pEntries->push_back( pEntry ); + + if ( xSubMenu.is() ) + { + // popup menu + OUString subMenuTitle( rBaseTitle ); + + if ( !subMenuTitle.isEmpty() ) + { + subMenuTitle += aMenuSeparatorStr; + } + else + { + pEntry->SetMain(); + } + + subMenuTitle += SvxConfigPageHelper::stripHotKey( aLabel ); + + LoadSubMenus( xSubMenu, subMenuTitle, pEntry, bContextMenu ); + } + } + else + { + SvxConfigEntry* pEntry = new SvxConfigEntry; + pEntry->SetUserDefined( bIsUserDefined ); + pEntries->push_back( pEntry ); + } + } + } +} + +bool MenuSaveInData::Apply() +{ + bool result = false; + + if ( IsModified() ) + { + // Apply new menu bar structure to our settings container + m_xMenuSettings = GetConfigManager()->createSettings(); + + uno::Reference< container::XIndexContainer > xIndexContainer ( + m_xMenuSettings, uno::UNO_QUERY ); + + uno::Reference< lang::XSingleComponentFactory > xFactory ( + m_xMenuSettings, uno::UNO_QUERY ); + + Apply( xIndexContainer, xFactory ); + + try + { + if ( GetConfigManager()->hasSettings( m_aMenuResourceURL ) ) + { + GetConfigManager()->replaceSettings( + m_aMenuResourceURL, m_xMenuSettings ); + } + else + { + GetConfigManager()->insertSettings( + m_aMenuResourceURL, m_xMenuSettings ); + } + } + catch ( css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "caught some other exception saving settings"); + } + + SetModified( false ); + + result = PersistChanges( GetConfigManager() ); + } + + return result; +} + +void MenuSaveInData::Apply( + uno::Reference< container::XIndexContainer > const & rMenuBar, + uno::Reference< lang::XSingleComponentFactory >& rFactory ) +{ + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + + for (auto const& entryData : *GetEntries()) + { + uno::Sequence< beans::PropertyValue > aPropValueSeq = + SvxConfigPageHelper::ConvertSvxConfigEntry(entryData); + + uno::Reference< container::XIndexContainer > xSubMenuBar( + rFactory->createInstanceWithContext( xContext ), + uno::UNO_QUERY ); + + sal_Int32 nIndex = aPropValueSeq.getLength(); + aPropValueSeq.realloc( nIndex + 1 ); + auto pPropValueSeq = aPropValueSeq.getArray(); + pPropValueSeq[nIndex].Name = m_aDescriptorContainer; + pPropValueSeq[nIndex].Value <<= xSubMenuBar; + rMenuBar->insertByIndex( + rMenuBar->getCount(), uno::Any( aPropValueSeq )); + ApplyMenu( xSubMenuBar, rFactory, entryData ); + } +} + +void SaveInData::ApplyMenu( + uno::Reference< container::XIndexContainer > const & rMenuBar, + uno::Reference< lang::XSingleComponentFactory >& rFactory, + SvxConfigEntry* pMenuData ) +{ + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + + for (auto const& entry : *pMenuData->GetEntries()) + { + if (entry->IsPopup()) + { + uno::Sequence< beans::PropertyValue > aPropValueSeq = + SvxConfigPageHelper::ConvertSvxConfigEntry(entry); + + uno::Reference< container::XIndexContainer > xSubMenuBar( + rFactory->createInstanceWithContext( xContext ), + uno::UNO_QUERY ); + + sal_Int32 nIndex = aPropValueSeq.getLength(); + aPropValueSeq.realloc( nIndex + 1 ); + auto pPropValueSeq = aPropValueSeq.getArray(); + pPropValueSeq[nIndex].Name = ITEM_DESCRIPTOR_CONTAINER; + pPropValueSeq[nIndex].Value <<= xSubMenuBar; + + rMenuBar->insertByIndex( + rMenuBar->getCount(), uno::Any( aPropValueSeq )); + + ApplyMenu( xSubMenuBar, rFactory, entry ); + entry->SetModified( false ); + } + else if (entry->IsSeparator()) + { + rMenuBar->insertByIndex( + rMenuBar->getCount(), uno::Any( m_aSeparatorSeq )); + } + else + { + uno::Sequence< beans::PropertyValue > aPropValueSeq = + SvxConfigPageHelper::ConvertSvxConfigEntry(entry); + rMenuBar->insertByIndex( + rMenuBar->getCount(), uno::Any( aPropValueSeq )); + } + } + pMenuData->SetModified( false ); +} + +void +MenuSaveInData::Reset() +{ + try + { + GetConfigManager()->removeSettings( m_aMenuResourceURL ); + } + catch ( const css::uno::Exception& ) + {} + + PersistChanges( GetConfigManager() ); + + pRootEntry.reset(); + + try + { + m_xMenuSettings = GetConfigManager()->getSettings( + m_aMenuResourceURL, false ); + } + catch ( container::NoSuchElementException& ) + { + // will use default settings + } +} + +ContextMenuSaveInData::ContextMenuSaveInData( + const css::uno::Reference< css::ui::XUIConfigurationManager >& xCfgMgr, + const css::uno::Reference< css::ui::XUIConfigurationManager >& xParentCfgMgr, + const OUString& aModuleId, bool bIsDocConfig ) + : SaveInData( xCfgMgr, xParentCfgMgr, aModuleId, bIsDocConfig ) +{ + css::uno::Reference< css::uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); + css::uno::Reference< css::container::XNameAccess > xConfig( css::ui::theWindowStateConfiguration::get( xContext ) ); + xConfig->getByName( aModuleId ) >>= m_xPersistentWindowState; +} + +ContextMenuSaveInData::~ContextMenuSaveInData() +{ +} + +OUString ContextMenuSaveInData::GetUIName( const OUString& rResourceURL ) +{ + if ( m_xPersistentWindowState.is() ) + { + css::uno::Sequence< css::beans::PropertyValue > aProps; + try + { + m_xPersistentWindowState->getByName( rResourceURL ) >>= aProps; + } + catch ( const css::uno::Exception& ) + {} + + for ( const auto& aProp : std::as_const(aProps) ) + { + if ( aProp.Name == ITEM_DESCRIPTOR_UINAME ) + { + OUString aResult; + aProp.Value >>= aResult; + return aResult; + } + } + } + return OUString(); +} + +SvxEntries* ContextMenuSaveInData::GetEntries() +{ + if ( !m_pRootEntry ) + { + std::unordered_map< OUString, bool > aMenuInfo; + + m_pRootEntry.reset( new SvxConfigEntry( "ContextMenus", OUString(), true, /*bParentData*/false ) ); + css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > aElementsInfo; + try + { + aElementsInfo = GetConfigManager()->getUIElementsInfo( css::ui::UIElementType::POPUPMENU ); + } + catch ( const css::lang::IllegalArgumentException& ) + {} + + for ( const auto& aElement : std::as_const(aElementsInfo) ) + { + OUString aUrl; + for ( const auto& aElementProp : aElement ) + { + if ( aElementProp.Name == ITEM_DESCRIPTOR_RESOURCEURL ) + { + aElementProp.Value >>= aUrl; + break; + } + } + + css::uno::Reference< css::container::XIndexAccess > xPopupMenu; + try + { + xPopupMenu = GetConfigManager()->getSettings( aUrl, false ); + } + catch ( const css::uno::Exception& ) + {} + + if ( xPopupMenu.is() ) + { + // insert into std::unordered_map to filter duplicates from the parent + aMenuInfo.emplace( aUrl, true ); + + OUString aUIMenuName = GetUIName( aUrl ); + if ( aUIMenuName.isEmpty() ) + // Menus without UI name aren't supposed to be customized. + continue; + + SvxConfigEntry* pEntry = new SvxConfigEntry( aUIMenuName, aUrl, true, /*bParentData*/false ); + pEntry->SetMain(); + m_pRootEntry->GetEntries()->push_back( pEntry ); + LoadSubMenus( xPopupMenu, aUIMenuName, pEntry, true ); + } + } + + // Retrieve also the parent menus, to make it possible to configure module menus and save them into the document. + css::uno::Reference< css::ui::XUIConfigurationManager > xParentCfgMgr = GetParentConfigManager(); + css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > aParentElementsInfo; + try + { + if ( xParentCfgMgr.is() ) + aParentElementsInfo = xParentCfgMgr->getUIElementsInfo( css::ui::UIElementType::POPUPMENU ); + } + catch ( const css::lang::IllegalArgumentException& ) + {} + + for ( const auto& aElement : std::as_const(aParentElementsInfo) ) + { + OUString aUrl; + for ( const auto& aElementProp : aElement ) + { + if ( aElementProp.Name == ITEM_DESCRIPTOR_RESOURCEURL ) + { + aElementProp.Value >>= aUrl; + break; + } + } + + css::uno::Reference< css::container::XIndexAccess > xPopupMenu; + try + { + if ( aMenuInfo.find( aUrl ) == aMenuInfo.end() ) + xPopupMenu = xParentCfgMgr->getSettings( aUrl, false ); + } + catch ( const css::uno::Exception& ) + {} + + if ( xPopupMenu.is() ) + { + OUString aUIMenuName = GetUIName( aUrl ); + if ( aUIMenuName.isEmpty() ) + continue; + + SvxConfigEntry* pEntry = new SvxConfigEntry( aUIMenuName, aUrl, true, true ); + pEntry->SetMain(); + m_pRootEntry->GetEntries()->push_back( pEntry ); + LoadSubMenus( xPopupMenu, aUIMenuName, pEntry, true ); + } + } + std::sort( m_pRootEntry->GetEntries()->begin(), m_pRootEntry->GetEntries()->end(), SvxConfigPageHelper::EntrySort ); + } + return m_pRootEntry->GetEntries(); +} + +void ContextMenuSaveInData::SetEntries( std::unique_ptr<SvxEntries> pNewEntries ) +{ + m_pRootEntry->SetEntries( std::move(pNewEntries) ); +} + +bool ContextMenuSaveInData::HasURL( const OUString& rURL ) +{ + SvxEntries* pEntries = GetEntries(); + for ( const auto& pEntry : *pEntries ) + if ( pEntry->GetCommand() == rURL ) + return true; + + return false; +} + +bool ContextMenuSaveInData::HasSettings() +{ + return m_pRootEntry && !m_pRootEntry->GetEntries()->empty(); +} + +bool ContextMenuSaveInData::Apply() +{ + if ( !IsModified() ) + return false; + + SvxEntries* pEntries = GetEntries(); + for ( const auto& pEntry : *pEntries ) + { + if ( pEntry->IsModified() || SvxConfigPageHelper::SvxConfigEntryModified( pEntry ) ) + { + css::uno::Reference< css::container::XIndexContainer > xIndexContainer = GetConfigManager()->createSettings(); + css::uno::Reference< css::lang::XSingleComponentFactory > xFactory( xIndexContainer, css::uno::UNO_QUERY ); + ApplyMenu( xIndexContainer, xFactory, pEntry ); + + const OUString& aUrl = pEntry->GetCommand(); + try + { + if ( GetConfigManager()->hasSettings( aUrl ) ) + GetConfigManager()->replaceSettings( aUrl, xIndexContainer ); + else + GetConfigManager()->insertSettings( aUrl, xIndexContainer ); + } + catch ( const css::uno::Exception& ) + {} + } + } + SetModified( false ); + return PersistChanges( GetConfigManager() ); +} + +void ContextMenuSaveInData::Reset() +{ + SvxEntries* pEntries = GetEntries(); + for ( const auto& pEntry : *pEntries ) + { + try + { + GetConfigManager()->removeSettings( pEntry->GetCommand() ); + } + catch ( const css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Exception caught while resetting context menus"); + } + } + PersistChanges( GetConfigManager() ); + m_pRootEntry.reset(); +} + +void ContextMenuSaveInData::ResetContextMenu( const SvxConfigEntry* pEntry ) +{ + try + { + GetConfigManager()->removeSettings( pEntry->GetCommand() ); + } + catch ( const css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Exception caught while resetting context menu"); + } + PersistChanges( GetConfigManager() ); + m_pRootEntry.reset(); +} + +void SvxMenuEntriesListBox::CreateDropDown() +{ + int nWidth = (m_xControl->get_text_height() * 3) / 4; + m_xDropDown->SetOutputSizePixel(Size(nWidth, nWidth)); + DecorationView aDecoView(m_xDropDown.get()); + aDecoView.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth, nWidth)), + SymbolType::SPIN_RIGHT, m_xDropDown->GetTextColor(), + DrawSymbolFlags::NONE); +} + +/****************************************************************************** + * + * SvxMenuEntriesListBox is the listbox in which the menu items for a + * particular menu are shown. We have a custom listbox because we need + * to add drag'n'drop support from the Macro Selector and within the + * listbox + * + *****************************************************************************/ +SvxMenuEntriesListBox::SvxMenuEntriesListBox(std::unique_ptr<weld::TreeView> xControl, SvxConfigPage* pPg) + : m_xControl(std::move(xControl)) + , m_xDropDown(m_xControl->create_virtual_device()) + , m_pPage(pPg) +{ + m_xControl->enable_toggle_buttons(weld::ColumnToggleType::Check); + CreateDropDown(); + m_xControl->connect_key_press(LINK(this, SvxMenuEntriesListBox, KeyInputHdl)); + m_xControl->connect_query_tooltip(LINK(this, SvxMenuEntriesListBox, QueryTooltip)); +} + +SvxMenuEntriesListBox::~SvxMenuEntriesListBox() +{ +} + +IMPL_LINK(SvxMenuEntriesListBox, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + vcl::KeyCode keycode = rKeyEvent.GetKeyCode(); + + // support DELETE for removing the current entry + if ( keycode == KEY_DELETE ) + { + m_pPage->DeleteSelectedContent(); + } + // support CTRL+UP and CTRL+DOWN for moving selected entries + else if ( keycode.GetCode() == KEY_UP && keycode.IsMod1() ) + { + m_pPage->MoveEntry( true ); + } + else if ( keycode.GetCode() == KEY_DOWN && keycode.IsMod1() ) + { + m_pPage->MoveEntry( false ); + } + else + { + return false; // pass on to default handler + } + return true; +} + +IMPL_LINK(SvxMenuEntriesListBox, QueryTooltip, const weld::TreeIter&, rIter, OUString) +{ + SvxConfigEntry *pEntry = weld::fromId<SvxConfigEntry*>(m_xControl->get_id(rIter)); + if (!pEntry || pEntry->GetCommand().isEmpty()) + return OUString(); + const OUString sCommand(pEntry->GetCommand()); + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(m_pPage->GetFrame())); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(sCommand, aModuleName); + OUString sTooltipLabel = vcl::CommandInfoProvider::GetTooltipForCommand(sCommand, aProperties, + m_pPage->GetFrame()); + return CuiResId(RID_CUISTR_COMMANDLABEL) + ": " + pEntry->GetName().replaceFirst("~", "") + "\n" + + CuiResId(RID_CUISTR_COMMANDNAME) + ": " + sCommand + "\n" + + CuiResId(RID_CUISTR_COMMANDTIP) + ": " + sTooltipLabel.replaceFirst("~", ""); +} + +/****************************************************************************** + * + * SvxConfigPage is the abstract base class on which the Menu and Toolbar + * configuration tabpages are based. It includes methods which are common to + * both tabpages to add, delete, move and rename items etc. + * + *****************************************************************************/ +SvxConfigPage::SvxConfigPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) + : SfxTabPage(pPage, pController, "cui/ui/menuassignpage.ui", "MenuAssignPage", &rSet) + , m_aUpdateDataTimer( "SvxConfigPage UpdateDataTimer" ) + , bInitialised(false) + , pCurrentSaveInData(nullptr) + , m_xCommandCategoryListBox(new CommandCategoryListBox(m_xBuilder->weld_combo_box("commandcategorylist"))) + , m_xFunctions(new CuiConfigFunctionListBox(m_xBuilder->weld_tree_view("functions"))) + , m_xCategoryLabel(m_xBuilder->weld_label("categorylabel")) + , m_xDescriptionFieldLb(m_xBuilder->weld_label("descriptionlabel")) + , m_xDescriptionField(m_xBuilder->weld_text_view("desc")) + , m_xLeftFunctionLabel(m_xBuilder->weld_label("leftfunctionlabel")) + , m_xSearchEdit(m_xBuilder->weld_entry("searchEntry")) + , m_xSearchLabel(m_xBuilder->weld_label("searchlabel")) + , m_xCustomizeLabel(m_xBuilder->weld_label("customizelabel")) + , m_xTopLevelListBox(m_xBuilder->weld_combo_box("toplevellist")) + , m_xMoveUpButton(m_xBuilder->weld_button("up")) + , m_xMoveDownButton(m_xBuilder->weld_button("down")) + , m_xSaveInListBox(m_xBuilder->weld_combo_box("savein")) + , m_xCustomizeBox(m_xBuilder->weld_widget("customizebox")) + , m_xInsertBtn(m_xBuilder->weld_menu_button("insert")) + , m_xModifyBtn(m_xBuilder->weld_menu_button("modify")) + , m_xResetBtn(m_xBuilder->weld_button("defaultsbtn")) + , m_xCommandButtons(m_xBuilder->weld_widget("arrowgrid")) + , m_xAddCommandButton(m_xBuilder->weld_button("add")) + , m_xRemoveCommandButton(m_xBuilder->weld_button("remove")) +{ + CustomNotebookbarGenerator::getFileNameAndAppName(m_sAppName, m_sFileName); + + m_xTopLevelListBox->connect_changed(LINK(this, SvxConfigPage, SelectElementHdl)); + + weld::TreeView& rTreeView = m_xFunctions->get_widget(); + Size aSize(rTreeView.get_approximate_digit_width() * 40, rTreeView.get_height_rows(8)); + m_xFunctions->set_size_request(aSize.Width(), aSize.Height()); + m_xDescriptionField->set_size_request(aSize.Width(), m_xDescriptionField->get_height_rows(3)); + + m_aUpdateDataTimer.SetInvokeHandler(LINK(this, SvxConfigPage, ImplUpdateDataHdl)); + m_aUpdateDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT); + + m_xSearchEdit->connect_changed(LINK(this, SvxConfigPage, SearchUpdateHdl)); + m_xSearchEdit->connect_focus_out(LINK(this, SvxConfigPage, FocusOut_Impl)); + + rTreeView.connect_row_activated(LINK(this, SvxConfigPage, FunctionDoubleClickHdl)); + rTreeView.connect_changed(LINK(this, SvxConfigPage, SelectFunctionHdl)); +} + +IMPL_LINK_NOARG(SvxConfigPage, SelectElementHdl, weld::ComboBox&, void) +{ + SelectElement(); +} + +SvxConfigPage::~SvxConfigPage() +{ + int cnt = m_xSaveInListBox->get_count(); + for(int i=0; i < cnt; ++i) + { + SaveInData *pData = weld::fromId<SaveInData*>(m_xSaveInListBox->get_id(i)); + delete pData; + } +} + +void SvxConfigPage::Reset( const SfxItemSet* ) +{ + // If we haven't initialised our XMultiServiceFactory reference + // then Reset is being called at the opening of the dialog. + + // Load menu configuration data for the module of the currently + // selected document, for the currently selected document, and for + // all other open documents of the same module type + if ( !bInitialised ) + { + sal_Int32 nPos = 0; + uno::Reference < css::ui::XUIConfigurationManager > xCfgMgr; + uno::Reference < css::ui::XUIConfigurationManager > xDocCfgMgr; + + uno::Reference< uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext(), uno::UNO_SET_THROW ); + + m_xFrame = GetFrame(); + m_aModuleId = GetFrameWithDefaultAndIdentify( m_xFrame ); + + // replace %MODULENAME in the label with the correct module name + uno::Reference< css::frame::XModuleManager2 > xModuleManager( + css::frame::ModuleManager::create( xContext )); + OUString aModuleName = SvxConfigPageHelper::GetUIModuleName( m_aModuleId, xModuleManager ); + + uno::Reference< css::ui::XModuleUIConfigurationManagerSupplier > + xModuleCfgSupplier( css::ui::theModuleUIConfigurationManagerSupplier::get(xContext) ); + + // Set up data for module specific menus + SaveInData* pModuleData = nullptr; + + try + { + xCfgMgr = + xModuleCfgSupplier->getUIConfigurationManager( m_aModuleId ); + + pModuleData = CreateSaveInData( xCfgMgr, + uno::Reference< css::ui::XUIConfigurationManager >(), + m_aModuleId, + false ); + } + catch ( container::NoSuchElementException& ) + { + } + + if ( pModuleData != nullptr ) + { + OUString sId(weld::toId(pModuleData)); + m_xSaveInListBox->append(sId, utl::ConfigManager::getProductName() + " " + aModuleName); + } + + // try to retrieve the document based ui configuration manager + OUString aTitle; + uno::Reference< frame::XController > xController = + m_xFrame->getController(); + if ( CanConfig( m_aModuleId ) && xController.is() ) + { + uno::Reference< frame::XModel > xModel( xController->getModel() ); + if ( xModel.is() ) + { + uno::Reference< css::ui::XUIConfigurationManagerSupplier > + xCfgSupplier( xModel, uno::UNO_QUERY ); + + if ( xCfgSupplier.is() ) + { + xDocCfgMgr = xCfgSupplier->getUIConfigurationManager(); + } + aTitle = ::comphelper::DocumentInfo::getDocumentTitle( xModel ); + } + } + + SaveInData* pDocData = nullptr; + if ( xDocCfgMgr.is() ) + { + pDocData = CreateSaveInData( xDocCfgMgr, xCfgMgr, m_aModuleId, true ); + + if ( !pDocData->IsReadOnly() ) + { + OUString sId(weld::toId(pDocData)); + m_xSaveInListBox->append(sId, aTitle); + } + } + + // if an item to select has been passed in (eg. the ResourceURL for a + // toolbar) then try to select the SaveInData entry that has that item + bool bURLToSelectFound = false; + if ( !m_aURLToSelect.isEmpty() ) + { + if ( pDocData && pDocData->HasURL( m_aURLToSelect ) ) + { + m_xSaveInListBox->set_active(nPos); + pCurrentSaveInData = pDocData; + bURLToSelectFound = true; + } + else if ( pModuleData && pModuleData->HasURL( m_aURLToSelect ) ) + { + m_xSaveInListBox->set_active(0); + pCurrentSaveInData = pModuleData; + bURLToSelectFound = true; + } + } + + if ( !bURLToSelectFound ) + { + // if the document has menu configuration settings select it + // it the SaveIn listbox, otherwise select the module data + if ( pDocData != nullptr && pDocData->HasSettings() ) + { + m_xSaveInListBox->set_active(nPos); + pCurrentSaveInData = pDocData; + } + else + { + m_xSaveInListBox->set_active(0); + pCurrentSaveInData = pModuleData; + } + } + +#ifdef DBG_UTIL + DBG_ASSERT( pCurrentSaveInData, "SvxConfigPage::Reset(): no SaveInData" ); +#endif + + if ( CanConfig( m_aModuleId ) ) + { + // Load configuration for other open documents which have + // same module type + uno::Sequence< uno::Reference< frame::XFrame > > aFrameList; + try + { + uno::Reference< frame::XDesktop2 > xFramesSupplier = frame::Desktop::create( + xContext ); + + uno::Reference< frame::XFrames > xFrames = + xFramesSupplier->getFrames(); + + aFrameList = xFrames->queryFrames( + frame::FrameSearchFlag::ALL & ~frame::FrameSearchFlag::SELF ); + + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("cui.customize"); + } + + for ( uno::Reference < frame::XFrame > const & xf : std::as_const(aFrameList) ) + { + if ( xf.is() && xf != m_xFrame ) + { + OUString aCheckId; + try{ + aCheckId = xModuleManager->identify( xf ); + } catch(const uno::Exception&) + { aCheckId.clear(); } + + if ( m_aModuleId == aCheckId ) + { + // try to get the document based ui configuration manager + OUString aTitle2; + uno::Reference< frame::XController > xController_ = + xf->getController(); + + if ( xController_.is() ) + { + uno::Reference< frame::XModel > xModel( + xController_->getModel() ); + + if ( xModel.is() ) + { + uno::Reference< + css::ui::XUIConfigurationManagerSupplier > + xCfgSupplier( xModel, uno::UNO_QUERY ); + + if ( xCfgSupplier.is() ) + { + xDocCfgMgr = + xCfgSupplier->getUIConfigurationManager(); + } + aTitle2 = ::comphelper::DocumentInfo::getDocumentTitle( xModel ); + } + } + + if ( xDocCfgMgr.is() ) + { + SaveInData* pData = CreateSaveInData( xDocCfgMgr, xCfgMgr, m_aModuleId, true ); + + if ( pData && !pData->IsReadOnly() ) + { + OUString sId(weld::toId(pData)); + m_xSaveInListBox->append(sId, aTitle2); + } + } + } + } + } + } + + m_xSaveInListBox->connect_changed( + LINK( this, SvxConfigPage, SelectSaveInLocation ) ); + + bInitialised = true; + + Init(); + } + else + { + if ( QueryReset() == RET_YES ) + { + // Reset menu configuration for currently selected SaveInData + GetSaveInData()->Reset(); + + Init(); + } + } +} + +OUString SvxConfigPage::GetFrameWithDefaultAndIdentify( uno::Reference< frame::XFrame >& _inout_rxFrame ) +{ + OUString sModuleID; + try + { + uno::Reference< uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext() ); + + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( + xContext ); + + if ( !_inout_rxFrame.is() ) + _inout_rxFrame = xDesktop->getActiveFrame(); + + if ( !_inout_rxFrame.is() ) + { + _inout_rxFrame = xDesktop->getCurrentFrame(); + } + + if ( !_inout_rxFrame.is()) + { + if (SfxViewFrame* pViewFrame = SfxViewFrame::Current()) + _inout_rxFrame = pViewFrame->GetFrame().GetFrameInterface(); + } + + if ( !_inout_rxFrame.is() ) + { + SAL_WARN( "cui.customize", "SvxConfigPage::GetFrameWithDefaultAndIdentify(): no frame found!" ); + return sModuleID; + } + + sModuleID = vcl::CommandInfoProvider::GetModuleIdentifier(_inout_rxFrame); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("cui.customize"); + } + + return sModuleID; +} + +OUString SvxConfigPage::GetScriptURL() const +{ + OUString result; + + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(m_xFunctions->get_selected_id()); + if (pData) + { + if ( ( pData->nKind == SfxCfgKind::FUNCTION_SLOT ) || + ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT ) || + ( pData->nKind == SfxCfgKind::GROUP_STYLES ) ) + { + result = pData->sCommand; + } + } + + return result; +} + +OUString SvxConfigPage::GetSelectedDisplayName() const +{ + return m_xFunctions->get_selected_text(); +} + +bool SvxConfigPage::FillItemSet( SfxItemSet* ) +{ + bool result = false; + + for (int i = 0, nCount = m_xSaveInListBox->get_count(); i < nCount; ++i) + { + OUString sId = m_xSaveInListBox->get_id(i); + if (sId != notebookbarTabScope) + { + SaveInData* pData = weld::fromId<SaveInData*>(sId); + result = pData->Apply(); + } + } + return result; +} + +IMPL_LINK_NOARG(SvxConfigPage, SelectSaveInLocation, weld::ComboBox&, void) +{ + OUString sId = m_xSaveInListBox->get_active_id(); + if (sId != notebookbarTabScope) + pCurrentSaveInData = weld::fromId<SaveInData*>(sId); + Init(); +} + +void SvxConfigPage::ReloadTopLevelListBox( SvxConfigEntry const * pToSelect ) +{ + int nSelectionPos = m_xTopLevelListBox->get_active(); + m_xTopLevelListBox->clear(); + + if ( GetSaveInData() && GetSaveInData()->GetEntries() ) + { + for (auto const& entryData : *GetSaveInData()->GetEntries()) + { + OUString sId(weld::toId(entryData)); + m_xTopLevelListBox->append(sId, SvxConfigPageHelper::stripHotKey(entryData->GetName())); + + if (entryData == pToSelect) + nSelectionPos = m_xTopLevelListBox->get_count() - 1; + + AddSubMenusToUI( SvxConfigPageHelper::stripHotKey( entryData->GetName() ), entryData ); + } + } +#ifdef DBG_UTIL + else + { + DBG_ASSERT( GetSaveInData(), "SvxConfigPage::ReloadTopLevelListBox(): no SaveInData" ); + DBG_ASSERT( GetSaveInData()->GetEntries() , + "SvxConfigPage::ReloadTopLevelListBox(): no SaveInData entries" ); + } +#endif + + nSelectionPos = (nSelectionPos != -1 && nSelectionPos < m_xTopLevelListBox->get_count()) ? + nSelectionPos : m_xTopLevelListBox->get_count() - 1; + + m_xTopLevelListBox->set_active(nSelectionPos); + SelectElement(); +} + +void SvxConfigPage::AddSubMenusToUI( + std::u16string_view rBaseTitle, SvxConfigEntry const * pParentData ) +{ + for (auto const& entryData : *pParentData->GetEntries()) + { + if (entryData->IsPopup()) + { + OUString subMenuTitle = OUString::Concat(rBaseTitle) + aMenuSeparatorStr + SvxConfigPageHelper::stripHotKey(entryData->GetName()); + + OUString sId(weld::toId(entryData)); + m_xTopLevelListBox->append(sId, subMenuTitle); + + AddSubMenusToUI( subMenuTitle, entryData ); + } + } +} + +SvxEntries* SvxConfigPage::FindParentForChild( + SvxEntries* pRootEntries, SvxConfigEntry* pChildData ) +{ + for (auto const& entryData : *pRootEntries) + { + + if (entryData == pChildData) + { + return pRootEntries; + } + else if (entryData->IsPopup()) + { + SvxEntries* result = + FindParentForChild( entryData->GetEntries(), pChildData ); + + if ( result != nullptr ) + { + return result; + } + } + } + return nullptr; +} + +SvxConfigEntry *SvxConfigPage::CreateCommandFromSelection(const OUString &aURL) +{ + OUString aDisplayName; + + if ( aURL.isEmpty() ) { + return nullptr; + } + + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aURL, m_aModuleId); + + if ( typeid(*pCurrentSaveInData) == typeid(ContextMenuSaveInData) ) + aDisplayName = vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties); + else if ( typeid(*pCurrentSaveInData) == typeid(MenuSaveInData) ) + aDisplayName = vcl::CommandInfoProvider::GetMenuLabelForCommand(aProperties); + else + aDisplayName = vcl::CommandInfoProvider::GetLabelForCommand(aProperties); + + SvxConfigEntry* toret = + new SvxConfigEntry( aDisplayName, aURL, false, /*bParentData*/false ); + + toret->SetUserDefined(); + + if ( aDisplayName.isEmpty() ) + toret->SetName( GetSelectedDisplayName() ); + + return toret; +} + +bool SvxConfigPage::IsCommandInMenuList(const SvxConfigEntry *pEntryData, + const SvxEntries *pEntries) +{ + bool toret = false; + + if ( pEntries != nullptr + && pEntryData != nullptr ) + { + for (auto const& entry : *pEntries) + { + if ( entry->GetCommand() == pEntryData->GetCommand() ) + { + toret = true; + break; + } + } + } + + return toret; +} + +int SvxConfigPage::AddFunction(int nTarget, bool bAllowDuplicates) +{ + int toret = -1; + OUString aURL = GetScriptURL(); + SvxConfigEntry* pParent = GetTopLevelSelection(); + + if ( aURL.isEmpty() || pParent == nullptr ) + { + return -1; + } + + + SvxConfigEntry * pNewEntryData = CreateCommandFromSelection( aURL ); + + // check that this function is not already in the menu + if ( !bAllowDuplicates + && IsCommandInMenuList( pNewEntryData, pParent->GetEntries() ) + ) + { + delete pNewEntryData; + } else { + toret = AppendEntry( pNewEntryData, nTarget ); + } + + UpdateButtonStates(); + return toret; +} + +int SvxConfigPage::AppendEntry( + SvxConfigEntry* pNewEntryData, + int nTarget) +{ + SvxConfigEntry* pTopLevelSelection = GetTopLevelSelection(); + + if (pTopLevelSelection == nullptr) + return -1; + + // Grab the entries list for the currently selected menu + SvxEntries* pEntries = pTopLevelSelection->GetEntries(); + + int nNewEntry = -1; + int nCurEntry = + nTarget != -1 ? nTarget : m_xContentsListBox->get_selected_index(); + + OUString sId(weld::toId(pNewEntryData)); + + if (nCurEntry == -1 || nCurEntry == m_xContentsListBox->n_children() - 1) + { + pEntries->push_back( pNewEntryData ); + m_xContentsListBox->insert(-1, sId); + nNewEntry = m_xContentsListBox->n_children() - 1; + } + else + { + SvxConfigEntry* pEntryData = + weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nCurEntry)); + + SvxEntries::iterator iter = pEntries->begin(); + SvxEntries::const_iterator end = pEntries->end(); + + // Advance the iterator to the data for currently selected entry + sal_uInt16 nPos = 0; + while (*iter != pEntryData && ++iter != end) + { + ++nPos; + } + + // Now step past it to the entry after the currently selected one + ++iter; + ++nPos; + + // Now add the new entry to the UI and to the parent's list + if ( iter != end ) + { + pEntries->insert( iter, pNewEntryData ); + m_xContentsListBox->insert(nPos, sId); + nNewEntry = nPos; + } + } + + if (nNewEntry != -1) + { + m_xContentsListBox->select(nNewEntry); + m_xContentsListBox->scroll_to_row(nNewEntry); + + GetSaveInData()->SetModified(); + GetTopLevelSelection()->SetModified(); + } + + return nNewEntry; +} + +namespace +{ + template<typename itertype> void TmplInsertEntryIntoUI(SvxConfigEntry* pNewEntryData, weld::TreeView& rTreeView, itertype& rIter, SaveInData* pSaveInData, + VirtualDevice& rDropDown, bool bMenu) + { + OUString sId(weld::toId(pNewEntryData)); + + rTreeView.set_id(rIter, sId); + + if (pNewEntryData->IsSeparator()) + { + rTreeView.set_text(rIter, "----------------------------------", 0); + } + else + { + auto xImage = pSaveInData->GetImage(pNewEntryData->GetCommand()); + if (xImage.is()) + rTreeView.set_image(rIter, xImage, -1); + OUString aName = SvxConfigPageHelper::stripHotKey( pNewEntryData->GetName() ); + rTreeView.set_text(rIter, aName, 0); + } + + if (bMenu) // menus + { + if (pNewEntryData->IsPopup() || pNewEntryData->GetStyle() & css::ui::ItemStyle::DROP_DOWN) + rTreeView.set_image(rIter, rDropDown, 1); + else + rTreeView.set_image(rIter, css::uno::Reference<css::graphic::XGraphic>(), 1); + } + } +} + +void SvxConfigPage::InsertEntryIntoUI(SvxConfigEntry* pNewEntryData, weld::TreeView& rTreeView, int nPos, bool bMenu) +{ + TmplInsertEntryIntoUI<int>(pNewEntryData, rTreeView, nPos, GetSaveInData(), + m_xContentsListBox->get_dropdown_image(), bMenu); +} + +void SvxConfigPage::InsertEntryIntoUI(SvxConfigEntry* pNewEntryData, weld::TreeView& rTreeView, weld::TreeIter& rIter, bool bMenu) +{ + TmplInsertEntryIntoUI<weld::TreeIter>(pNewEntryData, rTreeView, rIter, GetSaveInData(), + m_xContentsListBox->get_dropdown_image(), bMenu); +} + +IMPL_LINK(SvxConfigPage, MoveHdl, weld::Button&, rButton, void) +{ + MoveEntry(&rButton == m_xMoveUpButton.get()); +} + +IMPL_LINK_NOARG(SvxConfigPage, FunctionDoubleClickHdl, weld::TreeView&, bool) +{ + if (m_xAddCommandButton->get_sensitive()) + m_xAddCommandButton->clicked(); + return true; +} + +IMPL_LINK_NOARG(SvxConfigPage, SelectFunctionHdl, weld::TreeView&, void) +{ + // GetScriptURL() returns a non-empty string if a + // valid command is selected on the left box + OUString aSelectCommand = GetScriptURL(); + bool bIsValidCommand = !aSelectCommand.isEmpty(); + + // Enable/disable Add and Remove buttons depending on current selection + if (bIsValidCommand) + { + m_xAddCommandButton->set_sensitive(true); + m_xRemoveCommandButton->set_sensitive(true); + + if (SfxHelp::IsHelpInstalled()) + { + m_xDescriptionField->set_text(m_xFunctions->GetHelpText(false)); + } + else + { + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(m_xFunctions->get_selected_id()); + if (pData) + { + bool bIsExperimental + = vcl::CommandInfoProvider::IsExperimental(pData->sCommand, m_aModuleId); + + OUString aExperimental = "\n" + CuiResId(RID_CUISTR_COMMANDEXPERIMENTAL); + OUString aLabel = CuiResId(RID_CUISTR_COMMANDLABEL) + ": " + pData->sLabel + "\n"; + OUString aName = CuiResId(RID_CUISTR_COMMANDNAME) + ": " + pData->sCommand + "\n"; + OUString aTip = CuiResId(RID_CUISTR_COMMANDTIP) + ": " + pData->sTooltip; + if (bIsExperimental) + m_xDescriptionField->set_text(aLabel + aName + aTip + aExperimental); + else + m_xDescriptionField->set_text(aLabel + aName + aTip); + } + } + } + else + { + + m_xAddCommandButton->set_sensitive(false); + m_xRemoveCommandButton->set_sensitive(false); + + m_xDescriptionField->set_text(""); + } + + UpdateButtonStates(); +} + +IMPL_LINK_NOARG(SvxConfigPage, ImplUpdateDataHdl, Timer*, void) +{ + OUString aSearchTerm(m_xSearchEdit->get_text()); + m_xCommandCategoryListBox->categorySelected(m_xFunctions.get(), aSearchTerm, GetSaveInData()); + SelectFunctionHdl(m_xFunctions->get_widget()); +} + +IMPL_LINK_NOARG(SvxConfigPage, SearchUpdateHdl, weld::Entry&, void) +{ + m_aUpdateDataTimer.Start(); +} + +IMPL_LINK_NOARG(SvxConfigPage, FocusOut_Impl, weld::Widget&, void) +{ + if (m_aUpdateDataTimer.IsActive()) + { + m_aUpdateDataTimer.Stop(); + m_aUpdateDataTimer.Invoke(); + } +} + +void SvxConfigPage::MoveEntry(bool bMoveUp) +{ + weld::TreeView& rTreeView = m_xContentsListBox->get_widget(); + + int nSourceEntry = rTreeView.get_selected_index(); + int nTargetEntry = -1; + int nToSelect = -1; + + if (nSourceEntry == -1) + { + return; + } + + if ( bMoveUp ) + { + // Move Up is just a Move Down with the source and target reversed + nTargetEntry = nSourceEntry; + nSourceEntry = nTargetEntry - 1; + nToSelect = nSourceEntry; + } + else + { + nTargetEntry = nSourceEntry + 1; + nToSelect = nTargetEntry; + } + + if (MoveEntryData(nSourceEntry, nTargetEntry)) + { + rTreeView.swap(nSourceEntry, nTargetEntry); + rTreeView.select(nToSelect); + rTreeView.scroll_to_row(nToSelect); + + UpdateButtonStates(); + } +} + +bool SvxConfigPage::MoveEntryData(int nSourceEntry, int nTargetEntry) +{ + //#i53677# + if (nSourceEntry == -1 || nTargetEntry == -1) + { + return false; + } + + // Grab the entries list for the currently selected menu + SvxEntries* pEntries = GetTopLevelSelection()->GetEntries(); + + SvxConfigEntry* pSourceData = + weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nSourceEntry)); + + SvxConfigEntry* pTargetData = + weld::fromId<SvxConfigEntry*>(m_xContentsListBox->get_id(nTargetEntry)); + + if ( pSourceData == nullptr || pTargetData == nullptr ) + return false; + + // remove the source entry from our list + SvxConfigPageHelper::RemoveEntry( pEntries, pSourceData ); + + SvxEntries::iterator iter = pEntries->begin(); + SvxEntries::const_iterator end = pEntries->end(); + + // advance the iterator to the position of the target entry + while (*iter != pTargetData && ++iter != end) ; + + // insert the source entry at the position after the target + pEntries->insert( ++iter, pSourceData ); + + GetSaveInData()->SetModified(); + GetTopLevelSelection()->SetModified(); + + return true; +} + +SvxMainMenuOrganizerDialog::SvxMainMenuOrganizerDialog( + weld::Window* pParent, SvxEntries* entries, + SvxConfigEntry const * selection, bool bCreateMenu ) + : GenericDialogController(pParent, "cui/ui/movemenu.ui", "MoveMenuDialog") + , m_xMenuBox(m_xBuilder->weld_widget("namebox")) + , m_xMenuNameEdit(m_xBuilder->weld_entry("menuname")) + , m_xMenuListBox(m_xBuilder->weld_tree_view("menulist")) + , m_xMoveUpButton(m_xBuilder->weld_button("up")) + , m_xMoveDownButton(m_xBuilder->weld_button("down")) +{ + m_xMenuListBox->set_size_request(-1, m_xMenuListBox->get_height_rows(12)); + + // Copy the entries list passed in + if ( entries != nullptr ) + { + mpEntries.reset( new SvxEntries ); + for (auto const& entry : *entries) + { + m_xMenuListBox->append(weld::toId(entry), + SvxConfigPageHelper::stripHotKey(entry->GetName())); + mpEntries->push_back(entry); + if (entry == selection) + { + m_xMenuListBox->select(m_xMenuListBox->n_children() - 1); + } + } + } + + if ( bCreateMenu ) + { + // Generate custom name for new menu + OUString prefix = CuiResId( RID_CUISTR_NEW_MENU ); + + OUString newname = SvxConfigPageHelper::generateCustomName( prefix, entries ); + OUString newurl = SvxConfigPageHelper::generateCustomMenuURL( mpEntries.get() ); + + SvxConfigEntry* pNewEntryData = + new SvxConfigEntry( newname, newurl, true, /*bParentData*/false ); + pNewEntryData->SetName( newname ); + pNewEntryData->SetUserDefined(); + pNewEntryData->SetMain(); + + m_sNewMenuEntryId = weld::toId(pNewEntryData); + m_xMenuListBox->append(m_sNewMenuEntryId, + SvxConfigPageHelper::stripHotKey(pNewEntryData->GetName())); + m_xMenuListBox->select(m_xMenuListBox->n_children() - 1); + + if (mpEntries) + mpEntries->push_back(pNewEntryData); + + m_xMenuNameEdit->set_text(newname); + m_xMenuNameEdit->connect_changed(LINK(this, SvxMainMenuOrganizerDialog, ModifyHdl)); + } + else + { + // hide name label and textfield + m_xMenuBox->hide(); + // change the title + m_xDialog->set_title(CuiResId(RID_CUISTR_MOVE_MENU)); + } + + m_xMenuListBox->connect_changed(LINK(this, SvxMainMenuOrganizerDialog, SelectHdl)); + + m_xMoveUpButton->connect_clicked(LINK( this, SvxMainMenuOrganizerDialog, MoveHdl)); + m_xMoveDownButton->connect_clicked(LINK( this, SvxMainMenuOrganizerDialog, MoveHdl)); + + UpdateButtonStates(); +} + +SvxMainMenuOrganizerDialog::~SvxMainMenuOrganizerDialog() +{ +} + +IMPL_LINK_NOARG(SvxMainMenuOrganizerDialog, ModifyHdl, weld::Entry&, void) +{ + // if the Edit control is empty do not change the name + if (m_xMenuNameEdit->get_text().isEmpty()) + { + return; + } + + SvxConfigEntry* pNewEntryData = weld::fromId<SvxConfigEntry*>(m_sNewMenuEntryId); + pNewEntryData->SetName(m_xMenuNameEdit->get_text()); + + const int nNewMenuPos = m_xMenuListBox->find_id(m_sNewMenuEntryId); + const int nOldSelection = m_xMenuListBox->get_selected_index(); + m_xMenuListBox->remove(nNewMenuPos); + m_xMenuListBox->insert(nNewMenuPos, pNewEntryData->GetName(), &m_sNewMenuEntryId, nullptr, nullptr); + m_xMenuListBox->select(nOldSelection); +} + +IMPL_LINK_NOARG(SvxMainMenuOrganizerDialog, SelectHdl, weld::TreeView&, void) +{ + UpdateButtonStates(); +} + +void SvxMainMenuOrganizerDialog::UpdateButtonStates() +{ + // Disable Up and Down buttons depending on current selection + const int nSelected = m_xMenuListBox->get_selected_index(); + m_xMoveUpButton->set_sensitive(nSelected > 0); + m_xMoveDownButton->set_sensitive(nSelected != -1 && nSelected < m_xMenuListBox->n_children() - 1); +} + +IMPL_LINK( SvxMainMenuOrganizerDialog, MoveHdl, weld::Button&, rButton, void ) +{ + int nSourceEntry = m_xMenuListBox->get_selected_index(); + if (nSourceEntry == -1) + return; + + int nTargetEntry; + + if (&rButton == m_xMoveDownButton.get()) + { + nTargetEntry = nSourceEntry + 1; + } + else + { + // Move Up is just a Move Down with the source and target reversed + nTargetEntry = nSourceEntry - 1; + } + + OUString sId = m_xMenuListBox->get_id(nSourceEntry); + OUString sEntry = m_xMenuListBox->get_text(nSourceEntry); + m_xMenuListBox->remove(nSourceEntry); + m_xMenuListBox->insert(nTargetEntry, sEntry, &sId, nullptr, nullptr); + m_xMenuListBox->select(nTargetEntry); + + std::swap(mpEntries->at(nSourceEntry), mpEntries->at(nTargetEntry)); + + UpdateButtonStates(); +} + +SvxConfigEntry* SvxMainMenuOrganizerDialog::GetSelectedEntry() +{ + const int nSelected(m_xMenuListBox->get_selected_index()); + if (nSelected == -1) + return nullptr; + return weld::fromId<SvxConfigEntry*>(m_xMenuListBox->get_id(nSelected)); +} + +SvxConfigEntry::SvxConfigEntry( OUString aDisplayName, + OUString aCommandURL, bool bPopup, bool bParentData ) + : nId( 1 ) + , aLabel(std::move(aDisplayName)) + , aCommand(std::move(aCommandURL)) + , bPopUp(bPopup) + , bStrEdited( false ) + , bIsUserDefined( false ) + , bIsMain( false ) + , bIsParentData( bParentData ) + , bIsModified( false ) + , bIsVisible( true ) + , nStyle( 0 ) +{ + if (bPopUp) + { + mpEntries.reset( new SvxEntries ); + } +} + +SvxConfigEntry::~SvxConfigEntry() +{ + if (mpEntries) + { + for (auto const& entry : *mpEntries) + { + delete entry; + } + } +} + +bool SvxConfigEntry::IsMovable() const +{ + return !IsPopup() || IsMain(); +} + +bool SvxConfigEntry::IsDeletable() const +{ + return !IsMain() || IsUserDefined(); +} + +bool SvxConfigEntry::IsRenamable() const +{ + return !IsMain() || IsUserDefined(); +} + +ToolbarSaveInData::ToolbarSaveInData( + const uno::Reference < css::ui::XUIConfigurationManager >& xCfgMgr, + const uno::Reference < css::ui::XUIConfigurationManager >& xParentCfgMgr, + const OUString& aModuleId, + bool docConfig ) : + + SaveInData ( xCfgMgr, xParentCfgMgr, aModuleId, docConfig ), + m_aDescriptorContainer ( ITEM_DESCRIPTOR_CONTAINER ) + +{ + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + // Initialize the m_xPersistentWindowState variable which is used + // to get the default properties of system toolbars such as name + uno::Reference< container::XNameAccess > xPWSS = css::ui::theWindowStateConfiguration::get( xContext ); + + xPWSS->getByName( aModuleId ) >>= m_xPersistentWindowState; +} + +ToolbarSaveInData::~ToolbarSaveInData() +{ +} + +sal_Int32 ToolbarSaveInData::GetSystemStyle( const OUString& rResourceURL ) +{ + sal_Int32 result = 0; + + if ( rResourceURL.startsWith( "private" ) && + m_xPersistentWindowState.is() && + m_xPersistentWindowState->hasByName( rResourceURL ) ) + { + try + { + uno::Sequence< beans::PropertyValue > aProps; + uno::Any a( m_xPersistentWindowState->getByName( rResourceURL ) ); + + if ( a >>= aProps ) + { + for ( beans::PropertyValue const & prop : std::as_const(aProps) ) + { + if ( prop.Name == ITEM_DESCRIPTOR_STYLE ) + { + prop.Value >>= result; + break; + } + } + } + } + catch ( uno::Exception& ) + { + // do nothing, a default value is returned + } + } + + return result; +} + +void ToolbarSaveInData::SetSystemStyle( + const uno::Reference< frame::XFrame >& xFrame, + const OUString& rResourceURL, + sal_Int32 nStyle ) +{ + // change the style using the API + SetSystemStyle( rResourceURL, nStyle ); + + // this code is a temporary hack as the UI is not updating after + // changing the toolbar style via the API + uno::Reference< css::frame::XLayoutManager > xLayoutManager; + vcl::Window *window = nullptr; + + uno::Reference< beans::XPropertySet > xPropSet( xFrame, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + uno::Any a = xPropSet->getPropertyValue( "LayoutManager" ); + a >>= xLayoutManager; + } + + if ( xLayoutManager.is() ) + { + uno::Reference< css::ui::XUIElement > xUIElement = + xLayoutManager->getElement( rResourceURL ); + + // check reference before we call getRealInterface. The layout manager + // can only provide references for elements that have been created + // before. It's possible that the current element is not available. + uno::Reference< css::awt::XWindow > xWindow; + if ( xUIElement.is() ) + xWindow.set( xUIElement->getRealInterface(), uno::UNO_QUERY ); + + window = VCLUnoHelper::GetWindow( xWindow ); + } + + if ( window == nullptr || window->GetType() != WindowType::TOOLBOX ) + return; + + ToolBox* toolbox = static_cast<ToolBox*>(window); + + if ( nStyle == 0 ) + { + toolbox->SetButtonType( ButtonType::SYMBOLONLY ); + } + else if ( nStyle == 1 ) + { + toolbox->SetButtonType( ButtonType::TEXT ); + } + if ( nStyle == 2 ) + { + toolbox->SetButtonType( ButtonType::SYMBOLTEXT ); + } +} + +void ToolbarSaveInData::SetSystemStyle( + const OUString& rResourceURL, + sal_Int32 nStyle ) +{ + if ( !(rResourceURL.startsWith( "private" ) && + m_xPersistentWindowState.is() && + m_xPersistentWindowState->hasByName( rResourceURL )) ) + return; + + try + { + uno::Sequence< beans::PropertyValue > aProps; + + uno::Any a( m_xPersistentWindowState->getByName( rResourceURL ) ); + + if ( a >>= aProps ) + { + for ( beans::PropertyValue& prop : asNonConstRange(aProps) ) + { + if ( prop.Name == ITEM_DESCRIPTOR_STYLE ) + { + prop.Value <<= nStyle; + break; + } + } + } + + uno::Reference< container::XNameReplace > + xNameReplace( m_xPersistentWindowState, uno::UNO_QUERY ); + + xNameReplace->replaceByName( rResourceURL, uno::Any( aProps ) ); + } + catch ( uno::Exception& ) + { + // do nothing, a default value is returned + TOOLS_WARN_EXCEPTION("cui.customize", "Exception setting toolbar style"); + } +} + +OUString ToolbarSaveInData::GetSystemUIName( const OUString& rResourceURL ) +{ + OUString result; + + if ( rResourceURL.startsWith( "private" ) && + m_xPersistentWindowState.is() && + m_xPersistentWindowState->hasByName( rResourceURL ) ) + { + try + { + uno::Sequence< beans::PropertyValue > aProps; + uno::Any a( m_xPersistentWindowState->getByName( rResourceURL ) ); + + if ( a >>= aProps ) + { + for ( beans::PropertyValue const & prop : std::as_const(aProps) ) + { + if ( prop.Name == ITEM_DESCRIPTOR_UINAME ) + { + prop.Value >>= result; + } + } + } + } + catch ( uno::Exception& ) + { + // do nothing, an empty UIName will be returned + } + } + + if ( rResourceURL.startsWith( ".uno" ) && + m_xCommandToLabelMap.is() && + m_xCommandToLabelMap->hasByName( rResourceURL ) ) + { + uno::Any a; + try + { + a = m_xCommandToLabelMap->getByName( rResourceURL ); + + uno::Sequence< beans::PropertyValue > aPropSeq; + if ( a >>= aPropSeq ) + { + for ( beans::PropertyValue const & prop : std::as_const(aPropSeq) ) + { + if ( prop.Name == ITEM_DESCRIPTOR_LABEL ) + { + prop.Value >>= result; + } + } + } + } + catch ( uno::Exception& ) + { + // not a system command name + } + } + + return result; +} + +SvxEntries* ToolbarSaveInData::GetEntries() +{ + typedef std::unordered_map<OUString, bool > ToolbarInfo; + + ToolbarInfo aToolbarInfo; + + if ( pRootEntry == nullptr ) + { + + pRootEntry.reset( new SvxConfigEntry( "MainToolbars", OUString(), true, /*bParentData*/false) ); + + const uno::Sequence< uno::Sequence < beans::PropertyValue > > info = + GetConfigManager()->getUIElementsInfo( + css::ui::UIElementType::TOOLBAR ); + + for ( uno::Sequence<beans::PropertyValue> const & props : info ) + { + OUString url; + OUString systemname; + OUString uiname; + + for ( const beans::PropertyValue& prop : props ) + { + if ( prop.Name == ITEM_DESCRIPTOR_RESOURCEURL ) + { + prop.Value >>= url; + systemname = url.copy( url.lastIndexOf( '/' ) + 1 ); + } + else if ( prop.Name == ITEM_DESCRIPTOR_UINAME ) + { + prop.Value >>= uiname; + } + } + + try + { + uno::Reference< container::XIndexAccess > xToolbarSettings = + GetConfigManager()->getSettings( url, false ); + + if ( uiname.isEmpty() ) + { + // try to get the name from m_xPersistentWindowState + uiname = GetSystemUIName( url ); + + if ( uiname.isEmpty() ) + { + uiname = systemname; + } + } + + SvxConfigEntry* pEntry = new SvxConfigEntry( + uiname, url, true, /*bParentData*/false ); + + pEntry->SetMain(); + pEntry->SetStyle( GetSystemStyle( url ) ); + + + // insert into std::unordered_map to filter duplicates from the parent + aToolbarInfo.emplace( systemname, true ); + + if ( systemname.startsWith( CUSTOM_TOOLBAR_STR ) ) + { + pEntry->SetUserDefined(); + } + else + { + pEntry->SetUserDefined( false ); + } + + pRootEntry->GetEntries()->push_back( pEntry ); + + LoadToolbar( xToolbarSettings, pEntry ); + } + catch ( container::NoSuchElementException& ) + { + // TODO, handle resourceURL with no settings + } + } + + uno::Reference< css::ui::XUIConfigurationManager > xParentCfgMgr = GetParentConfigManager(); + if ( xParentCfgMgr.is() ) + { + // Retrieve also the parent toolbars to make it possible + // to configure module toolbars and save them into the document + // config manager. + const uno::Sequence< uno::Sequence < beans::PropertyValue > > info_ = + xParentCfgMgr->getUIElementsInfo( + css::ui::UIElementType::TOOLBAR ); + + for ( uno::Sequence<beans::PropertyValue> const & props : info_ ) + { + OUString url; + OUString systemname; + OUString uiname; + + for ( const beans::PropertyValue& prop : props ) + { + if ( prop.Name == ITEM_DESCRIPTOR_RESOURCEURL ) + { + prop.Value >>= url; + systemname = url.copy( url.lastIndexOf( '/' ) + 1 ); + } + else if ( prop.Name == ITEM_DESCRIPTOR_UINAME ) + { + prop.Value >>= uiname; + } + } + + // custom toolbars of the parent are not visible in the document layer + OUString custom(CUSTOM_TOOLBAR_STR); + if ( systemname.startsWith( custom ) ) + continue; + + // check if toolbar is already in the document layer + ToolbarInfo::const_iterator pIter = aToolbarInfo.find( systemname ); + if ( pIter == aToolbarInfo.end() ) + { + aToolbarInfo.emplace( systemname, true ); + + try + { + uno::Reference< container::XIndexAccess > xToolbarSettings = + xParentCfgMgr->getSettings( url, false ); + + if ( uiname.isEmpty() ) + { + // try to get the name from m_xPersistentWindowState + uiname = GetSystemUIName( url ); + + if ( uiname.isEmpty() ) + { + uiname = systemname; + } + } + + SvxConfigEntry* pEntry = new SvxConfigEntry( + uiname, url, true, true ); + + pEntry->SetMain(); + pEntry->SetStyle( GetSystemStyle( url ) ); + + if ( systemname.startsWith( custom ) ) + { + pEntry->SetUserDefined(); + } + else + { + pEntry->SetUserDefined( false ); + } + + pRootEntry->GetEntries()->push_back( pEntry ); + + LoadToolbar( xToolbarSettings, pEntry ); + } + catch ( container::NoSuchElementException& ) + { + // TODO, handle resourceURL with no settings + } + } + } + } + + std::sort( GetEntries()->begin(), GetEntries()->end(), SvxConfigPageHelper::EntrySort ); + } + + return pRootEntry->GetEntries(); +} + +void +ToolbarSaveInData::SetEntries( std::unique_ptr<SvxEntries> pNewEntries ) +{ + pRootEntry->SetEntries( std::move(pNewEntries) ); +} + +bool +ToolbarSaveInData::HasURL( const OUString& rURL ) +{ + for (auto const& entry : *GetEntries()) + { + if (entry->GetCommand() == rURL) + { + return !entry->IsParentData(); + } + } + return false; +} + +bool ToolbarSaveInData::HasSettings() +{ + // return true if there is at least one toolbar entry + return !GetEntries()->empty(); +} + +void ToolbarSaveInData::Reset() +{ + // reset each toolbar by calling removeSettings for its toolbar URL + for (auto const& entry : *GetEntries()) + { + try + { + const OUString& url = entry->GetCommand(); + GetConfigManager()->removeSettings( url ); + } + catch ( uno::Exception& ) + { + // error occurred removing the settings + // TODO - add error dialog in future? + } + } + + // persist changes to toolbar storage + PersistChanges( GetConfigManager() ); + + // now delete the root SvxConfigEntry the next call to GetEntries() + // causes it to be reinitialised + pRootEntry.reset(); + + // reset all icons to default + try + { + GetImageManager()->reset(); + PersistChanges( GetImageManager() ); + } + catch ( uno::Exception& ) + { + SAL_WARN("cui.customize", "Error resetting all icons when resetting toolbars"); + } +} + +bool ToolbarSaveInData::Apply() +{ + // toolbar changes are instantly applied + return false; +} + +void ToolbarSaveInData::ApplyToolbar( + uno::Reference< container::XIndexContainer > const & rToolbarBar, + uno::Reference< lang::XSingleComponentFactory >& rFactory, + SvxConfigEntry const * pToolbarData ) +{ + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + + for (auto const& entry : *pToolbarData->GetEntries()) + { + if (entry->IsPopup()) + { + uno::Sequence< beans::PropertyValue > aPropValueSeq = + SvxConfigPageHelper::ConvertToolbarEntry(entry); + + uno::Reference< container::XIndexContainer > xSubMenuBar( + rFactory->createInstanceWithContext( xContext ), + uno::UNO_QUERY ); + + sal_Int32 nIndex = aPropValueSeq.getLength(); + aPropValueSeq.realloc( nIndex + 1 ); + auto pPropValueSeq = aPropValueSeq.getArray(); + pPropValueSeq[nIndex].Name = m_aDescriptorContainer; + pPropValueSeq[nIndex].Value <<= xSubMenuBar; + rToolbarBar->insertByIndex( + rToolbarBar->getCount(), uno::Any( aPropValueSeq )); + + ApplyToolbar(xSubMenuBar, rFactory, entry); + } + else if (entry->IsSeparator()) + { + rToolbarBar->insertByIndex( + rToolbarBar->getCount(), uno::Any( m_aSeparatorSeq )); + } + else + { + uno::Sequence< beans::PropertyValue > aPropValueSeq = + SvxConfigPageHelper::ConvertToolbarEntry(entry); + + rToolbarBar->insertByIndex( + rToolbarBar->getCount(), uno::Any( aPropValueSeq )); + } + } +} + +void ToolbarSaveInData::ApplyToolbar( SvxConfigEntry* pToolbar ) +{ + // Apply new toolbar structure to our settings container + uno::Reference< container::XIndexAccess > xSettings = + GetConfigManager()->createSettings(); + + uno::Reference< container::XIndexContainer > xIndexContainer ( + xSettings, uno::UNO_QUERY ); + + uno::Reference< lang::XSingleComponentFactory > xFactory ( + xSettings, uno::UNO_QUERY ); + + ApplyToolbar( xIndexContainer, xFactory, pToolbar ); + + uno::Reference< beans::XPropertySet > xProps( + xSettings, uno::UNO_QUERY ); + + if ( pToolbar->IsUserDefined() ) + { + xProps->setPropertyValue( + ITEM_DESCRIPTOR_UINAME, + uno::Any( pToolbar->GetName() ) ); + } + + try + { + if ( GetConfigManager()->hasSettings( pToolbar->GetCommand() ) ) + { + GetConfigManager()->replaceSettings( + pToolbar->GetCommand(), xSettings ); + } + else + { + GetConfigManager()->insertSettings( + pToolbar->GetCommand(), xSettings ); + if ( pToolbar->IsParentData() ) + pToolbar->SetParentData( false ); + } + } + catch ( css::uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "caught exception saving settings"); + } + + PersistChanges( GetConfigManager() ); +} + +void ToolbarSaveInData::CreateToolbar( SvxConfigEntry* pToolbar ) +{ + // show the new toolbar in the UI also + uno::Reference< container::XIndexAccess > + xSettings = GetConfigManager()->createSettings(); + + uno::Reference< beans::XPropertySet > + xPropertySet( xSettings, uno::UNO_QUERY ); + + xPropertySet->setPropertyValue( + ITEM_DESCRIPTOR_UINAME, + uno::Any( pToolbar->GetName() ) ); + + try + { + GetConfigManager()->insertSettings( pToolbar->GetCommand(), xSettings ); + } + catch ( css::uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "caught exception saving settings"); + } + + GetEntries()->push_back( pToolbar ); + + PersistChanges( GetConfigManager() ); +} + +void ToolbarSaveInData::RemoveToolbar( SvxConfigEntry* pToolbar ) +{ + try + { + OUString url = pToolbar->GetCommand(); + GetConfigManager()->removeSettings( url ); + SvxConfigPageHelper::RemoveEntry( GetEntries(), pToolbar ); + delete pToolbar; + + PersistChanges( GetConfigManager() ); + + // remove the persistent window state data + css::uno::Reference< css::container::XNameContainer > xNameContainer( + m_xPersistentWindowState, css::uno::UNO_QUERY_THROW ); + + xNameContainer->removeByName( url ); + } + catch ( uno::Exception& ) + { + // error occurred removing the settings + } +} + +void ToolbarSaveInData::RestoreToolbar( SvxConfigEntry* pToolbar ) +{ + OUString url = pToolbar->GetCommand(); + + // Restore of toolbar is done by removing it from + // its configuration manager and then getting it again + bool bParentToolbar = pToolbar->IsParentData(); + + // Cannot restore parent toolbar + if ( bParentToolbar ) + return; + + try + { + GetConfigManager()->removeSettings( url ); + pToolbar->GetEntries()->clear(); + PersistChanges( GetConfigManager() ); + } + catch ( uno::Exception& ) + { + // if an error occurs removing the settings then just return + return; + } + + // Now reload the toolbar settings + try + { + uno::Reference< container::XIndexAccess > xToolbarSettings; + if ( IsDocConfig() ) + { + xToolbarSettings = GetParentConfigManager()->getSettings( url, false ); + pToolbar->SetParentData(); + } + else + xToolbarSettings = GetConfigManager()->getSettings( url, false ); + + LoadToolbar( xToolbarSettings, pToolbar ); + + // After reloading, ensure that the icon is reset of each entry + // in the toolbar + uno::Sequence< OUString > aURLSeq( 1 ); + auto pURLSeq = aURLSeq.getArray(); + for (auto const& entry : *pToolbar->GetEntries()) + { + pURLSeq[ 0 ] = entry->GetCommand(); + + try + { + GetImageManager()->removeImages( SvxConfigPageHelper::GetImageType(), aURLSeq ); + } + catch ( uno::Exception& ) + { + SAL_WARN("cui.customize", "Error restoring icon when resetting toolbar"); + } + } + PersistChanges( GetImageManager() ); + } + catch ( container::NoSuchElementException& ) + { + // cannot find the resource URL after removing it + // so no entry will appear in the toolbar list + } +} + +void ToolbarSaveInData::LoadToolbar( + const uno::Reference< container::XIndexAccess >& xToolbarSettings, + SvxConfigEntry const * pParentData ) +{ + SvxEntries* pEntries = pParentData->GetEntries(); + + for ( sal_Int32 nIndex = 0; nIndex < xToolbarSettings->getCount(); ++nIndex ) + { + OUString aCommandURL; + OUString aLabel; + bool bIsVisible; + sal_Int32 nStyle; + + sal_uInt16 nType( css::ui::ItemType::DEFAULT ); + + bool bItem = SvxConfigPageHelper::GetToolbarItemData( xToolbarSettings, nIndex, aCommandURL, + aLabel, nType, bIsVisible, nStyle ); + + if ( bItem ) + { + bool bIsUserDefined = true; + + if ( nType == css::ui::ItemType::DEFAULT ) + { + uno::Any a; + try + { + a = m_xCommandToLabelMap->getByName( aCommandURL ); + bIsUserDefined = false; + } + catch ( container::NoSuchElementException& ) + { + bIsUserDefined = true; + } + + bool bUseDefaultLabel = false; + // If custom label not set retrieve it from the command + // to info service + if ( aLabel.isEmpty() ) + { + bUseDefaultLabel = true; + uno::Sequence< beans::PropertyValue > aPropSeq; + if ( a >>= aPropSeq ) + { + for ( beans::PropertyValue const & prop : std::as_const(aPropSeq) ) + { + if ( prop.Name == "Name" ) + { + prop.Value >>= aLabel; + break; + } + } + } + } + + SvxConfigEntry* pEntry = new SvxConfigEntry( + aLabel, aCommandURL, false, /*bParentData*/false ); + + pEntry->SetUserDefined( bIsUserDefined ); + pEntry->SetVisible( bIsVisible ); + pEntry->SetStyle( nStyle ); + + if ( !bUseDefaultLabel ) + pEntry->SetName( aLabel ); + + pEntries->push_back( pEntry ); + } + else + { + SvxConfigEntry* pEntry = new SvxConfigEntry; + pEntry->SetUserDefined( bIsUserDefined ); + pEntries->push_back( pEntry ); + } + } + } +} + +SvxNewToolbarDialog::SvxNewToolbarDialog(weld::Window* pWindow, const OUString& rName) + : GenericDialogController(pWindow, "cui/ui/newtoolbardialog.ui", "NewToolbarDialog") + , m_xEdtName(m_xBuilder->weld_entry("edit")) + , m_xSaveInListBox(m_xBuilder->weld_combo_box("savein")) +{ + m_xEdtName->set_text(rName); + m_xEdtName->select_region(0, -1); +} + +SvxNewToolbarDialog::~SvxNewToolbarDialog() +{ +} + +/******************************************************************************* +* +* The SvxIconSelectorDialog class +* +*******************************************************************************/ +SvxIconSelectorDialog::SvxIconSelectorDialog(weld::Window *pWindow, + uno::Reference< css::ui::XImageManager > xImageManager, + uno::Reference< css::ui::XImageManager > xParentImageManager) + : GenericDialogController(pWindow, "cui/ui/iconselectordialog.ui", "IconSelector") + , m_xImageManager(std::move(xImageManager)) + , m_xParentImageManager(std::move(xParentImageManager)) + , m_xTbSymbol(new ValueSet(m_xBuilder->weld_scrolled_window("symbolswin", true))) + , m_xTbSymbolWin(new weld::CustomWeld(*m_xBuilder, "symbolsToolbar", *m_xTbSymbol)) + , m_xFtNote(m_xBuilder->weld_label("noteLabel")) + , m_xBtnImport(m_xBuilder->weld_button("importButton")) + , m_xBtnDelete(m_xBuilder->weld_button("deleteButton")) +{ + typedef std::unordered_map< OUString, bool > ImageInfo; + + m_nExpectedSize = 16; + if (SvxConfigPageHelper::GetImageType() & css::ui::ImageType::SIZE_LARGE) + m_nExpectedSize = 24; + else if (SvxConfigPageHelper::GetImageType() & css::ui::ImageType::SIZE_32) + m_nExpectedSize = 32; + + if ( m_nExpectedSize != 16 ) + { + m_xFtNote->set_label(SvxConfigPageHelper::replaceSixteen(m_xFtNote->get_label(), m_nExpectedSize)); + } + + m_xTbSymbol->SetStyle(m_xTbSymbol->GetStyle() | WB_ITEMBORDER | WB_VSCROLL); + m_xTbSymbol->SetColCount(11); + m_xTbSymbol->SetLineCount(5); + m_xTbSymbol->SetItemWidth(m_nExpectedSize); + m_xTbSymbol->SetItemHeight(m_nExpectedSize); + m_xTbSymbol->SetExtraSpacing(6); + Size aSize(m_xTbSymbol->CalcWindowSizePixel(Size(m_nExpectedSize, m_nExpectedSize), 11, 5)); + m_xTbSymbol->set_size_request(aSize.Width(), aSize.Height()); + + uno::Reference< uno::XComponentContext > xComponentContext = + ::comphelper::getProcessComponentContext(); + + m_xGraphProvider.set( graphic::GraphicProvider::create( xComponentContext ) ); + + uno::Reference< css::util::XPathSettings > xPathSettings = + css::util::thePathSettings::get( xComponentContext ); + + + OUString aDirectory = xPathSettings->getUserConfig(); + + sal_Int32 aCount = aDirectory.getLength(); + + if ( aCount > 0 ) + { + sal_Unicode aChar = aDirectory[ aCount-1 ]; + if ( aChar != '/') + { + aDirectory += "/"; + } + } + else + { + m_xBtnImport->set_sensitive(false); + } + + aDirectory += "soffice.cfg/import"; + + uno::Reference< lang::XSingleServiceFactory > xStorageFactory( + css::embed::FileSystemStorageFactory::create( xComponentContext ) ); + + uno::Sequence< uno::Any > aArgs{ uno::Any(aDirectory), + uno::Any(css::embed::ElementModes::READWRITE) }; + + uno::Reference< css::embed::XStorage > xStorage( + xStorageFactory->createInstanceWithArguments( aArgs ), uno::UNO_QUERY ); + + uno::Sequence<uno::Any> aProp(comphelper::InitAnyPropertySequence( + { + {"UserConfigStorage", uno::Any(xStorage)}, + {"OpenMode", uno::Any(css::embed::ElementModes::READWRITE)} + })); + m_xImportedImageManager = css::ui::ImageManager::create( xComponentContext ); + m_xImportedImageManager->initialize(aProp); + + ImageInfo aImageInfo1; + if ( m_xImportedImageManager.is() ) + { + const uno::Sequence< OUString > names = m_xImportedImageManager->getAllImageNames( SvxConfigPageHelper::GetImageType() ); + for (auto const & name : names ) + aImageInfo1.emplace( name, false ); + } + + uno::Sequence< OUString > name( 1 ); + auto pname = name.getArray(); + for (auto const& elem : aImageInfo1) + { + pname[ 0 ] = elem.first; + uno::Sequence< uno::Reference< graphic::XGraphic> > graphics = m_xImportedImageManager->getImages( SvxConfigPageHelper::GetImageType(), name ); + if ( graphics.hasElements() ) + { + m_aGraphics.push_back(graphics[0]); + Image img(graphics[0]); + m_xTbSymbol->InsertItem(m_aGraphics.size(), img, elem.first); + } + } + + ImageInfo aImageInfo; + + if ( m_xParentImageManager.is() ) + { + const uno::Sequence< OUString > names = m_xParentImageManager->getAllImageNames( SvxConfigPageHelper::GetImageType() ); + for ( auto const & i : names ) + aImageInfo.emplace( i, false ); + } + + const uno::Sequence< OUString > names = m_xImageManager->getAllImageNames( SvxConfigPageHelper::GetImageType() ); + for ( auto const & i : names ) + { + ImageInfo::iterator pIter = aImageInfo.find( i ); + if ( pIter != aImageInfo.end() ) + pIter->second = true; + else + aImageInfo.emplace( i, true ); + } + + // large growth factor, expecting many entries + for (auto const& elem : aImageInfo) + { + pname[ 0 ] = elem.first; + + uno::Sequence< uno::Reference< graphic::XGraphic> > graphics; + try + { + if (elem.second) + graphics = m_xImageManager->getImages( SvxConfigPageHelper::GetImageType(), name ); + else + graphics = m_xParentImageManager->getImages( SvxConfigPageHelper::GetImageType(), name ); + } + catch ( uno::Exception& ) + { + // can't get sequence for this name so it will not be + // added to the list + } + + if ( graphics.hasElements() ) + { + Image img(graphics[0]); + if (!img.GetBitmapEx().IsEmpty()) + { + m_aGraphics.push_back(graphics[0]); + m_xTbSymbol->InsertItem(m_aGraphics.size(), img, elem.first); + } + } + } + + m_xBtnDelete->set_sensitive( false ); + m_xTbSymbol->SetSelectHdl( LINK(this, SvxIconSelectorDialog, SelectHdl) ); + m_xBtnImport->connect_clicked( LINK(this, SvxIconSelectorDialog, ImportHdl) ); + m_xBtnDelete->connect_clicked( LINK(this, SvxIconSelectorDialog, DeleteHdl) ); +} + +SvxIconSelectorDialog::~SvxIconSelectorDialog() +{ +} + +uno::Reference< graphic::XGraphic> SvxIconSelectorDialog::GetSelectedIcon() +{ + uno::Reference<graphic::XGraphic> result; + + sal_uInt16 nId = m_xTbSymbol->GetSelectedItemId(); + + if (nId) + { + result = m_aGraphics[nId - 1]; + } + + return result; +} + +IMPL_LINK_NOARG(SvxIconSelectorDialog, SelectHdl, ValueSet*, void) +{ + sal_uInt16 nId = m_xTbSymbol->GetSelectedItemId(); + + if (!nId) + { + m_xBtnDelete->set_sensitive(false); + return; + } + + OUString aSelImageText = m_xTbSymbol->GetItemText(nId); + if (m_xImportedImageManager->hasImage(SvxConfigPageHelper::GetImageType(), aSelImageText)) + { + m_xBtnDelete->set_sensitive(true); + } + else + { + m_xBtnDelete->set_sensitive(false); + } +} + +IMPL_LINK_NOARG(SvxIconSelectorDialog, ImportHdl, weld::Button&, void) +{ + sfx2::FileDialogHelper aImportDialog( + css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW, + FileDialogFlags::Graphic | FileDialogFlags::MultiSelection, m_xDialog.get()); + aImportDialog.SetContext(sfx2::FileDialogHelper::IconImport); + + // disable the link checkbox in the dialog + uno::Reference< css::ui::dialogs::XFilePickerControlAccess > + xController( aImportDialog.GetFilePicker(), uno::UNO_QUERY); + if ( xController.is() ) + { + xController->enableControl( + css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK, + false); + } + + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + sal_uInt16 nFilter = rFilter.GetImportFormatNumberForShortName(u"png"); + aImportDialog.SetCurrentFilter(rFilter.GetImportFormatName(nFilter)); + + if ( ERRCODE_NONE == aImportDialog.Execute() ) + { + uno::Sequence< OUString > paths = aImportDialog.GetMPath(); + ImportGraphics ( paths ); + } +} + +IMPL_LINK_NOARG(SvxIconSelectorDialog, DeleteHdl, weld::Button&, void) +{ + OUString message = CuiResId( RID_CUISTR_DELETE_ICON_CONFIRM ); + + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::OkCancel, + message)); + if (xWarn->run() != RET_OK) + return; + + sal_uInt16 nId = m_xTbSymbol->GetSelectedItemId(); + + OUString aSelImageText = m_xTbSymbol->GetItemText( nId ); + uno::Sequence< OUString > URLs { aSelImageText }; + m_xTbSymbol->RemoveItem(nId); + m_xImportedImageManager->removeImages( SvxConfigPageHelper::GetImageType(), URLs ); + if ( m_xImportedImageManager->isModified() ) + { + m_xImportedImageManager->store(); + } +} + +bool SvxIconSelectorDialog::ReplaceGraphicItem( + const OUString& aURL ) +{ + uno::Reference< graphic::XGraphic > xGraphic; + uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue("URL", aURL) }; + + css::awt::Size aSize; + bool bOK = false; + try + { + xGraphic = m_xGraphProvider->queryGraphic( aMediaProps ); + + uno::Reference< beans::XPropertySet > props = + m_xGraphProvider->queryGraphicDescriptor( aMediaProps ); + uno::Any a = props->getPropertyValue( "SizePixel" ); + a >>= aSize; + if (0 == aSize.Width || 0 == aSize.Height) + return false; + else + bOK = true; + } + catch ( uno::Exception& ) + { + return false; + } + + bool bResult( false ); + size_t nCount = m_xTbSymbol->GetItemCount(); + for (size_t n = 0; n < nCount; ++n) + { + sal_uInt16 nId = m_xTbSymbol->GetItemId( n ); + + if ( m_xTbSymbol->GetItemText( nId ) == aURL ) + { + try + { + // replace/insert image with provided URL + size_t nPos = nId - 1; + assert(nPos == m_xTbSymbol->GetItemPos(nId)); + m_xTbSymbol->RemoveItem(nId); + + Image aImage( xGraphic ); + if ( bOK && ((aSize.Width != m_nExpectedSize) || (aSize.Height != m_nExpectedSize)) ) + { + BitmapEx aBitmap = aImage.GetBitmapEx(); + BitmapEx aBitmapex = BitmapEx::AutoScaleBitmap(aBitmap, m_nExpectedSize); + aImage = Image( aBitmapex); + } + m_xTbSymbol->InsertItem(nId, aImage, aURL, nPos); //modify + + m_aGraphics[nPos] = Graphic(aImage.GetBitmapEx()).GetXGraphic(); + + m_xImportedImageManager->replaceImages( SvxConfigPageHelper::GetImageType(), { aURL }, { xGraphic } ); + m_xImportedImageManager->store(); + + bResult = true; + break; + } + catch ( css::uno::Exception& ) + { + break; + } + } + } + + return bResult; +} + +namespace +{ + OUString ReplaceIconName(std::u16string_view rMessage) + { + OUString name; + OUString message = CuiResId( RID_CUISTR_REPLACE_ICON_WARNING ); + OUString placeholder("%ICONNAME" ); + sal_Int32 pos = message.indexOf( placeholder ); + if ( pos != -1 ) + { + name = message.replaceAt( + pos, placeholder.getLength(), rMessage ); + } + return name; + } + + class SvxIconReplacementDialog + { + private: + std::unique_ptr<weld::MessageDialog> m_xQueryBox; + public: + SvxIconReplacementDialog(weld::Window *pParent, std::u16string_view rMessage, bool bYestoAll) + : m_xQueryBox(Application::CreateMessageDialog(pParent, VclMessageType::Warning, VclButtonsType::NONE, ReplaceIconName(rMessage))) + { + m_xQueryBox->set_title(CuiResId(RID_CUISTR_REPLACE_ICON_CONFIRM)); + m_xQueryBox->add_button(GetStandardText(StandardButtonType::Yes), 2); + if (bYestoAll) + m_xQueryBox->add_button(CuiResId(RID_CUISTR_YESTOALL), 5); + m_xQueryBox->add_button(GetStandardText(StandardButtonType::No), 4); + m_xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), 6); + m_xQueryBox->set_default_response(2); + } + short run() { return m_xQueryBox->run(); } + }; +} + +void SvxIconSelectorDialog::ImportGraphics( + const uno::Sequence< OUString >& rPaths ) +{ + std::vector< OUString > rejected( rPaths.getLength() ); + sal_Int32 rejectedCount = 0; + + sal_uInt16 ret = 0; + sal_Int32 aIndex; + OUString aIconName; + + if ( rPaths.getLength() == 1 ) + { + if ( m_xImportedImageManager->hasImage( SvxConfigPageHelper::GetImageType(), rPaths[0] ) ) + { + aIndex = rPaths[0].lastIndexOf( '/' ); + aIconName = rPaths[0].copy( aIndex+1 ); + SvxIconReplacementDialog aDlg(m_xDialog.get(), aIconName, false); + ret = aDlg.run(); + if ( ret == 2 ) + { + ReplaceGraphicItem( rPaths[0] ); + } + } + else + { + if ( !ImportGraphic( rPaths[0] ) ) + { + rejected[0] = rPaths[0]; + rejectedCount = 1; + } + } + } + else + { + OUString aSourcePath( rPaths[0] ); + if ( rPaths[0].lastIndexOf( '/' ) != rPaths[0].getLength() -1 ) + aSourcePath = rPaths[0] + "/"; + + for ( sal_Int32 i = 1; i < rPaths.getLength(); ++i ) + { + OUString aPath = aSourcePath + rPaths[i]; + if ( m_xImportedImageManager->hasImage( SvxConfigPageHelper::GetImageType(), aPath ) ) + { + aIndex = rPaths[i].lastIndexOf( '/' ); + aIconName = rPaths[i].copy( aIndex+1 ); + SvxIconReplacementDialog aDlg(m_xDialog.get(), aIconName, true); + ret = aDlg.run(); + if ( ret == 2 ) + { + ReplaceGraphicItem( aPath ); + } + else if ( ret == 5 ) + { + for ( sal_Int32 k = i; k < rPaths.getLength(); ++k ) + { + aPath = aSourcePath + rPaths[k]; + bool bHasReplaced = ReplaceGraphicItem( aPath ); + + if ( !bHasReplaced ) + { + bool result = ImportGraphic( aPath ); + if ( !result ) + { + rejected[ rejectedCount ] = rPaths[i]; + ++rejectedCount; + } + } + } + break; + } + } + else + { + bool result = ImportGraphic( aSourcePath + rPaths[i] ); + if ( !result ) + { + rejected[ rejectedCount ] = rPaths[i]; + ++rejectedCount; + } + } + } + } + + if ( rejectedCount == 0 ) + return; + + OUStringBuffer message; + OUString fPath; + if (rejectedCount > 1) + fPath = OUString::Concat(rPaths[0].subView(8)) + "/"; + for ( sal_Int32 i = 0; i < rejectedCount; ++i ) + { + message.append(fPath + rejected[i] + "\n"); + } + + SvxIconChangeDialog aDialog(m_xDialog.get(), message.makeStringAndClear()); + aDialog.run(); +} + +bool SvxIconSelectorDialog::ImportGraphic( const OUString& aURL ) +{ + bool result = false; + + uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue("URL", aURL) }; + + try + { + uno::Reference< beans::XPropertySet > props = + m_xGraphProvider->queryGraphicDescriptor( aMediaProps ); + + uno::Any a = props->getPropertyValue("SizePixel"); + + uno::Reference< graphic::XGraphic > xGraphic = m_xGraphProvider->queryGraphic( aMediaProps ); + if ( xGraphic.is() ) + { + bool bOK = true; + css::awt::Size aSize; + + a >>= aSize; + if ( 0 == aSize.Width || 0 == aSize.Height ) + bOK = false; + + Image aImage( xGraphic ); + + if ( bOK && ((aSize.Width != m_nExpectedSize) || (aSize.Height != m_nExpectedSize)) ) + { + BitmapEx aBitmap = aImage.GetBitmapEx(); + BitmapEx aBitmapex = BitmapEx::AutoScaleBitmap(aBitmap, m_nExpectedSize); + aImage = Image( aBitmapex); + } + if ( bOK && !!aImage ) + { + m_aGraphics.push_back(Graphic(aImage.GetBitmapEx()).GetXGraphic()); + m_xTbSymbol->InsertItem(m_aGraphics.size(), aImage, aURL); + + uno::Sequence<OUString> aImportURL { aURL }; + uno::Sequence< uno::Reference<graphic::XGraphic > > aImportGraph{ xGraphic }; + m_xImportedImageManager->insertImages( SvxConfigPageHelper::GetImageType(), aImportURL, aImportGraph ); + if ( m_xImportedImageManager->isModified() ) + { + m_xImportedImageManager->store(); + } + + result = true; + } + else + { + SAL_WARN("cui.customize", "could not create Image from XGraphic"); + } + } + else + { + SAL_WARN("cui.customize", "could not get query XGraphic"); + } + } + catch( uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Caught exception importing XGraphic"); + } + return result; +} + +/******************************************************************************* +* +* The SvxIconChangeDialog class added for issue83555 +* +*******************************************************************************/ +SvxIconChangeDialog::SvxIconChangeDialog(weld::Window *pWindow, const OUString& rMessage) + : MessageDialogController(pWindow, "cui/ui/iconchangedialog.ui", "IconChange", "grid") + , m_xLineEditDescription(m_xBuilder->weld_text_view("addrTextview")) +{ + m_xLineEditDescription->set_size_request(m_xLineEditDescription->get_approximate_digit_width() * 48, + m_xLineEditDescription->get_text_height() * 8); + m_xLineEditDescription->set_text(rMessage); +} + +SvxConfigPageFunctionDropTarget::SvxConfigPageFunctionDropTarget(SvxConfigPage&rPage, weld::TreeView& rTreeView) + : weld::ReorderingDropTarget(rTreeView) + , m_rPage(rPage) +{ +} + +sal_Int8 SvxConfigPageFunctionDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt) +{ + sal_Int8 nRet = weld::ReorderingDropTarget::ExecuteDrop(rEvt); + m_rPage.ListModified(); + return nRet;; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/cfgutil.cxx b/cui/source/customize/cfgutil.cxx new file mode 100644 index 0000000000..3a2cdbc1b0 --- /dev/null +++ b/cui/source/customize/cfgutil.cxx @@ -0,0 +1,1393 @@ +/* -*- 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 <cfgutil.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/document/XScriptInvocationContext.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/theUICommandDescription.hpp> +#include <com/sun/star/frame/XDispatchInformationProvider.hpp> +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <com/sun/star/script/browse/theBrowseNodeFactory.hpp> +#include <com/sun/star/script/browse/BrowseNodeFactoryViewTypes.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/ui/theUICategoryDescription.hpp> + +#include <basic/basmgr.hxx> +#include <tools/urlobj.hxx> +#include <strings.hrc> +#include <bitmaps.hlst> +#include <sfx2/minfitem.hxx> +#include <comphelper/SetFlagContextHelper.hxx> +#include <comphelper/documentinfo.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <svtools/imagemgr.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <dialmgr.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/help.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/string_view.hxx> + +#include <sfx2/sidebar/ResourceManager.hxx> +#include <sfx2/sidebar/Context.hxx> +#include <unotools/viewoptions.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::document; + +SfxStylesInfo_Impl::SfxStylesInfo_Impl() +{} + +void SfxStylesInfo_Impl::init(const OUString& rModuleName, const css::uno::Reference< css::frame::XModel >& xModel) +{ + m_aModuleName = rModuleName; + m_xDoc = xModel; +} + +const char CMDURL_STYLEPROT_ONLY[] = ".uno:StyleApply?"; +const char CMDURL_SPART_ONLY [] = "Style:string="; +const char CMDURL_FPART_ONLY [] = "FamilyName:string="; + +constexpr OUString STYLEPROP_UINAME = u"DisplayName"_ustr; +constexpr OUString MACRO_SELECTOR_CONFIGNAME = u"MacroSelectorDialog"_ustr; +constexpr OUString LAST_RUN_MACRO_INFO = u"LastRunMacro"_ustr; + +OUString SfxStylesInfo_Impl::generateCommand( + std::u16string_view sFamily, std::u16string_view sStyle) +{ + return OUString::Concat(".uno:StyleApply?Style:string=") + + sStyle + + "&FamilyName:string=" + + sFamily; +} + +bool SfxStylesInfo_Impl::parseStyleCommand(SfxStyleInfo_Impl& aStyle) +{ + static const sal_Int32 LEN_STYLEPROT = strlen(CMDURL_STYLEPROT_ONLY); + static const sal_Int32 LEN_SPART = strlen(CMDURL_SPART_ONLY); + static const sal_Int32 LEN_FPART = strlen(CMDURL_FPART_ONLY); + + if (!aStyle.sCommand.startsWith(CMDURL_STYLEPROT_ONLY)) + return false; + + aStyle.sFamily.clear(); + aStyle.sStyle.clear(); + + sal_Int32 nCmdLen = aStyle.sCommand.getLength(); + OUString sCmdArgs = aStyle.sCommand.copy(LEN_STYLEPROT, nCmdLen-LEN_STYLEPROT); + sal_Int32 i = sCmdArgs.indexOf('&'); + if (i<0) + return false; + + OUString sArg = sCmdArgs.copy(0, i); + if (sArg.startsWith(CMDURL_SPART_ONLY)) + aStyle.sStyle = sArg.copy(LEN_SPART); + else if (sArg.startsWith(CMDURL_FPART_ONLY)) + aStyle.sFamily = sArg.copy(LEN_FPART); + + sArg = sCmdArgs.copy(i+1, sCmdArgs.getLength()-i-1); + if (sArg.startsWith(CMDURL_SPART_ONLY)) + aStyle.sStyle = sArg.copy(LEN_SPART); + else if (sArg.startsWith(CMDURL_FPART_ONLY)) + aStyle.sFamily = sArg.copy(LEN_FPART); + + return !(aStyle.sFamily.isEmpty() || aStyle.sStyle.isEmpty()); +} + +void SfxStylesInfo_Impl::getLabel4Style(SfxStyleInfo_Impl& aStyle) +{ + try + { + css::uno::Reference< css::style::XStyleFamiliesSupplier > xModel(m_xDoc, css::uno::UNO_QUERY); + + css::uno::Reference< css::container::XNameAccess > xFamilies; + if (xModel.is()) + xFamilies = xModel->getStyleFamilies(); + + css::uno::Reference< css::container::XNameAccess > xStyleSet; + if (xFamilies.is()) + xFamilies->getByName(aStyle.sFamily) >>= xStyleSet; + + css::uno::Reference< css::beans::XPropertySet > xStyle; + if (xStyleSet.is()) + xStyleSet->getByName(aStyle.sStyle) >>= xStyle; + + aStyle.sLabel.clear(); + if (xStyle.is()) + xStyle->getPropertyValue(STYLEPROP_UINAME) >>= aStyle.sLabel; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { aStyle.sLabel.clear(); } + + if (aStyle.sLabel.isEmpty()) + { + aStyle.sLabel = aStyle.sCommand; + } +} + +std::vector< SfxStyleInfo_Impl > SfxStylesInfo_Impl::getStyleFamilies() const +{ + // It's an optional interface! + css::uno::Reference< css::style::XStyleFamiliesSupplier > xModel(m_xDoc, css::uno::UNO_QUERY); + if (!xModel.is()) + return std::vector< SfxStyleInfo_Impl >(); + + css::uno::Reference< css::container::XNameAccess > xCont = xModel->getStyleFamilies(); + const css::uno::Sequence< OUString > lFamilyNames = xCont->getElementNames(); + std::vector< SfxStyleInfo_Impl > lFamilies; + for (const auto& aFamily : lFamilyNames) + { + if ((aFamily == "CellStyles" && m_aModuleName != "com.sun.star.sheet.SpreadsheetDocument") || + aFamily == "cell" || aFamily == "table" || aFamily == "Default") + continue; + + SfxStyleInfo_Impl aFamilyInfo; + aFamilyInfo.sFamily = aFamily; + + try + { + css::uno::Reference< css::beans::XPropertySet > xFamilyInfo; + xCont->getByName(aFamilyInfo.sFamily) >>= xFamilyInfo; + if (!xFamilyInfo.is()) + { + // TODO_AS currently there is no support for an UIName property .. use internal family name instead + aFamilyInfo.sLabel = aFamilyInfo.sFamily; + } + else + xFamilyInfo->getPropertyValue(STYLEPROP_UINAME) >>= aFamilyInfo.sLabel; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { return std::vector< SfxStyleInfo_Impl >(); } + + lFamilies.push_back(aFamilyInfo); + } + + return lFamilies; +} + +std::vector< SfxStyleInfo_Impl > SfxStylesInfo_Impl::getStyles(const OUString& sFamily) +{ + css::uno::Sequence< OUString > lStyleNames; + css::uno::Reference< css::style::XStyleFamiliesSupplier > xModel(m_xDoc, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XNameAccess > xFamilies = xModel->getStyleFamilies(); + css::uno::Reference< css::container::XNameAccess > xStyleSet; + try + { + xFamilies->getByName(sFamily) >>= xStyleSet; + lStyleNames = xStyleSet->getElementNames(); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { return std::vector< SfxStyleInfo_Impl >(); } + + std::vector< SfxStyleInfo_Impl > lStyles; + sal_Int32 c = lStyleNames.getLength(); + sal_Int32 i = 0; + for (i=0; i<c; ++i) + { + SfxStyleInfo_Impl aStyleInfo; + aStyleInfo.sFamily = sFamily; + aStyleInfo.sStyle = lStyleNames[i]; + aStyleInfo.sCommand = SfxStylesInfo_Impl::generateCommand(aStyleInfo.sFamily, aStyleInfo.sStyle); + + try + { + css::uno::Reference< css::beans::XPropertySet > xStyle; + xStyleSet->getByName(aStyleInfo.sStyle) >>= xStyle; + if (!xStyle.is()) + continue; + xStyle->getPropertyValue("DisplayName") >>= aStyleInfo.sLabel; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { continue; } + + lStyles.push_back(aStyleInfo); + } + return lStyles; +} + +OUString CuiConfigFunctionListBox::GetHelpText( bool bConsiderParent ) +{ + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(get_selected_id()); + if (pData) + { + if ( pData->nKind == SfxCfgKind::FUNCTION_SLOT ) + { + if (bConsiderParent) + return Application::GetHelp()->GetHelpText(pData->sCommand, m_xTreeView.get()); + else + return Application::GetHelp()->GetHelpText(pData->sCommand, static_cast<weld::Widget*>(nullptr)); + } + else if ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT ) + { + return pData->sHelpText; + } + } + return OUString(); +} + +OUString CuiConfigFunctionListBox::GetCurCommand() const +{ + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(get_selected_id()); + if (!pData) + return OUString(); + return pData->sCommand; +} + +OUString CuiConfigFunctionListBox::GetCurLabel() const +{ + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(get_selected_id()); + if (!pData) + return OUString(); + if (!pData->sLabel.isEmpty()) + return pData->sLabel; + return pData->sCommand; +} + +CuiConfigFunctionListBox::CuiConfigFunctionListBox(std::unique_ptr<weld::TreeView> xTreeView) + : m_xTreeView(std::move(xTreeView)) + , m_xScratchIter(m_xTreeView->make_iterator()) +{ + m_xTreeView->make_sorted(); + m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 35, m_xTreeView->get_height_rows(9)); + m_xTreeView->connect_query_tooltip(LINK(this, CuiConfigFunctionListBox, QueryTooltip)); +} + +CuiConfigFunctionListBox::~CuiConfigFunctionListBox() +{ + ClearAll(); +} + +IMPL_LINK(CuiConfigFunctionListBox, QueryTooltip, const weld::TreeIter&, rIter, OUString) +{ + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(m_xTreeView->get_id(rIter)); + if (!pData) + return OUString(); + OUString aLabel = CuiResId(RID_CUISTR_COMMANDLABEL) + ": "; + OUString aName = CuiResId(RID_CUISTR_COMMANDNAME) + ": "; + OUString aTip = CuiResId(RID_CUISTR_COMMANDTIP) + ": "; + return aLabel + pData->sLabel + "\n" + aName + pData->sCommand+ "\n" + aTip + pData->sTooltip; +} + +void CuiConfigFunctionListBox::ClearAll() +/* Description + Deletes all entries in the FunctionListBox, all UserData and all + possibly existing MacroInfo. +*/ +{ + sal_uInt16 nCount = aArr.size(); + for ( sal_uInt16 i=0; i<nCount; ++i ) + { + SfxGroupInfo_Impl *pData = aArr[i].get(); + + if ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT ) + { + OUString* pScriptURI = static_cast<OUString*>(pData->pObject); + delete pScriptURI; + } + + if ( pData->nKind == SfxCfgKind::GROUP_SCRIPTCONTAINER ) + { + XInterface* xi = static_cast<XInterface *>(pData->pObject); + if (xi != nullptr) + { + xi->release(); + } + } + } + + aArr.clear(); + m_xTreeView->clear(); +} + +OUString CuiConfigFunctionListBox::GetSelectedScriptURI() const +{ + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(get_selected_id()); + if (pData && pData->nKind == SfxCfgKind::FUNCTION_SCRIPT) + return *static_cast<OUString*>(pData->pObject); + return OUString(); +} + +struct SvxConfigGroupBoxResource_Impl +{ + OUString m_sMyMacros; + OUString m_sProdMacros; + OUString m_sDlgMacros; + OUString m_aStrGroupStyles; + OUString m_aStrGroupSidebarDecks; + + SvxConfigGroupBoxResource_Impl(); +}; + +SvxConfigGroupBoxResource_Impl::SvxConfigGroupBoxResource_Impl() : + m_sMyMacros(CuiResId(RID_CUISTR_MYMACROS)), + m_sProdMacros(CuiResId(RID_CUISTR_PRODMACROS)), + m_sDlgMacros(CuiResId(RID_CUISTR_PRODMACROS)), + m_aStrGroupStyles(CuiResId(RID_CUISTR_GROUP_STYLES)), + m_aStrGroupSidebarDecks(CuiResId(RID_CUISTR_GROUP_SIDEBARDECKS)) +{ +} + +void CuiConfigGroupListBox::SetStylesInfo(SfxStylesInfo_Impl* pStyles) +{ + m_pStylesInfo = pStyles; +} + +namespace +{ + + /** examines a component whether it supports XEmbeddedScripts, or provides access to such a + component by implementing XScriptInvocationContext. + @return + the model which supports the embedded scripts, or <NULL/> if it cannot find such a + model + */ + Reference< XModel > lcl_getDocumentWithScripts_throw( const Reference< XInterface >& _rxComponent ) + { + Reference< XEmbeddedScripts > xScripts( _rxComponent, UNO_QUERY ); + if ( !xScripts.is() ) + { + Reference< XScriptInvocationContext > xContext( _rxComponent, UNO_QUERY ); + if ( xContext.is() ) + xScripts = xContext->getScriptContainer(); + } + + return Reference< XModel >( xScripts, UNO_QUERY ); + } + + + Reference< XModel > lcl_getScriptableDocument_nothrow( const Reference< XFrame >& _rxFrame ) + { + Reference< XModel > xDocument; + + // examine our associated frame + try + { + OSL_ENSURE( _rxFrame.is(), "lcl_getScriptableDocument_nothrow: you need to pass a frame to this dialog/tab page!" ); + if ( _rxFrame.is() ) + { + // first try the model in the frame + Reference< XController > xController( _rxFrame->getController(), UNO_SET_THROW ); + xDocument = lcl_getDocumentWithScripts_throw( xController->getModel() ); + + if ( !xDocument.is() ) + { + // if there is no suitable document in the frame, try the controller + xDocument = lcl_getDocumentWithScripts_throw( _rxFrame->getController() ); + } + } + } + catch( const Exception& ) + { + } + + return xDocument; + } +} + +CuiConfigGroupListBox::CuiConfigGroupListBox(std::unique_ptr<weld::TreeView> xTreeView) + : xImp(new SvxConfigGroupBoxResource_Impl()) + , m_pFunctionListBox(nullptr) + , m_pStylesInfo(nullptr) + , m_xTreeView(std::move(xTreeView)) + , m_xScratchIter(m_xTreeView->make_iterator()) +{ + m_xTreeView->connect_expanding(LINK(this, CuiConfigGroupListBox, ExpandingHdl)); + m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 35, m_xTreeView->get_height_rows(9)); +} + +CuiConfigGroupListBox::~CuiConfigGroupListBox() +{ + ClearAll(); +} + +void CuiConfigGroupListBox::ClearAll() +{ + sal_uInt16 nCount = aArr.size(); + for ( sal_uInt16 i=0; i<nCount; ++i ) + { + SfxGroupInfo_Impl *pData = aArr[i].get(); + if (pData->nKind == SfxCfgKind::GROUP_STYLES && pData->pObject) + { + SfxStyleInfo_Impl* pStyle = static_cast<SfxStyleInfo_Impl*>(pData->pObject); + delete pStyle; + } + else if (pData->nKind == SfxCfgKind::FUNCTION_SCRIPT && pData->pObject ) + { + OUString* pScriptURI = static_cast<OUString*>(pData->pObject); + delete pScriptURI; + } + else if (pData->nKind == SfxCfgKind::GROUP_SCRIPTCONTAINER) + { + XInterface* xi = static_cast<XInterface *>(pData->pObject); + if (xi != nullptr) + { + xi->release(); + } + } + } + + aArr.clear(); + m_xTreeView->clear(); +} + +sal_Int32 CuiConfigGroupListBox::InitModule() +{ + try + { + // return the number of added groups + css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider(m_xFrame, css::uno::UNO_QUERY_THROW); + css::uno::Sequence< sal_Int16 > lGroups = xProvider->getSupportedCommandGroups(); + sal_Int32 c1 = lGroups.getLength(); + sal_Int32 i1 = 0; + sal_Int32 nAddedGroups = 0; + + for (i1=0; i1<c1; ++i1) + { + sal_Int16 nGroupID = lGroups[i1]; + OUString sGroupID = OUString::number(nGroupID); + OUString sGroupName ; + + try + { + m_xModuleCategoryInfo->getByName(sGroupID) >>= sGroupName; + if (sGroupName.isEmpty()) + continue; + } + catch(const css::container::NoSuchElementException&) + { continue; } + + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_FUNCTION, nGroupID ) ); + m_xTreeView->append(weld::toId(aArr.back().get()), sGroupName); + nAddedGroups++; + } + return nAddedGroups; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} + return 0; +} + +void CuiConfigGroupListBox::FillScriptList(const css::uno::Reference< css::script::browse::XBrowseNode >& xRootNode, + const weld::TreeIter* pParentEntry) +{ + try { + if ( xRootNode->hasChildNodes() ) + { + // tdf#120362: Don't ask to enable disabled Java when filling script list + css::uno::ContextLayer layer(comphelper::NoEnableJavaInteractionContext()); + + const Sequence< Reference< browse::XBrowseNode > > children = + xRootNode->getChildNodes(); + bool bIsRootNode = false; + + OUString user("user"); + OUString share("share"); + if ( xRootNode->getName() == "Root" ) + { + bIsRootNode = true; + } + + //To mimic current starbasic behaviour we + //need to make sure that only the current document + //is displayed in the config tree. Tests below + //set the bDisplay flag to FALSE if the current + //node is a first level child of the Root and is NOT + //either the current document, user or share + OUString currentDocTitle; + Reference< XModel > xDocument( lcl_getScriptableDocument_nothrow( m_xFrame ) ); + if ( xDocument.is() ) + { + currentDocTitle = ::comphelper::DocumentInfo::getDocumentTitle( xDocument ); + } + + for ( Reference< browse::XBrowseNode > const & theChild : children ) + { + bool bDisplay = true; + OUString uiName = theChild->getName(); + if ( bIsRootNode ) + { + if ( ! (uiName == user || uiName == share || + uiName == currentDocTitle ) ) + { + bDisplay=false; + } + else + { + if ( uiName == user ) + { + uiName = xImp->m_sMyMacros; + } + else if ( uiName == share ) + { + uiName = xImp->m_sProdMacros; + } + } + } + if (theChild->getType() != browse::BrowseNodeTypes::SCRIPT && bDisplay ) + { +// We call acquire on the XBrowseNode so that it does not +// get autodestructed and become invalid when accessed later. + theChild->acquire(); + + bool bChildOnDemand = false; + + if ( theChild->hasChildNodes() ) + { + const Sequence< Reference< browse::XBrowseNode > > grandchildren = + theChild->getChildNodes(); + + for ( const auto& rxNode : grandchildren ) + { + if ( rxNode->getType() == browse::BrowseNodeTypes::CONTAINER ) + { + bChildOnDemand = true; + break; + } + } + } + + OUString aImage = GetImage(theChild, m_xContext, bIsRootNode); + + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_SCRIPTCONTAINER, + 0, static_cast<void *>( theChild.get()))); + + OUString sId(weld::toId(aArr.back().get())); + m_xTreeView->insert(pParentEntry, -1, &uiName, &sId, nullptr, nullptr, bChildOnDemand, m_xScratchIter.get()); + m_xTreeView->set_image(*m_xScratchIter, aImage); + } + } + } + } + catch (RuntimeException&) { + // do nothing, the entry will not be displayed in the UI + } +} + +void CuiConfigGroupListBox::FillFunctionsList(const css::uno::Sequence<DispatchInformation>& xCommands) +{ + m_pFunctionListBox->freeze(); + for (const auto & rInfo : xCommands) + { + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rInfo.Command, m_sModuleLongName); + + OUString sUIName = MapCommand2UIName(rInfo.Command); + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::FUNCTION_SLOT, 0 ) ); + SfxGroupInfo_Impl* pGrpInfo = aArr.back().get(); + pGrpInfo->sCommand = rInfo.Command; + pGrpInfo->sLabel = sUIName; + pGrpInfo->sTooltip = vcl::CommandInfoProvider::GetTooltipForCommand(rInfo.Command, aProperties, m_xFrame); + m_pFunctionListBox->append(weld::toId(pGrpInfo), sUIName); + } + m_pFunctionListBox->thaw(); +} + +void CuiConfigGroupListBox::Init(const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Reference< css::frame::XFrame >& xFrame, + const OUString& sModuleLongName, + bool bEventMode) +{ + m_xTreeView->freeze(); + ClearAll(); // Remove all old entries from treelist box + + m_xContext = xContext; + m_xFrame = xFrame; + sal_Int32 nAddedGroups = 0; + if( bEventMode ) + { + m_sModuleLongName = sModuleLongName; + m_xGlobalCategoryInfo = css::ui::theUICategoryDescription::get( m_xContext ); + m_xModuleCategoryInfo.set(m_xGlobalCategoryInfo->getByName(m_sModuleLongName), css::uno::UNO_QUERY_THROW); + m_xUICmdDescription = css::frame::theUICommandDescription::get( m_xContext ); + + nAddedGroups = InitModule(); + } + + SAL_INFO("cui.customize", "** ** About to initialise SF Scripts"); + // Add Scripting Framework entries + Reference< browse::XBrowseNode > rootNode; + try + { + Reference< browse::XBrowseNodeFactory > xFac = browse::theBrowseNodeFactory::get( m_xContext ); + rootNode.set( xFac->createView( browse::BrowseNodeFactoryViewTypes::MACROSELECTOR ) ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("cui.customize", "Caught some exception whilst retrieving browse nodes from factory"); + // TODO exception handling + } + + m_xTreeView->thaw(); + m_xTreeView->make_sorted(); + m_xTreeView->make_unsorted(); + m_xTreeView->freeze(); + + // add All Commands to the top + if ( bEventMode && nAddedGroups ) + { + aArr.insert(aArr.begin(), std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_ALLFUNCTIONS, 0)); + OUString sId(weld::toId(aArr.front().get())); + OUString s(CuiResId(RID_CUISTR_ALLFUNCTIONS)); + m_xTreeView->insert(nullptr, 0, &s, &sId, nullptr, nullptr, false, nullptr); + } + + // add application macros to the end + if ( rootNode.is() ) + { + if ( bEventMode ) + { + //We call acquire on the XBrowseNode so that it does not + //get autodestructed and become invalid when accessed later. + rootNode->acquire(); + + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, + static_cast<void *>(rootNode.get()))); + OUString aTitle(xImp->m_sDlgMacros); + OUString sId(weld::toId(aArr.back().get())); + m_xTreeView->insert(nullptr, -1, &aTitle, &sId, nullptr, nullptr, true, nullptr); + } + else + { + //We are only showing scripts not slot APIs so skip + //Root node and show location nodes + FillScriptList(rootNode, nullptr); + } + } + + // add styles and sidebar decks to the end + if ( bEventMode ) + { + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_STYLES, 0, nullptr ) ); // TODO last parameter should contain user data + OUString sStyle(xImp->m_aStrGroupStyles); + OUString sId(weld::toId(aArr.back().get())); + m_xTreeView->insert(nullptr, -1, &sStyle, &sId, nullptr, nullptr, true, nullptr); + + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_SIDEBARDECKS, 0)); + OUString sSidebarDecks(xImp->m_aStrGroupSidebarDecks); + sId = weld::toId(aArr.back().get()); + m_xTreeView->insert(nullptr, -1, &sSidebarDecks, &sId, nullptr, nullptr, false, nullptr); + } + + m_xTreeView->thaw(); + m_xTreeView->scroll_to_row(0); + m_xTreeView->select(0); +} + +OUString CuiConfigGroupListBox::GetImage( + const Reference< browse::XBrowseNode >& node, + Reference< XComponentContext > const & xCtx, + bool bIsRootNode) +{ + OUString aImage; + if ( bIsRootNode ) + { + if (node->getName() == "user" || node->getName() == "share" ) + { + aImage = RID_CUIBMP_HARDDISK; + } + else + { + OUString factoryURL; + OUString nodeName = node->getName(); + Reference<XInterface> xDocumentModel = getDocumentModel(xCtx, nodeName ); + if ( xDocumentModel.is() ) + { + Reference< frame::XModuleManager2 > xModuleManager( frame::ModuleManager::create(xCtx) ); + // get the long name of the document: + OUString appModule( xModuleManager->identify( + xDocumentModel ) ); + Sequence<beans::PropertyValue> moduleDescr; + Any aAny = xModuleManager->getByName(appModule); + if( !( aAny >>= moduleDescr ) ) + { + throw RuntimeException("SFTreeListBox::Init: failed to get PropertyValue"); + } + beans::PropertyValue const * pmoduleDescr = + moduleDescr.getConstArray(); + for ( sal_Int32 pos = moduleDescr.getLength(); pos--; ) + { + if ( pmoduleDescr[ pos ].Name == "ooSetupFactoryEmptyDocumentURL" ) + { + pmoduleDescr[ pos ].Value >>= factoryURL; + SAL_INFO("cui.customize", "factory url for doc images is " << factoryURL); + break; + } + } + } + if( !factoryURL.isEmpty() ) + { + aImage = SvFileInformationManager::GetFileImageId(INetURLObject(factoryURL)); + } + else + { + aImage = RID_CUIBMP_DOC; + } + } + } + else + { + if( node->getType() == browse::BrowseNodeTypes::SCRIPT ) + aImage = RID_CUIBMP_MACRO; + else + aImage = RID_CUIBMP_LIB; + } + return aImage; +} + +Reference< XInterface > +CuiConfigGroupListBox::getDocumentModel( Reference< XComponentContext > const & xCtx, std::u16string_view docName ) +{ + Reference< XInterface > xModel; + Reference< frame::XDesktop2 > desktop = frame::Desktop::create( xCtx ); + + Reference< container::XEnumerationAccess > componentsAccess = + desktop->getComponents(); + Reference< container::XEnumeration > components = + componentsAccess->createEnumeration(); + while (components->hasMoreElements()) + { + Reference< frame::XModel > model( + components->nextElement(), UNO_QUERY ); + if ( model.is() ) + { + OUString sTdocUrl = + ::comphelper::DocumentInfo::getDocumentTitle( model ); + if( sTdocUrl == docName ) + { + xModel = model; + break; + } + } + } + return xModel; +} + +OUString CuiConfigGroupListBox::MapCommand2UIName(const OUString& sCommand) +{ + OUString sUIName; + try + { + css::uno::Reference< css::container::XNameAccess > xModuleConf; + m_xUICmdDescription->getByName(m_sModuleLongName) >>= xModuleConf; + if (xModuleConf.is()) + { + ::comphelper::SequenceAsHashMap lProps(xModuleConf->getByName(sCommand)); + sUIName = lProps.getUnpackedValueOrDefault("Name", OUString()); + } + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(css::uno::Exception&) + { sUIName.clear(); } + + // fallback for missing UINames !? + if (sUIName.isEmpty()) + { + sUIName = sCommand; + } + + return sUIName; +} + +void CuiConfigGroupListBox::GroupSelected() +/* Description + A function group or a basic module has been selected. + All functions/macros are displayed in the functionlistbox. +*/ +{ + std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator()); + if (!m_xTreeView->get_selected(xIter.get())) + return; + + SfxGroupInfo_Impl *pInfo = weld::fromId<SfxGroupInfo_Impl*>(m_xTreeView->get_id(*xIter)); + m_pFunctionListBox->freeze(); + m_pFunctionListBox->ClearAll(); + + switch ( pInfo->nKind ) + { + case SfxCfgKind::GROUP_ALLFUNCTIONS: + { + css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider( m_xFrame, UNO_QUERY ); + bool bValidIter = m_xTreeView->get_iter_first(*xIter); + while (bValidIter) + { + SfxGroupInfo_Impl *pCurrentInfo = weld::fromId<SfxGroupInfo_Impl*>(m_xTreeView->get_id(*xIter)); + if (pCurrentInfo->nKind == SfxCfgKind::GROUP_FUNCTION) + { + css::uno::Sequence< css::frame::DispatchInformation > lCommands; + try + { + lCommands = xProvider->getConfigurableDispatchInformation( pCurrentInfo->nUniqueID ); + FillFunctionsList( lCommands ); + } + catch ( container::NoSuchElementException& ) + { + } + } + bValidIter = m_xTreeView->iter_next(*xIter); + } + break; + } + + case SfxCfgKind::GROUP_FUNCTION : + { + sal_uInt16 nGroup = pInfo->nUniqueID; + css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider (m_xFrame, css::uno::UNO_QUERY_THROW); + css::uno::Sequence< css::frame::DispatchInformation > lCommands = xProvider->getConfigurableDispatchInformation(nGroup); + FillFunctionsList( lCommands ); + break; + } + + case SfxCfgKind::GROUP_SCRIPTCONTAINER: + { + if (!m_xTreeView->iter_has_child(*xIter)) + { + Reference< browse::XBrowseNode > rootNode( + static_cast< browse::XBrowseNode* >( pInfo->pObject ) ) ; + + try { + if ( rootNode->hasChildNodes() ) + { + const Sequence< Reference< browse::XBrowseNode > > children = + rootNode->getChildNodes(); + + for ( const Reference< browse::XBrowseNode >& childNode : children ) + { + if (childNode->getType() == browse::BrowseNodeTypes::SCRIPT) + { + OUString uri, description; + + Reference < beans::XPropertySet >xPropSet( childNode, UNO_QUERY ); + if (!xPropSet.is()) + { + continue; + } + + Any value = + xPropSet->getPropertyValue("URI"); + value >>= uri; + + try + { + value = xPropSet->getPropertyValue("Description"); + value >>= description; + } + catch (Exception &) { + // do nothing, the description will be empty + } + + OUString* pScriptURI = new OUString( uri ); + + OUString aImage = GetImage(childNode, Reference< XComponentContext >(), false); + m_pFunctionListBox->aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::FUNCTION_SCRIPT, 0, pScriptURI )); + m_pFunctionListBox->aArr.back()->sCommand = uri; + m_pFunctionListBox->aArr.back()->sLabel = childNode->getName(); + m_pFunctionListBox->aArr.back()->sHelpText = description; + + OUString sId(weld::toId(m_pFunctionListBox->aArr.back().get())); + m_pFunctionListBox->append(sId, childNode->getName(), aImage); + } + } + } + } + catch (RuntimeException&) { + // do nothing, the entry will not be displayed in the UI + } + } + break; + } + + case SfxCfgKind::GROUP_STYLES : + { + SfxStyleInfo_Impl* pFamily = static_cast<SfxStyleInfo_Impl*>(pInfo->pObject); + if (pFamily) + { + const std::vector< SfxStyleInfo_Impl > lStyles = m_pStylesInfo->getStyles(pFamily->sFamily); + for (auto const& lStyle : lStyles) + { + SfxStyleInfo_Impl* pStyle = new SfxStyleInfo_Impl(lStyle); + m_pFunctionListBox->aArr.push_back(std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_STYLES, 0, pStyle)); + m_pFunctionListBox->aArr.back()->sCommand = pStyle->sCommand; + m_pFunctionListBox->aArr.back()->sLabel = pStyle->sLabel; + OUString sId(weld::toId(m_pFunctionListBox->aArr.back().get())); + m_pFunctionListBox->append(sId, pStyle->sLabel); + } + } + break; + } + + case SfxCfgKind::GROUP_SIDEBARDECKS: + { + sfx2::sidebar::ResourceManager aResourceManager; + sfx2::sidebar::Context aContext(m_sModuleLongName, OUString()); + sfx2::sidebar::ResourceManager::DeckContextDescriptorContainer aDecks; + aResourceManager.GetMatchingDecks(aDecks, aContext, false, m_xFrame->getController()); + + for (auto const& rDeck : aDecks) + { + const OUString sCommand = ".uno:SidebarDeck." + rDeck.msId; + m_pFunctionListBox->aArr.push_back(std::make_unique<SfxGroupInfo_Impl>( + SfxCfgKind::GROUP_SIDEBARDECKS, 0, + nullptr)); + m_pFunctionListBox->aArr.back()->sCommand = sCommand; + m_pFunctionListBox->aArr.back()->sLabel = rDeck.msId; + m_pFunctionListBox->aArr.back()->sTooltip = + vcl::CommandInfoProvider::GetCommandShortcut(sCommand, m_xFrame); + m_pFunctionListBox->append(weld::toId(m_pFunctionListBox->aArr.back().get()), + rDeck.msId); + } + + break; + } + + default: + // Do nothing, the list box will stay empty + SAL_INFO( "cui.customize", "Ignoring unexpected SfxCfgKind: " << static_cast<int>(pInfo->nKind) ); + break; + } + + m_pFunctionListBox->thaw(); + + if (m_pFunctionListBox->n_children()) + m_pFunctionListBox->select(0); +} + +/* Description + A basic or a library is opened. +*/ +IMPL_LINK(CuiConfigGroupListBox, ExpandingHdl, const weld::TreeIter&, rIter, bool) +{ + SfxGroupInfo_Impl *pInfo = weld::fromId<SfxGroupInfo_Impl*>(m_xTreeView->get_id(rIter)); + switch ( pInfo->nKind ) + { + case SfxCfgKind::GROUP_SCRIPTCONTAINER: + { + if (!m_xTreeView->iter_has_child(rIter)) + { + Reference< browse::XBrowseNode > rootNode( + static_cast< browse::XBrowseNode* >( pInfo->pObject ) ) ; + FillScriptList(rootNode, &rIter); + } + break; + } + + case SfxCfgKind::GROUP_STYLES: + { + if (!m_xTreeView->iter_has_child(rIter)) + { + const std::vector<SfxStyleInfo_Impl> lStyleFamilies = m_pStylesInfo->getStyleFamilies(); + for (auto const& lStyleFamily : lStyleFamilies) + { + SfxStyleInfo_Impl* pFamily = new SfxStyleInfo_Impl(lStyleFamily); + aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_STYLES, 0, pFamily )); + OUString sId(weld::toId(aArr.back().get())); + m_xTreeView->insert(&rIter, -1, &pFamily->sLabel, &sId, nullptr, nullptr, false, nullptr); + } + } + break; + } + + default: + OSL_FAIL( "Wrong group type!" ); + break; + } + return true; +} + +#if HAVE_FEATURE_SCRIPTING +void CuiConfigGroupListBox::SelectMacro( const SfxMacroInfoItem *pItem ) +{ + auto const rMacro = pItem->GetQualifiedName(); + sal_Int32 nIdx {rMacro.lastIndexOf('.')}; + const std::u16string_view aMethod( rMacro.subView(nIdx + 1) ); + std::u16string_view aLib; + std::u16string_view aModule; + if ( nIdx>0 ) + { + // string contains at least 2 tokens + nIdx = rMacro.lastIndexOf('.', nIdx); + if (nIdx != -1) + { + // string contains at least 3 tokens + aLib = o3tl::getToken(rMacro, 0, '.' ); + sal_Int32 nIdx2 = nIdx + 1; + aModule = o3tl::getToken(rMacro, 0, '.', nIdx2 ); + } + } + + std::unique_ptr<weld::TreeIter> xIter = m_xTreeView->make_iterator(); + if (!m_xTreeView->get_iter_first(*xIter)) + return; + + do + { + OUString aEntryBas = m_xTreeView->get_text(*xIter); + if (aEntryBas == xImp->m_sDlgMacros) + { + m_xTreeView->expand_row(*xIter); + std::unique_ptr<weld::TreeIter> xLocationIter = m_xTreeView->make_iterator(xIter.get()); + if (m_xTreeView->iter_children(*xLocationIter)) + { + do + { + m_xTreeView->expand_row(*xLocationIter); + std::unique_ptr<weld::TreeIter> xLibIter = m_xTreeView->make_iterator(xLocationIter.get()); + if (m_xTreeView->iter_children(*xLibIter)) + { + do + { + OUString aEntryLib = m_xTreeView->get_text(*xLibIter); + if (aEntryLib == aLib) + { + m_xTreeView->expand_row(*xLibIter); + std::unique_ptr<weld::TreeIter> xModIter = m_xTreeView->make_iterator(xLibIter.get()); + if (m_xTreeView->iter_children(*xModIter)) + { + do + { + OUString aEntryMod = m_xTreeView->get_text(*xModIter); + if ( aEntryMod == aModule ) + { + m_xTreeView->expand_row(*xModIter); + m_xTreeView->scroll_to_row(*xModIter); + m_xTreeView->select(*xModIter); + GroupSelected(); + for (int i = 0, nCount = m_pFunctionListBox->n_children(); i < nCount; ++i) + { + OUString aEntryMethod = m_pFunctionListBox->get_text(i); + if (aEntryMethod == aMethod) + { + m_pFunctionListBox->select(i); + m_pFunctionListBox->scroll_to_row(i); + return; + } + } + m_xTreeView->collapse_row(*xModIter); + } + } while (m_xTreeView->iter_next_sibling(*xModIter)); + } + m_xTreeView->collapse_row(*xLibIter); + } + } while (m_xTreeView->iter_next_sibling(*xLibIter)); + } + m_xTreeView->collapse_row(*xLocationIter); + } while (m_xTreeView->iter_next_sibling(*xLocationIter)); + } + // If the macro can't be located, preselect the "Application Macros" category: + m_xTreeView->scroll_to_row(*xIter); + m_xTreeView->select(*xIter); + return; + } + } while (m_xTreeView->iter_next_sibling(*xIter)); +} +#endif + +/* + * Implementation of SvxScriptSelectorDialog + * + * This dialog is used for selecting Slot API commands + * and Scripting Framework Scripts. + */ + +SvxScriptSelectorDialog::SvxScriptSelectorDialog( + weld::Window* pParent, const css::uno::Reference< css::frame::XFrame >& xFrame) + : GenericDialogController(pParent, "cui/ui/macroselectordialog.ui", "MacroSelectorDialog") + , m_xDialogDescription(m_xBuilder->weld_label("helpmacro")) + , m_xCategories(new CuiConfigGroupListBox(m_xBuilder->weld_tree_view("categories"))) + , m_xCommands(new CuiConfigFunctionListBox(m_xBuilder->weld_tree_view("commands"))) + , m_xLibraryFT(m_xBuilder->weld_label("libraryft")) + , m_xMacronameFT(m_xBuilder->weld_label("macronameft")) + , m_xOKButton(m_xBuilder->weld_button("ok")) + , m_xCancelButton(m_xBuilder->weld_button("cancel")) + , m_xDescriptionText(m_xBuilder->weld_text_view("description")) + , m_xDescriptionFrame(m_xBuilder->weld_frame("descriptionframe")) +{ + m_xCancelButton->show(); + m_xDialogDescription->show(); + m_xOKButton->show(); + + m_xLibraryFT->set_visible(true); + m_xMacronameFT->set_visible(true); + + const OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame)); + m_xCategories->SetFunctionListBox(m_xCommands.get()); + m_xCategories->Init(comphelper::getProcessComponentContext(), xFrame, aModuleName, /*bShowSlots*/false); + + m_xCategories->connect_changed( + LINK( this, SvxScriptSelectorDialog, SelectHdl ) ); + m_xCommands->connect_changed( LINK( this, SvxScriptSelectorDialog, SelectHdl ) ); + m_xCommands->connect_row_activated( LINK( this, SvxScriptSelectorDialog, FunctionDoubleClickHdl ) ); + m_xCommands->connect_popup_menu( LINK( this, SvxScriptSelectorDialog, ContextMenuHdl ) ); + + m_xOKButton->connect_clicked( LINK( this, SvxScriptSelectorDialog, ClickHdl ) ); + m_xCancelButton->connect_clicked( LINK( this, SvxScriptSelectorDialog, ClickHdl ) ); + + m_sDefaultDesc = m_xDescriptionText->get_text(); + + // Support style commands + uno::Reference<frame::XController> xController; + uno::Reference<frame::XModel> xModel; + if (xFrame.is()) + xController = xFrame->getController(); + if (xController.is()) + xModel = xController->getModel(); + + m_aStylesInfo.init(aModuleName, xModel); + m_xCategories->SetStylesInfo(&m_aStylesInfo); + + // The following call is a workaround to make scroll_to_row work as expected in kf5/x11 + m_xDialog->resize_to_request(); + + LoadLastUsedMacro(); + UpdateUI(); + + if (comphelper::LibreOfficeKit::isActive()) + m_xDescriptionFrame->hide(); +} + +SvxScriptSelectorDialog::~SvxScriptSelectorDialog() +{ +} + +IMPL_LINK(SvxScriptSelectorDialog, SelectHdl, weld::TreeView&, rCtrl, void) +{ + if (&rCtrl == &m_xCategories->get_widget()) + { + m_xCategories->GroupSelected(); + } + UpdateUI(); +} + +IMPL_LINK_NOARG(SvxScriptSelectorDialog, FunctionDoubleClickHdl, weld::TreeView&, bool) +{ + if (m_xOKButton->get_sensitive()) + ClickHdl(*m_xOKButton); + return true; +} + +IMPL_LINK(SvxScriptSelectorDialog, ContextMenuHdl, const CommandEvent&, rCEvt, bool) +{ + weld::TreeView& xTreeView = m_xCommands->get_widget(); + if (rCEvt.GetCommand() != CommandEventId::ContextMenu || !xTreeView.n_children()) + return false; + + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(&xTreeView, "modules/BasicIDE/ui/sortmenu.ui")); + std::unique_ptr<weld::Menu> xPopup(xBuilder->weld_menu("sortmenu")); + std::unique_ptr<weld::Menu> xDropMenu(xBuilder->weld_menu("sortsubmenu")); + xDropMenu->set_active("alphabetically", xTreeView.get_sort_order()); + xDropMenu->set_active("properorder", !xTreeView.get_sort_order()); + + OUString sCommand(xPopup->popup_at_rect(&xTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)))); + if (sCommand == "alphabetically") + { + xTreeView.make_sorted(); + } + else if (sCommand == "properorder") + { + xTreeView.make_unsorted(); + m_xCategories->GroupSelected(); + } + else if (!sCommand.isEmpty()) + { + SAL_WARN("cui.customize", "Unknown context menu action: " << sCommand ); + } + + return true; +} + +// Check if command is selected and enable the OK button accordingly +// Grab the help text for this id if available and update the description field +void +SvxScriptSelectorDialog::UpdateUI() +{ + OUString url = GetScriptURL(); + if ( !url.isEmpty() ) + { + OUString sMessage = m_xCommands->GetHelpText(); + m_xDescriptionText->set_text(sMessage.isEmpty() ? m_sDefaultDesc : sMessage); + m_xOKButton->set_sensitive(true); + } + else + { + m_xDescriptionText->set_text(m_sDefaultDesc); + m_xOKButton->set_sensitive(false); + } +} + +IMPL_LINK(SvxScriptSelectorDialog, ClickHdl, weld::Button&, rButton, void) +{ + if (&rButton == m_xCancelButton.get()) + { + m_xDialog->response(RET_CANCEL); + } + else if (&rButton == m_xOKButton.get()) + { + SaveLastUsedMacro(); + m_xDialog->response(RET_OK); + } +} + +void +SvxScriptSelectorDialog::SetRunLabel() +{ + m_xOKButton->set_label(CuiResId(RID_CUISTR_SELECTOR_RUN)); +} + +OUString +SvxScriptSelectorDialog::GetScriptURL() const +{ + OUString result; + + std::unique_ptr<weld::TreeIter> xIter = m_xCommands->make_iterator(); + if (m_xCommands->get_selected(xIter.get())) + { + SfxGroupInfo_Impl *pData = weld::fromId<SfxGroupInfo_Impl*>(m_xCommands->get_id(*xIter)); + if ( ( pData->nKind == SfxCfgKind::FUNCTION_SLOT ) + || ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT ) + || ( pData->nKind == SfxCfgKind::GROUP_STYLES ) + ) + { + result = pData->sCommand; + } + } + + return result; +} + +void +SvxScriptSelectorDialog::SaveLastUsedMacro() +{ + // Gets the current selection in the dialog as a series of selected entries + OUString sMacroInfo; + sMacroInfo = m_xCommands->get_selected_text(); + weld::TreeView& xCategories = m_xCategories->get_widget(); + std::unique_ptr<weld::TreeIter> xIter = xCategories.make_iterator(); + + if (!xCategories.get_selected(xIter.get())) + return; + + do + { + sMacroInfo = xCategories.get_text(*xIter) + "|" + sMacroInfo; + } while (xCategories.iter_parent(*xIter)); + + SvtViewOptions( EViewType::Dialog, MACRO_SELECTOR_CONFIGNAME ).SetUserItem( + LAST_RUN_MACRO_INFO, Any(sMacroInfo)); +} + +void +SvxScriptSelectorDialog::LoadLastUsedMacro() +{ + SvtViewOptions aDlgOpt( EViewType::Dialog, MACRO_SELECTOR_CONFIGNAME ); + if (!aDlgOpt.Exists()) + return; + + OUString sMacroInfo; + aDlgOpt.GetUserItem(LAST_RUN_MACRO_INFO) >>= sMacroInfo; + if (sMacroInfo.isEmpty()) + return; + + // Counts how many entries exist in the macro info string + sal_Int16 nInfoParts = 0; + sal_Int16 nLastIndex = sMacroInfo.indexOf('|'); + if (nLastIndex > -1) + { + nInfoParts = 1; + while ( nLastIndex != -1 ) + { + nInfoParts++; + nLastIndex = sMacroInfo.indexOf('|', nLastIndex + 1); + } + } + + weld::TreeView& xCategories = m_xCategories->get_widget(); + std::unique_ptr<weld::TreeIter> xIter = xCategories.make_iterator(); + if (!xCategories.get_iter_first(*xIter)) + return; + + // Expand the nodes in the category tree + OUString sNodeToExpand; + bool bIsIterValid; + sal_Int16 nOpenedNodes = 0; + for (sal_Int16 i=0; i<nInfoParts - 1; i++) + { + sNodeToExpand = sMacroInfo.getToken(i, '|'); + bIsIterValid = true; + while (bIsIterValid && xCategories.get_text(*xIter) != sNodeToExpand) + bIsIterValid = xCategories.iter_next_sibling(*xIter); + + if (bIsIterValid) + { + xCategories.expand_row(*xIter); + nOpenedNodes++; + } + if (xCategories.iter_has_child(*xIter)) + (void)xCategories.iter_children(*xIter); + else if (nOpenedNodes < nInfoParts - 1) + // If the number of levels in the tree is smaller than the + // number of parts in the macro info string, then return + return; + } + xCategories.select(*xIter); + xCategories.scroll_to_row(*xIter); + m_xCategories->GroupSelected(); + + // Select the macro in the command tree + weld::TreeView& xCommands = m_xCommands->get_widget(); + xIter = xCommands.make_iterator(); + if (!xCommands.get_iter_first(*xIter)) + return; + + OUString sMacroName = sMacroInfo.getToken(nInfoParts - 1, '|'); + bIsIterValid = true; + while (bIsIterValid && xCommands.get_text(*xIter) != sMacroName) + bIsIterValid = xCommands.iter_next_sibling(*xIter); + + if (bIsIterValid) + { + xCommands.scroll_to_row(*xIter); + xCommands.select(*xIter); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/eventdlg.cxx b/cui/source/customize/eventdlg.cxx new file mode 100644 index 0000000000..755fd16b41 --- /dev/null +++ b/cui/source/customize/eventdlg.cxx @@ -0,0 +1,160 @@ +/* -*- 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/diagnose_ex.hxx> +#include <com/sun/star/document/XEventsSupplier.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp> +#include <com/sun/star/frame/XStorable.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/documentinfo.hxx> +#include <unotools/configmgr.hxx> +#include <rtl/ustring.hxx> + +#include "eventdlg.hxx" +#include "macropg_impl.hxx" + +#include <cfg.hxx> + +using namespace ::com::sun::star; + + +SvxEventConfigPage::SvxEventConfigPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet, + SvxEventConfigPage::EarlyInit) + : SvxMacroTabPage_(pPage, pController, "cui/ui/eventsconfigpage.ui", "EventsConfigPage", rSet) + , m_xSaveInListBox(m_xBuilder->weld_combo_box("savein")) +{ + mpImpl->xEventLB = m_xBuilder->weld_tree_view("events"); + mpImpl->xAssignPB = m_xBuilder->weld_button("macro"); + mpImpl->xDeletePB = m_xBuilder->weld_button("delete"); + mpImpl->xDeleteAllPB = m_xBuilder->weld_button("deleteall"); + mpImpl->xAssignComponentPB = m_xBuilder->weld_button("component"); + + mpImpl->xEventLB->set_size_request(mpImpl->xEventLB->get_approximate_digit_width() * 70, + mpImpl->xEventLB->get_height_rows(20)); + + InitResources(); + + m_xSaveInListBox->connect_changed( LINK( this, SvxEventConfigPage, + SelectHdl_Impl ) ); + + uno::Reference< frame::XGlobalEventBroadcaster > xSupplier = + frame::theGlobalEventBroadcaster::get(::comphelper::getProcessComponentContext()); + + m_xAppEvents = xSupplier->getEvents(); + m_xSaveInListBox->append(OUString::boolean(true), utl::ConfigManager::getProductName()); + m_xSaveInListBox->set_active(0); +} + +void SvxEventConfigPage::LateInit( const uno::Reference< frame::XFrame >& _rxFrame ) +{ + SetFrame( _rxFrame ); + ImplInitDocument(); + + InitAndSetHandler( m_xAppEvents, m_xDocumentEvents, m_xDocumentModifiable ); + + SelectHdl_Impl( *m_xSaveInListBox ); +} + +SvxEventConfigPage::~SvxEventConfigPage() +{ +} + +void SvxEventConfigPage::ImplInitDocument() +{ + uno::Reference< frame::XFrame > xFrame( GetFrame() ); + OUString aModuleId = SvxConfigPage::GetFrameWithDefaultAndIdentify( xFrame ); + if ( !xFrame.is() ) + return; + + try + { + uno::Reference< frame::XModel > xModel; + if ( !SvxConfigPage::CanConfig( aModuleId ) ) + return; + + uno::Reference< frame::XController > xController = + xFrame->getController(); + + if ( xController.is() ) + { + xModel = xController->getModel(); + } + + if ( !xModel.is() ) + return; + + uno::Reference< document::XEventsSupplier > xSupplier( xModel, uno::UNO_QUERY ); + + if ( xSupplier.is() ) + { + m_xDocumentEvents = xSupplier->getEvents(); + m_xDocumentModifiable.set(xModel, css::uno::UNO_QUERY); + + OUString aTitle = ::comphelper::DocumentInfo::getDocumentTitle( xModel ); + + m_xSaveInListBox->append(OUString::boolean(false), aTitle); + m_xSaveInListBox->set_active(m_xSaveInListBox->get_count() - 1); + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("cui.customize"); + } +} + +IMPL_LINK_NOARG( SvxEventConfigPage, SelectHdl_Impl, weld::ComboBox&, void ) +{ + bool bApp = m_xSaveInListBox->get_active_id().toBoolean(); + + if (bApp) + { + SetReadOnly( false ); + SvxMacroTabPage_::DisplayAppEvents( true ); + } + else + { + bool isReadonly = false; + + uno::Reference< frame::XDesktop2 > xFramesSupplier = frame::Desktop::create( + ::comphelper::getProcessComponentContext() ); + + uno::Reference< frame::XFrame > xFrame = + xFramesSupplier->getActiveFrame(); + + if ( xFrame.is() ) + { + uno::Reference< frame::XController > xController = + xFrame->getController(); + + if ( xController.is() ) + { + uno::Reference< frame::XStorable > xStorable( + xController->getModel(), uno::UNO_QUERY ); + isReadonly = xStorable->isReadonly(); + } + } + + SetReadOnly( isReadonly ); + SvxMacroTabPage_::DisplayAppEvents( false ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/eventdlg.hxx b/cui/source/customize/eventdlg.hxx new file mode 100644 index 0000000000..2d368e9828 --- /dev/null +++ b/cui/source/customize/eventdlg.hxx @@ -0,0 +1,52 @@ +/* -*- 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 <vcl/weld.hxx> +#include <macropg.hxx> + +#include <com/sun/star/frame/XFrame.hpp> + +class SvxEventConfigPage : public SvxMacroTabPage_ +{ + css::uno::Reference< css::container::XNameReplace > m_xAppEvents; + css::uno::Reference< css::container::XNameReplace > m_xDocumentEvents; + css::uno::Reference< css::util::XModifiable > m_xDocumentModifiable; + + std::unique_ptr<weld::ComboBox> m_xSaveInListBox; + + DECL_LINK( SelectHdl_Impl, weld::ComboBox&, void ); + + SvxEventConfigPage (const SvxEventConfigPage &) = delete; + SvxEventConfigPage & operator= (const SvxEventConfigPage &) = delete; + +public: + + /// this is only to let callers know that there is a LateInit which *must* be called + struct EarlyInit { }; + SvxEventConfigPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet, EarlyInit); + virtual ~SvxEventConfigPage() override; + + void LateInit( const css::uno::Reference< css::frame::XFrame >& _rxFrame ); + +private: + void ImplInitDocument(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/macropg.cxx b/cui/source/customize/macropg.cxx new file mode 100644 index 0000000000..79197be4bf --- /dev/null +++ b/cui/source/customize/macropg.cxx @@ -0,0 +1,717 @@ +/* -*- 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 <macropg.hxx> +#include <svl/eitem.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <bitmaps.hlst> +#include <cfgutil.hxx> +#include <dialmgr.hxx> +#include <helpids.h> +#include <headertablistbox.hxx> +#include "macropg_impl.hxx" +#include <o3tl/safeint.hxx> +#include <svl/macitem.hxx> +#include <svx/svxids.hrc> +#include <strings.hrc> +#include <comphelper/namedvaluecollection.hxx> +#include <o3tl/string_view.hxx> +#include <utility> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +constexpr OUString aVndSunStarUNO = u"vnd.sun.star.UNO:"_ustr; + +SvxMacroTabPage_Impl::SvxMacroTabPage_Impl( const SfxItemSet& rAttrSet ) + : bReadOnly(false) + , bIDEDialogMode(false) +{ + const SfxPoolItem* pItem; + if ( SfxItemState::SET == rAttrSet.GetItemState( SID_ATTR_MACROITEM, false, &pItem ) ) + bIDEDialogMode = static_cast<const SfxBoolItem*>(pItem)->GetValue(); +} + +MacroEventListBox::MacroEventListBox(std::unique_ptr<weld::TreeView> xTreeView) + : m_xTreeView(std::move(xTreeView)) +{ + m_xTreeView->set_help_id(HID_MACRO_HEADERTABLISTBOX); + m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 70, m_xTreeView->get_height_rows(9)); +} + +// assign button ("Add Command") is enabled only if it is not read only +// delete button ("Remove Command") is enabled if a current binding exists +// and it is not read only +void SvxMacroTabPage_::EnableButtons() +{ + mpImpl->xDeleteAllPB->set_sensitive(m_nAssignedEvents != 0); + int nEvent = mpImpl->xEventLB->get_selected_index(); + const EventPair* pEventPair = nEvent == -1 ? nullptr : LookupEvent(mpImpl->xEventLB->get_id(nEvent)); + const bool bAssigned = pEventPair && !pEventPair->second.isEmpty(); + mpImpl->xDeletePB->set_sensitive(!mpImpl->bReadOnly && bAssigned); + mpImpl->xAssignPB->set_sensitive(!mpImpl->bReadOnly); + if (mpImpl->xAssignComponentPB) + mpImpl->xAssignComponentPB->set_sensitive( !mpImpl->bReadOnly ); +} + +SvxMacroTabPage_::SvxMacroTabPage_(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, + const OUString& rID, const SfxItemSet& rAttrSet) + : SfxTabPage(pPage, pController, rUIXMLDescription, rID, &rAttrSet) + , m_nAssignedEvents(0) + , bDocModified(false) + , bAppEvents(false) + , bInitialized(false) +{ + mpImpl.reset( new SvxMacroTabPage_Impl( rAttrSet ) ); +} + +SvxMacroTabPage_::~SvxMacroTabPage_() +{ + mpImpl.reset(); +} + +void SvxMacroTabPage_::InitResources() +{ + // Note: the order here controls the order in which the events are displayed in the UI! + + // the event name to UI string mappings for App Events + aDisplayNames.emplace_back( "OnStartApp", RID_CUISTR_EVENT_STARTAPP ); + aDisplayNames.emplace_back( "OnCloseApp", RID_CUISTR_EVENT_CLOSEAPP ); + aDisplayNames.emplace_back( "OnCreate", RID_CUISTR_EVENT_CREATEDOC ); + aDisplayNames.emplace_back( "OnNew", RID_CUISTR_EVENT_NEWDOC ); + aDisplayNames.emplace_back( "OnLoadFinished", RID_CUISTR_EVENT_LOADDOCFINISHED ); + aDisplayNames.emplace_back( "OnLoad", RID_CUISTR_EVENT_OPENDOC ); + aDisplayNames.emplace_back( "OnPrepareUnload", RID_CUISTR_EVENT_PREPARECLOSEDOC ); + aDisplayNames.emplace_back( "OnUnload", RID_CUISTR_EVENT_CLOSEDOC ) ; + aDisplayNames.emplace_back( "OnViewCreated", RID_CUISTR_EVENT_VIEWCREATED ); + aDisplayNames.emplace_back( "OnPrepareViewClosing", RID_CUISTR_EVENT_PREPARECLOSEVIEW ); + aDisplayNames.emplace_back( "OnViewClosed", RID_CUISTR_EVENT_CLOSEVIEW ) ; + aDisplayNames.emplace_back( "OnFocus", RID_CUISTR_EVENT_ACTIVATEDOC ); + aDisplayNames.emplace_back( "OnUnfocus", RID_CUISTR_EVENT_DEACTIVATEDOC ); + aDisplayNames.emplace_back( "OnSave", RID_CUISTR_EVENT_SAVEDOC ); + aDisplayNames.emplace_back( "OnSaveDone", RID_CUISTR_EVENT_SAVEDOCDONE ); + aDisplayNames.emplace_back( "OnSaveFailed", RID_CUISTR_EVENT_SAVEDOCFAILED ); + aDisplayNames.emplace_back( "OnSaveAs", RID_CUISTR_EVENT_SAVEASDOC ); + aDisplayNames.emplace_back( "OnSaveAsDone", RID_CUISTR_EVENT_SAVEASDOCDONE ); + aDisplayNames.emplace_back( "OnSaveAsFailed", RID_CUISTR_EVENT_SAVEASDOCFAILED ); + aDisplayNames.emplace_back( "OnCopyTo", RID_CUISTR_EVENT_COPYTODOC ); + aDisplayNames.emplace_back( "OnCopyToDone", RID_CUISTR_EVENT_COPYTODOCDONE ); + aDisplayNames.emplace_back( "OnCopyToFailed", RID_CUISTR_EVENT_COPYTODOCFAILED ); + aDisplayNames.emplace_back( "OnPrint", RID_CUISTR_EVENT_PRINTDOC ); + aDisplayNames.emplace_back( "OnModifyChanged", RID_CUISTR_EVENT_MODIFYCHANGED ); + aDisplayNames.emplace_back( "OnTitleChanged", RID_CUISTR_EVENT_TITLECHANGED ); + + // application specific events + aDisplayNames.emplace_back( "OnMailMerge", RID_CUISTR_EVENT_MAILMERGE ); + aDisplayNames.emplace_back( "OnMailMergeFinished", RID_CUISTR_EVENT_MAILMERGE_END ); + aDisplayNames.emplace_back( "OnFieldMerge", RID_CUISTR_EVENT_FIELDMERGE ); + aDisplayNames.emplace_back( "OnFieldMergeFinished", RID_CUISTR_EVENT_FIELDMERGE_FINISHED ); + aDisplayNames.emplace_back( "OnPageCountChange", RID_CUISTR_EVENT_PAGECOUNTCHANGE ); + aDisplayNames.emplace_back( "OnSubComponentOpened", RID_CUISTR_EVENT_SUBCOMPONENT_OPENED ); + aDisplayNames.emplace_back( "OnSubComponentClosed", RID_CUISTR_EVENT_SUBCOMPONENT_CLOSED ); + aDisplayNames.emplace_back( "OnSelect", RID_CUISTR_EVENT_SELECTIONCHANGED ); + aDisplayNames.emplace_back( "OnDoubleClick", RID_CUISTR_EVENT_DOUBLECLICK ); + aDisplayNames.emplace_back( "OnRightClick", RID_CUISTR_EVENT_RIGHTCLICK ); + aDisplayNames.emplace_back( "OnCalculate", RID_CUISTR_EVENT_CALCULATE ); + aDisplayNames.emplace_back( "OnChange", RID_CUISTR_EVENT_CONTENTCHANGED ); + + // the event name to UI string mappings for forms & dialogs + + aDisplayNames.emplace_back( "approveAction", RID_CUISTR_EVENT_APPROVEACTIONPERFORMED ); + aDisplayNames.emplace_back( "actionPerformed", RID_CUISTR_EVENT_ACTIONPERFORMED ); + aDisplayNames.emplace_back( "changed", RID_CUISTR_EVENT_CHANGED ); + aDisplayNames.emplace_back( "textChanged", RID_CUISTR_EVENT_TEXTCHANGED ); + aDisplayNames.emplace_back( "itemStateChanged", RID_CUISTR_EVENT_ITEMSTATECHANGED ); + aDisplayNames.emplace_back( "focusGained", RID_CUISTR_EVENT_FOCUSGAINED ); + aDisplayNames.emplace_back( "focusLost", RID_CUISTR_EVENT_FOCUSLOST ); + aDisplayNames.emplace_back( "keyPressed", RID_CUISTR_EVENT_KEYTYPED ); + aDisplayNames.emplace_back( "keyReleased", RID_CUISTR_EVENT_KEYUP ); + aDisplayNames.emplace_back( "mouseEntered", RID_CUISTR_EVENT_MOUSEENTERED ); + aDisplayNames.emplace_back( "mouseDragged", RID_CUISTR_EVENT_MOUSEDRAGGED ); + aDisplayNames.emplace_back( "mouseMoved", RID_CUISTR_EVENT_MOUSEMOVED ); + aDisplayNames.emplace_back( "mousePressed", RID_CUISTR_EVENT_MOUSEPRESSED ); + aDisplayNames.emplace_back( "mouseReleased", RID_CUISTR_EVENT_MOUSERELEASED ); + aDisplayNames.emplace_back( "mouseExited", RID_CUISTR_EVENT_MOUSEEXITED ); + aDisplayNames.emplace_back( "approveReset", RID_CUISTR_EVENT_APPROVERESETTED ); + aDisplayNames.emplace_back( "resetted", RID_CUISTR_EVENT_RESETTED ); + aDisplayNames.emplace_back( "approveSubmit", RID_CUISTR_EVENT_SUBMITTED ); + aDisplayNames.emplace_back( "approveUpdate", RID_CUISTR_EVENT_BEFOREUPDATE ); + aDisplayNames.emplace_back( "updated", RID_CUISTR_EVENT_AFTERUPDATE ); + aDisplayNames.emplace_back( "loaded", RID_CUISTR_EVENT_LOADED ); + aDisplayNames.emplace_back( "reloading", RID_CUISTR_EVENT_RELOADING ); + aDisplayNames.emplace_back( "reloaded", RID_CUISTR_EVENT_RELOADED ); + aDisplayNames.emplace_back( "unloading", RID_CUISTR_EVENT_UNLOADING ); + aDisplayNames.emplace_back( "unloaded", RID_CUISTR_EVENT_UNLOADED ); + aDisplayNames.emplace_back( "confirmDelete", RID_CUISTR_EVENT_CONFIRMDELETE ); + aDisplayNames.emplace_back( "approveRowChange", RID_CUISTR_EVENT_APPROVEROWCHANGE ); + aDisplayNames.emplace_back( "rowChanged", RID_CUISTR_EVENT_ROWCHANGE ); + aDisplayNames.emplace_back( "approveCursorMove", RID_CUISTR_EVENT_POSITIONING ); + aDisplayNames.emplace_back( "cursorMoved", RID_CUISTR_EVENT_POSITIONED ); + aDisplayNames.emplace_back( "approveParameter", RID_CUISTR_EVENT_APPROVEPARAMETER ); + aDisplayNames.emplace_back( "errorOccured", RID_CUISTR_EVENT_ERROROCCURRED ); + aDisplayNames.emplace_back( "adjustmentValueChanged", RID_CUISTR_EVENT_ADJUSTMENTVALUECHANGED ); +} + +// the following method is called when the user clicks OK +// We use the contents of the hashes to replace the settings +bool SvxMacroTabPage_::FillItemSet( SfxItemSet* /*rSet*/ ) +{ + try + { + OUString eventName; + if( m_xAppEvents.is() ) + { + for (auto const& appEvent : m_appEventsHash) + { + eventName = appEvent.first; + try + { + m_xAppEvents->replaceByName( eventName, GetPropsByName( eventName, m_appEventsHash ) ); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("cui.customize"); + } + } + } + if( m_xDocEvents.is() && bDocModified ) + { + for (auto const& docEvent : m_docEventsHash) + { + eventName = docEvent.first; + try + { + m_xDocEvents->replaceByName( eventName, GetPropsByName( eventName, m_docEventsHash ) ); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("cui.customize"); + } + } + // if we have a valid XModifiable (in the case of doc events) + // call setModified(true) + // in principle this should not be necessary (see issue ??) + if(m_xModifiable.is()) + { + m_xModifiable->setModified( true ); + } + } + } + catch (const Exception&) + { + } + // what is the return value about?? + return false; +} + +// the following method clears the bindings in the hashes for both doc & app +void SvxMacroTabPage_::Reset( const SfxItemSet* ) +{ + // called once in creation - don't reset the data this time + if(!bInitialized) + { + bInitialized = true; + return; + } + + try + { + if( m_xAppEvents.is() ) + { + for (auto & appEvent : m_appEventsHash) + { + appEvent.second.second.clear(); + } + } + if( m_xDocEvents.is() && bDocModified ) + { + for (auto & docEvent : m_docEventsHash) + { + docEvent.second.second.clear(); + } + // if we have a valid XModifiable (in the case of doc events) + // call setModified(true) + if(m_xModifiable.is()) + { + m_xModifiable->setModified( true ); + } + } + } + catch (const Exception&) + { + } + DisplayAppEvents(bAppEvents); +} + +void SvxMacroTabPage_::SetReadOnly( bool bSet ) +{ + mpImpl->bReadOnly = bSet; +} + +bool SvxMacroTabPage_::IsReadOnly() const +{ + return mpImpl->bReadOnly; +} + +namespace +{ + std::u16string_view GetEventDisplayText(std::u16string_view rURL) + { + if (rURL.empty()) + return std::u16string_view(); + bool bUNO = o3tl::starts_with(rURL, aVndSunStarUNO); + std::u16string_view aPureMethod; + if (bUNO) + { + aPureMethod = rURL.substr(aVndSunStarUNO.getLength()); + } + else + { + aPureMethod = rURL.substr(strlen("vnd.sun.star.script:")); + aPureMethod = aPureMethod.substr( 0, aPureMethod.find( '?' ) ); + } + return aPureMethod; + } + + OUString GetEventDisplayImage(std::u16string_view rURL) + { + if (rURL.empty()) + return OUString(); + size_t nIndex = rURL.find(aVndSunStarUNO); + bool bUNO = nIndex == 0; + return bUNO ? RID_SVXBMP_COMPONENT : RID_SVXBMP_MACRO; + } +} + +// displays the app events if appEvents=true, otherwise displays the doc events +void SvxMacroTabPage_::DisplayAppEvents( bool appEvents) +{ + bAppEvents = appEvents; + + mpImpl->xEventLB->freeze(); + mpImpl->xEventLB->clear(); + m_nAssignedEvents = 0; + EventsHash* eventsHash; + Reference< container::XNameReplace> nameReplace; + if(bAppEvents) + { + eventsHash = &m_appEventsHash; + nameReplace = m_xAppEvents; + } + else + { + eventsHash = &m_docEventsHash; + nameReplace = m_xDocEvents; + } + // have to use the original XNameReplace since the hash iterators do + // not guarantee the order in which the elements are returned + if(!nameReplace.is()) + { + mpImpl->xEventLB->thaw(); + return; + } + + for (auto const& displayableEvent : aDisplayNames) + { + OUString sEventName( OUString::createFromAscii( displayableEvent.pAsciiEventName ) ); + if ( !nameReplace->hasByName( sEventName ) ) + continue; + + EventsHash::iterator h_it = eventsHash->find( sEventName ); + if( h_it == eventsHash->end() ) + { + OSL_FAIL( "SvxMacroTabPage_::DisplayAppEvents: something's suspicious here!" ); + continue; + } + + OUString eventURL = h_it->second.second; + OUString displayName(CuiResId(displayableEvent.pEventResourceID)); + + int nRow = mpImpl->xEventLB->n_children(); + mpImpl->xEventLB->append(sEventName, displayName); + mpImpl->xEventLB->set_image(nRow, GetEventDisplayImage(eventURL), 1); + mpImpl->xEventLB->set_text(nRow, OUString(GetEventDisplayText(eventURL)), 2); + + if (!eventURL.isEmpty()) + ++m_nAssignedEvents; + } + + mpImpl->xEventLB->thaw(); + + if (mpImpl->xEventLB->n_children()) + { + mpImpl->xEventLB->select(0); + mpImpl->xEventLB->scroll_to_row(0); + } + + EnableButtons(); +} + +// select event handler on the listbox +IMPL_LINK_NOARG( SvxMacroTabPage_, SelectEvent_Impl, weld::TreeView&, void) +{ + int nEntry = mpImpl->xEventLB->get_selected_index(); + + if (nEntry == -1) + { + DBG_ASSERT(false, "Where does the empty entry come from?" ); + return; + } + + EnableButtons(); +} + +IMPL_LINK( SvxMacroTabPage_, AssignDeleteHdl_Impl, weld::Button&, rBtn, void ) +{ + GenericHandler_Impl(&rBtn); +} + +IMPL_LINK_NOARG( SvxMacroTabPage_, DoubleClickHdl_Impl, weld::TreeView&, bool) +{ + GenericHandler_Impl(nullptr); + return true; +} + +const EventPair* SvxMacroTabPage_::LookupEvent(const OUString& rEventName) +{ + const EventPair* pRet = nullptr; + if (bAppEvents) + { + EventsHash::iterator h_it = m_appEventsHash.find(rEventName); + if (h_it != m_appEventsHash.end() ) + pRet = &h_it->second; + } + else + { + EventsHash::iterator h_it = m_docEventsHash.find(rEventName); + if (h_it != m_docEventsHash.end() ) + pRet = &h_it->second; + } + return pRet; +} + +// handler for double click on the listbox, and for the assign/delete buttons +void SvxMacroTabPage_::GenericHandler_Impl(const weld::Button* pBtn) +{ + weld::TreeView& rListBox = *mpImpl->xEventLB; + int nEntry = rListBox.get_selected_index(); + if (nEntry == -1) + { + DBG_ASSERT(false, "Where does the empty entry come from?"); + return; + } + + const bool bAssEnabled = pBtn != mpImpl->xDeletePB.get() && mpImpl->xAssignPB->get_sensitive(); + + OUString sEventName = rListBox.get_id(nEntry); + + OUString sEventURL; + OUString sEventType; + if (const EventPair* pEventPair = LookupEvent(sEventName)) + { + sEventType = pEventPair->first; + sEventURL = pEventPair->second; + } + + if (!sEventURL.isEmpty()) + --m_nAssignedEvents; + + bool bDoubleClick = (pBtn == nullptr); + bool bUNOAssigned = sEventURL.startsWith( aVndSunStarUNO ); + if (pBtn == mpImpl->xDeletePB.get()) + { + // delete pressed + sEventType = "Script" ; + sEventURL.clear(); + if (!bAppEvents) + bDocModified = true; + } + else if ( ( ( pBtn != nullptr ) + && ( pBtn == mpImpl->xAssignComponentPB.get() ) + ) + || ( bDoubleClick + && bUNOAssigned + ) + ) + { + AssignComponentDialog aAssignDlg(GetFrameWeld(), sEventURL); + + short ret = aAssignDlg.run(); + if( ret ) + { + sEventType = "UNO"; + sEventURL = aAssignDlg.getURL(); + if (!bAppEvents) + bDocModified = true; + } + } + else if( bAssEnabled ) + { + // assign pressed + SvxScriptSelectorDialog aDlg(GetFrameWeld(), GetFrame()); + short ret = aDlg.run(); + if ( ret ) + { + sEventType = "Script"; + sEventURL = aDlg.GetScriptURL(); + if (!bAppEvents) + bDocModified = true; + } + } + + // update the hashes + if (bAppEvents) + { + EventsHash::iterator h_it = m_appEventsHash.find(sEventName); + h_it->second.first = sEventType; + h_it->second.second = sEventURL; + } + else + { + EventsHash::iterator h_it = m_docEventsHash.find(sEventName); + h_it->second.first = sEventType; + h_it->second.second = sEventURL; + } + + if (!sEventURL.isEmpty()) + ++m_nAssignedEvents; + + rListBox.set_image(nEntry, GetEventDisplayImage(sEventURL), 1); + rListBox.set_text(nEntry, OUString(GetEventDisplayText(sEventURL)), 2); + + rListBox.select(nEntry ); + rListBox.scroll_to_row(nEntry); + + EnableButtons(); +} + +IMPL_LINK_NOARG(SvxMacroTabPage_, DeleteAllHdl_Impl, weld::Button&, void) +{ + OUString sEventType = "Script" ; + OUString sEmptyString; + + mpImpl->xEventLB->all_foreach([this, &sEventType, &sEmptyString](weld::TreeIter& rEntry) { + weld::TreeView& rListBox = *mpImpl->xEventLB; + OUString sEventName = rListBox.get_id(rEntry); + // update the hashes + if (bAppEvents) + { + EventsHash::iterator h_it = m_appEventsHash.find(sEventName); + assert(h_it != m_appEventsHash.end()); + h_it->second.first = sEventType; + h_it->second.second = sEmptyString; + } + else + { + EventsHash::iterator h_it = m_docEventsHash.find(sEventName); + assert(h_it != m_docEventsHash.end()); + h_it->second.first = sEventType; + h_it->second.second = sEmptyString; + } + + rListBox.set_image(rEntry, sEmptyString, 1); + rListBox.set_text(rEntry, sEmptyString, 2); + return false; + }); + + if (!bAppEvents) + bDocModified = true; + + m_nAssignedEvents = 0; + + EnableButtons(); +} + +// pass in the XNameReplace. +// can remove the 3rd arg once issue ?? is fixed +void SvxMacroTabPage_::InitAndSetHandler( const Reference< container::XNameReplace>& xAppEvents, const Reference< container::XNameReplace>& xDocEvents, const Reference< util::XModifiable >& xModifiable ) +{ + m_xAppEvents = xAppEvents; + m_xDocEvents = xDocEvents; + m_xModifiable = xModifiable; + Link<weld::Button&,void> aLnk(LINK(this, SvxMacroTabPage_, AssignDeleteHdl_Impl )); + mpImpl->xDeletePB->connect_clicked(aLnk); + mpImpl->xAssignPB->connect_clicked(aLnk); + mpImpl->xDeleteAllPB->connect_clicked(LINK(this, SvxMacroTabPage_, DeleteAllHdl_Impl)); + if( mpImpl->xAssignComponentPB ) + mpImpl->xAssignComponentPB->connect_clicked( aLnk ); + mpImpl->xEventLB->connect_row_activated( LINK(this, SvxMacroTabPage_, DoubleClickHdl_Impl ) ); + mpImpl->xEventLB->connect_changed( LINK( this, SvxMacroTabPage_, SelectEvent_Impl )); + + std::vector<int> aWidths + { + o3tl::narrowing<int>(mpImpl->xEventLB->get_approximate_digit_width() * 32), + mpImpl->xEventLB->get_checkbox_column_width() + }; + mpImpl->xEventLB->set_column_fixed_widths(aWidths); + + mpImpl->xEventLB->show(); + mpImpl->xEventLB->set_sensitive(true); + + if(!m_xAppEvents.is()) + { + return; + } + Sequence< OUString > eventNames = m_xAppEvents->getElementNames(); + sal_Int32 nEventCount = eventNames.getLength(); + for(sal_Int32 nEvent = 0; nEvent < nEventCount; ++nEvent ) + { + //need exception handling here + try + { + m_appEventsHash[ eventNames[nEvent] ] = GetPairFromAny( m_xAppEvents->getByName( eventNames[nEvent] ) ); + } + catch (const Exception&) + { + } + } + if(!m_xDocEvents.is()) + return; + + eventNames = m_xDocEvents->getElementNames(); + nEventCount = eventNames.getLength(); + for(sal_Int32 nEvent = 0; nEvent < nEventCount; ++nEvent ) + { + try + { + m_docEventsHash[ eventNames[nEvent] ] = GetPairFromAny( m_xDocEvents->getByName( eventNames[nEvent] ) ); + } + catch (const Exception&) + { + } + } +} + +// returns the two props EventType & Script for a given event name +Any SvxMacroTabPage_::GetPropsByName( const OUString& eventName, EventsHash& eventsHash ) +{ + const EventPair& rAssignedEvent(eventsHash[eventName]); + + Any aReturn; + ::comphelper::NamedValueCollection aProps; + if ( !(rAssignedEvent.first.isEmpty() || rAssignedEvent.second.isEmpty()) ) + { + aProps.put( "EventType", rAssignedEvent.first ); + aProps.put( "Script", rAssignedEvent.second ); + } + aReturn <<= aProps.getPropertyValues(); + + return aReturn; +} + +// converts the Any returned by GetByName into a pair which can be stored in +// the EventHash +EventPair SvxMacroTabPage_::GetPairFromAny( const Any& aAny ) +{ + Sequence< beans::PropertyValue > props; + OUString type, url; + if( aAny >>= props ) + { + ::comphelper::NamedValueCollection aProps( props ); + type = aProps.getOrDefault( "EventType", type ); + url = aProps.getOrDefault( "Script", url ); + } + return std::make_pair( type, url ); +} + +SvxMacroTabPage::SvxMacroTabPage(weld::Container* pPage, weld::DialogController* pController, + const Reference< frame::XFrame >& _rxDocumentFrame, + const SfxItemSet& rSet, + Reference< container::XNameReplace > const & xNameReplace, + sal_uInt16 nSelectedIndex) + : SvxMacroTabPage_(pPage, pController, "cui/ui/macroassignpage.ui", "MacroAssignPage", rSet) +{ + mpImpl->xEventLB = m_xBuilder->weld_tree_view("assignments"); + mpImpl->xEventLB->set_size_request(mpImpl->xEventLB->get_approximate_digit_width() * 70, + mpImpl->xEventLB->get_height_rows(9)); + mpImpl->xAssignPB = m_xBuilder->weld_button("assign"); + mpImpl->xDeletePB = m_xBuilder->weld_button("delete"); + mpImpl->xDeleteAllPB = m_xBuilder->weld_button("deleteall"); + mpImpl->xAssignComponentPB = m_xBuilder->weld_button("component"); + + SetFrame( _rxDocumentFrame ); + + if( !mpImpl->bIDEDialogMode ) + { + mpImpl->xAssignComponentPB->hide(); + mpImpl->xAssignComponentPB->set_sensitive(false); + } + + InitResources(); + + InitAndSetHandler( xNameReplace, Reference< container::XNameReplace>(nullptr), Reference< util::XModifiable >(nullptr)); + DisplayAppEvents(true); + mpImpl->xEventLB->select(nSelectedIndex); +} + +SvxMacroAssignDlg::SvxMacroAssignDlg(weld::Window* pParent, const Reference< frame::XFrame >& _rxDocumentFrame, const SfxItemSet& rSet, + const Reference< container::XNameReplace >& xNameReplace, sal_uInt16 nSelectedIndex) + : SvxMacroAssignSingleTabDialog(pParent, rSet) +{ + SetTabPage(std::make_unique<SvxMacroTabPage>(get_content_area(), this, _rxDocumentFrame, rSet, xNameReplace, nSelectedIndex)); +} + +IMPL_LINK_NOARG(AssignComponentDialog, ButtonHandler, weld::Button&, void) +{ + OUString aMethodName = mxMethodEdit->get_text(); + maURL.clear(); + if( !aMethodName.isEmpty() ) + { + maURL = aVndSunStarUNO; + maURL += aMethodName; + } + m_xDialog->response(RET_OK); +} + +AssignComponentDialog::AssignComponentDialog(weld::Window* pParent, OUString aURL) + : GenericDialogController(pParent, "cui/ui/assigncomponentdialog.ui", "AssignComponent") + , maURL(std::move( aURL )) + , mxMethodEdit(m_xBuilder->weld_entry("methodEntry")) + , mxOKButton(m_xBuilder->weld_button("ok")) +{ + mxOKButton->connect_clicked(LINK(this, AssignComponentDialog, ButtonHandler)); + + OUString aMethodName; + if( maURL.startsWith( aVndSunStarUNO ) ) + { + aMethodName = maURL.copy( aVndSunStarUNO.getLength() ); + } + mxMethodEdit->set_text(aMethodName); + mxMethodEdit->select_region(0, -1); +} + +AssignComponentDialog::~AssignComponentDialog() +{ +} + +IMPL_LINK_NOARG(SvxMacroAssignSingleTabDialog, OKHdl_Impl, weld::Button&, void) +{ + m_xSfxPage->FillItemSet(nullptr); + m_xDialog->response(RET_OK); +} + +SvxMacroAssignSingleTabDialog::SvxMacroAssignSingleTabDialog(weld::Window *pParent, + const SfxItemSet& rSet) + : SfxSingleTabDialogController(pParent, &rSet, "cui/ui/macroassigndialog.ui", "MacroAssignDialog") +{ + GetOKButton().connect_clicked(LINK(this, SvxMacroAssignSingleTabDialog, OKHdl_Impl)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/customize/macropg_impl.hxx b/cui/source/customize/macropg_impl.hxx new file mode 100644 index 0000000000..1073e4bb36 --- /dev/null +++ b/cui/source/customize/macropg_impl.hxx @@ -0,0 +1,56 @@ +/* -*- 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 <svl/itemset.hxx> +#include <vcl/weld.hxx> + +class SvxMacroTabPage_Impl +{ +public: + explicit SvxMacroTabPage_Impl( const SfxItemSet& rAttrSet ); + + std::unique_ptr<weld::Button> xAssignPB; + std::unique_ptr<weld::Button> xAssignComponentPB; + std::unique_ptr<weld::Button> xDeletePB; + std::unique_ptr<weld::Button> xDeleteAllPB; + std::unique_ptr<weld::TreeView> xEventLB; + bool bReadOnly; + bool bIDEDialogMode; +}; + +class AssignComponentDialog : public weld::GenericDialogController +{ +private: + OUString maURL; + + std::unique_ptr<weld::Entry> mxMethodEdit; + std::unique_ptr<weld::Button> mxOKButton; + + DECL_LINK(ButtonHandler, weld::Button&, void); + +public: + AssignComponentDialog(weld::Window* pParent, OUString aURL); + virtual ~AssignComponentDialog() override; + + const OUString& getURL() const { return maURL; } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |