Skip to content

Commit bbada34

Browse files
committed
Add GTK event emission for menu and item actions
Introduces GTK signal handlers to emit events for menu item activation, menu open/close, and submenu open/close. Connects these handlers to the relevant GTK signals in Menu and MenuItem constructors and submenu assignment. Also updates menu popup logic to position menus at explicit coordinates when available.
1 parent cd6f466 commit bbada34

File tree

1 file changed

+118
-1
lines changed

1 file changed

+118
-1
lines changed

src/platform/linux/menu_linux.cpp

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <vector>
1111
#include "../../image.h"
1212
#include "../../menu.h"
13+
#include "../../menu_event.h"
1314

1415
namespace nativeapi {
1516

@@ -21,6 +22,63 @@ static std::atomic<MenuId> g_next_menu_id{1};
2122
static std::unordered_map<MenuItemId, MenuItem*> g_menu_item_registry;
2223
static std::unordered_map<MenuId, Menu*> g_menu_registry;
2324

25+
// GTK signal handlers → Event emission
26+
static void OnGtkMenuItemActivate(GtkMenuItem* /*item*/, gpointer user_data) {
27+
MenuItemId item_id = static_cast<MenuItemId>(GPOINTER_TO_SIZE(user_data));
28+
auto it = g_menu_item_registry.find(item_id);
29+
if (it == g_menu_item_registry.end()) {
30+
return;
31+
}
32+
MenuItem* menu_item = it->second;
33+
std::string text = "";
34+
if (auto label = menu_item->GetLabel(); label.has_value()) {
35+
text = *label;
36+
}
37+
menu_item->EmitSync(MenuItemClickedEvent(item_id, text));
38+
}
39+
40+
static void OnGtkMenuShow(GtkWidget* /*menu*/, gpointer user_data) {
41+
MenuId menu_id = static_cast<MenuId>(GPOINTER_TO_SIZE(user_data));
42+
auto it = g_menu_registry.find(menu_id);
43+
if (it == g_menu_registry.end()) {
44+
return;
45+
}
46+
Menu* menu_obj = it->second;
47+
menu_obj->EmitSync(MenuOpenedEvent(menu_id));
48+
}
49+
50+
static void OnGtkMenuHide(GtkWidget* /*menu*/, gpointer user_data) {
51+
MenuId menu_id = static_cast<MenuId>(GPOINTER_TO_SIZE(user_data));
52+
auto it = g_menu_registry.find(menu_id);
53+
if (it == g_menu_registry.end()) {
54+
return;
55+
}
56+
Menu* menu_obj = it->second;
57+
menu_obj->EmitSync(MenuClosedEvent(menu_id));
58+
}
59+
60+
static void OnGtkSubmenuShow(GtkWidget* /*submenu*/, gpointer user_data) {
61+
MenuItemId item_id = static_cast<MenuItemId>(GPOINTER_TO_SIZE(user_data));
62+
auto it = g_menu_item_registry.find(item_id);
63+
if (it == g_menu_item_registry.end()) {
64+
return;
65+
}
66+
MenuItem* menu_item = it->second;
67+
// Emit submenu opened on the item
68+
menu_item->EmitSync(MenuItemSubmenuOpenedEvent(item_id));
69+
}
70+
71+
static void OnGtkSubmenuHide(GtkWidget* /*submenu*/, gpointer user_data) {
72+
MenuItemId item_id = static_cast<MenuItemId>(GPOINTER_TO_SIZE(user_data));
73+
auto it = g_menu_item_registry.find(item_id);
74+
if (it == g_menu_item_registry.end()) {
75+
return;
76+
}
77+
MenuItem* menu_item = it->second;
78+
// Emit submenu closed on the item
79+
menu_item->EmitSync(MenuItemSubmenuClosedEvent(item_id));
80+
}
81+
2482
// Private implementation class for MenuItem
2583
class MenuItem::Impl {
2684
public:
@@ -79,6 +137,13 @@ MenuItem::MenuItem(const std::string& text, MenuItemType type)
79137

80138
// Register the MenuItem for event emission
81139
g_menu_item_registry[id] = this;
140+
141+
// Connect activation signal for click events (except separators)
142+
if (gtk_item && type != MenuItemType::Separator) {
143+
g_signal_connect(G_OBJECT(gtk_item), "activate",
144+
G_CALLBACK(OnGtkMenuItemActivate),
145+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
146+
}
82147
}
83148

84149
MenuItem::MenuItem(void* menu_item)
@@ -94,6 +159,12 @@ MenuItem::MenuItem(void* menu_item)
94159
}
95160
}
96161
g_menu_item_registry[id] = this;
162+
163+
if (pimpl_->gtk_menu_item_) {
164+
g_signal_connect(G_OBJECT(pimpl_->gtk_menu_item_), "activate",
165+
G_CALLBACK(OnGtkMenuItemActivate),
166+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
167+
}
97168
}
98169

99170
MenuItem::~MenuItem() {
@@ -209,6 +280,17 @@ void MenuItem::SetSubmenu(std::shared_ptr<Menu> submenu) {
209280
if (pimpl_->gtk_menu_item_ && submenu) {
210281
gtk_menu_item_set_submenu(GTK_MENU_ITEM(pimpl_->gtk_menu_item_),
211282
(GtkWidget*)submenu->GetNativeObject());
283+
284+
// Emit submenu open/close events on the parent item when submenu shows/hides
285+
GtkWidget* submenu_widget = (GtkWidget*)submenu->GetNativeObject();
286+
if (submenu_widget) {
287+
g_signal_connect(G_OBJECT(submenu_widget), "show",
288+
G_CALLBACK(OnGtkSubmenuShow),
289+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
290+
g_signal_connect(G_OBJECT(submenu_widget), "hide",
291+
G_CALLBACK(OnGtkSubmenuHide),
292+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
293+
}
212294
}
213295
}
214296

@@ -252,11 +334,30 @@ Menu::Menu()
252334
: id(g_next_menu_id++),
253335
pimpl_(std::unique_ptr<Impl>(new Impl(gtk_menu_new()))) {
254336
g_menu_registry[id] = this;
337+
338+
// Connect menu show/hide to emit open/close events
339+
if (pimpl_->gtk_menu_) {
340+
g_signal_connect(G_OBJECT(pimpl_->gtk_menu_), "show",
341+
G_CALLBACK(OnGtkMenuShow),
342+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
343+
g_signal_connect(G_OBJECT(pimpl_->gtk_menu_), "hide",
344+
G_CALLBACK(OnGtkMenuHide),
345+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
346+
}
255347
}
256348

257349
Menu::Menu(void* menu)
258350
: id(g_next_menu_id++), pimpl_(new Impl((GtkWidget*)menu)) {
259351
g_menu_registry[id] = this;
352+
353+
if (pimpl_->gtk_menu_) {
354+
g_signal_connect(G_OBJECT(pimpl_->gtk_menu_), "show",
355+
G_CALLBACK(OnGtkMenuShow),
356+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
357+
g_signal_connect(G_OBJECT(pimpl_->gtk_menu_), "hide",
358+
G_CALLBACK(OnGtkMenuHide),
359+
GSIZE_TO_POINTER(static_cast<gsize>(id)));
360+
}
260361
}
261362

262363
Menu::~Menu() {
@@ -392,7 +493,23 @@ std::shared_ptr<MenuItem> Menu::FindItemByText(const std::string& text,
392493
bool Menu::Open(double x, double y) {
393494
if (pimpl_->gtk_menu_) {
394495
pimpl_->visible_ = true;
395-
gtk_menu_popup_at_pointer(GTK_MENU(pimpl_->gtk_menu_), nullptr);
496+
gtk_widget_show_all(pimpl_->gtk_menu_);
497+
498+
// Try to position at explicit coordinates if available
499+
GdkWindow* root_window = gdk_get_default_root_window();
500+
if (root_window) {
501+
GdkRectangle rect;
502+
rect.x = static_cast<int>(x);
503+
rect.y = static_cast<int>(y);
504+
rect.width = 1;
505+
rect.height = 1;
506+
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), root_window, &rect,
507+
GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST,
508+
nullptr);
509+
} else {
510+
// Fallback to pointer if root window not available
511+
gtk_menu_popup_at_pointer(GTK_MENU(pimpl_->gtk_menu_), nullptr);
512+
}
396513
return true;
397514
}
398515
return false;

0 commit comments

Comments
 (0)