Thu, 17 Apr 2025 17:51:07 +0200
implement toggle menu items (Cocoa)
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #import "MainWindow.h" #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 { NSRect frame = NSMakeRect(300, 200, 600, 500); self = [self initWithContentRect:frame styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable backing:NSBackingStoreBuffered defer:false]; // create a vertical stackview as default container BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; //GridLayout *vbox = [[GridLayout alloc] init]; vbox.translatesAutoresizingMaskIntoConstraints = false; [self.contentView addSubview:vbox]; [NSLayoutConstraint activateConstraints:@[ [vbox.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:4], [vbox.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], [vbox.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], [vbox.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] ]]; uic_object_push_container(obj, ui_create_container(obj, vbox)); 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) { 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; } 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; }