1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <CommandCategoryListBox.hxx>
22 #include <com/sun/star/uno/XInterface.hpp>
23 #include <com/sun/star/beans/XPropertySet.hpp>
24 #include <com/sun/star/frame/XDispatchInformationProvider.hpp>
25 #include <com/sun/star/frame/theUICommandDescription.hpp>
26 #include <com/sun/star/ui/theUICategoryDescription.hpp>
27 #include <com/sun/star/script/browse/XBrowseNode.hpp>
28 #include <com/sun/star/script/browse/BrowseNodeTypes.hpp>
29 #include <com/sun/star/script/browse/theBrowseNodeFactory.hpp>
30 #include <com/sun/star/script/browse/BrowseNodeFactoryViewTypes.hpp>
31 #include <vcl/commandinfoprovider.hxx>
32 #include <vcl/settings.hxx>
33 #include <vcl/svapp.hxx>
35 // include search util
36 #include <com/sun/star/util/SearchFlags.hpp>
37 #include <com/sun/star/util/SearchAlgorithms2.hpp>
38 #include <comphelper/diagnose_ex.hxx>
39 #include <unotools/textsearch.hxx>
41 #include <dialmgr.hxx>
42 #include <strings.hrc>
43 #include <comphelper/processfactory.hxx>
44 #include <comphelper/sequenceashashmap.hxx>
45 #include <comphelper/SetFlagContextHelper.hxx>
46 #include <comphelper/string.hxx>
47 #include <officecfg/Office/Common.hxx>
48 #include <i18nlangtag/languagetag.hxx>
49 #include <i18nutil/searchopt.hxx>
50 #include <sal/log.hxx>
52 #include <cfg.hxx> //for SaveInData
54 CommandCategoryListBox::CommandCategoryListBox(std::unique_ptr
<weld::ComboBox
> xControl
)
55 : pStylesInfo(nullptr)
56 , m_xControl(std::move(xControl
))
58 //Initialize search util
59 m_searchOptions
.AlgorithmType2
= css::util::SearchAlgorithms2::ABSOLUTE
;
60 m_searchOptions
.transliterateFlags
|= TransliterationFlags::IGNORE_CASE
;
61 m_searchOptions
.searchFlag
|= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
62 | css::util::SearchFlags::REG_NOT_ENDOFLINE
);
65 CommandCategoryListBox::~CommandCategoryListBox() { ClearAll(); }
67 void CommandCategoryListBox::ClearAll()
69 // Clear objects from m_aGroupInfo vector to avoid memory leak
70 for (const auto& It
: m_aGroupInfo
)
72 if (It
->nKind
== SfxCfgKind::GROUP_STYLES
&& It
->pObject
)
74 SfxStyleInfo_Impl
* pStyle
= static_cast<SfxStyleInfo_Impl
*>(It
->pObject
);
77 else if (It
->nKind
== SfxCfgKind::FUNCTION_SCRIPT
&& It
->pObject
)
79 OUString
* pScriptURI
= static_cast<OUString
*>(It
->pObject
);
82 else if (It
->nKind
== SfxCfgKind::GROUP_SCRIPTCONTAINER
&& It
->pObject
)
84 css::uno::XInterface
* xi
= static_cast<css::uno::XInterface
*>(It
->pObject
);
96 void CommandCategoryListBox::Init(const css::uno::Reference
<css::uno::XComponentContext
>& xContext
,
97 const css::uno::Reference
<css::frame::XFrame
>& xFrame
,
98 const OUString
& sModuleLongName
)
100 // User will not see incomplete UI
101 m_xControl
->freeze();
104 m_xContext
= xContext
;
107 m_sModuleLongName
= sModuleLongName
;
108 m_xGlobalCategoryInfo
= css::ui::theUICategoryDescription::get(m_xContext
);
109 m_xModuleCategoryInfo
.set(m_xGlobalCategoryInfo
->getByName(m_sModuleLongName
),
110 css::uno::UNO_QUERY_THROW
);
111 m_xUICmdDescription
= css::frame::theUICommandDescription::get(m_xContext
);
113 // Support style commands
114 css::uno::Reference
<css::frame::XController
> xController
;
115 css::uno::Reference
<css::frame::XModel
> xModel
;
117 xController
= xFrame
->getController();
118 if (xController
.is())
119 xModel
= xController
->getModel();
121 m_aStylesInfo
.init(sModuleLongName
, xModel
);
122 SetStylesInfo(&m_aStylesInfo
);
126 css::uno::Reference
<css::frame::XDispatchInformationProvider
> xProvider(
127 m_xFrame
, css::uno::UNO_QUERY_THROW
);
128 css::uno::Sequence
<sal_Int16
> lGroups
= xProvider
->getSupportedCommandGroups();
130 sal_Int32 nGroupsLength
= lGroups
.getLength();
132 if (nGroupsLength
> 0)
134 // Add the category of "All commands"
135 m_aGroupInfo
.push_back(
136 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_ALLFUNCTIONS
, 0));
137 m_xControl
->append(weld::toId(m_aGroupInfo
.back().get()),
138 CuiResId(RID_CUISTR_ALLFUNCTIONS
));
141 // Separate the "All commands"category from the actual categories
142 m_xControl
->append_separator(u
""_ustr
);
144 typedef std::pair
<OUString
, sal_Int16
> str_id
;
145 std::vector
<str_id
> aCategories
;
147 // Add the actual categories
148 for (sal_Int32 i
= 0; i
< nGroupsLength
; ++i
)
150 sal_Int16 nGroupID
= lGroups
[i
];
151 OUString sGroupID
= OUString::number(nGroupID
);
156 m_xModuleCategoryInfo
->getByName(sGroupID
) >>= sGroupName
;
157 if (sGroupName
.isEmpty())
160 catch (const css::container::NoSuchElementException
&)
164 aCategories
.emplace_back(std::make_pair(sGroupName
, nGroupID
));
167 auto const sort
= comphelper::string::NaturalStringSorter(
168 comphelper::getProcessComponentContext(),
169 Application::GetSettings().GetUILanguageTag().getLocale());
171 std::sort(aCategories
.begin(), aCategories
.end(),
172 [&sort
](const str_id
& a
, const str_id
& b
) {
173 return sort
.compare(a
.first
, b
.first
) < 0;
176 // Add the actual categories
177 for (const auto& a
: aCategories
)
179 const OUString
& rGroupName
= a
.first
;
180 sal_Int16 nGroupID
= a
.second
;
181 m_aGroupInfo
.push_back(
182 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_FUNCTION
, nGroupID
));
183 m_xControl
->append(weld::toId(m_aGroupInfo
.back().get()), rGroupName
);
186 // Separate regular commands from styles and macros
187 m_xControl
->append_separator(u
""_ustr
);
189 // Add macros category
190 m_aGroupInfo
.push_back(
191 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0, nullptr));
192 m_xControl
->append(weld::toId(m_aGroupInfo
.back().get()), CuiResId(RID_CUISTR_MACROS
));
194 // Add styles category
195 //TODO: last param should contain user data?
196 m_aGroupInfo
.push_back(
197 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_STYLES
, 0, nullptr));
198 m_xControl
->append(weld::toId(m_aGroupInfo
.back().get()),
199 CuiResId(RID_CUISTR_GROUP_STYLES
));
201 catch (const css::uno::RuntimeException
&)
205 catch (const css::uno::Exception
&)
209 // Reveal the updated UI to user
211 m_xControl
->set_active(0);
214 void CommandCategoryListBox::FillFunctionsList(
215 const css::uno::Sequence
<css::frame::DispatchInformation
>& xCommands
,
216 CuiConfigFunctionListBox
* pFunctionListBox
, const OUString
& filterTerm
,
217 SaveInData
* pCurrentSaveInData
)
219 // Setup search filter parameters
220 m_searchOptions
.searchString
= filterTerm
;
221 utl::TextSearch
textSearch(m_searchOptions
);
222 const bool bInExperimentalMode
= officecfg::Office::Common::Misc::ExperimentalMode::get();
224 for (const auto& rInfo
: xCommands
)
227 = vcl::CommandInfoProvider::GetCommandProperties(rInfo
.Command
, m_sModuleLongName
);
229 OUString sUIName
= getCommandName(rInfo
.Command
);
230 OUString sLabel
= vcl::CommandInfoProvider::GetLabelForCommand(aProperties
);
231 OUString sTooltipLabel
232 = vcl::CommandInfoProvider::GetTooltipForCommand(rInfo
.Command
, aProperties
, m_xFrame
);
233 OUString sPopupLabel
= (vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties
))
234 .replaceFirst("~", "");
236 = vcl::CommandInfoProvider::IsExperimental(rInfo
.Command
, m_sModuleLongName
);
238 // Hide experimental commands when not in experimental mode
239 bool bHideExperimental
= bIsExperimental
&& !bInExperimentalMode
;
241 // Apply the search filter
242 if (bHideExperimental
243 || (!filterTerm
.isEmpty() && !textSearch
.searchForward(sUIName
)
244 && !textSearch
.searchForward(sLabel
) && !textSearch
.searchForward(sTooltipLabel
)
245 && !textSearch
.searchForward(sPopupLabel
)))
250 css::uno::Reference
<css::graphic::XGraphic
> xImage
;
251 if (pCurrentSaveInData
)
252 xImage
= pCurrentSaveInData
->GetImage(rInfo
.Command
);
254 m_aGroupInfo
.push_back(std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::FUNCTION_SLOT
, 0));
255 SfxGroupInfo_Impl
* pGrpInfo
= m_aGroupInfo
.back().get();
256 pGrpInfo
->sCommand
= rInfo
.Command
;
257 pGrpInfo
->sLabel
= sUIName
;
258 pGrpInfo
->sTooltip
= sTooltipLabel
;
259 pFunctionListBox
->append(weld::toId(m_aGroupInfo
.back().get()), sUIName
, xImage
);
263 OUString
CommandCategoryListBox::getCommandName(const OUString
& sCommand
)
268 css::uno::Reference
<css::container::XNameAccess
> xModuleConf
;
269 m_xUICmdDescription
->getByName(m_sModuleLongName
) >>= xModuleConf
;
270 if (xModuleConf
.is())
272 ::comphelper::SequenceAsHashMap
lProps(xModuleConf
->getByName(sCommand
));
273 sUIName
= lProps
.getUnpackedValueOrDefault(u
"Name"_ustr
, OUString());
276 catch (const css::uno::RuntimeException
&)
280 catch (css::uno::Exception
&)
285 // fallback for missing UINames !?
286 if (sUIName
.isEmpty())
294 void CommandCategoryListBox::categorySelected(CuiConfigFunctionListBox
* pFunctionListBox
,
295 const OUString
& filterTerm
,
296 SaveInData
* pCurrentSaveInData
)
298 SfxGroupInfo_Impl
* pInfo
= weld::fromId
<SfxGroupInfo_Impl
*>(m_xControl
->get_active_id());
299 std::vector
<std::unique_ptr
<weld::TreeIter
>> aNodesToExpand
;
300 pFunctionListBox
->freeze();
301 pFunctionListBox
->ClearAll();
303 switch (pInfo
->nKind
)
305 case SfxCfgKind::GROUP_ALLFUNCTIONS
:
307 css::uno::Reference
<css::frame::XDispatchInformationProvider
> xProvider(
308 m_xFrame
, css::uno::UNO_QUERY
);
309 sal_Int32 nEntryCount
= m_xControl
->get_count();
311 for (sal_Int32 nCurPos
= 0; nCurPos
< nEntryCount
; ++nCurPos
)
313 SfxGroupInfo_Impl
* pCurrentInfo
314 = weld::fromId
<SfxGroupInfo_Impl
*>(m_xControl
->get_id(nCurPos
));
316 if (!pCurrentInfo
) //separator
319 if (pCurrentInfo
->nKind
== SfxCfgKind::GROUP_FUNCTION
)
321 css::uno::Sequence
<css::frame::DispatchInformation
> lCommands
;
324 lCommands
= xProvider
->getConfigurableDispatchInformation(
325 pCurrentInfo
->nUniqueID
);
326 FillFunctionsList(lCommands
, pFunctionListBox
, filterTerm
,
329 catch (css::container::NoSuchElementException
&)
337 case SfxCfgKind::GROUP_FUNCTION
:
339 sal_uInt16 nGroup
= pInfo
->nUniqueID
;
340 css::uno::Reference
<css::frame::XDispatchInformationProvider
> xProvider(
341 m_xFrame
, css::uno::UNO_QUERY_THROW
);
342 css::uno::Sequence
<css::frame::DispatchInformation
> lCommands
343 = xProvider
->getConfigurableDispatchInformation(nGroup
);
344 FillFunctionsList(lCommands
, pFunctionListBox
, filterTerm
, pCurrentSaveInData
);
347 case SfxCfgKind::GROUP_SCRIPTCONTAINER
: //Macros
349 SAL_INFO("cui.customize", "** ** About to initialise SF Scripts");
350 // Add Scripting Framework entries
351 css::uno::Reference
<css::script::browse::XBrowseNode
> rootNode
;
354 css::uno::Reference
<css::script::browse::XBrowseNodeFactory
> xFac
355 = css::script::browse::theBrowseNodeFactory::get(m_xContext
);
356 rootNode
.set(xFac
->createView(
357 css::script::browse::BrowseNodeFactoryViewTypes::MACROSELECTOR
));
359 catch (css::uno::Exception
const&)
361 TOOLS_WARN_EXCEPTION(
363 "Caught some exception whilst retrieving browse nodes from factory");
364 // TODO exception handling
367 if (rootNode
.is() && rootNode
->hasChildNodes())
369 //We call acquire on the XBrowseNode so that it does not
370 //get autodestructed and become invalid when accessed later.
373 m_aGroupInfo
.push_back(std::make_unique
<SfxGroupInfo_Impl
>(
374 SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0, static_cast<void*>(rootNode
.get())));
376 // Add main macro groups
377 const css::uno::Sequence
<css::uno::Reference
<css::script::browse::XBrowseNode
>>
378 aChildNodes
= rootNode
->getChildNodes();
379 for (auto const& childGroup
: aChildNodes
)
381 childGroup
->acquire();
383 if (childGroup
->hasChildNodes())
386 if (childGroup
->getName() == "user")
388 sUIName
= CuiResId(RID_CUISTR_MYMACROS
);
390 else if (childGroup
->getName() == "share")
392 sUIName
= CuiResId(RID_CUISTR_PRODMACROS
);
396 sUIName
= childGroup
->getName();
399 if (sUIName
.isEmpty())
404 m_aGroupInfo
.push_back(std::make_unique
<SfxGroupInfo_Impl
>(
405 SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0));
406 std::unique_ptr
<weld::TreeIter
> xMacroGroup(pFunctionListBox
->tree_append(
407 weld::toId(m_aGroupInfo
.back().get()), sUIName
));
410 // tdf#128010: Do not nag user asking to enable JRE: if it's disabled,
411 // simply don't show relevant entries (user chose to not use JRE)
412 css::uno::ContextLayer
layer(
413 comphelper::NoEnableJavaInteractionContext());
414 //Add the children and the grand children
415 addChildren(xMacroGroup
.get(), childGroup
, pFunctionListBox
, filterTerm
,
416 pCurrentSaveInData
, aNodesToExpand
);
419 // Remove the main group if empty
420 if (!pFunctionListBox
->iter_has_child(*xMacroGroup
))
422 pFunctionListBox
->remove(*xMacroGroup
);
424 else if (!filterTerm
.isEmpty())
426 aNodesToExpand
.emplace_back(std::move(xMacroGroup
));
434 case SfxCfgKind::GROUP_STYLES
:
436 const std::vector
<SfxStyleInfo_Impl
> lStyleFamilies
= pStylesInfo
->getStyleFamilies();
438 for (const auto& pIt
: lStyleFamilies
)
440 if (pIt
.sLabel
.isEmpty())
445 m_aGroupInfo
.push_back(
446 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_STYLES
, 0));
447 // pIt.sLabel is Name of the style family
448 std::unique_ptr
<weld::TreeIter
> xFuncEntry(pFunctionListBox
->tree_append(
449 weld::toId(m_aGroupInfo
.back().get()), pIt
.sLabel
));
451 const std::vector
<SfxStyleInfo_Impl
> lStyles
= pStylesInfo
->getStyles(pIt
.sFamily
);
453 // Setup search filter parameters
454 m_searchOptions
.searchString
= filterTerm
;
455 utl::TextSearch
textSearch(m_searchOptions
);
457 // Insert children (styles)
458 for (const auto& pStyleIt
: lStyles
)
460 OUString sUIName
= pStyleIt
.sLabel
;
461 sal_Int32 aStartPos
= 0;
462 sal_Int32 aEndPos
= sUIName
.getLength();
464 // Apply the search filter
465 if (!filterTerm
.isEmpty()
466 && !textSearch
.SearchForward(sUIName
, &aStartPos
, &aEndPos
))
471 SfxStyleInfo_Impl
* pStyle
= new SfxStyleInfo_Impl(pStyleIt
);
473 m_aGroupInfo
.push_back(
474 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_STYLES
, 0, pStyle
));
476 m_aGroupInfo
.back()->sCommand
= pStyle
->sCommand
;
477 m_aGroupInfo
.back()->sLabel
= pStyle
->sLabel
;
479 pFunctionListBox
->append(weld::toId(m_aGroupInfo
.back().get()), sUIName
,
483 // Remove the style group from the list if no children
484 if (!pFunctionListBox
->iter_has_child(*xFuncEntry
))
486 pFunctionListBox
->remove(*xFuncEntry
);
488 else if (!filterTerm
.isEmpty())
490 aNodesToExpand
.emplace_back(std::move(xFuncEntry
));
497 // Do nothing, the list box will stay empty
498 SAL_INFO("cui.customize",
499 "Ignoring unexpected SfxCfgKind: " << static_cast<int>(pInfo
->nKind
));
503 pFunctionListBox
->thaw();
505 if (pFunctionListBox
->n_children())
506 pFunctionListBox
->select(0);
509 for (const auto& it
: aNodesToExpand
)
510 pFunctionListBox
->expand_row(*it
);
513 void CommandCategoryListBox::SetStylesInfo(SfxStylesInfo_Impl
* pStyles
) { pStylesInfo
= pStyles
; }
515 void CommandCategoryListBox::addChildren(
516 const weld::TreeIter
* parentEntry
,
517 const css::uno::Reference
<css::script::browse::XBrowseNode
>& parentNode
,
518 CuiConfigFunctionListBox
* pFunctionListBox
, const OUString
& filterTerm
,
519 SaveInData
* pCurrentSaveInData
, std::vector
<std::unique_ptr
<weld::TreeIter
>>& rNodesToExpand
)
521 // Setup search filter parameters
522 m_searchOptions
.searchString
= filterTerm
;
523 utl::TextSearch
textSearch(m_searchOptions
);
525 const css::uno::Sequence
<css::uno::Reference
<css::script::browse::XBrowseNode
>> aChildNodes
526 = parentNode
->getChildNodes();
527 for (auto const& child
: aChildNodes
)
529 // Acquire to prevent auto-destruction
532 if (child
->hasChildNodes())
534 OUString sUIName
= child
->getName();
536 m_aGroupInfo
.push_back(std::make_unique
<SfxGroupInfo_Impl
>(
537 SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0, static_cast<void*>(child
.get())));
538 std::unique_ptr
<weld::TreeIter
> xNewEntry(pFunctionListBox
->tree_append(
539 weld::toId(m_aGroupInfo
.back().get()), sUIName
, parentEntry
));
541 addChildren(xNewEntry
.get(), child
, pFunctionListBox
, filterTerm
, pCurrentSaveInData
,
544 // Remove the group if empty
545 if (!pFunctionListBox
->iter_has_child(*xNewEntry
))
546 pFunctionListBox
->remove(*xNewEntry
);
548 rNodesToExpand
.emplace_back(std::move(xNewEntry
));
550 else if (child
->getType() == css::script::browse::BrowseNodeTypes::SCRIPT
)
552 // Prepare for filtering
553 OUString sUIName
= child
->getName();
554 sal_Int32 aStartPos
= 0;
555 sal_Int32 aEndPos
= sUIName
.getLength();
557 // Apply the search filter
558 if (!filterTerm
.isEmpty() && !textSearch
.SearchForward(sUIName
, &aStartPos
, &aEndPos
))
563 OUString uri
, description
;
565 css::uno::Reference
<css::beans::XPropertySet
> xPropSet(child
, css::uno::UNO_QUERY
);
572 css::uno::Any value
= xPropSet
->getPropertyValue(u
"URI"_ustr
);
577 value
= xPropSet
->getPropertyValue(u
"Description"_ustr
);
578 value
>>= description
;
580 catch (css::uno::Exception
&)
582 // do nothing, the description will be empty
585 if (description
.isEmpty())
587 description
= CuiResId(RID_CUISTR_NOMACRODESC
);
590 OUString
* pScriptURI
= new OUString(uri
);
592 css::uno::Reference
<css::graphic::XGraphic
> xImage
;
593 if (pCurrentSaveInData
)
594 xImage
= pCurrentSaveInData
->GetImage(uri
);
596 m_aGroupInfo
.push_back(
597 std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::FUNCTION_SCRIPT
, 0, pScriptURI
));
598 m_aGroupInfo
.back()->sCommand
= uri
;
599 m_aGroupInfo
.back()->sLabel
= sUIName
;
600 m_aGroupInfo
.back()->sHelpText
= description
;
601 pFunctionListBox
->append(weld::toId(m_aGroupInfo
.back().get()), sUIName
, xImage
,
607 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */