office-gobmx/cui/source/customize/CommandCategoryListBox.cxx
Noel Grandin ecf6f6f256 loplugin:ostr in cui
Change-Id: I593eb3c02768423fc8cea8d93c112655860228d9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167258
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2024-05-07 12:20:06 +02:00

607 lines
23 KiB
C++

/* -*- 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(u""_ustr);
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(u""_ustr);
// 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(u"Name"_ustr, 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(u"URI"_ustr);
value >>= uri;
try
{
value = xPropSet->getPropertyValue(u"Description"_ustr);
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: */