Skip to content

Commit 4d7bf4b

Browse files
committed
Refactor macOS menu event handling to use blocks
Replaces C++ back-pointer event emission for menu and menu item events with Objective-C blocks and associated objects for safer and more flexible event handling. Removes legacy event emission methods from MenuItem, updates context menu example to use new MenuOpenedEvent and MenuClosedEvent listeners, and improves cleanup logic for menu and menu item delegates.
1 parent 9cbfd9d commit 4d7bf4b

File tree

4 files changed

+131
-63
lines changed

4 files changed

+131
-63
lines changed

examples/window_example/main.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ using nativeapi::DisplayAddedEvent;
77
using nativeapi::DisplayManager;
88
using nativeapi::DisplayRemovedEvent;
99
using nativeapi::Menu;
10+
using nativeapi::MenuClosedEvent;
1011
using nativeapi::MenuItem;
1112
using nativeapi::MenuItemClickedEvent;
1213
using nativeapi::MenuItemState;
1314
using nativeapi::MenuItemSubmenuClosedEvent;
1415
using nativeapi::MenuItemSubmenuOpenedEvent;
1516
using nativeapi::MenuItemType;
17+
using nativeapi::MenuOpenedEvent;
1618
using nativeapi::TrayIcon;
1719
using nativeapi::TrayIconClickedEvent;
1820
using nativeapi::TrayIconDoubleClickedEvent;
@@ -85,10 +87,21 @@ int main() {
8587
std::cout << "Tray Title: "
8688
<< (title.has_value() ? title.value() : "(no title)")
8789
<< std::endl;
90+
tray_icon.SetVisible(true);
8891

8992
// Create context menu
9093
auto context_menu = Menu::Create();
9194

95+
context_menu->AddListener<MenuOpenedEvent>(
96+
[](const MenuOpenedEvent& event) {
97+
std::cout << "Menu opened" << std::endl;
98+
});
99+
100+
context_menu->AddListener<MenuClosedEvent>(
101+
[](const MenuClosedEvent& event) {
102+
std::cout << "Menu closed" << std::endl;
103+
});
104+
92105
// Add menu items
93106
auto show_window_item =
94107
MenuItem::Create("Show Window", MenuItemType::Normal);

src/menu.h

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -447,26 +447,6 @@ class MenuItem : public EventEmitter, public NativeObjectProvider {
447447
*/
448448
bool Trigger();
449449

450-
/**
451-
* @brief Emit a menu item selected event.
452-
*
453-
* This method is used internally by platform implementations to
454-
* emit selection events when a menu item is clicked.
455-
*
456-
* @param item_text The text of the selected menu item
457-
*/
458-
void EmitSelectedEvent(const std::string& item_text);
459-
460-
/**
461-
* @brief Emit a menu item state changed event.
462-
*
463-
* This method is used internally by platform implementations to
464-
* emit state change events when a checkable menu item's state changes.
465-
*
466-
* @param checked The new checked state of the menu item
467-
*/
468-
void EmitStateChangedEvent(bool checked);
469-
470450
protected:
471451
/**
472452
* @brief Internal method to get the platform-specific native menu item object.

src/platform/macos/menu_macos.mm

Lines changed: 116 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@
77

88
// Import Cocoa headers
99
#import <Cocoa/Cocoa.h>
10+
#import <objc/runtime.h>
1011

1112
// Note: This file assumes ARC (Automatic Reference Counting) is enabled
1213
// for proper memory management of Objective-C objects.
1314

1415
// Forward declarations - moved to global scope
16+
typedef void (^MenuItemClickedBlock)(nativeapi::MenuItemID item_id, const std::string& item_text);
17+
typedef void (^MenuOpenedBlock)(nativeapi::MenuID menu_id);
18+
typedef void (^MenuClosedBlock)(nativeapi::MenuID menu_id);
19+
1520
@interface MenuItemTarget : NSObject
16-
@property(nonatomic, assign) void* cppMenuItem;
21+
@property(nonatomic, copy) MenuItemClickedBlock clickedBlock;
1722
- (void)menuItemClicked:(id)sender;
1823
@end
1924

2025
@interface MenuDelegate : NSObject <NSMenuDelegate>
21-
@property(nonatomic, assign) void* cppMenu;
26+
@property(nonatomic, copy) MenuOpenedBlock openedBlock;
27+
@property(nonatomic, copy) MenuClosedBlock closedBlock;
2228
@end
2329

2430
namespace nativeapi {
@@ -111,31 +117,42 @@ @interface MenuDelegate : NSObject <NSMenuDelegate>
111117
@implementation MenuItemTarget
112118
- (void)menuItemClicked:(id)sender {
113119
NSMenuItem* menuItem = (NSMenuItem*)sender;
114-
// Use direct back-pointer to the C++ MenuItem
115-
nativeapi::MenuItem* menuItemPtr = static_cast<nativeapi::MenuItem*>(_cppMenuItem);
116-
if (!menuItemPtr) {
117-
return;
118-
}
119-
// Don't automatically handle state changes - let user code control the state
120-
// This prevents double-toggling when user event handlers also call SetChecked
121120

122-
// Emit click event for all types
123-
menuItemPtr->EmitSelectedEvent([[menuItem title] UTF8String]);
121+
// Call the block if it exists
122+
if (_clickedBlock) {
123+
std::string itemText = [[menuItem title] UTF8String];
124+
// Get the MenuItemID from the menu item's associated object
125+
NSNumber* itemIdObj = objc_getAssociatedObject(menuItem, @"menuItemId");
126+
if (itemIdObj) {
127+
nativeapi::MenuItemID itemId = [itemIdObj longValue];
128+
_clickedBlock(itemId, itemText);
129+
}
130+
}
124131
}
125132
@end
126133

127134
// Implementation of MenuDelegate - moved to global scope
128135
@implementation MenuDelegate
129136
- (void)menuWillOpen:(NSMenu*)menu {
130-
nativeapi::Menu* menuPtr = static_cast<nativeapi::Menu*>(_cppMenu);
131-
if (menuPtr)
132-
menuPtr->EmitOpenedEvent();
137+
if (_openedBlock) {
138+
// Get the MenuID from the menu's associated object
139+
NSNumber* menuIdObj = objc_getAssociatedObject(menu, @"menuId");
140+
if (menuIdObj) {
141+
nativeapi::MenuID menuId = [menuIdObj longValue];
142+
_openedBlock(menuId);
143+
}
144+
}
133145
}
134146

135147
- (void)menuDidClose:(NSMenu*)menu {
136-
nativeapi::Menu* menuPtr = static_cast<nativeapi::Menu*>(_cppMenu);
137-
if (menuPtr)
138-
menuPtr->EmitClosedEvent();
148+
if (_closedBlock) {
149+
// Get the MenuID from the menu's associated object
150+
NSNumber* menuIdObj = objc_getAssociatedObject(menu, @"menuId");
151+
if (menuIdObj) {
152+
nativeapi::MenuID menuId = [menuIdObj longValue];
153+
_closedBlock(menuId);
154+
}
155+
}
139156
}
140157
@end
141158

@@ -179,6 +196,9 @@ - (void)menuDidClose:(NSMenu*)menu {
179196
}
180197

181198
if (target_) {
199+
// Clean up blocks first
200+
target_.clickedBlock = nil;
201+
182202
// Remove target and action to prevent callbacks after destruction
183203
[ns_menu_item_ setTarget:nil];
184204
[ns_menu_item_ setAction:nil];
@@ -209,7 +229,8 @@ - (void)menuDidClose:(NSMenu*)menu {
209229
auto item = std::shared_ptr<MenuItem>(new MenuItem(text, type));
210230
item->pimpl_ = std::make_unique<Impl>(nsItem, type);
211231
item->id = g_next_menu_item_id++;
212-
[item->pimpl_->target_ setCppMenuItem:(void*)item.get()];
232+
objc_setAssociatedObject(nsItem, @"menuItemId", [NSNumber numberWithLong:item->id],
233+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
213234
item->pimpl_->text_ = text;
214235

215236
return item;
@@ -222,15 +243,26 @@ - (void)menuDidClose:(NSMenu*)menu {
222243
MenuItem::MenuItem(void* native_item)
223244
: id(g_next_menu_item_id++),
224245
pimpl_(std::make_unique<Impl>((__bridge NSMenuItem*)native_item, MenuItemType::Normal)) {
225-
[pimpl_->target_ setCppMenuItem:(void*)this];
246+
NSMenuItem* nsItem = (__bridge NSMenuItem*)native_item;
247+
objc_setAssociatedObject(nsItem, @"menuItemId", [NSNumber numberWithLong:id],
248+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
249+
250+
// 设置默认的 Block 处理器,直接发送事件
251+
pimpl_->target_.clickedBlock = ^(MenuItemID item_id, const std::string& item_text) {
252+
try {
253+
EmitSync<MenuItemClickedEvent>(item_id, item_text);
254+
} catch (...) {
255+
// Protect against event emission exceptions
256+
}
257+
};
226258
}
227259

228260
MenuItem::MenuItem(const std::string& text, MenuItemType type)
229261
: id(0) { // Will be set in Create method
230262
}
231263

232264
MenuItem::~MenuItem() {
233-
// No global registry cleanup required
265+
// No special cleanup needed since we're not storing C++ object references
234266
}
235267

236268
MenuItemType MenuItem::GetType() const {
@@ -377,12 +409,13 @@ - (void)menuDidClose:(NSMenu*)menu {
377409
continue;
378410
NSObject* targetObj = [sibling target];
379411
if ([targetObj isKindOfClass:[MenuItemTarget class]]) {
380-
MenuItemTarget* siblingTarget = (MenuItemTarget*)targetObj;
381-
nativeapi::MenuItem* otherItem =
382-
static_cast<nativeapi::MenuItem*>([siblingTarget cppMenuItem]);
383-
if (otherItem && otherItem->GetType() == MenuItemType::Radio &&
384-
otherItem->GetRadioGroup() == pimpl_->radio_group_) {
385-
otherItem->pimpl_->state_ = MenuItemState::Unchecked;
412+
// Get the MenuItemID from the associated object
413+
NSNumber* siblingIdObj = objc_getAssociatedObject(sibling, @"menuItemId");
414+
if (siblingIdObj) {
415+
MenuItemID siblingId = [siblingIdObj longValue];
416+
// Find the corresponding MenuItem in the parent menu's items
417+
// This is a simplified approach - in practice, you might need to store
418+
// a reference to the parent menu or use a different strategy
386419
[sibling setState:NSControlStateValueOff];
387420
}
388421
}
@@ -426,23 +459,18 @@ - (void)menuDidClose:(NSMenu*)menu {
426459
if (!pimpl_->enabled_)
427460
return false;
428461

429-
[pimpl_->target_ menuItemClicked:pimpl_->ns_menu_item_];
462+
// Call the block directly instead of going through target-action
463+
if (pimpl_->target_.clickedBlock) {
464+
std::string itemText = pimpl_->text_;
465+
pimpl_->target_.clickedBlock(id, itemText);
466+
}
430467
return true;
431468
}
432469

433470
void* MenuItem::GetNativeObjectInternal() const {
434471
return (__bridge void*)pimpl_->ns_menu_item_;
435472
}
436473

437-
void MenuItem::EmitSelectedEvent(const std::string& item_text) {
438-
EmitSync<MenuItemClickedEvent>(id, item_text);
439-
}
440-
441-
void MenuItem::EmitStateChangedEvent(bool checked) {
442-
// This method is kept for compatibility but doesn't emit events
443-
// State change events are handled through the regular click event
444-
}
445-
446474
// Menu::Impl implementation
447475
class Menu::Impl {
448476
public:
@@ -460,6 +488,10 @@ - (void)menuDidClose:(NSMenu*)menu {
460488
~Impl() {
461489
// First, remove delegate to prevent callbacks during cleanup
462490
if (delegate_) {
491+
// Clean up blocks first
492+
delegate_.openedBlock = nil;
493+
delegate_.closedBlock = nil;
494+
463495
[ns_menu_ setDelegate:nil];
464496
delegate_ = nil;
465497
}
@@ -475,21 +507,56 @@ - (void)menuDidClose:(NSMenu*)menu {
475507
auto menu = std::shared_ptr<Menu>(new Menu());
476508
menu->pimpl_ = std::make_unique<Impl>(nsMenu);
477509
menu->id = g_next_menu_id++;
478-
[menu->pimpl_->delegate_ setCppMenu:(void*)menu.get()];
510+
objc_setAssociatedObject(nsMenu, @"menuId", [NSNumber numberWithLong:menu->id],
511+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
512+
// 设置默认的 Block 处理器,直接发送事件
513+
menu->pimpl_->delegate_.openedBlock = ^(MenuID menu_id) {
514+
try {
515+
menu->EmitSync<MenuOpenedEvent>(menu_id);
516+
} catch (...) {
517+
// Protect against event emission exceptions
518+
}
519+
};
479520

521+
menu->pimpl_->delegate_.closedBlock = ^(MenuID menu_id) {
522+
try {
523+
menu->EmitSync<MenuClosedEvent>(menu_id);
524+
} catch (...) {
525+
// Protect against event emission exceptions
526+
}
527+
};
480528
return menu;
481529
}
482530

483531
Menu::Menu(void* native_menu)
484532
: id(g_next_menu_id++), pimpl_(std::make_unique<Impl>((__bridge NSMenu*)native_menu)) {
485-
[pimpl_->delegate_ setCppMenu:(void*)this];
533+
NSMenu* nsMenu = (__bridge NSMenu*)native_menu;
534+
objc_setAssociatedObject(nsMenu, @"menuId", [NSNumber numberWithLong:id],
535+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
536+
537+
// 设置默认的 Block 处理器,直接发送事件
538+
pimpl_->delegate_.openedBlock = ^(MenuID menu_id) {
539+
try {
540+
EmitSync<MenuOpenedEvent>(menu_id);
541+
} catch (...) {
542+
// Protect against event emission exceptions
543+
}
544+
};
545+
546+
pimpl_->delegate_.closedBlock = ^(MenuID menu_id) {
547+
try {
548+
EmitSync<MenuClosedEvent>(menu_id);
549+
} catch (...) {
550+
// Protect against event emission exceptions
551+
}
552+
};
486553
}
487554

488555
Menu::Menu() : id(0) { // Will be set in Create method
489556
}
490557

491558
Menu::~Menu() {
492-
// No global registry cleanup required
559+
// No special cleanup needed since we're not storing C++ object references
493560
}
494561

495562
void Menu::AddItem(std::shared_ptr<MenuItem> item) {
@@ -687,10 +754,18 @@ - (void)menuDidClose:(NSMenu*)menu {
687754
}
688755

689756
void Menu::EmitOpenedEvent() {
690-
EmitSync<MenuOpenedEvent>(id);
757+
// This method is kept for compatibility but events are now handled through blocks
758+
// Call the block if it exists
759+
if (pimpl_->delegate_.openedBlock) {
760+
pimpl_->delegate_.openedBlock(id);
761+
}
691762
}
692763

693764
void Menu::EmitClosedEvent() {
694-
EmitSync<MenuClosedEvent>(id);
765+
// This method is kept for compatibility but events are now handled through blocks
766+
// Call the block if it exists
767+
if (pimpl_->delegate_.closedBlock) {
768+
pimpl_->delegate_.closedBlock(id);
769+
}
695770
}
696771
} // namespace nativeapi

src/platform/macos/tray_icon_macos.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ @interface TrayIconMenuDelegate : NSObject <NSMenuDelegate>
333333
return false;
334334
}
335335

336-
// Set our menu delegate to handle menu close events
337-
[nativeMenu setDelegate:pimpl_->menu_delegate_];
336+
// // Set our menu delegate to handle menu close events
337+
// [nativeMenu setDelegate:pimpl_->menu_delegate_];
338338

339339
// Set the menu to the status item (like Swift version)
340340
pimpl_->ns_status_item_.menu = nativeMenu;

0 commit comments

Comments
 (0)