From 0f7acd39fcfef5f424c7c9bbe2db57de913f2a4d Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Tue, 12 Jan 2021 20:39:19 +1100 Subject: [PATCH] [WIP] Fix the madness --- v2/internal/ffenestri/contextmenus_darwin.c | 112 +++ v2/internal/ffenestri/contextmenus_darwin.h | 238 +----- .../ffenestri/contextmenustore_darwin.c | 50 ++ .../ffenestri/contextmenustore_darwin.h | 7 +- v2/internal/ffenestri/menu_darwin.c | 793 ++++++++++++++++++ v2/internal/ffenestri/menu_darwin.h | 792 +---------------- v2/internal/ffenestri/traymenu_darwin.c | 18 + v2/internal/ffenestri/traymenu_darwin.h | 3 + v2/internal/ffenestri/traymenustore_darwin.c | 18 + 9 files changed, 1032 insertions(+), 999 deletions(-) create mode 100644 v2/internal/ffenestri/contextmenus_darwin.c create mode 100644 v2/internal/ffenestri/contextmenustore_darwin.c create mode 100644 v2/internal/ffenestri/menu_darwin.c create mode 100644 v2/internal/ffenestri/traymenu_darwin.c diff --git a/v2/internal/ffenestri/contextmenus_darwin.c b/v2/internal/ffenestri/contextmenus_darwin.c new file mode 100644 index 00000000..6aed34c8 --- /dev/null +++ b/v2/internal/ffenestri/contextmenus_darwin.c @@ -0,0 +1,112 @@ +//// +//// Created by Lea Anthony on 6/1/21. +//// +// + +#include "ffenestri_darwin.h" +#include "common.h" +#include "contextmenus_darwin.h" +#include "menu_darwin.h" + +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; +} + + +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 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); + } + +} + +void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *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); + } + + // Show popup + msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView); + +} + diff --git a/v2/internal/ffenestri/contextmenus_darwin.h b/v2/internal/ffenestri/contextmenus_darwin.h index 325246bf..05b3069c 100644 --- a/v2/internal/ffenestri/contextmenus_darwin.h +++ b/v2/internal/ffenestri/contextmenus_darwin.h @@ -5,7 +5,7 @@ #ifndef CONTEXTMENU_DARWIN_H #define CONTEXTMENU_DARWIN_H -#include "common.h" +#include "json.h" #include "menu_darwin.h" #include "contextmenustore_darwin.h" @@ -16,237 +16,13 @@ typedef struct { } 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; -} +ContextMenu* NewContextMenu(JsonNode* menuData, ContextMenuStore* store); +ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID); +void DeleteContextMenu(ContextMenu* contextMenu); +int freeContextMenu(void *const context, struct hashmap_element_s *const e); +void ProcessContextMenus( ContextMenuStore* store); -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) { - - // Guard against NULLs - if( store == NULL ) { - return; - } - - // 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) { - - // 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); - } - - // Show popup - msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView); - -} +void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData); #endif //CONTEXTMENU_DARWIN_H diff --git a/v2/internal/ffenestri/contextmenustore_darwin.c b/v2/internal/ffenestri/contextmenustore_darwin.c new file mode 100644 index 00000000..2ce51281 --- /dev/null +++ b/v2/internal/ffenestri/contextmenustore_darwin.c @@ -0,0 +1,50 @@ + +#include "contextmenus_darwin.h" +#include "contextmenustore_darwin.h" + +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; +} + + +void DeleteContextMenuStore(ContextMenuStore* store) { + + // Guard against NULLs + if( store == NULL ) { + return; + } + + // 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); + } +} diff --git a/v2/internal/ffenestri/contextmenustore_darwin.h b/v2/internal/ffenestri/contextmenustore_darwin.h index cbdc2a39..7ba12895 100644 --- a/v2/internal/ffenestri/contextmenustore_darwin.h +++ b/v2/internal/ffenestri/contextmenustore_darwin.h @@ -5,6 +5,8 @@ #ifndef CONTEXTMENUSTORE_DARWIN_H #define CONTEXTMENUSTORE_DARWIN_H +#include "common.h" + typedef struct { // This is our context menu store which keeps track // of all instances of ContextMenus @@ -21,4 +23,7 @@ typedef struct { } ContextMenuStore; -#endif //ASSETS_C_CONTEXTMENUSTORE_DARWIN_H +ContextMenuStore* NewContextMenuStore(const char* contextMenusAsJSON); +void DeleteContextMenuStore(ContextMenuStore* store); + +#endif //CONTEXTMENUSTORE_DARWIN_H diff --git a/v2/internal/ffenestri/menu_darwin.c b/v2/internal/ffenestri/menu_darwin.c new file mode 100644 index 00000000..fe63e533 --- /dev/null +++ b/v2/internal/ffenestri/menu_darwin.c @@ -0,0 +1,793 @@ +// +// Created by Lea Anthony on 6/1/21. +// + +#include "ffenestri_darwin.h" +#include "menu_darwin.h" +#include "contextmenus_darwin.h" + +// NewMenu creates a new Menu struct, saving the given menu structure as JSON +Menu* NewMenu(JsonNode *menuData) { + + Menu *result = malloc(sizeof(Menu)); + + result->processedMenu = menuData; + + // No title by default + result->title = ""; + + // Initialise menuCallbackDataCache + vec_init(&result->callbackDataCache); + + // Allocate MenuItem Map + if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) { + ABORT("[NewMenu] Not enough memory to allocate menuItemMap!"); + } + // Allocate the Radio Group Map + if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) { + ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!"); + } + + return result; +} + +Menu* NewApplicationMenu(const char *menuAsJSON) { + + // 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; +} + +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); + + return result; +} + + + +void DeleteMenu(Menu *menu) { + + // Free menu item hashmap + hashmap_destroy(&menu->menuItemMap); + + // Free radio group members + if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) { + if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) { + ABORT("[DeleteMenu] Failed to release radioGroupMap entries!"); + } + } + + // Free radio groups hashmap + hashmap_destroy(&menu->radioGroupMap); + + // Free up the processed menu memory + json_delete(menu->processedMenu); + + // Release the vector memory + vec_deinit(&menu->callbackDataCache); + + msg(menu->menu, s("release")); + + free(menu); +} + +// Creates a JSON message for the given menuItemID and data +const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType) { + JsonNode *jsonObject = json_mkobject(); + json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID)); + json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType])); + if (data != NULL) { + json_append_member(jsonObject, "data", json_mkstring(data)); + } + const char *payload = json_encode(jsonObject); + json_delete(jsonObject); + const char *result = concat("MC", payload); + MEMFREE(payload); + return result; +} + +// Callback for text menu items +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); + } + + const char *menuID = callbackData->menuID; + const char *data = NULL; + enum MenuType menuType = callbackData->menu->menuType; + + // Generate message to send to backend + if( menuType == ContextMenuType ) { + // Get the context menu data from the menu + ContextMenuStore* store = (ContextMenuStore*) callbackData->menu->parentData; + data = store->contextMenuData; + } + + message = createMenuClickedMessage(menuID, data, menuType); + + // TODO: Add other menu types here! + + // Notify the backend + messageFromWindowCallback(message); + MEMFREE(message); +} + +id processAcceleratorKey(const char *key) { + + // Guard against no accelerator key + if( key == NULL ) { + return str(""); + } + + if( STREQ(key, "Backspace") ) { + return strunicode(0x0008); + } + if( STREQ(key, "Tab") ) { + return strunicode(0x0009); + } + if( STREQ(key, "Return") ) { + return strunicode(0x000d); + } + if( STREQ(key, "Escape") ) { + return strunicode(0x001b); + } + if( STREQ(key, "Left") ) { + return strunicode(0x001c); + } + if( STREQ(key, "Right") ) { + return strunicode(0x001d); + } + if( STREQ(key, "Up") ) { + return strunicode(0x001e); + } + if( STREQ(key, "Down") ) { + return strunicode(0x001f); + } + if( STREQ(key, "Space") ) { + return strunicode(0x0020); + } + if( STREQ(key, "Delete") ) { + return strunicode(0x007f); + } + if( STREQ(key, "Home") ) { + return strunicode(0x2196); + } + if( STREQ(key, "End") ) { + return strunicode(0x2198); + } + if( STREQ(key, "Page Up") ) { + return strunicode(0x21de); + } + if( STREQ(key, "Page Down") ) { + return strunicode(0x21df); + } + if( STREQ(key, "F1") ) { + return strunicode(0xf704); + } + if( STREQ(key, "F2") ) { + return strunicode(0xf705); + } + if( STREQ(key, "F3") ) { + return strunicode(0xf706); + } + if( STREQ(key, "F4") ) { + return strunicode(0xf707); + } + if( STREQ(key, "F5") ) { + return strunicode(0xf708); + } + if( STREQ(key, "F6") ) { + return strunicode(0xf709); + } + if( STREQ(key, "F7") ) { + return strunicode(0xf70a); + } + if( STREQ(key, "F8") ) { + return strunicode(0xf70b); + } + if( STREQ(key, "F9") ) { + return strunicode(0xf70c); + } + if( STREQ(key, "F10") ) { + return strunicode(0xf70d); + } + if( STREQ(key, "F11") ) { + return strunicode(0xf70e); + } + if( STREQ(key, "F12") ) { + return strunicode(0xf70f); + } + if( STREQ(key, "F13") ) { + return strunicode(0xf710); + } + if( STREQ(key, "F14") ) { + return strunicode(0xf711); + } + if( STREQ(key, "F15") ) { + return strunicode(0xf712); + } + if( STREQ(key, "F16") ) { + return strunicode(0xf713); + } + if( STREQ(key, "F17") ) { + return strunicode(0xf714); + } + if( STREQ(key, "F18") ) { + return strunicode(0xf715); + } + if( STREQ(key, "F19") ) { + return strunicode(0xf716); + } + if( STREQ(key, "F20") ) { + return strunicode(0xf717); + } + if( STREQ(key, "F21") ) { + return strunicode(0xf718); + } + if( STREQ(key, "F22") ) { + return strunicode(0xf719); + } + if( STREQ(key, "F23") ) { + return strunicode(0xf71a); + } + if( STREQ(key, "F24") ) { + return strunicode(0xf71b); + } + if( STREQ(key, "F25") ) { + return strunicode(0xf71c); + } + if( STREQ(key, "F26") ) { + return strunicode(0xf71d); + } + if( STREQ(key, "F27") ) { + return strunicode(0xf71e); + } + if( STREQ(key, "F28") ) { + return strunicode(0xf71f); + } + if( STREQ(key, "F29") ) { + return strunicode(0xf720); + } + if( STREQ(key, "F30") ) { + return strunicode(0xf721); + } + if( STREQ(key, "F31") ) { + return strunicode(0xf722); + } + if( STREQ(key, "F32") ) { + return strunicode(0xf723); + } + if( STREQ(key, "F33") ) { + return strunicode(0xf724); + } + if( STREQ(key, "F34") ) { + return strunicode(0xf725); + } + if( STREQ(key, "F35") ) { + return strunicode(0xf726); + } +// if( STREQ(key, "Insert") ) { +// return strunicode(0xf727); +// } +// if( STREQ(key, "PrintScreen") ) { +// return strunicode(0xf72e); +// } +// if( STREQ(key, "ScrollLock") ) { +// return strunicode(0xf72f); +// } + if( STREQ(key, "NumLock") ) { + return strunicode(0xf739); + } + + return str(key); +} + + +void addSeparator(id menu) { + id item = msg(c("NSMenuItem"), s("separatorItem")); + msg(menu, s("addItem:"), item); +} + +id createMenuItemNoAutorelease( id title, const char *action, const char *key) { + id item = ALLOC("NSMenuItem"); + msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key)); + return item; +} + +id createMenuItem(id title, const char *action, const char *key) { + id item = ALLOC("NSMenuItem"); + msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key)); + msg(item, s("autorelease")); + return item; +} + +id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) { + id item = createMenuItem(str(title), action, key); + msg(item, s("setEnabled:"), !disabled); + msg(menu, s("addItem:"), item); + return item; +} + +id createMenu(id title) { + id menu = ALLOC("NSMenu"); + msg(menu, s("initWithTitle:"), title); + msg(menu, s("setAutoenablesItems:"), NO); +// msg(menu, s("autorelease")); + return menu; +} + +void createDefaultAppMenu(id parentMenu) { +// App Menu + id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName")); + id appMenuItem = createMenuItemNoAutorelease(appName, NULL, ""); + id appMenu = createMenu(appName); + + msg(appMenuItem, s("setSubmenu:"), appMenu); + msg(parentMenu, s("addItem:"), appMenuItem); + + id title = msg(str("Hide "), s("stringByAppendingString:"), appName); + id item = createMenuItem(title, "hide:", "h"); + msg(appMenu, s("addItem:"), item); + + id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE); + msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand)); + + addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE); + + addSeparator(appMenu); + + title = msg(str("Quit "), s("stringByAppendingString:"), appName); + item = createMenuItem(title, "terminate:", "q"); + msg(appMenu, s("addItem:"), item); +} + +void createDefaultEditMenu(id parentMenu) { + // Edit Menu + id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, ""); + id editMenu = createMenu(str("Edit")); + + msg(editMenuItem, s("setSubmenu:"), editMenu); + msg(parentMenu, s("addItem:"), editMenuItem); + + addMenuItem(editMenu, "Undo", "undo:", "z", FALSE); + addMenuItem(editMenu, "Redo", "redo:", "y", FALSE); + addSeparator(editMenu); + addMenuItem(editMenu, "Cut", "cut:", "x", FALSE); + addMenuItem(editMenu, "Copy", "copy:", "c", FALSE); + addMenuItem(editMenu, "Paste", "paste:", "v", FALSE); + addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE); +} + +void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) { + const char *roleName = item->string_; + + if ( STREQ(roleName, "appMenu") ) { + createDefaultAppMenu(parentMenu); + return; + } + if ( STREQ(roleName, "editMenu")) { + createDefaultEditMenu(parentMenu); + return; + } + if ( STREQ(roleName, "hide")) { + addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE); + return; + } + if ( STREQ(roleName, "hideothers")) { + id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE); + msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand)); + return; + } + if ( STREQ(roleName, "unhide")) { + addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE); + return; + } + if ( STREQ(roleName, "front")) { + addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE); + return; + } + if ( STREQ(roleName, "undo")) { + addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE); + return; + } + if ( STREQ(roleName, "redo")) { + addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE); + return; + } + if ( STREQ(roleName, "cut")) { + addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE); + return; + } + if ( STREQ(roleName, "copy")) { + addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE); + return; + } + if ( STREQ(roleName, "paste")) { + addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE); + return; + } + if ( STREQ(roleName, "delete")) { + addMenuItem(parentMenu, "Delete", "delete:", "", FALSE); + return; + } + if( STREQ(roleName, "pasteandmatchstyle")) { + id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE); + msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)); + } + if ( STREQ(roleName, "selectall")) { + addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE); + return; + } + if ( STREQ(roleName, "minimize")) { + addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE); + return; + } + if ( STREQ(roleName, "zoom")) { + addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE); + return; + } + if ( STREQ(roleName, "quit")) { + addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE); + return; + } + if ( STREQ(roleName, "togglefullscreen")) { + addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE); + return; + } + +} + +// This converts a string array of modifiers into the +// equivalent MacOS Modifier Flags +unsigned long parseModifiers(const char **modifiers) { + + // Our result is a modifier flag list + unsigned long result = 0; + + const char *thisModifier = modifiers[0]; + int count = 0; + while( thisModifier != NULL ) { + // Determine flags + if( STREQ(thisModifier, "CmdOrCtrl") ) { + result |= NSEventModifierFlagCommand; + } + if( STREQ(thisModifier, "OptionOrAlt") ) { + result |= NSEventModifierFlagOption; + } + if( STREQ(thisModifier, "Shift") ) { + result |= NSEventModifierFlagShift; + } + if( STREQ(thisModifier, "Super") ) { + result |= NSEventModifierFlagCommand; + } + if( STREQ(thisModifier, "Control") ) { + result |= NSEventModifierFlagControl; + } + count++; + thisModifier = modifiers[count]; + } + return result; +} + +id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) { + id item = ALLOC("NSMenuItem"); + + // Store the item in the menu item map + hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item); + + // 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), s("menuItemCallback:"), key); + + msg(item, s("setEnabled:"), !disabled); + msg(item, s("autorelease")); + msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); + + msg(parentmenu, s("addItem:"), item); + return item; + +} + +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); + + // 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), s("menuItemCallback:"), str(key)); + msg(item, s("setEnabled:"), !disabled); + msg(item, s("autorelease")); + msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); + msg(parentmenu, s("addItem:"), item); + return item; +} + +id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) { + id item = ALLOC("NSMenuItem"); + + // Create a MenuItemCallbackData + 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), + s("menuItemCallback:"), key); + + msg(item, s("setEnabled:"), !disabled); + msg(item, s("autorelease")); + + // Process modifiers + if( modifiers != NULL ) { + unsigned long modifierFlags = parseModifiers(modifiers); + msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags); + } + msg(parentMenu, s("addItem:"), item); + + return item; +} + +void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) { + + // Check if this item is hidden and if so, exit early! + bool hidden = false; + getJSONBool(item, "Hidden", &hidden); + if( hidden ) { + return; + } + + // Get the role + JsonNode *role = json_find_member(item, "Role"); + if( role != NULL ) { + processMenuRole(menu, parentMenu, role); + return; + } + + // Check if this is a submenu + JsonNode *submenu = json_find_member(item, "SubMenu"); + if( submenu != NULL ) { + // Get the label + JsonNode *menuNameNode = json_find_member(item, "Label"); + const char *name = ""; + if ( menuNameNode != NULL) { + name = menuNameNode->string_; + } + + id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, ""); + id thisMenu = createMenu(str(name)); + + msg(thisMenuItem, s("setSubmenu:"), thisMenu); + msg(parentMenu, s("addItem:"), thisMenuItem); + + JsonNode *submenuItems = json_find_member(submenu, "Items"); + // If we have no items, just return + if ( submenuItems == NULL ) { + return; + } + + // Loop over submenu items + JsonNode *item; + json_foreach(item, submenuItems) { + // Get item label + processMenuItem(menu, thisMenu, item); + } + + return; + } + + // This is a user menu. Get the common data + // Get the label + const char *label = getJSONString(item, "Label"); + if ( label == NULL) { + label = "(empty)"; + } + + const char *menuid = getJSONString(item, "ID"); + if ( menuid == NULL) { + menuid = ""; + } + + bool disabled = false; + getJSONBool(item, "Disabled", &disabled); + + // Get the Accelerator + JsonNode *accelerator = json_find_member(item, "Accelerator"); + const char *acceleratorkey = NULL; + const char **modifiers = NULL; + + // If we have an accelerator + if( accelerator != NULL ) { + // Get the key + acceleratorkey = getJSONString(accelerator, "Key"); + // Check if there are modifiers + JsonNode *modifiersList = json_find_member(accelerator, "Modifiers"); + if ( modifiersList != NULL ) { + // Allocate an array of strings + int noOfModifiers = json_array_length(modifiersList); + + // Do we have any? + if (noOfModifiers > 0) { + modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1)); + JsonNode *modifier; + int count = 0; + // Iterate the modifiers and save a reference to them in our new array + json_foreach(modifier, modifiersList) { + // Get modifier name + modifiers[count] = modifier->string_; + count++; + } + // Null terminate the modifier list + modifiers[count] = NULL; + } + } + } + + // Get the Type + JsonNode *type = json_find_member(item, "Type"); + if( type != NULL ) { + + if( STREQ(type->string_, "Text")) { + processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers); + } + else if ( STREQ(type->string_, "Separator")) { + addSeparator(parentMenu); + } + else if ( STREQ(type->string_, "Checkbox")) { + // Get checked state + bool checked = false; + getJSONBool(item, "Checked", &checked); + + processCheckboxMenuItem(menu, parentMenu, label, menuid, disabled, checked, ""); + } + else if ( STREQ(type->string_, "Radio")) { + // Get checked state + bool checked = false; + getJSONBool(item, "Checked", &checked); + + processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, ""); + } + + } + + if ( modifiers != NULL ) { + free(modifiers); + } + + return; +} + +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!"); + } + + // Iterate items + JsonNode *item; + json_foreach(item, items) { + // Process each menu item + processMenuItem(menu, menu->menu, item); + } +} + +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) { + + // Pull out the menu data + JsonNode *menuData = json_find_member(menu->processedMenu, "Menu"); + if( menuData == NULL ) { + ABORT("Unable to find Menu data: %s", menu->processedMenu); + } + + menu->menu = createMenu(str("")); + + // Process the menu data + processMenuData(menu, menuData); + + // 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; +} + diff --git a/v2/internal/ffenestri/menu_darwin.h b/v2/internal/ffenestri/menu_darwin.h index dc086394..62191ae3 100644 --- a/v2/internal/ffenestri/menu_darwin.h +++ b/v2/internal/ffenestri/menu_darwin.h @@ -6,7 +6,7 @@ #define MENU_DARWIN_H #include "common.h" -#include "contextmenustore_darwin.h" +#include "ffenestri_darwin.h" enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2}; enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2}; @@ -46,44 +46,6 @@ typedef struct { } Menu; -// NewMenu creates a new Menu struct, saving the given menu structure as JSON -Menu* NewMenu(JsonNode *menuData) { - - Menu *result = malloc(sizeof(Menu)); - - result->processedMenu = menuData; - - // No title by default - result->title = ""; - - // Initialise menuCallbackDataCache - vec_init(&result->callbackDataCache); - - // Allocate MenuItem Map - if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) { - ABORT("[NewMenu] Not enough memory to allocate menuItemMap!"); - } - // Allocate the Radio Group Map - if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) { - ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!"); - } - - return result; -} - -Menu* NewApplicationMenu(const char *menuAsJSON) { - - // 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; -} typedef struct { id menuItem; @@ -93,750 +55,46 @@ typedef struct { } MenuItemCallbackData; -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; +// NewMenu creates a new Menu struct, saving the given menu structure as JSON +Menu* NewMenu(JsonNode *menuData); - // Store reference to this so we can destroy later - vec_push(&menu->callbackDataCache, result); +Menu* NewApplicationMenu(const char *menuAsJSON); +MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType); - return result; -} - - - -void DeleteMenu(Menu *menu) { - - // Free menu item hashmap - hashmap_destroy(&menu->menuItemMap); - - // Free radio group members - if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) { - if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) { - ABORT("[DeleteMenu] Failed to release radioGroupMap entries!"); - } - } - - // Free radio groups hashmap - hashmap_destroy(&menu->radioGroupMap); - - // Free up the processed menu memory - json_delete(menu->processedMenu); - - // Release the vector memory - vec_deinit(&menu->callbackDataCache); - - msg(menu->menu, s("release")); - - free(menu); -} +void DeleteMenu(Menu *menu); // Creates a JSON message for the given menuItemID and data -const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType) { - JsonNode *jsonObject = json_mkobject(); - json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID)); - json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType])); - if (data != NULL) { - json_append_member(jsonObject, "data", json_mkstring(data)); - } - const char *payload = json_encode(jsonObject); - json_delete(jsonObject); - const char *result = concat("MC", payload); - MEMFREE(payload); - return result; -} - +const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType); // Callback for text menu items -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); - } - - const char *menuID = callbackData->menuID; - const char *data = NULL; - enum MenuType menuType = callbackData->menu->menuType; - - // Generate message to send to backend - if( menuType == ContextMenuType ) { - // Get the context menu data from the menu - ContextMenuStore* store = (ContextMenuStore*) callbackData->menu->parentData; - data = store->contextMenuData; - } - - message = createMenuClickedMessage(menuID, data, menuType); - - // TODO: Add other menu types here! - - // Notify the backend - messageFromWindowCallback(message); - MEMFREE(message); -} - -id processAcceleratorKey(const char *key) { - - // Guard against no accelerator key - if( key == NULL ) { - return str(""); - } - - if( STREQ(key, "Backspace") ) { - return strunicode(0x0008); - } - if( STREQ(key, "Tab") ) { - return strunicode(0x0009); - } - if( STREQ(key, "Return") ) { - return strunicode(0x000d); - } - if( STREQ(key, "Escape") ) { - return strunicode(0x001b); - } - if( STREQ(key, "Left") ) { - return strunicode(0x001c); - } - if( STREQ(key, "Right") ) { - return strunicode(0x001d); - } - if( STREQ(key, "Up") ) { - return strunicode(0x001e); - } - if( STREQ(key, "Down") ) { - return strunicode(0x001f); - } - if( STREQ(key, "Space") ) { - return strunicode(0x0020); - } - if( STREQ(key, "Delete") ) { - return strunicode(0x007f); - } - if( STREQ(key, "Home") ) { - return strunicode(0x2196); - } - if( STREQ(key, "End") ) { - return strunicode(0x2198); - } - if( STREQ(key, "Page Up") ) { - return strunicode(0x21de); - } - if( STREQ(key, "Page Down") ) { - return strunicode(0x21df); - } - if( STREQ(key, "F1") ) { - return strunicode(0xf704); - } - if( STREQ(key, "F2") ) { - return strunicode(0xf705); - } - if( STREQ(key, "F3") ) { - return strunicode(0xf706); - } - if( STREQ(key, "F4") ) { - return strunicode(0xf707); - } - if( STREQ(key, "F5") ) { - return strunicode(0xf708); - } - if( STREQ(key, "F6") ) { - return strunicode(0xf709); - } - if( STREQ(key, "F7") ) { - return strunicode(0xf70a); - } - if( STREQ(key, "F8") ) { - return strunicode(0xf70b); - } - if( STREQ(key, "F9") ) { - return strunicode(0xf70c); - } - if( STREQ(key, "F10") ) { - return strunicode(0xf70d); - } - if( STREQ(key, "F11") ) { - return strunicode(0xf70e); - } - if( STREQ(key, "F12") ) { - return strunicode(0xf70f); - } - if( STREQ(key, "F13") ) { - return strunicode(0xf710); - } - if( STREQ(key, "F14") ) { - return strunicode(0xf711); - } - if( STREQ(key, "F15") ) { - return strunicode(0xf712); - } - if( STREQ(key, "F16") ) { - return strunicode(0xf713); - } - if( STREQ(key, "F17") ) { - return strunicode(0xf714); - } - if( STREQ(key, "F18") ) { - return strunicode(0xf715); - } - if( STREQ(key, "F19") ) { - return strunicode(0xf716); - } - if( STREQ(key, "F20") ) { - return strunicode(0xf717); - } - if( STREQ(key, "F21") ) { - return strunicode(0xf718); - } - if( STREQ(key, "F22") ) { - return strunicode(0xf719); - } - if( STREQ(key, "F23") ) { - return strunicode(0xf71a); - } - if( STREQ(key, "F24") ) { - return strunicode(0xf71b); - } - if( STREQ(key, "F25") ) { - return strunicode(0xf71c); - } - if( STREQ(key, "F26") ) { - return strunicode(0xf71d); - } - if( STREQ(key, "F27") ) { - return strunicode(0xf71e); - } - if( STREQ(key, "F28") ) { - return strunicode(0xf71f); - } - if( STREQ(key, "F29") ) { - return strunicode(0xf720); - } - if( STREQ(key, "F30") ) { - return strunicode(0xf721); - } - if( STREQ(key, "F31") ) { - return strunicode(0xf722); - } - if( STREQ(key, "F32") ) { - return strunicode(0xf723); - } - if( STREQ(key, "F33") ) { - return strunicode(0xf724); - } - if( STREQ(key, "F34") ) { - return strunicode(0xf725); - } - if( STREQ(key, "F35") ) { - return strunicode(0xf726); - } -// if( STREQ(key, "Insert") ) { -// return strunicode(0xf727); -// } -// if( STREQ(key, "PrintScreen") ) { -// return strunicode(0xf72e); -// } -// if( STREQ(key, "ScrollLock") ) { -// return strunicode(0xf72f); -// } - if( STREQ(key, "NumLock") ) { - return strunicode(0xf739); - } - - return str(key); -} +void menuItemCallback(id self, SEL cmd, id sender); +id processAcceleratorKey(const char *key); -void addSeparator(id menu) { - id item = msg(c("NSMenuItem"), s("separatorItem")); - msg(menu, s("addItem:"), item); -} +void addSeparator(id menu); +id createMenuItemNoAutorelease( id title, const char *action, const char *key); -id createMenuItemNoAutorelease( id title, const char *action, const char *key) { - id item = ALLOC("NSMenuItem"); - msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key)); - return item; -} +id createMenuItem(id title, const char *action, const char *key); -id createMenuItem(id title, const char *action, const char *key) { - id item = ALLOC("NSMenuItem"); - msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key)); - msg(item, s("autorelease")); - return item; -} +id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled); -id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) { - id item = createMenuItem(str(title), action, key); - msg(item, s("setEnabled:"), !disabled); - msg(menu, s("addItem:"), item); - return item; -} - -id createMenu(id title) { - id menu = ALLOC("NSMenu"); - msg(menu, s("initWithTitle:"), title); - msg(menu, s("setAutoenablesItems:"), NO); -// msg(menu, s("autorelease")); - return menu; -} - -void createDefaultAppMenu(id parentMenu) { -// App Menu - id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName")); - id appMenuItem = createMenuItemNoAutorelease(appName, NULL, ""); - id appMenu = createMenu(appName); - - msg(appMenuItem, s("setSubmenu:"), appMenu); - msg(parentMenu, s("addItem:"), appMenuItem); - - id title = msg(str("Hide "), s("stringByAppendingString:"), appName); - id item = createMenuItem(title, "hide:", "h"); - msg(appMenu, s("addItem:"), item); - - id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE); - msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand)); - - addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE); - - addSeparator(appMenu); - - title = msg(str("Quit "), s("stringByAppendingString:"), appName); - item = createMenuItem(title, "terminate:", "q"); - msg(appMenu, s("addItem:"), item); -} - -void createDefaultEditMenu(id parentMenu) { - // Edit Menu - id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, ""); - id editMenu = createMenu(str("Edit")); - - msg(editMenuItem, s("setSubmenu:"), editMenu); - msg(parentMenu, s("addItem:"), editMenuItem); - - addMenuItem(editMenu, "Undo", "undo:", "z", FALSE); - addMenuItem(editMenu, "Redo", "redo:", "y", FALSE); - addSeparator(editMenu); - addMenuItem(editMenu, "Cut", "cut:", "x", FALSE); - addMenuItem(editMenu, "Copy", "copy:", "c", FALSE); - addMenuItem(editMenu, "Paste", "paste:", "v", FALSE); - addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE); -} - -void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) { - const char *roleName = item->string_; - - if ( STREQ(roleName, "appMenu") ) { - createDefaultAppMenu(parentMenu); - return; - } - if ( STREQ(roleName, "editMenu")) { - createDefaultEditMenu(parentMenu); - return; - } - if ( STREQ(roleName, "hide")) { - addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE); - return; - } - if ( STREQ(roleName, "hideothers")) { - id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE); - msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand)); - return; - } - if ( STREQ(roleName, "unhide")) { - addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE); - return; - } - if ( STREQ(roleName, "front")) { - addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE); - return; - } - if ( STREQ(roleName, "undo")) { - addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE); - return; - } - if ( STREQ(roleName, "redo")) { - addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE); - return; - } - if ( STREQ(roleName, "cut")) { - addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE); - return; - } - if ( STREQ(roleName, "copy")) { - addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE); - return; - } - if ( STREQ(roleName, "paste")) { - addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE); - return; - } - if ( STREQ(roleName, "delete")) { - addMenuItem(parentMenu, "Delete", "delete:", "", FALSE); - return; - } - if( STREQ(roleName, "pasteandmatchstyle")) { - id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE); - msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)); - } - if ( STREQ(roleName, "selectall")) { - addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE); - return; - } - if ( STREQ(roleName, "minimize")) { - addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE); - return; - } - if ( STREQ(roleName, "zoom")) { - addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE); - return; - } - if ( STREQ(roleName, "quit")) { - addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE); - return; - } - if ( STREQ(roleName, "togglefullscreen")) { - addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE); - return; - } - -} +id createMenu(id title); +void createDefaultAppMenu(id parentMenu); +void createDefaultEditMenu(id parentMenu); +void processMenuRole(Menu *menu, id parentMenu, JsonNode *item); // This converts a string array of modifiers into the // equivalent MacOS Modifier Flags -unsigned long parseModifiers(const char **modifiers) { +unsigned long parseModifiers(const char **modifiers); +id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey); - // Our result is a modifier flag list - unsigned long result = 0; +id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key); - const char *thisModifier = modifiers[0]; - int count = 0; - while( thisModifier != NULL ) { - // Determine flags - if( STREQ(thisModifier, "CmdOrCtrl") ) { - result |= NSEventModifierFlagCommand; - } - if( STREQ(thisModifier, "OptionOrAlt") ) { - result |= NSEventModifierFlagOption; - } - if( STREQ(thisModifier, "Shift") ) { - result |= NSEventModifierFlagShift; - } - if( STREQ(thisModifier, "Super") ) { - result |= NSEventModifierFlagCommand; - } - if( STREQ(thisModifier, "Control") ) { - result |= NSEventModifierFlagControl; - } - count++; - thisModifier = modifiers[count]; - } - return result; -} +id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers); -id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) { - id item = ALLOC("NSMenuItem"); - - // Store the item in the menu item map - hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item); - - // 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), s("menuItemCallback:"), key); - - msg(item, s("setEnabled:"), !disabled); - msg(item, s("autorelease")); - msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); - - msg(parentmenu, s("addItem:"), item); - return item; - -} - -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); - - // 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), s("menuItemCallback:"), str(key)); - msg(item, s("setEnabled:"), !disabled); - msg(item, s("autorelease")); - msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); - msg(parentmenu, s("addItem:"), item); - return item; -} - -id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) { - id item = ALLOC("NSMenuItem"); - - // Create a MenuItemCallbackData - 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), - s("menuItemCallback:"), key); - - msg(item, s("setEnabled:"), !disabled); - msg(item, s("autorelease")); - - // Process modifiers - if( modifiers != NULL ) { - unsigned long modifierFlags = parseModifiers(modifiers); - msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags); - } - msg(parentMenu, s("addItem:"), item); - - return item; -} - -void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) { - - // Check if this item is hidden and if so, exit early! - bool hidden = false; - getJSONBool(item, "Hidden", &hidden); - if( hidden ) { - return; - } - - // Get the role - JsonNode *role = json_find_member(item, "Role"); - if( role != NULL ) { - processMenuRole(menu, parentMenu, role); - return; - } - - // Check if this is a submenu - JsonNode *submenu = json_find_member(item, "SubMenu"); - if( submenu != NULL ) { - // Get the label - JsonNode *menuNameNode = json_find_member(item, "Label"); - const char *name = ""; - if ( menuNameNode != NULL) { - name = menuNameNode->string_; - } - - id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, ""); - id thisMenu = createMenu(str(name)); - - msg(thisMenuItem, s("setSubmenu:"), thisMenu); - msg(parentMenu, s("addItem:"), thisMenuItem); - - JsonNode *submenuItems = json_find_member(submenu, "Items"); - // If we have no items, just return - if ( submenuItems == NULL ) { - return; - } - - // Loop over submenu items - JsonNode *item; - json_foreach(item, submenuItems) { - // Get item label - processMenuItem(menu, thisMenu, item); - } - - return; - } - - // This is a user menu. Get the common data - // Get the label - const char *label = getJSONString(item, "Label"); - if ( label == NULL) { - label = "(empty)"; - } - - const char *menuid = getJSONString(item, "ID"); - if ( menuid == NULL) { - menuid = ""; - } - - bool disabled = false; - getJSONBool(item, "Disabled", &disabled); - - // Get the Accelerator - JsonNode *accelerator = json_find_member(item, "Accelerator"); - const char *acceleratorkey = NULL; - const char **modifiers = NULL; - - // If we have an accelerator - if( accelerator != NULL ) { - // Get the key - acceleratorkey = getJSONString(accelerator, "Key"); - // Check if there are modifiers - JsonNode *modifiersList = json_find_member(accelerator, "Modifiers"); - if ( modifiersList != NULL ) { - // Allocate an array of strings - int noOfModifiers = json_array_length(modifiersList); - - // Do we have any? - if (noOfModifiers > 0) { - modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1)); - JsonNode *modifier; - int count = 0; - // Iterate the modifiers and save a reference to them in our new array - json_foreach(modifier, modifiersList) { - // Get modifier name - modifiers[count] = modifier->string_; - count++; - } - // Null terminate the modifier list - modifiers[count] = NULL; - } - } - } - - // Get the Type - JsonNode *type = json_find_member(item, "Type"); - if( type != NULL ) { - - if( STREQ(type->string_, "Text")) { - processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers); - } - else if ( STREQ(type->string_, "Separator")) { - addSeparator(parentMenu); - } - else if ( STREQ(type->string_, "Checkbox")) { - // Get checked state - bool checked = false; - getJSONBool(item, "Checked", &checked); - - processCheckboxMenuItem(menu, parentMenu, label, menuid, disabled, checked, ""); - } - else if ( STREQ(type->string_, "Radio")) { - // Get checked state - bool checked = false; - getJSONBool(item, "Checked", &checked); - - processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, ""); - } - - } - - if ( modifiers != NULL ) { - free(modifiers); - } - - return; -} - -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!"); - } - - // Iterate items - JsonNode *item; - json_foreach(item, items) { - // Process each menu item - processMenuItem(menu, menu->menu, item); - } -} - -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) { - - // Pull out the menu data - JsonNode *menuData = json_find_member(menu->processedMenu, "Menu"); - if( menuData == NULL ) { - ABORT("Unable to find Menu data: %s", menu->processedMenu); - } - - menu->menu = createMenu(str("")); - - // Process the menu data - processMenuData(menu, menuData); - - // 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; -} +void processMenuItem(Menu *menu, id parentMenu, JsonNode *item); +void processMenuData(Menu *menu, JsonNode *menuData); +void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ; +id GetMenu(Menu *menu); #endif //ASSETS_C_MENU_DARWIN_H diff --git a/v2/internal/ffenestri/traymenu_darwin.c b/v2/internal/ffenestri/traymenu_darwin.c new file mode 100644 index 00000000..4faa07a5 --- /dev/null +++ b/v2/internal/ffenestri/traymenu_darwin.c @@ -0,0 +1,18 @@ +// +// Created by Lea Anthony on 12/1/21. +// + +#include "common.h" +#include "traymenu_darwin.h" + + +TrayMenu* NewTrayMenu(const char* menuJSON) { + TrayMenu* result = malloc(sizeof(TrayMenu)); + + return result; +} + +void DeleteTrayMenu(TrayMenu* trayMenu) { + // Free the tray menu memory + MEMFREE(trayMenu); +} \ No newline at end of file diff --git a/v2/internal/ffenestri/traymenu_darwin.h b/v2/internal/ffenestri/traymenu_darwin.h index 81ac9c9d..816752c1 100644 --- a/v2/internal/ffenestri/traymenu_darwin.h +++ b/v2/internal/ffenestri/traymenu_darwin.h @@ -17,4 +17,7 @@ typedef struct { } TrayMenu; +TrayMenu* NewTrayMenu(const char *trayJSON); +void DeleteTrayMenu(TrayMenu* trayMenu); + #endif //TRAYMENU_DARWIN_H diff --git a/v2/internal/ffenestri/traymenustore_darwin.c b/v2/internal/ffenestri/traymenustore_darwin.c index 2c686713..e2d40861 100644 --- a/v2/internal/ffenestri/traymenustore_darwin.c +++ b/v2/internal/ffenestri/traymenustore_darwin.c @@ -4,6 +4,7 @@ #include "common.h" #include "traymenustore_darwin.h" +#include "traymenu_darwin.h" TrayMenuStore* NewTrayMenuStore() { @@ -18,11 +19,28 @@ TrayMenuStore* NewTrayMenuStore() { } void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) { + TrayMenu* newMenu = NewTrayMenu(menuJSON); + const char *ID = "TEST"; + + hashmap_put(&store->trayMenuMap, ID, strlen(ID), newMenu); + +} + +int freeTrayMenu(void *const context, struct hashmap_element_s *const e) { + DeleteTrayMenu(e->data); + return -1; } void DeleteTrayMenuStore(TrayMenuStore *trayMenuStore) { + // Delete context menus + if( hashmap_num_entries(&trayMenuStore->trayMenuMap) > 0 ) { + if (0 != hashmap_iterate_pairs(&trayMenuStore->trayMenuMap, freeTrayMenu, NULL)) { + ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!"); + } + } + // Destroy tray menu map hashmap_destroy(&trayMenuStore->trayMenuMap); } \ No newline at end of file