ladybird/UI/Qt/Menu.cpp
Timothy Flynn a5be0f0a18 LibWebView+UI: Add structures to hold context menu and action data
We currently duplicate a lot of code to handle application/context menus
and actions. The goal here is to hold the data for the menus and actions
in LibWebView. Each UI will then be able to generate menus from the data
on-the-fly.

The structures added here are meant to support generic and checkable
actions, action groups, submenus, etc.
2025-09-11 14:23:45 -04:00

130 lines
3.9 KiB
C++

/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <UI/Qt/Icon.h>
#include <UI/Qt/Menu.h>
#include <UI/Qt/StringUtils.h>
#include <UI/Qt/WebContentView.h>
#include <QAction>
#include <QMenu>
#include <QPointer>
#include <QWidget>
namespace Ladybird {
class ActionObserver final : public WebView::Action::Observer {
public:
static NonnullOwnPtr<ActionObserver> create(WebView::Action& action, QAction& qaction)
{
return adopt_own(*new ActionObserver(action, qaction));
}
virtual void on_text_changed(WebView::Action& action) override
{
if (m_action)
m_action->setText(qstring_from_ak_string(action.text()));
}
virtual void on_tooltip_changed(WebView::Action& action) override
{
if (m_action)
m_action->setToolTip(qstring_from_ak_string(action.tooltip()));
}
virtual void on_enabled_state_changed(WebView::Action& action) override
{
if (m_action)
m_action->setEnabled(action.enabled());
}
virtual void on_visible_state_changed(WebView::Action& action) override
{
if (m_action)
m_action->setVisible(action.visible());
}
virtual void on_checked_state_changed(WebView::Action& action) override
{
if (m_action)
m_action->setChecked(action.checked());
}
private:
ActionObserver(WebView::Action& action, QAction& qaction)
: m_action(&qaction)
{
QObject::connect(m_action, &QAction::triggered, [weak_action = action.make_weak_ptr()](bool checked) {
if (auto action = weak_action.strong_ref()) {
if (action->is_checkable())
action->set_checked(checked);
action->activate();
}
});
QObject::connect(m_action->parent(), &QObject::destroyed, [this, weak_action = action.make_weak_ptr()]() {
if (auto action = weak_action.strong_ref())
action->remove_observer(*this);
});
}
QPointer<QAction> m_action;
};
static void initialize_native_control(WebView::Action& action, QAction& qaction)
{
if (action.is_checkable())
qaction.setCheckable(true);
action.add_observer(ActionObserver::create(action, qaction));
}
static void add_items_to_menu(QMenu& menu, QWidget& parent, Span<WebView::Menu::MenuItem> menu_items)
{
for (auto& menu_item : menu_items) {
menu_item.visit(
[&](NonnullRefPtr<WebView::Action>& action) {
auto* qaction = create_application_action(parent, action);
menu.addAction(qaction);
},
[&](NonnullRefPtr<WebView::Menu> const& submenu) {
auto* qsubmenu = new QMenu(qstring_from_ak_string(submenu->title()), &menu);
add_items_to_menu(*qsubmenu, parent, submenu->items());
menu.addMenu(qsubmenu);
},
[&](WebView::Separator) {
menu.addSeparator();
});
}
}
QMenu* create_application_menu(QWidget& parent, WebView::Menu& menu)
{
auto* application_menu = new QMenu(qstring_from_ak_string(menu.title()), &parent);
add_items_to_menu(*application_menu, parent, menu.items());
return application_menu;
}
QMenu* create_context_menu(QWidget& parent, WebContentView& view, WebView::Menu& menu)
{
auto* application_menu = create_application_menu(parent, menu);
menu.on_activation = [view = QPointer { &view }, application_menu = QPointer { application_menu }](Gfx::IntPoint position) {
if (view && application_menu)
application_menu->exec(view->map_point_to_global_position(position));
};
return application_menu;
}
QAction* create_application_action(QWidget& parent, WebView::Action& action)
{
auto* qaction = new QAction(&parent);
initialize_native_control(action, *qaction);
return qaction;
}
}