diff --git a/v2/internal/ffenestri/common.h b/v2/internal/ffenestri/common.h index 3156f653a..32fff2f14 100644 --- a/v2/internal/ffenestri/common.h +++ b/v2/internal/ffenestri/common.h @@ -8,6 +8,12 @@ #include "hashmap.h" #include "vec.h" +#define STREQ(a,b) strcmp(a, b) == 0 +#define STRCOPY(a) concat(a, "") +#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0 +#define MEMFREE(input) free((void*)input); input = NULL; +#define FREE_AND_SET(variable, value) if( variable != NULL ) { MEMFREE(variable); } variable = value + // Credit: https://stackoverflow.com/a/8465083 char* concat(const char *string1, const char *string2) { diff --git a/v2/internal/ffenestri/contextmenus_darwin.h b/v2/internal/ffenestri/contextmenus_darwin.h new file mode 100644 index 000000000..bb0ef896e --- /dev/null +++ b/v2/internal/ffenestri/contextmenus_darwin.h @@ -0,0 +1,251 @@ +//// +//// Created by Lea Anthony on 6/1/21. +//// +// +#ifndef CONTEXTMENU_DARWIN_H +#define CONTEXTMENU_DARWIN_H + +#include "common.h" +#include "menu_darwin.h" +#include "contextmenustore_darwin.h" + +typedef struct { + const char* ID; + id nsmenu; + Menu* menu; +} ContextMenu; + + +ContextMenu* NewContextMenu(JsonNode* menuData, ContextMenuStore *store) { + ContextMenu* result = malloc(sizeof(ContextMenu)); + result->menu = NewMenu(menuData); + result->nsmenu = NULL; + result->menu->menuType = ContextMenuType; + result->menu->parentData = store; + return result; +} + + +ContextMenuStore* NewContextMenuStore(const char* contextMenusAsJSON) { + + ContextMenuStore* result = malloc(sizeof(ContextMenuStore)); + + // Init members + result->contextMenusAsJSON = contextMenusAsJSON; + result->processedContextMenus = NULL; + result->contextMenuData = NULL; + + // Allocate Context Menu Store + if( 0 != hashmap_create((const unsigned)4, &result->contextMenuStore)) { + ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!"); + } + + return result; +} + +ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) { + return (ContextMenu*)hashmap_get(&store->contextMenuStore, (char*)contextMenuID, strlen(contextMenuID)); +} + +void DeleteContextMenu(ContextMenu* contextMenu) { + // Free Menu + DeleteMenu(contextMenu->menu); + + // Free context menu + free(contextMenu); +} + +int freeContextMenu(void *const context, struct hashmap_element_s *const e) { + DeleteContextMenu(e->data); + return -1; +} + +void DeleteContextMenuStore(ContextMenuStore* store) { + + // Delete context menus + if( hashmap_num_entries(&store->contextMenuStore) > 0 ) { + if (0 != hashmap_iterate_pairs(&store->contextMenuStore, freeContextMenu, NULL)) { + ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!"); + } + } + + // Free context menu hashmap + hashmap_destroy(&store->contextMenuStore); + + // Destroy processed Context Menus + if( store->processedContextMenus != NULL) { + json_delete(store->processedContextMenus); + store->processedContextMenus = NULL; + } + + // Delete any context menu data we may have stored + if( store->contextMenuData != NULL ) { + MEMFREE(store->contextMenuData); + } +} + +void ProcessContextMenus(ContextMenuStore* store) { + + // Decode the context menus JSON + store->processedContextMenus = json_decode(store->contextMenusAsJSON); + if( store->processedContextMenus == NULL ) { + ABORT("[ProcessContextMenus] Unable to parse Context Menus JSON: %s", store->contextMenusAsJSON); + } + +// // Get the context menu items +// JsonNode *contextMenuItems = json_find_member(store->processedContextMenus, "Items"); +// if( contextMenuItems == NULL ) { +// ABORT("[ProcessContextMenus] Unable to find Items in processedContextMenus!"); +// } + + // Iterate context menus + JsonNode *contextMenu; + json_foreach(contextMenu, store->processedContextMenus) { + + const char* ID = getJSONString(contextMenu, "ID"); + if ( ID == NULL ) { + ABORT("Unable to read ID of contextMenu\n"); + } + + JsonNode* processedMenu = json_find_member(contextMenu, "ProcessedMenu"); + if ( processedMenu == NULL ) { + ABORT("Unable to read ProcessedMenu of contextMenu\n"); + } + // Create a new context menu instance + ContextMenu *thisContextMenu = NewContextMenu(processedMenu, store); + + // Store the item in the context menu map + hashmap_put(&store->contextMenuStore, (char*)ID, strlen(ID), thisContextMenu); + } + +} + +// +// +//bool ContextMenuExists(ContextMenus *contextMenus, const char* contextMenuID) { +// return hashmap_get(&contextMenus->contextMenuStore, contextMenuID, strlen(contextMenuID)) != NULL; +//} +// +//bool AddContextMenu(ContextMenu* contextMenu) { +// +// // Check if we already have this +// if( ContextMenuExists(contextMenu->ID) ) { +// return false; +// } +// +// // Store the context menu +// if (0 != hashmap_put(&contextMenus->contextMenuStore, contextMenu->ID, strlen(contextMenu->ID), contextMenu)) { +// ABORT("Unable to add context menu with ID '%s'", contextMenu->ID); +// } +// +// return true; +//} +// +//ContextMenus* NewContextMenus(const char* contextMenusAsJSON) { +// +// ContextMenus* result = malloc(sizeof(ContextMenus)); +// +// // Allocate Context Menu Store +// if( 0 != hashmap_create((const unsigned)4, &result->contextMenuStore)) { +// ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!"); +// } +// +// // +// +// return result; +//} +// +//void ProcessContextMenus() { +// // Parse the context menu json +// processedContextMenus = json_decode(contextMenusAsJSON); +// if( processedContextMenus == NULL ) { +// // Parse error! +// ABORT("Unable to parse Context Menus JSON: %s", contextMenusAsJSON); +// } +// +// JsonNode *contextMenuItems = json_find_member(processedContextMenus, "Items"); +// if( contextMenuItems == NULL ) { +// // Parse error! +// ABORT("Unable to find Items in Context menus"); +// } +// // Iterate context menus +// JsonNode *contextMenu; +// json_foreach(contextMenu, contextMenuItems) { +// Menu *contextMenu = NewMenu() +// +// // Store the item in the context menu map +// hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu); +// } +// +//} +// +//ContextMenu* NewContextMenu() { +// ContextMenu* result = malloc(sizeof(ContextMenu)); +// +// result->menu = NewMenu(contextMenuAsJSON); +// +// return result; +//} +// + +// +//void InitContextMenuStore() { +// +//} +// + +// +//void DeleteContextMenuStore() { +// // Free radio group members +// if( hashmap_num_entries(&contextMenuStore) > 0 ) { +// if (0 != hashmap_iterate_pairs(&contextMenuStore, freeContextMenu, NULL)) { +// ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!"); +// } +// } +//} +// + +void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) { + + printf("Show context menu '%s'. Also got data '%s'.\n\n", contextMenuID, contextMenuData); + + // If no context menu ID was given, abort + if( contextMenuID == NULL ) { + return; + } + + ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID); + + // We don't need the ID now + MEMFREE(contextMenuID); + + if( contextMenu == NULL ) { + // Free context menu data + if( contextMenuData != NULL ) {} + MEMFREE(contextMenuData); + return; + } + + // We need to store the context menu data. Free existing data if we have it + // and set to the new value. + FREE_AND_SET(store->contextMenuData, contextMenuData); + + // Grab the content view and show the menu + id contentView = msg(mainWindow, s("contentView")); + + // Get the triggering event + id menuEvent = msg(mainWindow, s("currentEvent")); + + if( contextMenu->nsmenu == NULL ) { + // GetMenu creates the NSMenu + contextMenu->nsmenu = GetMenu(contextMenu->menu); + } + + printf("\n\nContext menu NSMenu = %p\n\n", contextMenu->menu->menu); + + // Show popup + msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView); + +} + +#endif //CONTEXTMENU_DARWIN_H diff --git a/v2/internal/ffenestri/contextmenustore_darwin.h b/v2/internal/ffenestri/contextmenustore_darwin.h new file mode 100644 index 000000000..cbdc2a393 --- /dev/null +++ b/v2/internal/ffenestri/contextmenustore_darwin.h @@ -0,0 +1,24 @@ +// +// Created by Lea Anthony on 7/1/21. +// + +#ifndef CONTEXTMENUSTORE_DARWIN_H +#define CONTEXTMENUSTORE_DARWIN_H + +typedef struct { + // This is our context menu store which keeps track + // of all instances of ContextMenus + struct hashmap_s contextMenuStore; + + // The raw JSON defining the context menus + const char* contextMenusAsJSON; + + // The optional data that may be passed with a context menu selection + const char* contextMenuData; + + // The processed context menus + JsonNode* processedContextMenus; + +} ContextMenuStore; + +#endif //ASSETS_C_CONTEXTMENUSTORE_DARWIN_H diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index e400e4562..af15829a3 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -3,6 +3,7 @@ #include "ffenestri_darwin.h" #include "menu_darwin.h" +#include "contextmenus_darwin.h" // References to assets @@ -28,15 +29,6 @@ struct hashmap_s radioGroupMapForTrayMenu; // A cache for all our tray icons struct hashmap_s trayIconCache; -// contextMenuMap is a hashmap of context menus keyed on a string ID -struct hashmap_s contextMenuMap; - -// MenuItem map for the context menus -struct hashmap_s menuItemMapForContextMenus; - -// RadioGroup map for the context menus. Maps a menuitem id with its associated radio group items -struct hashmap_s radioGroupMapForContextMenus; - // A cache for all our dialog icons struct hashmap_s dialogIconCache; @@ -140,6 +132,7 @@ struct Application { id statusItem; // Context Menus + ContextMenuStore *contextMenuStore; const char *contextMenusAsJSON; JsonNode *processedContextMenus; @@ -232,40 +225,6 @@ void applyWindowColour(struct Application *app) { } } -void showContextMenu(struct Application *app, const char *contextMenuID) { - - // If no context menu ID was given - if( contextMenuID == NULL ) { - // Show default context menu if we have one - return; - } - - // Look for the context menu for this ID - id contextMenu = (id)hashmap_get(&contextMenuMap, (char*)contextMenuID, strlen(contextMenuID)); - - if( contextMenu == NULL ) { -// printf("No context menu called '%s'. Available:", contextMenuID); -// dumpHashmap("contextMenuMap", &contextMenuMap); - - // Free menu id - MEMFREE(contextMenuID); - return; - } - - // Free menu id - MEMFREE(contextMenuID); - - // Grab the content view and show the menu - id contentView = msg(app->mainWindow, s("contentView")); - - // Get the triggering event - id menuEvent = msg(app->mainWindow, s("currentEvent")); - - // Show popup - msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu, menuEvent, contentView); - -} - void SetColour(struct Application *app, int red, int green, int blue, int alpha) { app->red = red; app->green = green; @@ -345,10 +304,12 @@ void messageHandler(id self, SEL cmd, id contentController, id message) { JsonNode *contextMenuIDNode = json_find_member(contextMenuMessageJSON, "id"); if( contextMenuIDNode == NULL ) { Debug(app, "Error decoding context menu ID: %s", contextMenuMessage); + json_delete(contextMenuMessageJSON); return; } if( contextMenuIDNode->tag != JSON_STRING ) { Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage); + json_delete(contextMenuMessageJSON); return; } @@ -356,23 +317,26 @@ void messageHandler(id self, SEL cmd, id contentController, id message) { JsonNode *contextMenuDataNode = json_find_member(contextMenuMessageJSON, "data"); if( contextMenuDataNode == NULL ) { Debug(app, "Error decoding context menu data: %s", contextMenuMessage); + json_delete(contextMenuMessageJSON); return; } if( contextMenuDataNode->tag != JSON_STRING ) { Debug(app, "Error decoding context menu data (Not a string): %s", contextMenuMessage); + json_delete(contextMenuMessageJSON); return; } - // Save a copy of the context menu data - if ( contextMenuData != NULL ) { - MEMFREE(contextMenuData); - } - contextMenuData = STRCOPY(contextMenuDataNode->string_); + // We need to copy these as the JSON node will be destroyed on this thread and the + // string data will become corrupt. These need to be freed by the context menu code. + const char* contextMenuID = STRCOPY(contextMenuIDNode->string_); + const char* contextMenuData = STRCOPY(contextMenuDataNode->string_); ON_MAIN_THREAD( - showContextMenu(app, contextMenuIDNode->string_); + ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData); ); + json_delete(contextMenuMessageJSON); + } else { // const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String")); const char *m = cstr(msg(message, s("body"))); @@ -380,15 +344,7 @@ void messageHandler(id self, SEL cmd, id contentController, id message) { } } -// Creates a JSON message for the given menuItemID and data -const char* createContextMenuMessage(const char *menuItemID, const char *givenContextMenuData) { - JsonNode *jsonObject = json_mkobject(); - json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID)); - json_append_member(jsonObject, "data", json_mkstring(givenContextMenuData)); - const char *result = json_encode(jsonObject); - json_delete(jsonObject); - return result; -} + // Callback for tray items void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) { @@ -400,16 +356,16 @@ void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) { } -// Callback for context menu items -void menuItemPressedForContextMenus(id self, SEL cmd, id sender) { - const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); - // Notify the backend - const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData); - const char *message = concat("XC", contextMenuMessage); - messageFromWindowCallback(message); - MEMFREE(message); - MEMFREE(contextMenuMessage); -} +//// Callback for context menu items +//void menuItemPressedForContextMenus(id self, SEL cmd, id sender) { +// const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue")); +// // Notify the backend +// const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData); +// const char *message = concat("XC", contextMenuMessage); +// messageFromWindowCallback(message); +// MEMFREE(message); +// MEMFREE(contextMenuMessage); +//} // Callback for tray menu items void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) { @@ -431,25 +387,25 @@ void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hash } // Callback for context menu items -void checkboxMenuItemPressedForContextMenus(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(&menuItemMapForContextMenus, (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 *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData); - const char *message = concat("XC", contextMenuMessage); - messageFromWindowCallback(message); - MEMFREE(message); - MEMFREE(contextMenuMessage); -} +//void checkboxMenuItemPressedForContextMenus(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(&menuItemMapForContextMenus, (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 *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData); +// const char *message = concat("XC", contextMenuMessage); +// messageFromWindowCallback(message); +// MEMFREE(message); +// MEMFREE(contextMenuMessage); +//} // radioMenuItemPressedForTrayMenu void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) { @@ -488,42 +444,42 @@ void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) { } // radioMenuItemPressedForContextMenus -void radioMenuItemPressedForContextMenus(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(&menuItemMapForContextMenus, (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(&radioGroupMapForContextMenus, (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 *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData); - const char *message = concat("XC", contextMenuMessage); - messageFromWindowCallback(message); - MEMFREE(message); - MEMFREE(contextMenuMessage); -} +//void radioMenuItemPressedForContextMenus(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(&menuItemMapForContextMenus, (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(&radioGroupMapForContextMenus, (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 *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData); +// const char *message = concat("XC", contextMenuMessage); +// messageFromWindowCallback(message); +// MEMFREE(message); +// MEMFREE(contextMenuMessage); +//} // closeWindow is called when the close button is pressed void closeWindow(id self, SEL cmd, id sender) { @@ -600,28 +556,6 @@ void allocateTrayHashMaps(struct Application *app) { } } -void allocateContextMenuHashMaps(struct Application *app) { - - // Allocate new context menu map - if( 0 != hashmap_create((const unsigned)4, &contextMenuMap)) { - // Couldn't allocate map - Fatal(app, "Not enough memory to allocate contextMenuMap!"); - } - - // Allocate new menuItem map - if( 0 != hashmap_create((const unsigned)16, &menuItemMapForContextMenus)) { - // Couldn't allocate map - Fatal(app, "Not enough memory to allocate menuItemMapForContextMenus!"); - } - - // Allocate the Radio Group Cache - if( 0 != hashmap_create((const unsigned)4, &radioGroupMapForContextMenus)) { - // Couldn't allocate map - Fatal(app, "Not enough memory to allocate radioGroupMapForContextMenus!"); - return; - } -} - void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) { // Setup main application struct struct Application *result = malloc(sizeof(struct Application)); @@ -670,6 +604,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in result->statusItem = NULL; // Context Menus + result->contextMenuStore = NULL; result->contextMenusAsJSON = NULL; result->processedContextMenus = NULL; contextMenuData = NULL; @@ -690,36 +625,7 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) { } void destroyContextMenus(struct Application *app) { - - // Free menu item hashmap - hashmap_destroy(&menuItemMapForContextMenus); - - // Free radio group members - if( hashmap_num_entries(&radioGroupMapForContextMenus) > 0 ) { - if (0!=hashmap_iterate_pairs(&radioGroupMapForContextMenus, freeHashmapItem, NULL)) { - Fatal(app, "failed to deallocate hashmap entries!"); - } - } - - //Free radio groups hashmap - hashmap_destroy(&radioGroupMapForContextMenus); - - // Free context menus - if( hashmap_num_entries(&contextMenuMap) > 0 ) { - if (0!=hashmap_iterate_pairs(&contextMenuMap, releaseNSObject, NULL)) { - Fatal(app, "failed to deallocate hashmap entries!"); - } - } - - //Free context menu map - hashmap_destroy(&contextMenuMap); - - // Destroy processed Context Menus - if( app->processedContextMenus != NULL) { - json_delete(app->processedContextMenus); - app->processedContextMenus = NULL; - } - + DeleteContextMenuStore(app->contextMenuStore); } void freeDialogIconCache(struct Application *app) { @@ -1298,10 +1204,9 @@ void SetTray(struct Application *app, const char *trayMenuAsJSON, const char *tr // SetContextMenus sets the context menu map for this application void SetContextMenus(struct Application *app, const char *contextMenusAsJSON) { - app->contextMenusAsJSON = contextMenusAsJSON; + app->contextMenuStore = NewContextMenuStore(contextMenusAsJSON); } - void SetBindings(struct Application *app, const char *bindings) { const char* temp = concat("window.wailsbindings = \"", bindings); const char* jscall = concat(temp, "\";"); @@ -1391,9 +1296,9 @@ void createDelegate(struct Application *app) { class_addMethod(delegateClass, s("menuCallbackForTrayMenu:"), (IMP)menuItemPressedForTrayMenu, "v@:@"); class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@"); class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@"); - class_addMethod(delegateClass, s("menuCallbackForContextMenus:"), (IMP)menuItemPressedForContextMenus, "v@:@"); - class_addMethod(delegateClass, s("checkboxMenuCallbackForContextMenus:"), (IMP) checkboxMenuItemPressedForContextMenus, "v@:@"); - class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@"); +// class_addMethod(delegateClass, s("menuCallbackForContextMenus:"), (IMP)menuItemPressedForContextMenus, "v@:@"); +// class_addMethod(delegateClass, s("checkboxMenuCallbackForContextMenus:"), (IMP) checkboxMenuItemPressedForContextMenus, "v@:@"); +// class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@"); // Refactoring menu handling class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@"); @@ -1799,52 +1704,52 @@ void UpdateMenu(struct Application *app, const char *menuAsJSON) { ); } -void dumpContextMenus(struct Application *app) { - dumpHashmap("menuItemMapForContextMenus", &menuItemMapForContextMenus); - printf("&menuItemMapForContextMenus = %p\n", &menuItemMapForContextMenus); +//void dumpContextMenus(struct Application *app) { +// dumpHashmap("menuItemMapForContextMenus", &menuItemMapForContextMenus); +// printf("&menuItemMapForContextMenus = %p\n", &menuItemMapForContextMenus); +// +// //Free radio groups hashmap +// dumpHashmap("radioGroupMapForContextMenus", &radioGroupMapForContextMenus); +// printf("&radioGroupMapForContextMenus = %p\n", &radioGroupMapForContextMenus); +// +// //Free context menu map +// dumpHashmap("contextMenuMap", &contextMenuMap); +// printf("&contextMenuMap = %p\n", &contextMenuMap); +//} - //Free radio groups hashmap - dumpHashmap("radioGroupMapForContextMenus", &radioGroupMapForContextMenus); - printf("&radioGroupMapForContextMenus = %p\n", &radioGroupMapForContextMenus); - - //Free context menu map - dumpHashmap("contextMenuMap", &contextMenuMap); - printf("&contextMenuMap = %p\n", &contextMenuMap); -} - -void parseContextMenus(struct Application *app) { - - // Parse the context menu json - app->processedContextMenus = json_decode(app->contextMenusAsJSON); - - if( app->processedContextMenus == NULL ) { - // Parse error! - Fatal(app, "Unable to parse Context Menus JSON: %s", app->contextMenusAsJSON); - return; - } - - JsonNode *contextMenuItems = json_find_member(app->processedContextMenus, "Items"); - if( contextMenuItems == NULL ) { - // Parse error! - Fatal(app, "Unable to find Items:", app->processedContextMenus); - return; - } - // Iterate context menus - JsonNode *contextMenu; - json_foreach(contextMenu, contextMenuItems) { - // Create a new menu - id menu = createMenu(str("")); - - // parse the menu - parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus, - "checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:"); - - // Store the item in the context menu map - hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu); - } - -// dumpContextMenus(app); -} +//void parseContextMenus(struct Application *app) { +// +// // Parse the context menu json +// app->processedContextMenus = json_decode(app->contextMenusAsJSON); +// +// if( app->processedContextMenus == NULL ) { +// // Parse error! +// Fatal(app, "Unable to parse Context Menus JSON: %s", app->contextMenusAsJSON); +// return; +// } +// +// JsonNode *contextMenuItems = json_find_member(app->processedContextMenus, "Items"); +// if( contextMenuItems == NULL ) { +// // Parse error! +// Fatal(app, "Unable to find Items:", app->processedContextMenus); +// return; +// } +// // Iterate context menus +// JsonNode *contextMenu; +// json_foreach(contextMenu, contextMenuItems) { +// // Create a new menu +// id menu = createMenu(str("")); +// +// // parse the menu +// parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus, +// "checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:"); +// +// // Store the item in the context menu map +// hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu); +// } +// +//// dumpContextMenus(app); +//} void UpdateTrayLabel(struct Application *app, const char *label) { id statusBarButton = msg(app->statusItem, s("button")); @@ -1971,17 +1876,17 @@ void UpdateTray(struct Application *app, const char *trayMenuAsJSON) { } void UpdateContextMenus(struct Application *app, const char *contextMenusAsJSON) { - ON_MAIN_THREAD ( - - dumpContextMenus(app); - - // Free up memory - destroyContextMenus(app); - - // Set the context menu JSON - app->contextMenusAsJSON = contextMenusAsJSON; - parseContextMenus(app); - ); +// ON_MAIN_THREAD ( +// +// dumpContextMenus(app); +// +// // Free up memory +// destroyContextMenus(app); +// +// // Set the context menu JSON +// app->contextMenusAsJSON = contextMenusAsJSON; +// parseContextMenus(app); +// ); } void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) { @@ -2207,12 +2112,9 @@ void Run(struct Application *app, int argc, char **argv) { parseTrayData(app); } - // Allocation the hashmaps we need - allocateContextMenuHashMaps(app); - // If we have context menus, process them - if( app->contextMenusAsJSON != NULL ) { - parseContextMenus(app); + if( app->contextMenuStore != NULL ) { + ProcessContextMenus(app->contextMenuStore); } // Process dialog icons diff --git a/v2/internal/ffenestri/ffenestri_darwin.go b/v2/internal/ffenestri/ffenestri_darwin.go index 9a438557c..85de136c2 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.go +++ b/v2/internal/ffenestri/ffenestri_darwin.go @@ -119,7 +119,24 @@ func (a *Application) processPlatformSettings() error { // Process context menus contextMenus := options.GetContextMenus(a.config) if contextMenus != nil { - contextMenusJSON, err := json.Marshal(contextMenus) + + type ProcessedContextMenu struct { + ID string + ProcessedMenu *ProcessedMenu + } + + var processedContextMenus []*ProcessedContextMenu + + // We need to iterate each context menu and pre-process it + for contextMenuID, contextMenu := range contextMenus.Items { + thisContextMenu := &ProcessedContextMenu{ + ID: contextMenuID, + ProcessedMenu: NewProcessedMenu(contextMenu), + } + processedContextMenus = append(processedContextMenus, thisContextMenu ) + } + + contextMenusJSON, err := json.Marshal(processedContextMenus) fmt.Printf("\n\nCONTEXT MENUS:\n %+v\n\n", string(contextMenusJSON)) if err != nil { return err diff --git a/v2/internal/ffenestri/ffenestri_darwin.h b/v2/internal/ffenestri/ffenestri_darwin.h index 90eb16f8c..b6f8c3400 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.h +++ b/v2/internal/ffenestri/ffenestri_darwin.h @@ -27,11 +27,6 @@ #define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds")) #define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor")) -#define STREQ(a,b) strcmp(a, b) == 0 -#define STRCOPY(a) concat(a, "") -#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0 -#define MEMFREE(input) free((void*)input); input = NULL; - #define ON_MAIN_THREAD(str) dispatch( ^{ str; } ) #define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str))) diff --git a/v2/internal/ffenestri/menu_darwin.h b/v2/internal/ffenestri/menu_darwin.h index 8e6cc54fa..0d1c7204d 100644 --- a/v2/internal/ffenestri/menu_darwin.h +++ b/v2/internal/ffenestri/menu_darwin.h @@ -6,9 +6,10 @@ #define MENU_DARWIN_H #include "common.h" +#include "contextmenustore_darwin.h" enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2}; -enum MenuType {ApplicationMenu = 0}; +enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1}; extern void messageFromWindowCallback(const char *); @@ -18,7 +19,7 @@ typedef struct { /*** Internal ***/ - const char *menuAsJSON; + // The decoded version of the Menu JSON JsonNode *processedMenu; struct hashmap_s menuItemMap; @@ -30,6 +31,9 @@ typedef struct { // The NSMenu for this menu id menu; + // The parent data, eg ContextMenuStore or Tray + void *parentData; + // The commands for the menu callbacks const char *callbackCommand; @@ -40,12 +44,11 @@ typedef struct { } Menu; // NewMenu creates a new Menu struct, saving the given menu structure as JSON -Menu* NewMenu(const char *menuAsJSON) { +Menu* NewMenu(JsonNode *menuData) { Menu *result = malloc(sizeof(Menu)); - // menuAsJSON is allocated and freed by Go - result->menuAsJSON = menuAsJSON; + result->processedMenu = menuData; // No title by default result->title = ""; @@ -66,8 +69,16 @@ Menu* NewMenu(const char *menuAsJSON) { } Menu* NewApplicationMenu(const char *menuAsJSON) { - Menu *result = NewMenu(menuAsJSON); - result->menuType = ApplicationMenu; + + // Parse the menu json + JsonNode *processedMenu = json_decode(menuAsJSON); + if( processedMenu == NULL ) { + // Parse error! + ABORT("Unable to parse Menu JSON: %s", menuAsJSON); + } + + Menu *result = NewMenu(processedMenu); + result->menuType = ApplicationMenuType; return result; } @@ -121,31 +132,14 @@ void DeleteMenu(Menu *menu) { 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; +// Creates a JSON message for the given menuItemID and data +const char* createContextMenuMessage(const char *menuItemID, const char *givenContextMenuData) { + JsonNode *jsonObject = json_mkobject(); + json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID)); + json_append_member(jsonObject, "data", json_mkstring(givenContextMenuData)); + const char *result = json_encode(jsonObject); + json_delete(jsonObject); + return result; } // Callback for text menu items @@ -182,8 +176,13 @@ void menuItemCallback(id self, SEL cmd, id sender) { } // Generate message to send to backend - if( callbackData->menu->menuType == ApplicationMenu ) { + if( callbackData->menu->menuType == ApplicationMenuType ) { message = concat("MC", callbackData->menuID); + } else if( callbackData->menu->menuType == ContextMenuType ) { + // Get the context menu data from the menu + ContextMenuStore* store = (ContextMenuStore*) callbackData->menu->parentData; + const char *contextMenuMessage = createContextMenuMessage(callbackData->menuID, store->contextMenuData); + message = concat("XC", contextMenuMessage); } // TODO: Add other menu types here! @@ -647,8 +646,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) { name = menuNameNode->string_; } - printf("\n\nProcessing submenu %s!!!\n\n", name); - id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, ""); id thisMenu = createMenu(str(name)); @@ -750,7 +747,7 @@ void processMenuData(Menu *menu, JsonNode *menuData) { JsonNode *items = json_find_member(menuData, "Items"); if( items == NULL ) { // Parse error! - ABORT("Unable to find 'Items' in menu JSON:", menu->menuAsJSON); + ABORT("Unable to find 'Items' in menu JSON!"); } // Iterate items @@ -797,27 +794,17 @@ void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) { id GetMenu(Menu *menu) { - menu->menu = createMenu(str("")); - - // Parse the menu json - JsonNode *processedMenu = json_decode(menu->menuAsJSON); - if( processedMenu == NULL ) { - // Parse error! - ABORT("Unable to parse Menu JSON: %s", menu->menuAsJSON); - } - // Pull out the menu data - JsonNode *menuData = json_find_member(processedMenu, "Menu"); + JsonNode *menuData = json_find_member(menu->processedMenu, "Menu"); if( menuData == NULL ) { - ABORT("Unable to find Menu data: %s", processedMenu); + ABORT("Unable to find Menu data: %s", menu->processedMenu); } + menu->menu = createMenu(str("")); + // Process the menu data processMenuData(menu, menuData); - // 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 ) { diff --git a/v2/internal/messagedispatcher/message/messageparser.go b/v2/internal/messagedispatcher/message/messageparser.go index 50671d3e8..fd7a4ae9e 100644 --- a/v2/internal/messagedispatcher/message/messageparser.go +++ b/v2/internal/messagedispatcher/message/messageparser.go @@ -26,6 +26,10 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){ // Parse will attempt to parse the given message func Parse(message string) (*parsedMessage, error) { + if len(message) == 0 { + return nil, fmt.Errorf("MessageParser received blank message"); + } + parseMethod := messageParsers[message[0]] if parseMethod == nil { return nil, fmt.Errorf("message type '%c' invalid", message[0])