Files
loongoffice/sfx2/source/commandpopup/CommandPopup.cxx
Tomaž Vajngerl 83d91ebbbd tdf#91874 Command Popup - HUD to search and run LO commands
This adds Command Popup functionality, which is a HUD like pop-up
window, which can be used to search and run commands presented in
the main menu (but not limited to that only).
This is the initial version, which has limitation in searching
and running the command (doesn't work for some currently).

Change-Id: I92cdd3130b8de42ee0863c9e7154e7c7246d9377
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115380
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
2021-05-11 16:08:36 +02:00

259 lines
9.8 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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/.
*/
#include <commandpopup/CommandPopup.hxx>
#include <workwin.hxx>
#include <sfx2/msgpool.hxx>
#include <sfx2/bindings.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/dispatchcommand.hxx>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/frame/theUICommandDescription.hpp>
#include <com/sun/star/ui/theUICategoryDescription.hpp>
#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/util/URL.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <vcl/commandinfoprovider.hxx>
using namespace css;
MenuContentHandler::MenuContentHandler(uno::Reference<frame::XFrame> const& xFrame)
: m_xFrame(xFrame)
, m_sModuleLongName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame))
{
auto xComponentContext = comphelper::getProcessComponentContext();
uno::Reference<ui::XModuleUIConfigurationManagerSupplier> xModuleConfigSupplier;
xModuleConfigSupplier.set(ui::theModuleUIConfigurationManagerSupplier::get(xComponentContext));
uno::Reference<ui::XUIConfigurationManager> xConfigurationManager;
xConfigurationManager = xModuleConfigSupplier->getUIConfigurationManager(m_sModuleLongName);
uno::Reference<container::XIndexAccess> xConfigData;
xConfigData = xConfigurationManager->getSettings("private:resource/menubar/menubar", false);
gatherMenuContent(xConfigData, m_aMenuContent);
}
void MenuContentHandler::gatherMenuContent(
uno::Reference<container::XIndexAccess> const& xIndexAccess, MenuContent& rMenuContent)
{
for (sal_Int32 n = 0; n < xIndexAccess->getCount(); n++)
{
MenuContent aNewContent;
uno::Sequence<beans::PropertyValue> aProperties;
uno::Reference<container::XIndexAccess> xIndexContainer;
if (!(xIndexAccess->getByIndex(n) >>= aProperties))
continue;
bool bIsVisible = true;
bool bIsEnabled = true;
for (auto const& rProperty : std::as_const(aProperties))
{
OUString aPropertyName = rProperty.Name;
if (aPropertyName == "CommandURL")
rProperty.Value >>= aNewContent.m_aCommandURL;
else if (aPropertyName == "ItemDescriptorContainer")
rProperty.Value >>= xIndexContainer;
else if (aPropertyName == "IsVisible")
rProperty.Value >>= bIsVisible;
else if (aPropertyName == "Enabled")
rProperty.Value >>= bIsEnabled;
}
if (!bIsEnabled || !bIsVisible)
continue;
auto aCommandProperties = vcl::CommandInfoProvider::GetCommandProperties(
aNewContent.m_aCommandURL, m_sModuleLongName);
OUString aLabel = vcl::CommandInfoProvider::GetLabelForCommand(aCommandProperties);
aNewContent.m_aMenuLabel = aLabel;
if (!rMenuContent.m_aFullLabelWithPath.isEmpty())
aNewContent.m_aFullLabelWithPath = rMenuContent.m_aFullLabelWithPath + " / ";
aNewContent.m_aFullLabelWithPath += aNewContent.m_aMenuLabel;
aNewContent.m_aTooltip = vcl::CommandInfoProvider::GetTooltipForCommand(
aNewContent.m_aCommandURL, aCommandProperties, m_xFrame);
if (xIndexContainer.is())
gatherMenuContent(xIndexContainer, aNewContent);
rMenuContent.m_aSubMenuContent.push_back(aNewContent);
}
}
void MenuContentHandler::findInMenu(OUString const& rText,
std::unique_ptr<weld::TreeView>& rpCommandTreeView,
std::vector<CurrentEntry>& rCommandList)
{
findInMenuRecursive(m_aMenuContent, rText, rpCommandTreeView, rCommandList);
}
void MenuContentHandler::findInMenuRecursive(MenuContent const& rMenuContent, OUString const& rText,
std::unique_ptr<weld::TreeView>& rpCommandTreeView,
std::vector<CurrentEntry>& rCommandList)
{
for (MenuContent const& aSubContent : rMenuContent.m_aSubMenuContent)
{
if (aSubContent.m_aMenuLabel.toAsciiLowerCase().startsWith(rText))
{
OUString sCommandURL = aSubContent.m_aCommandURL;
util::URL aCommandURL;
aCommandURL.Complete = sCommandURL;
uno::Reference<uno::XComponentContext> xContext
= comphelper::getProcessComponentContext();
uno::Reference<util::XURLTransformer> xParser = util::URLTransformer::create(xContext);
xParser->parseStrict(aCommandURL);
auto* pViewFrame = SfxViewFrame::Current();
SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(pViewFrame);
const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path);
if (pSlot)
{
std::unique_ptr<SfxPoolItem> pState;
SfxItemState eState
= pViewFrame->GetBindings().QueryState(pSlot->GetSlotId(), pState);
if (eState != SfxItemState::DISABLED)
{
auto xGraphic
= vcl::CommandInfoProvider::GetXGraphicForCommand(sCommandURL, m_xFrame);
rCommandList.emplace_back(sCommandURL, aSubContent.m_aTooltip);
auto pIter = rpCommandTreeView->make_iterator();
rpCommandTreeView->insert(nullptr, -1, &aSubContent.m_aFullLabelWithPath,
nullptr, nullptr, nullptr, false, pIter.get());
rpCommandTreeView->set_image(*pIter, xGraphic);
}
}
}
findInMenuRecursive(aSubContent, rText, rpCommandTreeView, rCommandList);
}
}
CommandListBox::CommandListBox(weld::Window* pParent, uno::Reference<frame::XFrame> const& xFrame)
: mxBuilder(Application::CreateBuilder(pParent, "sfx/ui/commandpopup.ui"))
, mxPopover(mxBuilder->weld_popover("CommandPopup"))
, mpEntry(mxBuilder->weld_entry("command_entry"))
, mpCommandTreeView(mxBuilder->weld_tree_view("command_treeview"))
, mpMenuContentHandler(std::make_unique<MenuContentHandler>(xFrame))
{
mpEntry->connect_changed(LINK(this, CommandListBox, ModifyHdl));
mpEntry->connect_key_press(LINK(this, CommandListBox, TreeViewKeyPress));
mpCommandTreeView->connect_query_tooltip(LINK(this, CommandListBox, QueryTooltip));
mpCommandTreeView->connect_row_activated(LINK(this, CommandListBox, RowActivated));
Size aFrameSize = pParent->get_size();
// Set size of the pop-over window
tools::Long nWidth = std::max(tools::Long(400), aFrameSize.Width() / 3);
mpCommandTreeView->set_size_request(nWidth, 400);
// Set the location of the pop-over window
tools::Rectangle aRect(Point(aFrameSize.Width() / 2, 0), Size(0, 0));
mxPopover->popup_at_rect(pParent, aRect);
mpEntry->grab_focus();
}
IMPL_LINK_NOARG(CommandListBox, QueryTooltip, const weld::TreeIter&, OUString)
{
size_t nSelected = mpCommandTreeView->get_selected_index();
if (nSelected < maCommandList.size())
{
auto const& rCurrent = maCommandList[nSelected];
return rCurrent.m_aTooltip;
}
return OUString();
}
IMPL_LINK_NOARG(CommandListBox, RowActivated, weld::TreeView&, bool)
{
OUString aCommandURL;
int nSelected = mpCommandTreeView->get_selected_index();
if (nSelected < int(maCommandList.size()))
{
auto const& rCurrent = maCommandList[nSelected];
aCommandURL = rCurrent.m_aCommandURL;
}
dispatchCommandAndClose(aCommandURL);
return true;
}
IMPL_LINK(CommandListBox, TreeViewKeyPress, const KeyEvent&, rKeyEvent, bool)
{
if (rKeyEvent.GetKeyCode().GetCode() == KEY_DOWN || rKeyEvent.GetKeyCode().GetCode() == KEY_UP)
{
int nDirection = rKeyEvent.GetKeyCode().GetCode() == KEY_DOWN ? 1 : -1;
int nNewIndex = mpCommandTreeView->get_selected_index() + nDirection;
nNewIndex = std::clamp(nNewIndex, 0, mpCommandTreeView->n_children() - 1);
mpCommandTreeView->select(nNewIndex);
mpCommandTreeView->set_cursor(nNewIndex);
return true;
}
else if (rKeyEvent.GetKeyCode().GetCode() == KEY_RETURN)
{
RowActivated(*mpCommandTreeView);
}
return false;
}
IMPL_LINK_NOARG(CommandListBox, ModifyHdl, weld::Entry&, void)
{
mpCommandTreeView->clear();
maCommandList.clear();
OUString sText = mpEntry->get_text();
if (sText.isEmpty())
return;
mpCommandTreeView->freeze();
mpMenuContentHandler->findInMenu(sText.toAsciiLowerCase(), mpCommandTreeView, maCommandList);
mpCommandTreeView->thaw();
if (mpCommandTreeView->n_children() > 0)
{
mpCommandTreeView->set_cursor(0);
mpCommandTreeView->select(0);
}
mpEntry->grab_focus();
}
void CommandListBox::dispatchCommandAndClose(OUString const& rCommand)
{
mxPopover->popdown();
if (!rCommand.isEmpty())
comphelper::dispatchCommand(rCommand, uno::Sequence<beans::PropertyValue>());
}
void CommandPopupHandler::showPopup(weld::Window* pParent,
css::uno::Reference<css::frame::XFrame> const& xFrame)
{
auto pCommandListBox = std::make_unique<CommandListBox>(pParent, xFrame);
pCommandListBox->connect_closed(LINK(this, CommandPopupHandler, PopupModeEnd));
mpListBox = std::move(pCommandListBox);
}
IMPL_LINK_NOARG(CommandPopupHandler, PopupModeEnd, weld::Popover&, void) { mpListBox.reset(); }
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */