Normalisation of callbacks for menus. App menu converted to new Menus.

This commit is contained in:
Lea Anthony
2021-01-06 20:50:41 +11:00
parent c65522f0b6
commit bd74d45a91
2 changed files with 147 additions and 222 deletions

View File

@@ -19,12 +19,6 @@ extern const unsigned char *defaultDialogIcons[];
// MAIN DEBUG FLAG
int debug;
// MenuItem map for the application menu
struct hashmap_s menuItemMapForApplicationMenu;
// RadioGroup map for the application menu. Maps a menuitem id with its associated radio group items
struct hashmap_s radioGroupMapForApplicationMenu;
// MenuItem map for the tray menu
struct hashmap_s menuItemMapForTrayMenu;
@@ -136,8 +130,6 @@ struct Application {
// Menu
Menu *applicationMenu;
const char *menuAsJSON;
JsonNode *processedMenu;
// Tray
const char *trayMenuAsJSON;
@@ -398,15 +390,6 @@ const char* createContextMenuMessage(const char *menuItemID, const char *givenCo
return result;
}
// Callback for menu items
void menuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Notify the backend
const char *message = concat("MC", menuItemID);
messageFromWindowCallback(message);
MEMFREE(message);
}
// Callback for tray items
void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
@@ -428,25 +411,6 @@ void menuItemPressedForContextMenus(id self, SEL cmd, id sender) {
MEMFREE(contextMenuMessage);
}
// Callback for menu items
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Get the menu item from the menu item map
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
// Get the current state
bool state = msg(menuItem, s("state"));
// Toggle the state
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
// Notify the backend
const char *message = concat("MC", menuItemID);
messageFromWindowCallback(message);
MEMFREE(message);
}
// Callback for tray menu items
void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
@@ -487,43 +451,6 @@ void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct
MEMFREE(contextMenuMessage);
}
// radioMenuItemPressedForApplicationMenu
void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Get the menu item from the menu item map
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
// Check the menu items' current state
bool selected = msg(menuItem, s("state"));
// If it's already selected, exit early
if (selected) {
return;
}
// Get this item's radio group members and turn them off
id *members = (id*)hashmap_get(&radioGroupMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
// Uncheck all members of the group
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(menuItem, s("setState:"), NSControlStateValueOn);
// Notify the backend
const char *message = concat("MC", menuItemID);
messageFromWindowCallback(message);
MEMFREE(message);
}
// radioMenuItemPressedForTrayMenu
void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
@@ -650,22 +577,6 @@ void themeChanged(id self, SEL cmd, id sender) {
// Debug(app, "willFinishLaunching called!");
// }
void allocateMenuHashMaps(struct Application *app) {
// Allocate new menuItem map
if( 0 != hashmap_create((const unsigned)16, &menuItemMapForApplicationMenu)) {
// Couldn't allocate map
Fatal(app, "Not enough memory to allocate menuItemMapForApplicationMenu!");
return;
}
// Allocate the Radio Group Cache
if( 0 != hashmap_create((const unsigned)4, &radioGroupMapForApplicationMenu)) {
// Couldn't allocate map
Fatal(app, "Not enough memory to allocate radioGroupMapForApplicationMenu!");
return;
}
}
void allocateTrayHashMaps(struct Application *app) {
// Allocate new menuItem map
if( 0 != hashmap_create((const unsigned)16, &menuItemMapForTrayMenu)) {
@@ -749,8 +660,6 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
// Menu
result->applicationMenu = NULL;
result->menuAsJSON = NULL;
result->processedMenu = NULL;
// Tray
result->trayMenuAsJSON = NULL;
@@ -780,37 +689,6 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
return -1;
}
void destroyMenu(struct Application *app) {
if( app->menuAsJSON == NULL ) {
return;
}
// Free menu item hashmap
hashmap_destroy(&menuItemMapForApplicationMenu);
// Free radio group members
if( hashmap_num_entries(&radioGroupMapForApplicationMenu) > 0 ) {
if (0!=hashmap_iterate_pairs(&radioGroupMapForApplicationMenu, freeHashmapItem, NULL)) {
Fatal(app, "failed to release hashmap entries!");
}
}
//Free radio groups hashmap
hashmap_destroy(&radioGroupMapForApplicationMenu);
// Release processed menu
if( app->processedMenu != NULL) {
json_delete(app->processedMenu);
app->processedMenu = NULL;
}
// Remove the menu if we have one
id menubar = msg(msg(c("NSApplication"), s("sharedApplication")), s("mainMenu"));
Debug(app, "Destroying menubar: %p", menubar);
msg(menubar, s("release"));
}
void destroyContextMenus(struct Application *app) {
// Free menu item hashmap
@@ -919,9 +797,6 @@ void DestroyApplication(struct Application *app) {
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
}
// Destroy the menu
destroyMenu(app);
// Delete the application menu if we have one
if( app->applicationMenu != NULL ) {
DeleteMenu(app->applicationMenu);
@@ -1411,7 +1286,6 @@ void SetDebug(void *applicationPointer, int flag) {
// SetMenu sets the initial menu for the application
void SetMenu(struct Application *app, const char *menuAsJSON) {
app->menuAsJSON = menuAsJSON;
app->applicationMenu = NewApplicationMenu(menuAsJSON);
}
@@ -1514,9 +1388,6 @@ void createDelegate(struct Application *app) {
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// Menu Callbacks
class_addMethod(delegateClass, s("menuCallbackForApplicationMenu:"), (IMP)menuItemPressedForApplicationMenu, "v@:@");
class_addMethod(delegateClass, s("checkboxMenuCallbackForApplicationMenu:"), (IMP) checkboxMenuItemPressedForApplicationMenu, "v@:@");
class_addMethod(delegateClass, s("radioMenuCallbackForApplicationMenu:"), (IMP) radioMenuItemPressedForApplicationMenu, "v@:@");
class_addMethod(delegateClass, s("menuCallbackForTrayMenu:"), (IMP)menuItemPressedForTrayMenu, "v@:@");
class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@");
class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@");
@@ -1525,7 +1396,7 @@ void createDelegate(struct Application *app) {
class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@");
// Refactoring menu handling
class_addMethod(delegateClass, s("textMenuItemCallback:"), (IMP)textMenuItemCallback, "v@:@");
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
// Script handler
@@ -1855,7 +1726,7 @@ void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct ha
JsonNode *items = json_find_member(menu, "Items");
if( items == NULL ) {
// Parse error!
Fatal(app, "Unable to find Items:", app->menuAsJSON);
Fatal(app, "Unable to find Items in Menu");
return;
}
@@ -1916,68 +1787,15 @@ struct hashmap_s *radioGroupMap) {
}
void parseMenuData(struct Application *app) {
// Allocate the hashmaps we need
allocateMenuHashMaps(app);
// Create a new menu bar
id menubar = createMenu(str(""));
// Parse the processed menu json
app->processedMenu = json_decode(app->menuAsJSON);
if( app->processedMenu == NULL ) {
// Parse error!
Fatal(app, "Unable to parse Menu JSON: %s", app->menuAsJSON);
return;
}
// Pull out the Menu
JsonNode *menuData = json_find_member(app->processedMenu, "Menu");
if( menuData == NULL ) {
// Parse error!
Fatal(app, "Unable to find Menu data: %s", app->processedMenu);
return;
}
parseMenu(app, menubar, menuData, &menuItemMapForApplicationMenu,
"checkboxMenuCallbackForApplicationMenu:", "radioMenuCallbackForApplicationMenu:", "menuCallbackForApplicationMenu:");
// Create the radiogroup cache
JsonNode *radioGroups = json_find_member(app->processedMenu, "RadioGroups");
if( radioGroups == NULL ) {
// Parse error!
Fatal(app, "Unable to find RadioGroups data: %s", app->processedMenu);
return;
}
// Iterate radio groups
JsonNode *radioGroup;
json_foreach(radioGroup, radioGroups) {
// Get item label
processRadioGroup(radioGroup, &menuItemMapForApplicationMenu, &radioGroupMapForApplicationMenu);
}
// Apply the menu bar
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menubar);
}
// UpdateMenu replaces the current menu with the given one
void UpdateMenu(struct Application *app, const char *menuAsJSON) {
Debug(app, "Menu is now: %s", menuAsJSON);
ON_MAIN_THREAD (
// Free up memory
destroyMenu(app);
// Set the menu JSON
app->menuAsJSON = menuAsJSON;
parseMenuData(app);
DeleteMenu(app->applicationMenu);
Menu* newMenu = NewApplicationMenu(menuAsJSON);
id menu = GetMenu(newMenu);
app->applicationMenu = newMenu;
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
);
}
@@ -2378,11 +2196,6 @@ void Run(struct Application *app, int argc, char **argv) {
msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
}
// If we have a menu, process it
// if( app->menuAsJSON != NULL ) {
// parseMenuData(app);
// }
// If we have an application menu, process it
if( app->applicationMenu != NULL ) {
id menu = GetMenu(app->applicationMenu);

View File

@@ -7,6 +7,9 @@
#include "common.h"
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
enum MenuType {ApplicationMenu = 0};
extern void messageFromWindowCallback(const char *);
typedef struct {
@@ -27,14 +30,13 @@ typedef struct {
// The NSMenu for this menu
id menu;
// The names of the menu callbacks
SEL textMenuCallbackName;
SEL checkboxMenuCallbackName;
SEL radioMenuCallbackName;
// The commands for the menu callbacks
const char *callbackCommand;
// This indicates if we are an Application Menu, tray menu or context menu
enum MenuType menuType;
} Menu;
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
@@ -48,14 +50,6 @@ Menu* NewMenu(const char *menuAsJSON) {
// No title by default
result->title = "";
// No callbacks by default
result->textMenuCallbackName = NULL;
result->checkboxMenuCallbackName = NULL;
result->radioMenuCallbackName = NULL;
// Callback Commands
result->callbackCommand = "";
// Initialise menuCallbackDataCache
vec_init(&result->callbackDataCache);
@@ -73,10 +67,7 @@ Menu* NewMenu(const char *menuAsJSON) {
Menu* NewApplicationMenu(const char *menuAsJSON) {
Menu *result = NewMenu(menuAsJSON);
result->textMenuCallbackName = s("textMenuItemCallback:");
result->checkboxMenuCallbackName = s("checkboxMenuCallbackForApplicationMenu:");
result->radioMenuCallbackName = s("radioMenuCallbackForApplicationMenu:");
result->callbackCommand = "MC";
result->menuType = ApplicationMenu;
return result;
}
@@ -84,15 +75,17 @@ typedef struct {
id menuItem;
Menu *menu;
const char *menuID;
enum MenuItemType menuItemType;
} MenuItemCallbackData;
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID) {
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType) {
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
result->menu = menu;
result->menuID = menuID;
result->menuItem = menuItem;
result->menuItemType = menuItemType;
// Store reference to this so we can destroy later
vec_push(&menu->callbackDataCache, result);
@@ -100,6 +93,8 @@ MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const
return result;
}
void DeleteMenu(Menu *menu) {
// Free menu item hashmap
@@ -121,15 +116,79 @@ void DeleteMenu(Menu *menu) {
// Release the vector memory
vec_deinit(&menu->callbackDataCache);
msg(menu->menu, s("release"));
free(menu);
}
const char* createTextMenuMessage(MenuItemCallbackData *callbackData) {
switch( callbackData->menu->menuType ) {
case ApplicationMenu:
return concat("MC", callbackData->menuID);
}
return NULL;
}
const char* createCheckBoxMenuMessage(MenuItemCallbackData *callbackData) {
switch( callbackData->menu->menuType ) {
case ApplicationMenu:
return concat("MC", callbackData->menuID);
}
return NULL;
}
const char* createRadioMenuMessage(MenuItemCallbackData *callbackData) {
switch( callbackData->menu->menuType ) {
case ApplicationMenu:
return concat("MC", callbackData->menuID);
}
return NULL;
}
// Callback for text menu items
void textMenuItemCallback(id self, SEL cmd, id sender) {
void menuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
const char *message;
// Update checkbox / radio item
if( callbackData->menuItemType == Checkbox) {
// Toggle state
bool state = msg(callbackData->menuItem, s("state"));
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
} else if( callbackData->menuItemType == Radio ) {
// Check the menu items' current state
bool selected = msg(callbackData->menuItem, s("state"));
// If it's already selected, exit early
if (selected) return;
// Get this item's radio group members and turn them off
id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuID, strlen(callbackData->menuID));
// Uncheck all members of the group
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
}
// Generate message to send to backend
if( callbackData->menu->menuType == ApplicationMenu ) {
message = concat("MC", callbackData->menuID);
}
// TODO: Add other menu types here!
// Notify the backend
const char *message = concat(callbackData->menu->callbackCommand, callbackData->menuID);
messageFromWindowCallback(message);
MEMFREE(message);
}
@@ -497,12 +556,15 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
// Store the item in the menu item map
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), menu->radioMenuCallbackName, key);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
@@ -514,14 +576,18 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
}
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), menu->checkboxMenuCallbackName, str(key));
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
@@ -533,14 +599,14 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid);
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
menu->textMenuCallbackName, key);
s("menuItemCallback:"), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
@@ -557,8 +623,6 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
printf("Processing Menu Item! menu = %p, parentMenu = %p, item = %p\n", menu, parentMenu, item);
// Check if this item is hidden and if so, exit early!
bool hidden = false;
getJSONBool(item, "Hidden", &hidden);
@@ -697,6 +761,40 @@ void processMenuData(Menu *menu, JsonNode *menuData) {
}
}
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
int groupLength;
getJSONInt(radioGroup, "Length", &groupLength);
JsonNode *members = json_find_member(radioGroup, "Members");
JsonNode *member;
// Allocate array
size_t arrayLength = sizeof(id)*(groupLength+1);
id memberList[arrayLength];
// Build the radio group items
int count=0;
json_foreach(member, members) {
// Get menu by id
id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
// Save Member
memberList[count] = menuItem;
count = count + 1;
}
// Null terminate array
memberList[groupLength] = 0;
// Store the members
json_foreach(member, members) {
// Copy the memberList
char *newMemberList = (char *)malloc(arrayLength);
memcpy(newMemberList, memberList, arrayLength);
// add group to each member of group
hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
}
}
id GetMenu(Menu *menu) {
menu->menu = createMenu(str(""));
@@ -720,6 +818,20 @@ id GetMenu(Menu *menu) {
// Save the reference so we can delete it later
menu->processedMenu = processedMenu;
// Create the radiogroup cache
JsonNode *radioGroups = json_find_member(menu->processedMenu, "RadioGroups");
if( radioGroups == NULL ) {
// Parse error!
ABORT("Unable to find RadioGroups data: %s", menu->processedMenu);
}
// Iterate radio groups
JsonNode *radioGroup;
json_foreach(radioGroup, radioGroups) {
// Get item label
processRadioGroupJSON(menu, radioGroup);
}
return menu->menu;
}