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 <tools/diagnose_ex.h>
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 <i18nlangtag/languagetag.hxx>
48 #include <i18nutil/searchopt.hxx>
49 #include <sal/log.hxx>
51 #include <cfg.hxx> //for SaveInData
53 CommandCategoryListBox::CommandCategoryListBox(std::unique_ptr
<weld::ComboBox
> xControl
)
54 : pStylesInfo( nullptr )
55 , m_xControl(std::move(xControl
))
57 //Initialize search util
58 m_searchOptions
.AlgorithmType2
= css::util::SearchAlgorithms2::ABSOLUTE
;
59 m_searchOptions
.transliterateFlags
|= TransliterationFlags::IGNORE_CASE
;
60 m_searchOptions
.searchFlag
|= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
61 | css::util::SearchFlags::REG_NOT_ENDOFLINE
);
64 CommandCategoryListBox::~CommandCategoryListBox()
69 void CommandCategoryListBox::ClearAll()
71 // Clear objects from m_aGroupInfo vector to avoid memory leak
72 for (const auto & It
: m_aGroupInfo
)
74 if ( It
->nKind
== SfxCfgKind::GROUP_STYLES
&& It
->pObject
)
76 SfxStyleInfo_Impl
* pStyle
= static_cast<SfxStyleInfo_Impl
*>(It
->pObject
);
79 else if ( It
->nKind
== SfxCfgKind::FUNCTION_SCRIPT
&& It
->pObject
)
81 OUString
* pScriptURI
= static_cast<OUString
*>(It
->pObject
);
84 else if ( It
->nKind
== SfxCfgKind::GROUP_SCRIPTCONTAINER
&& It
->pObject
)
86 css::uno::XInterface
* xi
= static_cast<css::uno::XInterface
*>(It
->pObject
);
98 void CommandCategoryListBox::Init(
99 const css::uno::Reference
< css::uno::XComponentContext
>& xContext
,
100 const css::uno::Reference
< css::frame::XFrame
>& xFrame
,
101 const OUString
& sModuleLongName
)
103 // User will not see incomplete UI
104 m_xControl
->freeze();
107 m_xContext
= xContext
;
110 m_sModuleLongName
= sModuleLongName
;
111 m_xGlobalCategoryInfo
= css::ui::theUICategoryDescription::get( m_xContext
);
112 m_xModuleCategoryInfo
.set(m_xGlobalCategoryInfo
->getByName(m_sModuleLongName
), css::uno::UNO_QUERY_THROW
);
113 m_xUICmdDescription
= css::frame::theUICommandDescription::get( m_xContext
);
115 // Support style commands
116 css::uno::Reference
<css::frame::XController
> xController
;
117 css::uno::Reference
<css::frame::XModel
> xModel
;
119 xController
= xFrame
->getController();
120 if (xController
.is())
121 xModel
= xController
->getModel();
123 m_aStylesInfo
.init(sModuleLongName
, xModel
);
124 SetStylesInfo(&m_aStylesInfo
);
128 css::uno::Reference
< css::frame::XDispatchInformationProvider
> xProvider(m_xFrame
, css::uno::UNO_QUERY_THROW
);
129 css::uno::Sequence
< sal_Int16
> lGroups
= xProvider
->getSupportedCommandGroups();
131 sal_Int32 nGroupsLength
= lGroups
.getLength();
133 if ( nGroupsLength
> 0 )
135 // Add the category of "All commands"
136 m_aGroupInfo
.push_back( std::make_unique
<SfxGroupInfo_Impl
>( SfxCfgKind::GROUP_ALLFUNCTIONS
, 0 ) );
137 m_xControl
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), CuiResId(RID_SVXSTR_ALLFUNCTIONS
));
140 // Separate the "All commands"category from the actual categories
141 m_xControl
->append_separator("");
143 typedef std::pair
<OUString
, sal_Int16
> str_id
;
144 std::vector
<str_id
> aCategories
;
146 // Add the actual categories
147 for (sal_Int32 i
= 0; i
< nGroupsLength
; ++i
)
149 sal_Int16 nGroupID
= lGroups
[i
];
150 OUString sGroupID
= OUString::number(nGroupID
);
155 m_xModuleCategoryInfo
->getByName(sGroupID
) >>= sGroupName
;
156 if (sGroupName
.isEmpty())
159 catch(const css::container::NoSuchElementException
&)
163 aCategories
.emplace_back(std::make_pair(sGroupName
, nGroupID
));
166 auto const sort
= comphelper::string::NaturalStringSorter(
167 comphelper::getProcessComponentContext(),
168 Application::GetSettings().GetUILanguageTag().getLocale());
170 std::sort(aCategories
.begin(), aCategories
.end(),
171 [&sort
](const str_id
& a
, const str_id
& b
)
172 { return sort
.compare(a
.first
, b
.first
) < 0; });
174 // Add the actual categories
175 for (const auto &a
: aCategories
)
177 const OUString
& rGroupName
= a
.first
;
178 sal_Int16 nGroupID
= a
.second
;
179 m_aGroupInfo
.push_back(std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_FUNCTION
, nGroupID
));
180 m_xControl
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), rGroupName
);
183 // Separate regular commands from styles and macros
184 m_xControl
->append_separator("");
186 // Add macros category
187 m_aGroupInfo
.push_back(
188 std::make_unique
<SfxGroupInfo_Impl
>( SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0, nullptr) );
189 m_xControl
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), CuiResId(RID_SVXSTR_MACROS
));
191 // Add styles category
192 //TODO: last param should contain user data?
193 m_aGroupInfo
.push_back(
194 std::make_unique
<SfxGroupInfo_Impl
>( SfxCfgKind::GROUP_STYLES
, 0, nullptr ) );
195 m_xControl
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), CuiResId(RID_SVXSTR_GROUP_STYLES
));
197 catch(const css::uno::RuntimeException
&)
199 catch(const css::uno::Exception
&)
202 // Reveal the updated UI to user
204 m_xControl
->set_active(0);
207 void CommandCategoryListBox::FillFunctionsList(
208 const css::uno::Sequence
<css::frame::DispatchInformation
>& xCommands
,
209 CuiConfigFunctionListBox
* pFunctionListBox
,
210 const OUString
& filterTerm
,
211 SaveInData
*pCurrentSaveInData
)
213 // Setup search filter parameters
214 m_searchOptions
.searchString
= filterTerm
;
215 utl::TextSearch
textSearch( m_searchOptions
);
217 for (const auto & rInfo
: xCommands
)
219 OUString sUIName
= getCommandName(rInfo
.Command
);
220 OUString sLabel
= vcl::CommandInfoProvider::GetLabelForCommand(rInfo
.Command
, m_sModuleLongName
);
221 OUString sTooltipLabel
= vcl::CommandInfoProvider::GetTooltipForCommand( rInfo
.Command
, m_xFrame
);
222 OUString sPopupLabel
=
223 (vcl::CommandInfoProvider::GetPopupLabelForCommand(rInfo
.Command
, m_sModuleLongName
))
224 .replaceFirst("~", "");
226 // Apply the search filter
227 if (!filterTerm
.isEmpty()
228 && !textSearch
.searchForward( sUIName
)
229 && !textSearch
.searchForward( sLabel
)
230 && !textSearch
.searchForward( sTooltipLabel
)
231 && !textSearch
.searchForward( sPopupLabel
) )
236 css::uno::Reference
<css::graphic::XGraphic
> xImage
;
237 if (pCurrentSaveInData
)
238 xImage
= pCurrentSaveInData
->GetImage(rInfo
.Command
);
240 m_aGroupInfo
.push_back( std::make_unique
<SfxGroupInfo_Impl
>( SfxCfgKind::FUNCTION_SLOT
, 0 ) );
241 SfxGroupInfo_Impl
* pGrpInfo
= m_aGroupInfo
.back().get();
242 pGrpInfo
->sCommand
= rInfo
.Command
;
243 pGrpInfo
->sLabel
= sUIName
;
244 pFunctionListBox
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), sUIName
, xImage
);
248 OUString
CommandCategoryListBox::getCommandName(const OUString
& sCommand
)
253 css::uno::Reference
< css::container::XNameAccess
> xModuleConf
;
254 m_xUICmdDescription
->getByName(m_sModuleLongName
) >>= xModuleConf
;
255 if (xModuleConf
.is())
257 ::comphelper::SequenceAsHashMap
lProps(xModuleConf
->getByName(sCommand
));
258 sUIName
= lProps
.getUnpackedValueOrDefault("Name", OUString());
261 catch(const css::uno::RuntimeException
&)
263 catch(css::uno::Exception
&)
266 // fallback for missing UINames !?
267 if (sUIName
.isEmpty())
275 void CommandCategoryListBox::categorySelected(CuiConfigFunctionListBox
* pFunctionListBox
,
276 const OUString
& filterTerm
, SaveInData
*pCurrentSaveInData
)
278 SfxGroupInfo_Impl
*pInfo
= reinterpret_cast<SfxGroupInfo_Impl
*>(m_xControl
->get_active_id().toInt64());
279 std::vector
<std::unique_ptr
<weld::TreeIter
>> aNodesToExpand
;
280 pFunctionListBox
->freeze();
281 pFunctionListBox
->ClearAll();
283 switch ( pInfo
->nKind
)
285 case SfxCfgKind::GROUP_ALLFUNCTIONS
:
287 css::uno::Reference
< css::frame::XDispatchInformationProvider
>
288 xProvider( m_xFrame
, css::uno::UNO_QUERY
);
289 sal_Int32 nEntryCount
= m_xControl
->get_count();
291 for (sal_Int32 nCurPos
= 0; nCurPos
< nEntryCount
; ++nCurPos
)
293 SfxGroupInfo_Impl
*pCurrentInfo
=
294 reinterpret_cast<SfxGroupInfo_Impl
*>(m_xControl
->get_id(nCurPos
).toInt64());
296 if (!pCurrentInfo
) //separator
299 if (pCurrentInfo
->nKind
== SfxCfgKind::GROUP_FUNCTION
)
301 css::uno::Sequence
< css::frame::DispatchInformation
> lCommands
;
304 lCommands
= xProvider
->getConfigurableDispatchInformation(
305 pCurrentInfo
->nUniqueID
);
306 FillFunctionsList( lCommands
, pFunctionListBox
, filterTerm
, pCurrentSaveInData
);
308 catch( css::container::NoSuchElementException
& )
316 case SfxCfgKind::GROUP_FUNCTION
:
318 sal_uInt16 nGroup
= pInfo
->nUniqueID
;
319 css::uno::Reference
< css::frame::XDispatchInformationProvider
>
320 xProvider (m_xFrame
, css::uno::UNO_QUERY_THROW
);
321 css::uno::Sequence
< css::frame::DispatchInformation
> lCommands
=
322 xProvider
->getConfigurableDispatchInformation(nGroup
);
323 FillFunctionsList( lCommands
, pFunctionListBox
, filterTerm
, pCurrentSaveInData
);
326 case SfxCfgKind::GROUP_SCRIPTCONTAINER
: //Macros
328 SAL_INFO("cui.customize", "** ** About to initialise SF Scripts");
329 // Add Scripting Framework entries
330 css::uno::Reference
< css::script::browse::XBrowseNode
> rootNode
;
333 css::uno::Reference
< css::script::browse::XBrowseNodeFactory
> xFac
334 = css::script::browse::theBrowseNodeFactory::get( m_xContext
);
335 rootNode
.set( xFac
->createView( css::script::browse::BrowseNodeFactoryViewTypes::MACROSELECTOR
) );
337 catch( css::uno::Exception
const & )
339 TOOLS_WARN_EXCEPTION("cui.customize", "Caught some exception whilst retrieving browse nodes from factory");
340 // TODO exception handling
343 if ( rootNode
.is() && rootNode
->hasChildNodes() )
345 //We call acquire on the XBrowseNode so that it does not
346 //get autodestructed and become invalid when accessed later.
349 m_aGroupInfo
.push_back(
350 std::make_unique
<SfxGroupInfo_Impl
>(
351 SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0, static_cast<void *>(rootNode
.get()) ) );
353 // Add main macro groups
354 const css::uno::Sequence
<css::uno::Reference
<css::script::browse::XBrowseNode
>> aChildNodes
= rootNode
->getChildNodes();
355 for ( auto const & childGroup
: aChildNodes
)
358 childGroup
->acquire();
360 if ( childGroup
->hasChildNodes() )
362 if ( childGroup
->getName() == "user" )
364 sUIName
= CuiResId( RID_SVXSTR_MYMACROS
);
366 else if ( childGroup
->getName() == "share" )
368 sUIName
= CuiResId( RID_SVXSTR_PRODMACROS
);
372 sUIName
= childGroup
->getName();
375 if (sUIName
.isEmpty())
380 m_aGroupInfo
.push_back(
381 std::make_unique
<SfxGroupInfo_Impl
>(
382 SfxCfgKind::GROUP_SCRIPTCONTAINER
, 0 ) );
383 std::unique_ptr
<weld::TreeIter
> xMacroGroup(pFunctionListBox
->tree_append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), sUIName
));
386 // tdf#128010: Do not nag user asking to enable JRE: if it's disabled,
387 // simply don't show relevant entries (user chose to not use JRE)
388 css::uno::ContextLayer
layer(
389 comphelper::NoEnableJavaInteractionContext());
390 //Add the children and the grand children
391 addChildren(xMacroGroup
.get(), childGroup
, pFunctionListBox
, filterTerm
,
392 pCurrentSaveInData
, aNodesToExpand
);
395 // Remove the main group if empty
396 if (!pFunctionListBox
->iter_has_child(*xMacroGroup
))
398 pFunctionListBox
->remove(*xMacroGroup
);
400 else if (!filterTerm
.isEmpty())
402 aNodesToExpand
.emplace_back(std::move(xMacroGroup
));
410 case SfxCfgKind::GROUP_STYLES
:
412 const std::vector
< SfxStyleInfo_Impl
> lStyleFamilies
= pStylesInfo
->getStyleFamilies();
414 for ( const auto & pIt
: lStyleFamilies
)
416 if ( pIt
.sLabel
.isEmpty() )
422 m_aGroupInfo
.push_back( std::make_unique
<SfxGroupInfo_Impl
>( SfxCfgKind::GROUP_STYLES
, 0 ) );
423 // pIt.sLabel is Name of the style family
424 std::unique_ptr
<weld::TreeIter
> xFuncEntry(pFunctionListBox
->tree_append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), pIt
.sLabel
));
426 const std::vector
< SfxStyleInfo_Impl
> lStyles
= pStylesInfo
->getStyles(pIt
.sFamily
);
428 // Setup search filter parameters
429 m_searchOptions
.searchString
= filterTerm
;
430 utl::TextSearch
textSearch( m_searchOptions
);
432 // Insert children (styles)
433 for ( const auto & pStyleIt
: lStyles
)
435 OUString sUIName
= pStyleIt
.sLabel
;
436 sal_Int32 aStartPos
= 0;
437 sal_Int32 aEndPos
= sUIName
.getLength();
439 // Apply the search filter
440 if (!filterTerm
.isEmpty()
441 && !textSearch
.SearchForward( sUIName
, &aStartPos
, &aEndPos
) )
446 SfxStyleInfo_Impl
* pStyle
= new SfxStyleInfo_Impl(pStyleIt
);
448 m_aGroupInfo
.push_back(
449 std::make_unique
<SfxGroupInfo_Impl
>(
450 SfxCfgKind::GROUP_STYLES
, 0, pStyle
) );
452 m_aGroupInfo
.back()->sCommand
= pStyle
->sCommand
;
453 m_aGroupInfo
.back()->sLabel
= pStyle
->sLabel
;
455 pFunctionListBox
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), sUIName
, xFuncEntry
.get());
458 // Remove the style group from the list if no children
459 if (!pFunctionListBox
->iter_has_child(*xFuncEntry
))
461 pFunctionListBox
->remove(*xFuncEntry
);
463 else if (!filterTerm
.isEmpty())
465 aNodesToExpand
.emplace_back(std::move(xFuncEntry
));
472 // Do nothing, the list box will stay empty
473 SAL_INFO( "cui.customize", "Ignoring unexpected SfxCfgKind: " << static_cast<int>(pInfo
->nKind
) );
477 pFunctionListBox
->thaw();
479 if (pFunctionListBox
->n_children())
480 pFunctionListBox
->select(0);
483 for (const auto& it
: aNodesToExpand
)
484 pFunctionListBox
->expand_row(*it
);
487 void CommandCategoryListBox::SetStylesInfo(SfxStylesInfo_Impl
* pStyles
)
489 pStylesInfo
= pStyles
;
492 void CommandCategoryListBox::addChildren(
493 const weld::TreeIter
* parentEntry
, const css::uno::Reference
< css::script::browse::XBrowseNode
> &parentNode
,
494 CuiConfigFunctionListBox
* pFunctionListBox
, const OUString
& filterTerm
, SaveInData
*pCurrentSaveInData
,
495 std::vector
<std::unique_ptr
<weld::TreeIter
>> &rNodesToExpand
)
497 // Setup search filter parameters
498 m_searchOptions
.searchString
= filterTerm
;
499 utl::TextSearch
textSearch( m_searchOptions
);
501 const css::uno::Sequence
<css::uno::Reference
<css::script::browse::XBrowseNode
>> aChildNodes
= parentNode
->getChildNodes();
502 for (auto const & child
: aChildNodes
)
504 // Acquire to prevent auto-destruction
507 if (child
->hasChildNodes())
509 OUString sUIName
= child
->getName();
511 m_aGroupInfo
.push_back( std::make_unique
<SfxGroupInfo_Impl
>(SfxCfgKind::GROUP_SCRIPTCONTAINER
,
512 0, static_cast<void *>( child
.get())));
513 std::unique_ptr
<weld::TreeIter
> xNewEntry(pFunctionListBox
->tree_append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), sUIName
, parentEntry
));
515 addChildren(xNewEntry
.get(), child
, pFunctionListBox
, filterTerm
, pCurrentSaveInData
, rNodesToExpand
);
517 // Remove the group if empty
518 if (!pFunctionListBox
->iter_has_child(*xNewEntry
))
519 pFunctionListBox
->remove(*xNewEntry
);
521 rNodesToExpand
.emplace_back(std::move(xNewEntry
));
523 else if ( child
->getType() == css::script::browse::BrowseNodeTypes::SCRIPT
)
525 // Prepare for filtering
526 OUString sUIName
= child
->getName();
527 sal_Int32 aStartPos
= 0;
528 sal_Int32 aEndPos
= sUIName
.getLength();
530 // Apply the search filter
531 if (!filterTerm
.isEmpty()
532 && !textSearch
.SearchForward( sUIName
, &aStartPos
, &aEndPos
) )
537 OUString uri
, description
;
539 css::uno::Reference
< css::beans::XPropertySet
>xPropSet( child
, css::uno::UNO_QUERY
);
546 css::uno::Any value
= xPropSet
->getPropertyValue("URI");
551 value
= xPropSet
->getPropertyValue("Description");
552 value
>>= description
;
554 catch (css::uno::Exception
&) {
555 // do nothing, the description will be empty
558 if (description
.isEmpty())
560 description
= CuiResId( RID_SVXSTR_NOMACRODESC
);
563 OUString
* pScriptURI
= new OUString( uri
);
565 css::uno::Reference
<css::graphic::XGraphic
> xImage
;
566 if (pCurrentSaveInData
)
567 xImage
= pCurrentSaveInData
->GetImage(uri
);
569 m_aGroupInfo
.push_back( std::make_unique
<SfxGroupInfo_Impl
>( SfxCfgKind::FUNCTION_SCRIPT
, 0, pScriptURI
));
570 m_aGroupInfo
.back()->sCommand
= uri
;
571 m_aGroupInfo
.back()->sLabel
= sUIName
;
572 m_aGroupInfo
.back()->sHelpText
= description
;
573 pFunctionListBox
->append(OUString::number(reinterpret_cast<sal_Int64
>(m_aGroupInfo
.back().get())), sUIName
, xImage
, parentEntry
);
578 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */