diff options
Diffstat (limited to '')
-rw-r--r-- | cui/source/customize/CommandCategoryListBox.cxx | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/cui/source/customize/CommandCategoryListBox.cxx b/cui/source/customize/CommandCategoryListBox.cxx new file mode 100644 index 000000000..f6b34076f --- /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 <tools/diagnose_ex.h> +#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: */ |