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
2430namespace 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 {
222243MenuItem::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
228260MenuItem::MenuItem (const std::string& text, MenuItemType type)
229261 : id(0 ) { // Will be set in Create method
230262}
231263
232264MenuItem::~MenuItem () {
233- // No global registry cleanup required
265+ // No special cleanup needed since we're not storing C++ object references
234266}
235267
236268MenuItemType 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
433470void * 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
447475class 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
483531Menu::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
488555Menu::Menu () : id (0 ) { // Will be set in Create method
489556}
490557
491558Menu::~Menu () {
492- // No global registry cleanup required
559+ // No special cleanup needed since we're not storing C++ object references
493560}
494561
495562void Menu::AddItem (std::shared_ptr<MenuItem> item) {
@@ -687,10 +754,18 @@ - (void)menuDidClose:(NSMenu*)menu {
687754}
688755
689756void 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
693764void 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
0 commit comments