implement toggle menu items (Cocoa)

Thu, 17 Apr 2025 17:51:07 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 17 Apr 2025 17:51:07 +0200
changeset 570
a2df724b4cb9
parent 569
5c06bb9ea458
child 571
f6e92de49959

implement toggle menu items (Cocoa)

make/xcode/toolkit/toolkit/main.m file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.h file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.m file | annotate | diff | comparison | revisions
ui/cocoa/menu.h file | annotate | diff | comparison | revisions
ui/cocoa/menu.m file | annotate | diff | comparison | revisions
ui/cocoa/window.m file | annotate | diff | comparison | revisions
--- a/make/xcode/toolkit/toolkit/main.m	Thu Apr 17 11:18:57 2025 +0200
+++ b/make/xcode/toolkit/toolkit/main.m	Thu Apr 17 17:51:07 2025 +0200
@@ -54,6 +54,10 @@
     }
     
     ui_show(obj);
+    
+    UiObject *obj2 = ui_window("Window 2", NULL);
+    ui_textarea(obj2, .fill = UI_ON);
+    ui_show(obj2);
 }
 
 int main(int argc, char * argv[]) {
--- a/ui/cocoa/MainWindow.h	Thu Apr 17 11:18:57 2025 +0200
+++ b/ui/cocoa/MainWindow.h	Thu Apr 17 17:51:07 2025 +0200
@@ -31,10 +31,32 @@
 
 @interface MainWindow : NSWindow
 
-@property UiObject *uiobj;
+- (MainWindow*)init:(UiObject*)obj;
+
+@end
+
+
+@interface MainWindowController : NSWindowController<NSMenuItemValidation>
 
-- (MainWindow*)init:(UiObject*)obj;
+@property UiObject *uiobj;
+@property NSMutableDictionary *checkItemStates;
+
+- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window;
+
+- (void) windowDidLoad;
 
 - (void)menuItemAction:(id)sender;
 
+- (BOOL) validateMenuItem:(NSMenuItem *) menuItem;
+
 @end
+
+@interface MenuCheckItem : NSObject
+@property (weak) MainWindowController *mainWindow;
+@property UiVar *var;
+@property int state;
+@end
+
+
+int64_t ui_menu_check_item_get(UiInteger *i);
+void ui_menu_check_item_set(UiInteger *i, int64_t value);
--- a/ui/cocoa/MainWindow.m	Thu Apr 17 11:18:57 2025 +0200
+++ b/ui/cocoa/MainWindow.m	Thu Apr 17 17:51:07 2025 +0200
@@ -33,11 +33,11 @@
 #import <objc/runtime.h>
 
 #import "EventData.h"
+#import "menu.h"
 
 @implementation MainWindow
 
 - (MainWindow*)init:(UiObject*)obj {
-    self.uiobj = obj;
     NSRect frame = NSMakeRect(300, 200, 600, 500);
     
     self = [self initWithContentRect:frame
@@ -65,6 +65,51 @@
     return self;
 }
 
+@end
+
+
+
+@implementation MainWindowController
+
+- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window {
+    self = [super initWithWindow:window];
+    _uiobj = obj;
+    
+    // bind all stateful menu items (checkbox, radiobuttons, lists)
+    self.checkItemStates = [[NSMutableDictionary alloc] init];
+    NSArray *menuBindItems = ui_get_binding_items(); // returns all items that require binding
+    for(MenuItem *item in menuBindItems) {
+        if(item.checkItem) {
+            // simple check item (ui_menu_toggleitem_create)
+            UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, item.checkItem->varname, UI_VAR_INTEGER);
+            // create the state object for this item/window
+            MenuCheckItem *state = [[MenuCheckItem alloc] init];
+            state.mainWindow = self;
+            state.var = var;
+            if(var) {
+                // bind toggle item
+                UiInteger *i = var->value;
+                state.state = (int)i->value;
+                i->obj = (__bridge void*)state;
+                i->get = ui_menu_check_item_get;
+                i->set = ui_menu_check_item_set;
+            } else {
+                state.state = 0;
+            }
+            [_checkItemStates setObject:state forKey:item.itemId];
+        } else if(item.radioItem) {
+            // bind radio item
+            
+        }
+    }
+    
+    return self;
+}
+
+- (void) windowDidLoad {
+    [self.window setNextResponder:self];
+}
+
 - (void)menuItemAction:(id)sender {
     EventData *event = objc_getAssociatedObject(sender, "eventdata");
     if(event) {
@@ -73,4 +118,57 @@
     }
 }
 
+- (void)menuCheckItemAction:(id)sender {
+    NSMenuItem *menuItem = sender;
+    MenuItem *item = objc_getAssociatedObject(sender, "menuitem");
+    if(!item || !item.checkItem) {
+        return;
+    }
+    UiMenuCheckItem *it = item.checkItem;
+    
+    MenuCheckItem *state = [_checkItemStates objectForKey:item.itemId];
+    state.state = state.state == NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff;
+    menuItem.state = state.state;
+    
+    if(it->callback) {
+        UiEvent event;
+        event.obj = _uiobj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = state.var ? state.var->value : NULL;
+        event.intval = state.state;
+        it->callback(&event, it->userdata);
+    }
+}
+
+- (BOOL) validateMenuItem:(NSMenuItem *) menuItem {
+    MenuItem *item = objc_getAssociatedObject(menuItem, "menuitem");
+    if(item) {
+        MenuCheckItem *state = [_checkItemStates objectForKey:item.itemId];
+        if(state) {
+            menuItem.state = state.state;
+        } else {
+            menuItem.state = NSControlStateValueOff;
+        }
+    }
+    
+    return YES;
+}
+
 @end
+
+@implementation MenuCheckItem
+
+@end
+
+int64_t ui_menu_check_item_get(UiInteger *i) {
+    MenuCheckItem *state = (__bridge MenuCheckItem*)i->obj;
+    i->value = state.state;
+    return i->value;
+}
+
+void ui_menu_check_item_set(UiInteger *i, int64_t value) {
+    MenuCheckItem *state = (__bridge MenuCheckItem*)i->obj;
+    i->value = value;
+    state.state = (int)value;
+}
--- a/ui/cocoa/menu.h	Thu Apr 17 11:18:57 2025 +0200
+++ b/ui/cocoa/menu.h	Thu Apr 17 17:51:07 2025 +0200
@@ -31,6 +31,18 @@
 
 #import "../common/menu.h"
 
+@interface MenuItem : NSObject
+
+@property (strong) NSString *itemId;
+@property UiMenuCheckItem *checkItem;
+@property UiMenuRadioItem *radioItem;
+@property ui_callback     callback;
+@property void            *userdata;
+
+- (MenuItem*)init:(int)itId;
+
+@end
+
 void ui_menu_init(void);
 
 typedef void(*ui_menu_add_f)(NSMenu*, int, UiMenuItemI*);
@@ -42,3 +54,5 @@
 void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item);
 void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item);
 void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item);
+
+NSArray* ui_get_binding_items(void);
--- a/ui/cocoa/menu.m	Thu Apr 17 11:18:57 2025 +0200
+++ b/ui/cocoa/menu.m	Thu Apr 17 17:51:07 2025 +0200
@@ -36,6 +36,22 @@
 #import "window.h"
 #import "EventData.h"
 
+#pragma GCC diagnostic ignored "-Wundeclared-selector"
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+
+// holds all items that need bindings
+// value type: MenuItem*
+static NSMutableArray *bindingItems;
+
+@implementation MenuItem
+
+- (MenuItem*)init:(int)itId {
+    self.itemId = [[NSString alloc] initWithFormat:@"item%d", itId];
+    return self;
+}
+
+@end
+
 static ui_menu_add_f createMenuItem[] = {
     /* UI_MENU                 */ add_menu_widget,
     /* UI_MENU_ITEM            */ add_menuitem_widget,
@@ -80,15 +96,40 @@
 }
 
 void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item) {
-    
+    NSMenuItem *menuItem = [NSMenuItem separatorItem];
+    [parent addItem:menuItem];
 }
 
+static int nItem = 0;
+
 void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+    UiMenuCheckItem *it = (UiMenuCheckItem*)item;
     
+    NSString *str = [[NSString alloc] initWithUTF8String:it->label];
+    NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuCheckItemAction:) keyEquivalent:@""];
+    
+    MenuItem *mItem = [[MenuItem alloc] init:nItem++];
+    mItem.callback = it->callback;
+    mItem.userdata = it->userdata;
+    mItem.checkItem = it;
+    
+    objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN);
+    [bindingItems addObject:mItem];
 }
 
 void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item) {
+    UiMenuRadioItem *it = (UiMenuRadioItem*)item;
     
+    NSString *str = [[NSString alloc] initWithUTF8String:it->label];
+    NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuRadioItemAction:) keyEquivalent:@""];
+    
+    MenuItem *mItem = [[MenuItem alloc] init:nItem++];
+    mItem.callback = it->callback;
+    mItem.userdata = it->userdata;
+    mItem.radioItem = it;
+    
+    objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN);
+    [bindingItems addObject:mItem];
 }
 
 void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item) {
@@ -101,6 +142,8 @@
 
 
 void ui_menu_init(void) {
+    bindingItems = [[NSMutableArray alloc] init];
+    
     UiMenu *menus_begin = uic_get_menu_list();
     UiMenu *ls = menus_begin;
     int index = 1;
@@ -117,3 +160,7 @@
         index++;
     }
 }
+
+NSArray* ui_get_binding_items(void) {
+    return bindingItems;
+}
--- a/ui/cocoa/window.m	Thu Apr 17 11:18:57 2025 +0200
+++ b/ui/cocoa/window.m	Thu Apr 17 17:51:07 2025 +0200
@@ -31,6 +31,8 @@
 #import "MainWindow.h"
 #import "WindowManager.h"
 
+#import <objc/runtime.h>
+
 #include "../ui/window.h"
 #include "../ui/properties.h"
 #include "../common/context.h"
@@ -39,6 +41,7 @@
 
 #include <cx/mempool.h>
 
+
 static UiObject* create_window(const char *title, BOOL simple) {
     CxMempool *mp = cxMempoolCreateSimple(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
@@ -52,6 +55,11 @@
     
     obj->wobj = (__bridge void*)window;
     
+    MainWindowController *controller = [[MainWindowController alloc] initWithWindow:obj window:window];
+    window.windowController = controller;
+    [window setNextResponder:(NSResponder*)controller];
+    objc_setAssociatedObject(window, "windowcontroller", controller, OBJC_ASSOCIATION_RETAIN);
+    
     return obj;
 }
 

mercurial