tdf#142524 Search Commands: use ▸ to indicate sequence
[LibreOffice.git] / sfx2 / source / commandpopup / CommandPopup.cxx
blob0d948a4e84e66465c222c34e714a2027e5c0a130
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
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>
30 using namespace css;
32 MenuContentHandler::MenuContentHandler(uno::Reference<frame::XFrame> const& xFrame)
33 : m_xContext(comphelper::getProcessComponentContext())
34 , m_xFrame(xFrame)
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))
62 continue;
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)
81 continue;
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)
108 m_aAdded.clear();
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,
127 aTextAllCriterium);
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())
150 return;
152 OUString sCommandURL = rMenuContent.m_aCommandURL;
153 util::URL aCommandURL;
154 aCommandURL.Complete = sCommandURL;
156 if (!m_xURLTransformer->parseStrict(aCommandURL))
157 return;
159 auto* pViewFrame = SfxViewFrame::Current();
160 if (!pViewFrame)
161 return;
163 SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(pViewFrame);
164 const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path);
165 if (!pSlot)
166 return;
168 std::unique_ptr<SfxPoolItem> pState;
169 SfxItemState eState = pViewFrame->GetBindings().QueryState(pSlot->GetSlotId(), pState);
170 if (eState == SfxItemState::DISABLED)
171 return;
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;
222 return OUString();
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);
235 return true;
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);
247 return true;
249 else if (rKeyEvent.GetKeyCode().GetCode() == KEY_RETURN)
251 RowActivated(*mpCommandTreeView);
252 return true;
255 return false;
258 IMPL_LINK_NOARG(CommandListBox, ModifyHdl, weld::Entry&, void)
260 mpCommandTreeView->clear();
261 maCommandList.clear();
263 OUString sText = mpEntry->get_text();
264 if (sText.isEmpty())
265 return;
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: */