From bd74d45a91523779b045fbc0ac832d1cad78d382 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 6 Jan 2021 20:50:41 +1100 Subject: [PATCH] Normalisation of callbacks for menus. App menu converted to new Menus. --- v2/internal/ffenestri/ffenestri_darwin.c | 201 +---------------------- v2/internal/ffenestri/menu_darwin.h | 168 +++++++++++++++---- 2 files changed, 147 insertions(+), 222 deletions(-) diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index 9365c182..e400e456 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -19,12 +19,6 @@ extern const unsigned char *defaultDialogIcons[]; // MAIN DEBUG FLAG int debug; -// MenuItem map for the application menu -struct hashmap_s menuItemMapForApplicationMenu; - -// RadioGroup map for the application menu. Maps a menuitem id with its associated radio group items -struct hashmap_s radioGroupMapForApplicationMenu; - // MenuItem map for the tray menu struct hashmap_s menuItemMapForTrayMenu; @@ -136,8 +130,6 @@ struct Application { // Menu Menu *applicationMenu; - const char *menuAsJSON; - JsonNode *processedMenu; // Tray const char *trayMenuAsJSON; @@ -398,15 +390,6 @@ const char* createContextMenuMessage(const char *menuItemID, const char *givenCo return result; } -// Callback for menu items -void menuItemPressedForApplicationMenu(id self, SEL cmd, id sender) { - const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); - // Notify the backend - const char *message = concat("MC", menuItemID); - messageFromWindowCallback(message); - MEMFREE(message); -} - // Callback for tray items void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) { const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); @@ -428,25 +411,6 @@ void menuItemPressedForContextMenus(id self, SEL cmd, id sender) { MEMFREE(contextMenuMessage); } -// Callback for menu items -void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) { - const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); - - // Get the menu item from the menu item map - id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID)); - - // Get the current state - bool state = msg(menuItem, s("state")); - - // Toggle the state - msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn)); - - // Notify the backend - const char *message = concat("MC", menuItemID); - messageFromWindowCallback(message); - MEMFREE(message); -} - // Callback for tray menu items void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) { const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); @@ -487,43 +451,6 @@ void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct MEMFREE(contextMenuMessage); } -// radioMenuItemPressedForApplicationMenu -void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) { - const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); - - // Get the menu item from the menu item map - id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID)); - - // Check the menu items' current state - bool selected = msg(menuItem, s("state")); - - // If it's already selected, exit early - if (selected) { - return; - } - - // Get this item's radio group members and turn them off - id *members = (id*)hashmap_get(&radioGroupMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID)); - - // Uncheck all members of the group - id thisMember = members[0]; - int count = 0; - while(thisMember != NULL) { - msg(thisMember, s("setState:"), NSControlStateValueOff); - count = count + 1; - thisMember = members[count]; - } - - // check the selected menu item - msg(menuItem, s("setState:"), NSControlStateValueOn); - - // Notify the backend - const char *message = concat("MC", menuItemID); - messageFromWindowCallback(message); - MEMFREE(message); -} - - // radioMenuItemPressedForTrayMenu void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) { const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); @@ -650,22 +577,6 @@ void themeChanged(id self, SEL cmd, id sender) { // Debug(app, "willFinishLaunching called!"); // } -void allocateMenuHashMaps(struct Application *app) { - // Allocate new menuItem map - if( 0 != hashmap_create((const unsigned)16, &menuItemMapForApplicationMenu)) { - // Couldn't allocate map - Fatal(app, "Not enough memory to allocate menuItemMapForApplicationMenu!"); - return; - } - - // Allocate the Radio Group Cache - if( 0 != hashmap_create((const unsigned)4, &radioGroupMapForApplicationMenu)) { - // Couldn't allocate map - Fatal(app, "Not enough memory to allocate radioGroupMapForApplicationMenu!"); - return; - } -} - void allocateTrayHashMaps(struct Application *app) { // Allocate new menuItem map if( 0 != hashmap_create((const unsigned)16, &menuItemMapForTrayMenu)) { @@ -749,8 +660,6 @@ void* NewApplication(const char *title, int width, int height, int resizable, in // Menu result->applicationMenu = NULL; - result->menuAsJSON = NULL; - result->processedMenu = NULL; // Tray result->trayMenuAsJSON = NULL; @@ -780,37 +689,6 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) { return -1; } -void destroyMenu(struct Application *app) { - - if( app->menuAsJSON == NULL ) { - return; - } - - // Free menu item hashmap - hashmap_destroy(&menuItemMapForApplicationMenu); - - // Free radio group members - if( hashmap_num_entries(&radioGroupMapForApplicationMenu) > 0 ) { - if (0!=hashmap_iterate_pairs(&radioGroupMapForApplicationMenu, freeHashmapItem, NULL)) { - Fatal(app, "failed to release hashmap entries!"); - } - } - - //Free radio groups hashmap - hashmap_destroy(&radioGroupMapForApplicationMenu); - - // Release processed menu - if( app->processedMenu != NULL) { - json_delete(app->processedMenu); - app->processedMenu = NULL; - } - - // Remove the menu if we have one - id menubar = msg(msg(c("NSApplication"), s("sharedApplication")), s("mainMenu")); - Debug(app, "Destroying menubar: %p", menubar); - msg(menubar, s("release")); -} - void destroyContextMenus(struct Application *app) { // Free menu item hashmap @@ -919,9 +797,6 @@ void DestroyApplication(struct Application *app) { msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor); } - // Destroy the menu - destroyMenu(app); - // Delete the application menu if we have one if( app->applicationMenu != NULL ) { DeleteMenu(app->applicationMenu); @@ -1411,7 +1286,6 @@ void SetDebug(void *applicationPointer, int flag) { // SetMenu sets the initial menu for the application void SetMenu(struct Application *app, const char *menuAsJSON) { - app->menuAsJSON = menuAsJSON; app->applicationMenu = NewApplicationMenu(menuAsJSON); } @@ -1514,9 +1388,6 @@ void createDelegate(struct Application *app) { class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@"); // Menu Callbacks - class_addMethod(delegateClass, s("menuCallbackForApplicationMenu:"), (IMP)menuItemPressedForApplicationMenu, "v@:@"); - class_addMethod(delegateClass, s("checkboxMenuCallbackForApplicationMenu:"), (IMP) checkboxMenuItemPressedForApplicationMenu, "v@:@"); - class_addMethod(delegateClass, s("radioMenuCallbackForApplicationMenu:"), (IMP) radioMenuItemPressedForApplicationMenu, "v@:@"); class_addMethod(delegateClass, s("menuCallbackForTrayMenu:"), (IMP)menuItemPressedForTrayMenu, "v@:@"); class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@"); class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@"); @@ -1525,7 +1396,7 @@ void createDelegate(struct Application *app) { class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@"); // Refactoring menu handling - class_addMethod(delegateClass, s("textMenuItemCallback:"), (IMP)textMenuItemCallback, "v@:@"); + class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@"); // Script handler @@ -1855,7 +1726,7 @@ void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct ha JsonNode *items = json_find_member(menu, "Items"); if( items == NULL ) { // Parse error! - Fatal(app, "Unable to find Items:", app->menuAsJSON); + Fatal(app, "Unable to find Items in Menu"); return; } @@ -1916,68 +1787,15 @@ struct hashmap_s *radioGroupMap) { } -void parseMenuData(struct Application *app) { - - // Allocate the hashmaps we need - allocateMenuHashMaps(app); - - // Create a new menu bar - id menubar = createMenu(str("")); - - // Parse the processed menu json - app->processedMenu = json_decode(app->menuAsJSON); - - if( app->processedMenu == NULL ) { - // Parse error! - Fatal(app, "Unable to parse Menu JSON: %s", app->menuAsJSON); - return; - } - - - // Pull out the Menu - JsonNode *menuData = json_find_member(app->processedMenu, "Menu"); - if( menuData == NULL ) { - // Parse error! - Fatal(app, "Unable to find Menu data: %s", app->processedMenu); - return; - } - - - parseMenu(app, menubar, menuData, &menuItemMapForApplicationMenu, - "checkboxMenuCallbackForApplicationMenu:", "radioMenuCallbackForApplicationMenu:", "menuCallbackForApplicationMenu:"); - - // Create the radiogroup cache - JsonNode *radioGroups = json_find_member(app->processedMenu, "RadioGroups"); - if( radioGroups == NULL ) { - // Parse error! - Fatal(app, "Unable to find RadioGroups data: %s", app->processedMenu); - return; - } - - // Iterate radio groups - JsonNode *radioGroup; - json_foreach(radioGroup, radioGroups) { - // Get item label - processRadioGroup(radioGroup, &menuItemMapForApplicationMenu, &radioGroupMapForApplicationMenu); - } - - // Apply the menu bar - msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menubar); - -} - - // UpdateMenu replaces the current menu with the given one void UpdateMenu(struct Application *app, const char *menuAsJSON) { Debug(app, "Menu is now: %s", menuAsJSON); ON_MAIN_THREAD ( - - // Free up memory - destroyMenu(app); - - // Set the menu JSON - app->menuAsJSON = menuAsJSON; - parseMenuData(app); + DeleteMenu(app->applicationMenu); + Menu* newMenu = NewApplicationMenu(menuAsJSON); + id menu = GetMenu(newMenu); + app->applicationMenu = newMenu; + msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu); ); } @@ -2378,11 +2196,6 @@ void Run(struct Application *app, int argc, char **argv) { msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground")); } - // If we have a menu, process it -// if( app->menuAsJSON != NULL ) { -// parseMenuData(app); -// } - // If we have an application menu, process it if( app->applicationMenu != NULL ) { id menu = GetMenu(app->applicationMenu); diff --git a/v2/internal/ffenestri/menu_darwin.h b/v2/internal/ffenestri/menu_darwin.h index fbcbffa4..8e6cc54f 100644 --- a/v2/internal/ffenestri/menu_darwin.h +++ b/v2/internal/ffenestri/menu_darwin.h @@ -7,6 +7,9 @@ #include "common.h" +enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2}; +enum MenuType {ApplicationMenu = 0}; + extern void messageFromWindowCallback(const char *); typedef struct { @@ -27,14 +30,13 @@ typedef struct { // The NSMenu for this menu id menu; - // The names of the menu callbacks - SEL textMenuCallbackName; - SEL checkboxMenuCallbackName; - SEL radioMenuCallbackName; - // The commands for the menu callbacks const char *callbackCommand; + // This indicates if we are an Application Menu, tray menu or context menu + enum MenuType menuType; + + } Menu; // NewMenu creates a new Menu struct, saving the given menu structure as JSON @@ -48,14 +50,6 @@ Menu* NewMenu(const char *menuAsJSON) { // No title by default result->title = ""; - // No callbacks by default - result->textMenuCallbackName = NULL; - result->checkboxMenuCallbackName = NULL; - result->radioMenuCallbackName = NULL; - - // Callback Commands - result->callbackCommand = ""; - // Initialise menuCallbackDataCache vec_init(&result->callbackDataCache); @@ -73,10 +67,7 @@ Menu* NewMenu(const char *menuAsJSON) { Menu* NewApplicationMenu(const char *menuAsJSON) { Menu *result = NewMenu(menuAsJSON); - result->textMenuCallbackName = s("textMenuItemCallback:"); - result->checkboxMenuCallbackName = s("checkboxMenuCallbackForApplicationMenu:"); - result->radioMenuCallbackName = s("radioMenuCallbackForApplicationMenu:"); - result->callbackCommand = "MC"; + result->menuType = ApplicationMenu; return result; } @@ -84,15 +75,17 @@ typedef struct { id menuItem; Menu *menu; const char *menuID; + enum MenuItemType menuItemType; } MenuItemCallbackData; -MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID) { +MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType) { MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData)); result->menu = menu; result->menuID = menuID; result->menuItem = menuItem; + result->menuItemType = menuItemType; // Store reference to this so we can destroy later vec_push(&menu->callbackDataCache, result); @@ -100,6 +93,8 @@ MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const return result; } + + void DeleteMenu(Menu *menu) { // Free menu item hashmap @@ -121,15 +116,79 @@ void DeleteMenu(Menu *menu) { // Release the vector memory vec_deinit(&menu->callbackDataCache); + msg(menu->menu, s("release")); + free(menu); } +const char* createTextMenuMessage(MenuItemCallbackData *callbackData) { + + switch( callbackData->menu->menuType ) { + case ApplicationMenu: + return concat("MC", callbackData->menuID); + } + return NULL; +} + +const char* createCheckBoxMenuMessage(MenuItemCallbackData *callbackData) { + + switch( callbackData->menu->menuType ) { + case ApplicationMenu: + return concat("MC", callbackData->menuID); + } + return NULL; +} + +const char* createRadioMenuMessage(MenuItemCallbackData *callbackData) { + + switch( callbackData->menu->menuType ) { + case ApplicationMenu: + return concat("MC", callbackData->menuID); + } + return NULL; +} // Callback for text menu items -void textMenuItemCallback(id self, SEL cmd, id sender) { +void menuItemCallback(id self, SEL cmd, id sender) { MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue")); + const char *message; + + // Update checkbox / radio item + if( callbackData->menuItemType == Checkbox) { + // Toggle state + bool state = msg(callbackData->menuItem, s("state")); + msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn)); + } else if( callbackData->menuItemType == Radio ) { + // Check the menu items' current state + bool selected = msg(callbackData->menuItem, s("state")); + + // If it's already selected, exit early + if (selected) return; + + // Get this item's radio group members and turn them off + id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuID, strlen(callbackData->menuID)); + + // Uncheck all members of the group + id thisMember = members[0]; + int count = 0; + while(thisMember != NULL) { + msg(thisMember, s("setState:"), NSControlStateValueOff); + count = count + 1; + thisMember = members[count]; + } + + // check the selected menu item + msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn); + } + + // Generate message to send to backend + if( callbackData->menu->menuType == ApplicationMenu ) { + message = concat("MC", callbackData->menuID); + } + + // TODO: Add other menu types here! + // Notify the backend - const char *message = concat(callbackData->menu->callbackCommand, callbackData->menuID); messageFromWindowCallback(message); MEMFREE(message); } @@ -497,12 +556,15 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char // Store the item in the menu item map hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item); - id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid); + // Create a MenuItemCallbackData + MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio); + + id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback); msg(item, s("setRepresentedObject:"), wrappedId); id key = processAcceleratorKey(acceleratorkey); - msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), menu->radioMenuCallbackName, key); + msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key); msg(item, s("setEnabled:"), !disabled); msg(item, s("autorelease")); @@ -514,14 +576,18 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char } id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key) { + id item = ALLOC("NSMenuItem"); // Store the item in the menu item map hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item); - id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid); + // Create a MenuItemCallbackData + MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox); + + id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback); msg(item, s("setRepresentedObject:"), wrappedId); - msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), menu->checkboxMenuCallbackName, str(key)); + msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key)); msg(item, s("setEnabled:"), !disabled); msg(item, s("autorelease")); msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); @@ -533,14 +599,14 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char id item = ALLOC("NSMenuItem"); // Create a MenuItemCallbackData - MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid); + MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text); id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback); msg(item, s("setRepresentedObject:"), wrappedId); id key = processAcceleratorKey(acceleratorkey); msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), - menu->textMenuCallbackName, key); + s("menuItemCallback:"), key); msg(item, s("setEnabled:"), !disabled); msg(item, s("autorelease")); @@ -557,8 +623,6 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) { - printf("Processing Menu Item! menu = %p, parentMenu = %p, item = %p\n", menu, parentMenu, item); - // Check if this item is hidden and if so, exit early! bool hidden = false; getJSONBool(item, "Hidden", &hidden); @@ -697,6 +761,40 @@ void processMenuData(Menu *menu, JsonNode *menuData) { } } +void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) { + + int groupLength; + getJSONInt(radioGroup, "Length", &groupLength); + JsonNode *members = json_find_member(radioGroup, "Members"); + JsonNode *member; + + // Allocate array + size_t arrayLength = sizeof(id)*(groupLength+1); + id memberList[arrayLength]; + + // Build the radio group items + int count=0; + json_foreach(member, members) { + // Get menu by id + id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_)); + // Save Member + memberList[count] = menuItem; + count = count + 1; + } + // Null terminate array + memberList[groupLength] = 0; + + // Store the members + json_foreach(member, members) { + // Copy the memberList + char *newMemberList = (char *)malloc(arrayLength); + memcpy(newMemberList, memberList, arrayLength); + // add group to each member of group + hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList); + } + +} + id GetMenu(Menu *menu) { menu->menu = createMenu(str("")); @@ -720,6 +818,20 @@ id GetMenu(Menu *menu) { // Save the reference so we can delete it later menu->processedMenu = processedMenu; + // Create the radiogroup cache + JsonNode *radioGroups = json_find_member(menu->processedMenu, "RadioGroups"); + if( radioGroups == NULL ) { + // Parse error! + ABORT("Unable to find RadioGroups data: %s", menu->processedMenu); + } + + // Iterate radio groups + JsonNode *radioGroup; + json_foreach(radioGroup, radioGroups) { + // Get item label + processRadioGroupJSON(menu, radioGroup); + } + return menu->menu; }