1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <commandpopup/CommandPopup.hxx>
12 #include <sfx2/msgpool.hxx>
13 #include <sfx2/bindings.hxx>
14 #include <sfx2/msg.hxx>
15 #include <sfx2/viewfrm.hxx>
17 #include <comphelper/processfactory.hxx>
18 #include <comphelper/dispatchcommand.hxx>
20 #include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
21 #include <com/sun/star/util/URL.hpp>
22 #include <com/sun/star/util/URLTransformer.hpp>
23 #include <com/sun/star/i18n/CharacterClassification.hpp>
25 #include <vcl/commandinfoprovider.hxx>
26 #include <vcl/event.hxx>
27 #include <vcl/svapp.hxx>
28 #include <i18nlangtag/languagetag.hxx>
32 MenuContentHandler::MenuContentHandler(uno::Reference
<frame::XFrame
> const& xFrame
)
33 : m_xContext(comphelper::getProcessComponentContext())
35 , m_xCharacterClassification(i18n::CharacterClassification::create(m_xContext
))
36 , m_xURLTransformer(util::URLTransformer::create(m_xContext
))
37 , m_sModuleLongName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame
))
39 uno::Reference
<ui::XModuleUIConfigurationManagerSupplier
> xModuleConfigSupplier
;
40 xModuleConfigSupplier
.set(ui::theModuleUIConfigurationManagerSupplier::get(m_xContext
));
42 uno::Reference
<ui::XUIConfigurationManager
> xConfigurationManager
;
43 xConfigurationManager
= xModuleConfigSupplier
->getUIConfigurationManager(m_sModuleLongName
);
45 uno::Reference
<container::XIndexAccess
> xConfigData
;
46 xConfigData
= xConfigurationManager
->getSettings("private:resource/menubar/menubar", false);
48 gatherMenuContent(xConfigData
, m_aMenuContent
);
51 void MenuContentHandler::gatherMenuContent(
52 uno::Reference
<container::XIndexAccess
> const& xIndexAccess
, MenuContent
& rMenuContent
)
54 std::u16string_view aMenuLabelSeparator
= AllSettings::GetLayoutRTL() ? u
" ◂ " : u
" ▸ ";
55 for (sal_Int32 n
= 0; n
< xIndexAccess
->getCount(); n
++)
57 MenuContent aNewContent
;
58 uno::Sequence
<beans::PropertyValue
> aProperties
;
59 uno::Reference
<container::XIndexAccess
> xIndexContainer
;
61 if (!(xIndexAccess
->getByIndex(n
) >>= aProperties
))
64 bool bIsVisible
= true;
65 bool bIsEnabled
= true;
67 for (auto const& rProperty
: std::as_const(aProperties
))
69 OUString aPropertyName
= rProperty
.Name
;
70 if (aPropertyName
== "CommandURL")
71 rProperty
.Value
>>= aNewContent
.m_aCommandURL
;
72 else if (aPropertyName
== "ItemDescriptorContainer")
73 rProperty
.Value
>>= xIndexContainer
;
74 else if (aPropertyName
== "IsVisible")
75 rProperty
.Value
>>= bIsVisible
;
76 else if (aPropertyName
== "Enabled")
77 rProperty
.Value
>>= bIsEnabled
;
80 if (!bIsEnabled
|| !bIsVisible
)
83 auto aCommandProperties
= vcl::CommandInfoProvider::GetCommandProperties(
84 aNewContent
.m_aCommandURL
, m_sModuleLongName
);
85 OUString aLabel
= vcl::CommandInfoProvider::GetLabelForCommand(aCommandProperties
);
86 aNewContent
.m_aMenuLabel
= aLabel
;
88 if (!rMenuContent
.m_aFullLabelWithPath
.isEmpty())
89 aNewContent
.m_aFullLabelWithPath
90 = rMenuContent
.m_aFullLabelWithPath
+ aMenuLabelSeparator
;
91 aNewContent
.m_aFullLabelWithPath
+= aNewContent
.m_aMenuLabel
;
92 aNewContent
.m_aSearchableMenuLabel
= toLower(aNewContent
.m_aFullLabelWithPath
);
94 aNewContent
.m_aTooltip
= vcl::CommandInfoProvider::GetTooltipForCommand(
95 aNewContent
.m_aCommandURL
, aCommandProperties
, m_xFrame
);
97 if (xIndexContainer
.is())
98 gatherMenuContent(xIndexContainer
, aNewContent
);
100 rMenuContent
.m_aSubMenuContent
.push_back(aNewContent
);
104 void MenuContentHandler::findInMenu(OUString
const& rText
,
105 std::unique_ptr
<weld::TreeView
>& rpCommandTreeView
,
106 std::vector
<CurrentEntry
>& rCommandList
)
110 OUString aLowerCaseText
= toLower(rText
);
112 // find submenus and menu items that start with the searched text
113 auto aTextStartCriterium
= [](MenuContent
const& rMenuContent
, OUString
const& rSearchText
) {
114 OUString aSearchText
= " / " + rSearchText
;
115 return rMenuContent
.m_aSearchableMenuLabel
.indexOf(aSearchText
) > 0;
118 findInMenuRecursive(m_aMenuContent
, aLowerCaseText
, rpCommandTreeView
, rCommandList
,
119 aTextStartCriterium
);
121 // find submenus and menu items that contain the searched text
122 auto aTextAllCriterium
= [](MenuContent
const& rMenuContent
, OUString
const& rSearchText
) {
123 return rMenuContent
.m_aSearchableMenuLabel
.indexOf(rSearchText
) > 0;
126 findInMenuRecursive(m_aMenuContent
, aLowerCaseText
, rpCommandTreeView
, rCommandList
,
130 void MenuContentHandler::findInMenuRecursive(
131 MenuContent
const& rMenuContent
, OUString
const& rText
,
132 std::unique_ptr
<weld::TreeView
>& rpCommandTreeView
, std::vector
<CurrentEntry
>& rCommandList
,
133 std::function
<bool(MenuContent
const&, OUString
const&)> const& rSearchCriterium
)
135 for (MenuContent
const& aSubContent
: rMenuContent
.m_aSubMenuContent
)
137 if (rSearchCriterium(aSubContent
, rText
))
139 addCommandIfPossible(aSubContent
, rpCommandTreeView
, rCommandList
);
141 findInMenuRecursive(aSubContent
, rText
, rpCommandTreeView
, rCommandList
, rSearchCriterium
);
145 void MenuContentHandler::addCommandIfPossible(
146 MenuContent
const& rMenuContent
, const std::unique_ptr
<weld::TreeView
>& rpCommandTreeView
,
147 std::vector
<CurrentEntry
>& rCommandList
)
149 if (m_aAdded
.find(rMenuContent
.m_aFullLabelWithPath
) != m_aAdded
.end())
152 OUString sCommandURL
= rMenuContent
.m_aCommandURL
;
153 util::URL aCommandURL
;
154 aCommandURL
.Complete
= sCommandURL
;
156 if (!m_xURLTransformer
->parseStrict(aCommandURL
))
159 auto* pViewFrame
= SfxViewFrame::Current();
163 SfxSlotPool
& rSlotPool
= SfxSlotPool::GetSlotPool(pViewFrame
);
164 const SfxSlot
* pSlot
= rSlotPool
.GetUnoSlot(aCommandURL
.Path
);
168 std::unique_ptr
<SfxPoolItem
> pState
;
169 SfxItemState eState
= pViewFrame
->GetBindings().QueryState(pSlot
->GetSlotId(), pState
);
170 if (eState
== SfxItemState::DISABLED
)
173 auto xGraphic
= vcl::CommandInfoProvider::GetXGraphicForCommand(sCommandURL
, m_xFrame
);
174 rCommandList
.emplace_back(sCommandURL
, rMenuContent
.m_aTooltip
);
176 auto pIter
= rpCommandTreeView
->make_iterator();
177 rpCommandTreeView
->insert(nullptr, -1, &rMenuContent
.m_aFullLabelWithPath
, nullptr, nullptr,
178 nullptr, false, pIter
.get());
179 rpCommandTreeView
->set_image(*pIter
, xGraphic
);
180 m_aAdded
.insert(rMenuContent
.m_aFullLabelWithPath
);
183 OUString
MenuContentHandler::toLower(OUString
const& rString
)
185 const css::lang::Locale
& rLocale
= Application::GetSettings().GetUILanguageTag().getLocale();
187 return m_xCharacterClassification
->toLower(rString
, 0, rString
.getLength(), rLocale
);
190 CommandListBox::CommandListBox(weld::Window
* pParent
, uno::Reference
<frame::XFrame
> const& xFrame
)
191 : mxBuilder(Application::CreateBuilder(pParent
, "sfx/ui/commandpopup.ui"))
192 , mxPopover(mxBuilder
->weld_popover("CommandPopup"))
193 , mpEntry(mxBuilder
->weld_entry("command_entry"))
194 , mpCommandTreeView(mxBuilder
->weld_tree_view("command_treeview"))
195 , mpMenuContentHandler(std::make_unique
<MenuContentHandler
>(xFrame
))
197 mpEntry
->connect_changed(LINK(this, CommandListBox
, ModifyHdl
));
198 mpEntry
->connect_key_press(LINK(this, CommandListBox
, TreeViewKeyPress
));
199 mpCommandTreeView
->connect_query_tooltip(LINK(this, CommandListBox
, QueryTooltip
));
200 mpCommandTreeView
->connect_row_activated(LINK(this, CommandListBox
, RowActivated
));
202 Size aFrameSize
= pParent
->get_size();
204 // Set size of the pop-over window
205 tools::Long nWidth
= std::max(tools::Long(400), aFrameSize
.Width() / 3);
206 mpCommandTreeView
->set_size_request(nWidth
, 400);
208 // Set the location of the pop-over window
209 tools::Rectangle
aRect(Point(aFrameSize
.Width() / 2, 0), Size(0, 0));
210 mxPopover
->popup_at_rect(pParent
, aRect
);
211 mpEntry
->grab_focus();
214 IMPL_LINK_NOARG(CommandListBox
, QueryTooltip
, const weld::TreeIter
&, OUString
)
216 size_t nSelected
= mpCommandTreeView
->get_selected_index();
217 if (nSelected
< maCommandList
.size())
219 auto const& rCurrent
= maCommandList
[nSelected
];
220 return rCurrent
.m_aTooltip
;
225 IMPL_LINK_NOARG(CommandListBox
, RowActivated
, weld::TreeView
&, bool)
227 OUString aCommandURL
;
228 int nSelected
= mpCommandTreeView
->get_selected_index();
229 if (nSelected
!= -1 && nSelected
< int(maCommandList
.size()))
231 auto const& rCurrent
= maCommandList
[nSelected
];
232 aCommandURL
= rCurrent
.m_aCommandURL
;
234 dispatchCommandAndClose(aCommandURL
);
238 IMPL_LINK(CommandListBox
, TreeViewKeyPress
, const KeyEvent
&, rKeyEvent
, bool)
240 if (rKeyEvent
.GetKeyCode().GetCode() == KEY_DOWN
|| rKeyEvent
.GetKeyCode().GetCode() == KEY_UP
)
242 int nDirection
= rKeyEvent
.GetKeyCode().GetCode() == KEY_DOWN
? 1 : -1;
243 int nNewIndex
= mpCommandTreeView
->get_selected_index() + nDirection
;
244 nNewIndex
= std::clamp(nNewIndex
, 0, mpCommandTreeView
->n_children() - 1);
245 mpCommandTreeView
->select(nNewIndex
);
246 mpCommandTreeView
->set_cursor(nNewIndex
);
249 else if (rKeyEvent
.GetKeyCode().GetCode() == KEY_RETURN
)
251 RowActivated(*mpCommandTreeView
);
258 IMPL_LINK_NOARG(CommandListBox
, ModifyHdl
, weld::Entry
&, void)
260 mpCommandTreeView
->clear();
261 maCommandList
.clear();
263 OUString sText
= mpEntry
->get_text();
267 mpCommandTreeView
->freeze();
268 mpMenuContentHandler
->findInMenu(sText
, mpCommandTreeView
, maCommandList
);
269 mpCommandTreeView
->thaw();
271 if (mpCommandTreeView
->n_children() > 0)
273 mpCommandTreeView
->set_cursor(0);
274 mpCommandTreeView
->select(0);
277 mpEntry
->grab_focus();
280 void CommandListBox::dispatchCommandAndClose(OUString
const& rCommand
)
282 mxPopover
->popdown();
284 if (!rCommand
.isEmpty())
285 comphelper::dispatchCommand(rCommand
, uno::Sequence
<beans::PropertyValue
>());
288 void CommandPopupHandler::showPopup(weld::Window
* pParent
,
289 css::uno::Reference
<css::frame::XFrame
> const& xFrame
)
291 auto pCommandListBox
= std::make_unique
<CommandListBox
>(pParent
, xFrame
);
292 pCommandListBox
->connect_closed(LINK(this, CommandPopupHandler
, PopupModeEnd
));
293 mpListBox
= std::move(pCommandListBox
);
296 IMPL_LINK_NOARG(CommandPopupHandler
, PopupModeEnd
, weld::Popover
&, void) { mpListBox
.reset(); }
298 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */