mirror of
https://github.com/wavetermdev/wails.git
synced 2026-08-05 13:53:43 -07:00
[WIP] Normalisation of context menus creation/callbacks.
TODO: UpdateContextMenu()
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user