--- a/ui/cocoa/MainWindow.m Sat Apr 05 17:57:04 2025 +0200 +++ b/ui/cocoa/MainWindow.m Sun Jul 20 22:04:39 2025 +0200 @@ -30,11 +30,14 @@ #import "Container.h" #import "GridLayout.h" #import "../common/object.h" +#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 @@ -63,3 +66,198 @@ } @end + + + +@implementation MainWindowController + +- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window { + self = [super initWithWindow:window]; + _uiobj = obj; + + self.checkItemStates = [[NSMutableDictionary alloc] init]; + self.radioItems = [[NSMutableDictionary alloc] init]; + + // bind all stateful menu items (checkbox, radiobuttons, lists) + NSArray *menuBindItems = ui_get_binding_items(); // returns all items that require binding + for(MenuItem *item in menuBindItems) { + if(item.checkItem || item.radioItem) { + // simple check item (ui_menu_toggleitem_create) + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, item.checkItem ? item.checkItem->varname : item.radioItem->varname, UI_VAR_INTEGER); + // create the state object for this item/window + MenuItemState *state = [[MenuItemState alloc] init]; + state.mainWindow = self; + state.var = var; + if(var) { + UiInteger *i = var->value; + if(item.checkItem) { + // bind toggle item + 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 { + // bind radio item + NSMutableArray *rgroup = nil; + if(i->obj) { + rgroup = (__bridge NSMutableArray*)i->obj; + } else { + // create a new rgroup array and register it in the window + rgroup = [[NSMutableArray alloc] init]; + NSString *varname = [[NSString alloc] initWithUTF8String:item.radioItem->varname]; + [_radioItems setObject:rgroup forKey:varname]; + i->obj = (__bridge void*)rgroup; + } + i->get = ui_menu_radio_item_get; + i->set = ui_menu_radio_item_set; + [rgroup addObject:state]; // add this item state to the radio group + // i->value can contain a non-zero value, which means a specific radiobutton + // should be pre-selected + if(i->value == rgroup.count) { + state.state = NSControlStateValueOn; + } + } + } else { + state.state = 0; + } + [_checkItemStates setObject:state forKey:item.itemId]; + } + } + + return self; +} + +- (void) windowDidLoad { + [self.window setNextResponder:self]; +} + +- (void)menuItemAction:(id)sender { + EventData *event = objc_getAssociatedObject(sender, "eventdata"); + if(event) { + event.obj = self.uiobj; // temporary set the event object + [event handleEvent:sender]; + } +} + +- (void)menuCheckItemAction:(id)sender { + NSMenuItem *menuItem = sender; + MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); + if(!item || !item.checkItem) { + return; + } + + MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; + state.state = state.state == NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff; + menuItem.state = state.state; + + UiMenuCheckItem *it = item.checkItem; + 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); + } +} + +- (void)menuRadioItemAction:(id)sender { + NSMenuItem *menuItem = sender; + MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); + if(!item || !item.radioItem) { + return; + } + + UiMenuRadioItem *it = item.radioItem; + if(!it->varname) { + return; + } + + MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; // current state of this menu item + + NSString *varname = [[NSString alloc] initWithUTF8String:it->varname]; + NSArray *radioGroup = [_radioItems objectForKey:varname]; + if(!radioGroup) { + return; + } + int index = 1; + int value = 0; + for(MenuItemState *g in radioGroup) { + if(g == state) { + menuItem.state = NSControlStateValueOn; + g.state = NSControlStateValueOn; + value = index; + } else { + menuItem.state = NSControlStateValueOff; + g.state = NSControlStateValueOff; + } + } + + 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 = value; + it->callback(&event, it->userdata); + } +} + + +- (BOOL) validateMenuItem:(NSMenuItem *) menuItem { + MenuItem *item = objc_getAssociatedObject(menuItem, "menuitem"); + if(item) { + MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; + if(state) { + menuItem.state = state.state; + } else { + menuItem.state = NSControlStateValueOff; + } + } + + return YES; +} + +@end + +@implementation MenuItemState + +@end + +int64_t ui_menu_check_item_get(UiInteger *i) { + MenuItemState *state = (__bridge MenuItemState*)i->obj; + i->value = state.state; + return i->value; +} + +void ui_menu_check_item_set(UiInteger *i, int64_t value) { + MenuItemState *state = (__bridge MenuItemState*)i->obj; + i->value = value; + state.state = (int)value; +} + +int64_t ui_menu_radio_item_get(UiInteger *i) { + NSArray *rgroup = (__bridge NSArray*)i->obj; + i->value = 0; + int index = 1; + for(MenuItemState *state in rgroup) { + if(state.state == NSControlStateValueOn) { + i->value = index; + break; + } + index++; + } + return i->value; +} + +void ui_menu_radio_item_set(UiInteger *i, int64_t value) { + NSArray *rgroup = (__bridge NSArray*)i->obj; + i->value = 0; + int index = 1; + for(MenuItemState *state in rgroup) { + state.state = value == index; + index++; + } +}