[WIP] Normalisation of context menus creation/callbacks.

TODO: UpdateContextMenu()
This commit is contained in:
Lea Anthony
2021-01-07 21:34:04 +11:00
parent bd74d45a91
commit f460bf91ef
8 changed files with 483 additions and 297 deletions
+6
View File
@@ -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)
{
+251
View File
@@ -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
@@ -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
File diff suppressed because it is too large Load Diff
+18 -1
View File
@@ -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
-5
View File
@@ -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)))
+37 -50
View File
@@ -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 ) {
@@ -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])