5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-05 07:19:04 +08:00

Huge refactor of menus. Start of normalisation of callbacks.

This commit is contained in:
Lea Anthony 2021-01-06 17:36:59 +11:00
parent 5f2c437136
commit c65522f0b6
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
3 changed files with 729 additions and 374 deletions

View File

@ -6,9 +6,32 @@
#define COMMON_H #define COMMON_H
#include "hashmap.h" #include "hashmap.h"
#include "vec.h"
void ABORT(const char *message) { // Credit: https://stackoverflow.com/a/8465083
printf("%s\n", message); char* concat(const char *string1, const char *string2)
{
const size_t len1 = strlen(string1);
const size_t len2 = strlen(string2);
char *result = malloc(len1 + len2 + 1);
strcpy(result, string1);
memcpy(result + len1, string2, len2 + 1);
return result;
}
// 10k is more than enough for a log message
#define MAXMESSAGE 1024*10
char logbuffer[MAXMESSAGE];
void ABORT(const char *message, ...) {
const char *temp = concat("FATAL: ", message);
va_list args;
va_start(args, message);
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
printf("%s\n", &logbuffer[0]);
MEMFREE(temp);
va_end(args);
exit(1); exit(1);
} }
@ -17,4 +40,32 @@ int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
return -1; return -1;
} }
const char* getJSONString(JsonNode *item, const char* key) {
// Get key
JsonNode *node = json_find_member(item, key);
const char *result = "";
if ( node != NULL && node->tag == JSON_STRING) {
result = node->string_;
}
return result;
}
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_BOOL) {
*result = node->bool_;
return true;
}
return false;
}
bool getJSONInt(JsonNode *item, const char* key, int *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_NUMBER) {
*result = (int) node->number_;
return true;
}
return false;
}
#endif //ASSETS_C_COMMON_H #endif //ASSETS_C_COMMON_H

View File

@ -57,18 +57,6 @@ typedef void (^dispatchMethod)(void);
void dispatch(dispatchMethod func) { void dispatch(dispatchMethod func) {
dispatch_async(dispatch_get_main_queue(), func); dispatch_async(dispatch_get_main_queue(), func);
} }
// Credit: https://stackoverflow.com/a/8465083
char* concat(const char *string1, const char *string2)
{
const size_t len1 = strlen(string1);
const size_t len2 = strlen(string2);
char *result = malloc(len1 + len2 + 1);
strcpy(result, string1);
memcpy(result + len1, string2, len2 + 1);
return result;
}
// yes command simply returns YES! // yes command simply returns YES!
BOOL yes(id self, SEL cmd) BOOL yes(id self, SEL cmd)
{ {
@ -174,9 +162,7 @@ struct Application {
// Debug works like sprintf but mutes if the global debug flag is true // Debug works like sprintf but mutes if the global debug flag is true
// Credit: https://stackoverflow.com/a/20639708 // Credit: https://stackoverflow.com/a/20639708
// 5k is more than enough for a log message
#define MAXMESSAGE 1024*10
char logbuffer[MAXMESSAGE];
void Debug(struct Application *app, const char *message, ... ) { void Debug(struct Application *app, const char *message, ... ) {
if ( debug ) { if ( debug ) {
const char *temp = concat("LTFfenestri (C) | ", message); const char *temp = concat("LTFfenestri (C) | ", message);
@ -1426,7 +1412,7 @@ void SetDebug(void *applicationPointer, int flag) {
// SetMenu sets the initial menu for the application // SetMenu sets the initial menu for the application
void SetMenu(struct Application *app, const char *menuAsJSON) { void SetMenu(struct Application *app, const char *menuAsJSON) {
app->menuAsJSON = menuAsJSON; app->menuAsJSON = menuAsJSON;
app->applicationMenu = NewMenu(menuAsJSON); app->applicationMenu = NewApplicationMenu(menuAsJSON);
} }
// SetTray sets the initial tray menu for the application // SetTray sets the initial tray menu for the application
@ -1538,6 +1524,10 @@ void createDelegate(struct Application *app) {
class_addMethod(delegateClass, s("checkboxMenuCallbackForContextMenus:"), (IMP) checkboxMenuItemPressedForContextMenus, "v@:@"); class_addMethod(delegateClass, s("checkboxMenuCallbackForContextMenus:"), (IMP) checkboxMenuItemPressedForContextMenus, "v@:@");
class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@"); class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@");
// Refactoring menu handling
class_addMethod(delegateClass, s("textMenuItemCallback:"), (IMP)textMenuItemCallback, "v@:@");
// Script handler // Script handler
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@"); class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
objc_registerClassPair(delegateClass); objc_registerClassPair(delegateClass);
@ -1593,82 +1583,6 @@ const char* getInitialState(struct Application *app) {
return result; return result;
} }
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 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 createMenu(id title) {
id menu = ALLOC("NSMenu");
msg(menu, s("initWithTitle:"), title);
msg(menu, s("setAutoenablesItems:"), NO);
// msg(menu, s("autorelease"));
return menu;
}
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;
}
void addSeparator(id menu) {
id item = msg(c("NSMenuItem"), s("separatorItem"));
msg(menu, s("addItem:"), item);
}
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 parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) { void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
const char *roleName = item->string_; const char *roleName = item->string_;
@ -1748,237 +1662,6 @@ void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
} }
const char* getJSONString(JsonNode *item, const char* key) {
// Get key
JsonNode *node = json_find_member(item, key);
const char *result = "";
if ( node != NULL && node->tag == JSON_STRING) {
result = node->string_;
}
return result;
}
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_BOOL) {
*result = node->bool_;
return true;
}
return false;
}
bool getJSONInt(JsonNode *item, const char* key, int *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_NUMBER) {
*result = (int) node->number_;
return true;
}
return false;
}
// 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 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);
}
id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) { id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) {
id item = ALLOC("NSMenuItem"); id item = ALLOC("NSMenuItem");
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid); id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
@ -2696,8 +2379,14 @@ void Run(struct Application *app, int argc, char **argv) {
} }
// If we have a menu, process it // If we have a menu, process it
if( app->menuAsJSON != NULL ) { // if( app->menuAsJSON != NULL ) {
parseMenuData(app); // parseMenuData(app);
// }
// If we have an application menu, process it
if( app->applicationMenu != NULL ) {
id menu = GetMenu(app->applicationMenu);
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
} }
// If we have a tray menu, process it // If we have a tray menu, process it

View File

@ -7,15 +7,34 @@
#include "common.h" #include "common.h"
extern void messageFromWindowCallback(const char *);
typedef struct { typedef struct {
const char *title;
/*** Internal ***/ /*** Internal ***/
const char *menuAsJSON; const char *menuAsJSON;
JsonNode *processedMenu;
struct hashmap_s menuItemMap; struct hashmap_s menuItemMap;
struct hashmap_s radioGroupMap; struct hashmap_s radioGroupMap;
// Vector to keep track of callback data memory
vec_void_t callbackDataCache;
// The NSMenu for this menu
id menu;
// The names of the menu callbacks
SEL textMenuCallbackName;
SEL checkboxMenuCallbackName;
SEL radioMenuCallbackName;
// The commands for the menu callbacks
const char *callbackCommand;
} Menu; } Menu;
// NewMenu creates a new Menu struct, saving the given menu structure as JSON // NewMenu creates a new Menu struct, saving the given menu structure as JSON
@ -26,6 +45,20 @@ Menu* NewMenu(const char *menuAsJSON) {
// menuAsJSON is allocated and freed by Go // menuAsJSON is allocated and freed by Go
result->menuAsJSON = menuAsJSON; result->menuAsJSON = menuAsJSON;
// No title by default
result->title = "";
// No callbacks by default
result->textMenuCallbackName = NULL;
result->checkboxMenuCallbackName = NULL;
result->radioMenuCallbackName = NULL;
// Callback Commands
result->callbackCommand = "";
// Initialise menuCallbackDataCache
vec_init(&result->callbackDataCache);
// Allocate MenuItem Map // Allocate MenuItem Map
if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) { if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
ABORT("[NewMenu] Not enough memory to allocate menuItemMap!"); ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
@ -38,6 +71,35 @@ Menu* NewMenu(const char *menuAsJSON) {
return result; return result;
} }
Menu* NewApplicationMenu(const char *menuAsJSON) {
Menu *result = NewMenu(menuAsJSON);
result->textMenuCallbackName = s("textMenuItemCallback:");
result->checkboxMenuCallbackName = s("checkboxMenuCallbackForApplicationMenu:");
result->radioMenuCallbackName = s("radioMenuCallbackForApplicationMenu:");
result->callbackCommand = "MC";
return result;
}
typedef struct {
id menuItem;
Menu *menu;
const char *menuID;
} MenuItemCallbackData;
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID) {
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
result->menu = menu;
result->menuID = menuID;
result->menuItem = menuItem;
// Store reference to this so we can destroy later
vec_push(&menu->callbackDataCache, result);
return result;
}
void DeleteMenu(Menu *menu) { void DeleteMenu(Menu *menu) {
// Free menu item hashmap // Free menu item hashmap
@ -53,59 +115,612 @@ void DeleteMenu(Menu *menu) {
// Free radio groups hashmap // Free radio groups hashmap
hashmap_destroy(&menu->radioGroupMap); hashmap_destroy(&menu->radioGroupMap);
// Free up the processed menu memory
json_delete(menu->processedMenu);
// Release the vector memory
vec_deinit(&menu->callbackDataCache);
free(menu); free(menu);
} }
void Create() { // Callback for text menu items
void textMenuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Notify the backend
const char *message = concat(callbackData->menu->callbackCommand, callbackData->menuID);
messageFromWindowCallback(message);
MEMFREE(message);
}
// id processAcceleratorKey(const char *key) {
// // Allocate the hashmaps we need
// allocateMenuHashMaps(app); // Guard against no accelerator key
// if( key == NULL ) {
// // Create a new menu bar return str("");
// id menubar = createMenu(str("")); }
//
// // Parse the processed menu json if( STREQ(key, "Backspace") ) {
// app->processedMenu = json_decode(app->menuAsJSON); return strunicode(0x0008);
// }
// if( app->processedMenu == NULL ) { if( STREQ(key, "Tab") ) {
// // Parse error! return strunicode(0x0009);
// Fatal(app, "Unable to parse Menu JSON: %s", app->menuAsJSON); }
// return; if( STREQ(key, "Return") ) {
// } return strunicode(0x000d);
// }
// if( STREQ(key, "Escape") ) {
// // Pull out the Menu return strunicode(0x001b);
// JsonNode *menuData = json_find_member(app->processedMenu, "Menu"); }
// if( menuData == NULL ) { if( STREQ(key, "Left") ) {
// // Parse error! return strunicode(0x001c);
// Fatal(app, "Unable to find Menu data: %s", app->processedMenu); }
// return; if( STREQ(key, "Right") ) {
// } return strunicode(0x001d);
// }
// if( STREQ(key, "Up") ) {
// parseMenu(app, menubar, menuData, &menuItemMapForApplicationMenu, return strunicode(0x001e);
// "checkboxMenuCallbackForApplicationMenu:", "radioMenuCallbackForApplicationMenu:", "menuCallbackForApplicationMenu:"); }
// if( STREQ(key, "Down") ) {
// // Create the radiogroup cache return strunicode(0x001f);
// JsonNode *radioGroups = json_find_member(app->processedMenu, "RadioGroups"); }
// if( radioGroups == NULL ) { if( STREQ(key, "Space") ) {
// // Parse error! return strunicode(0x0020);
// Fatal(app, "Unable to find RadioGroups data: %s", app->processedMenu); }
// return; if( STREQ(key, "Delete") ) {
// } return strunicode(0x007f);
// }
// // Iterate radio groups if( STREQ(key, "Home") ) {
// JsonNode *radioGroup; return strunicode(0x2196);
// json_foreach(radioGroup, radioGroups) { }
// // Get item label if( STREQ(key, "End") ) {
// processRadioGroup(radioGroup, &menuItemMapForApplicationMenu, &radioGroupMapForApplicationMenu); return strunicode(0x2198);
// } }
// if( STREQ(key, "Page Up") ) {
// // Apply the menu bar return strunicode(0x21de);
// msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menubar); }
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);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), menu->radioMenuCallbackName, 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);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
msg(item, s("setRepresentedObject:"), wrappedId);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), menu->checkboxMenuCallbackName, 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);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
menu->textMenuCallbackName, key);
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) {
printf("Processing Menu Item! menu = %p, parentMenu = %p, item = %p\n", menu, parentMenu, item);
// Check if this item is hidden and if so, exit early!
bool hidden = false;
getJSONBool(item, "Hidden", &hidden);
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_;
}
printf("\n\nProcessing submenu %s!!!\n\n", name);
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
id thisMenu = createMenu(str(name));
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
msg(parentMenu, s("addItem:"), thisMenuItem);
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenu) {
// 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:", menu->menuAsJSON);
}
// Iterate items
JsonNode *item;
json_foreach(item, items) {
// Process each menu item
processMenuItem(menu, menu->menu, item);
}
}
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");
if( menuData == NULL ) {
ABORT("Unable to find Menu data: %s", processedMenu);
}
// Process the menu data
processMenuData(menu, menuData);
// Save the reference so we can delete it later
menu->processedMenu = processedMenu;
return menu->menu;
}
#endif //ASSETS_C_MENU_DARWIN_H #endif //ASSETS_C_MENU_DARWIN_H