Compare commits

...

3 Commits

Author SHA1 Message Date
Lea Anthony
480dcb7895 TEMP COMMIT 2021-01-22 15:26:58 +11:00
Lea Anthony
c8d89cf002 [WIP] Use simpler menu id mechanism. Smaller json. Remove pointer reliance. 2021-01-18 06:07:55 +11:00
Lea Anthony
fc669ede37 Tidy up debug output 2021-01-16 14:43:32 +11:00
47 changed files with 4465 additions and 1836 deletions

View File

@@ -0,0 +1,40 @@
package counter
import "sync"
type Counter struct {
initialValue uint64
value uint64
lock sync.Mutex
}
func NewCounter(initialValue uint64) *Counter {
return &Counter{
initialValue: initialValue,
value: initialValue,
}
}
// SetValue sets the value to the given value
func (c *Counter) SetValue(value uint64) {
c.lock.Lock()
c.value = value
c.lock.Unlock()
}
// Increment adds 1 to the counter and returns its value
func (c *Counter) Increment() uint64 {
var result uint64
c.lock.Lock()
c.value++
result = c.value
c.lock.Unlock()
return result
}
// Reset the value to the initial value
func (c *Counter) Reset() {
c.lock.Lock()
c.value = c.initialValue
c.lock.Unlock()
}

View File

@@ -10,4 +10,8 @@ License: http://git.ozlabs.org/?p=ccan;a=blob;f=licenses/BSD-MIT;h=89de354795ec7
## hashmap
Homepage: https://github.com/sheredom/hashmap.h
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
## utf8.h
Homepage: https://github.com/sheredom/utf8.h
License: https://github.com/sheredom/utf8.h/blob/master/LICENSE

View File

@@ -38,21 +38,31 @@ int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
const char* getJSONString(JsonNode *item, const char* key) {
// Get key
JsonNode *node = json_find_member(item, key);
const char *result = "";
const char *result = NULL;
if ( node != NULL && node->tag == JSON_STRING) {
result = node->string_;
}
return result;
}
const char* getJSONStringDefault(JsonNode *item, const char* key, const char* defaultValue) {
const char* result = getJSONString(item, key);
if ( result == NULL ) result = defaultValue;
return result;
}
void ABORT_JSON(JsonNode *node, const char* key) {
ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node));
}
const char* mustJSONString(JsonNode *node, const char* key) {
const char* result = getJSONString(node, key);
if ( result == NULL ) {
ABORT_JSON(node, key);
const char* mustJSONString(JsonNode *item, const char* key) {
JsonNode *member = json_find_member(item, key);
if ( member == NULL ) {
ABORT_JSON(item, key);
}
const char *result = "";
if ( member != NULL && member->tag == JSON_STRING) {
result = member->string_;
}
return result;
}
@@ -68,15 +78,23 @@ JsonNode* getJSONObject(JsonNode* node, const char* key) {
return json_find_member(node, key);
}
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;
bool getJSONBool(JsonNode *node, const char* key) {
JsonNode *result = json_find_member(node, key);
if ( result != NULL && result->tag == JSON_BOOL) {
return result->bool_;
}
return false;
}
int mustJSONInt(JsonNode *node, const char* key) {
JsonNode *result = json_find_member(node, key);
if ( result == NULL || result->tag != JSON_NUMBER) {
ABORT_JSON(result, key);
}
return (int) result->number_;
}
bool getJSONInt(JsonNode *item, const char* key, int *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_NUMBER) {

View File

@@ -18,23 +18,45 @@
#define STREQ(a,b) strcmp(a, b) == 0
#define STREMPTY(string) strlen(string) == 0
#define STRCOPY(a) concat(a, "")
#define STRCOPY(str) (str == NULL ? NULL : concat(str, ""))
#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0
#define MEMFREE(input) free((void*)input); input = NULL;
#define MEMFREE(input) if(input != NULL) { free((void*)input); input = NULL; }
#define FREE_AND_SET(variable, value) if( variable != NULL ) { MEMFREE(variable); } variable = value
#define NEW(struct) malloc(sizeof(struct));
#define HASHMAP_INIT(hashmap, initialSize, name) \
if( 0 != hashmap_create((const unsigned)initialSize, &hashmap)) { \
ABORT("Not enough memory to allocate %s!\n", name); \
}
#define HASHMAP_PUT(hashmap, key, value) hashmap_put(&hashmap, key, strlen(key), value);
#define HASHMAP_GET(hashmap, key) hashmap_get(&hashmap, key, strlen(key));
#define HASHMAP_DESTROY(hashmap) hashmap_destroy(&hashmap);
#define HASHMAP_SIZE(hashmap) hashmap_num_entries(&hashmap)
#define HASHMAP_ITERATE(hashmap, function, context) if( hashmap_num_entries(&hashmap) > 0 ) { \
if (0!=hashmap_iterate_pairs(&hashmap, function, context)) { \
ABORT("failed to iterate hashmap entries!"); \
} \
}
#define JSON_ADD_STRING(jsonObject, key, value) if( value != NULL ) { json_append_member(jsonObject, (char*)key, json_mkstring(value)); }
#define JSON_ADD_NUMBER(jsonObject, key, value) json_append_member(jsonObject, (char*)key, json_mknumber(value));
#define JSON_ADD_OBJECT(jsonObject, key, value) if( value != NULL ) { json_append_member(jsonObject, (char*)key, value); }
#define JSON_ADD_BOOL(jsonObject, key, value) json_append_member(jsonObject, (char*)key, json_mkbool(value));
#define JSON_ADD_ELEMENT(jsonObject, value) json_append_element(jsonObject, value);
// Credit: https://stackoverflow.com/a/8465083
char* concat(const char *string1, const char *string2);
void ABORT(const char *message, ...);
int freeHashmapItem(void *const context, struct hashmap_element_s *const e);
int freeHashmapItem(void *context, struct hashmap_element_s *e);
const char* getJSONString(JsonNode *item, const char* key);
const char* getJSONStringDefault(JsonNode *item, const char* key, const char* defaultValue);
const char* mustJSONString(JsonNode *node, const char* key);
JsonNode* getJSONObject(JsonNode* node, const char* key);
JsonNode* mustJSONObject(JsonNode *node, const char* key);
bool getJSONBool(JsonNode *item, const char* key, bool *result);
bool getJSONBool(JsonNode *item, const char* key);
bool getJSONInt(JsonNode *item, const char* key, int *result);
int mustJSONInt(JsonNode *node, const char* key);
JsonNode* mustParseJSON(const char* JSON);
#endif //ASSETS_C_COMMON_H

View File

@@ -1,99 +1,99 @@
////
//// Created by Lea Anthony on 6/1/21.
//////
////// 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(const char* contextMenuJSON) {
ContextMenu* result = malloc(sizeof(ContextMenu));
JsonNode* processedJSON = json_decode(contextMenuJSON);
if( processedJSON == NULL ) {
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
}
// Save reference to this json
result->processedJSON = processedJSON;
result->ID = mustJSONString(processedJSON, "ID");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->menu = NewMenu(processedMenu);
result->nsmenu = NULL;
result->menu->menuType = ContextMenuType;
result->menu->parentData = result;
result->contextMenuData = NULL;
return result;
}
ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
}
void DeleteContextMenu(ContextMenu* contextMenu) {
// Free Menu
DeleteMenu(contextMenu->menu);
// Delete any context menu data we may have stored
if( contextMenu->contextMenuData != NULL ) {
MEMFREE(contextMenu->contextMenuData);
}
// Free JSON
if (contextMenu->processedJSON != NULL ) {
json_delete(contextMenu->processedJSON);
contextMenu->processedJSON = NULL;
}
// Free context menu
free(contextMenu);
}
int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
DeleteContextMenu(e->data);
return -1;
}
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(contextMenu->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);
}
//#include "ffenestri_darwin.h"
//#include "common.h"
//#include "contextmenus_darwin.h"
//#include "menu_darwin.h"
//
//ContextMenu* NewContextMenu(const char* contextMenuJSON, struct TrayMenuStore *store) {
// ContextMenu* result = malloc(sizeof(ContextMenu));
//
// JsonNode* processedJSON = json_decode(contextMenuJSON);
// if( processedJSON == NULL ) {
// ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
// }
// // Save reference to this json
// result->processedJSON = processedJSON;
//
// result->ID = mustJSONString(processedJSON, "ID");
// JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
//
//// result->menu = NewMenu(processedMenu);
// result->nsmenu = NULL;
// result->menu->menuType = ContextMenuType;
// result->menu->store = store;
// result->contextMenuData = NULL;
// return result;
//}
//
//ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
// return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
//}
//
//void DeleteContextMenu(ContextMenu* contextMenu) {
// // Free Menu
// DeleteMenu(contextMenu->menu);
//
// // Delete any context menu data we may have stored
// if( contextMenu->contextMenuData != NULL ) {
// MEMFREE(contextMenu->contextMenuData);
// }
//
// // Free JSON
// if (contextMenu->processedJSON != NULL ) {
// json_delete(contextMenu->processedJSON);
// contextMenu->processedJSON = NULL;
// }
//
// // Free context menu
// free(contextMenu);
//}
//
//int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
// DeleteContextMenu(e->data);
// return -1;
//}
//
//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(contextMenu->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);
//
//}
//

View File

@@ -1,33 +1,33 @@
//////
////// Created by Lea Anthony on 6/1/21.
//////
////
//// Created by Lea Anthony on 6/1/21.
////
//#ifndef CONTEXTMENU_DARWIN_H
//#define CONTEXTMENU_DARWIN_H
//
#ifndef CONTEXTMENU_DARWIN_H
#define CONTEXTMENU_DARWIN_H
#include "json.h"
#include "menu_darwin.h"
#include "contextmenustore_darwin.h"
typedef struct {
const char* ID;
id nsmenu;
Menu* menu;
JsonNode* processedJSON;
// Context menu data is given by the frontend when clicking a context menu.
// We send this to the backend when an item is selected
const char* contextMenuData;
} ContextMenu;
ContextMenu* NewContextMenu(const char* contextMenuJSON);
ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
void DeleteContextMenu(ContextMenu* contextMenu);
int freeContextMenu(void *const context, struct hashmap_element_s *const e);
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
#endif //CONTEXTMENU_DARWIN_H
//#include "json.h"
//#include "menu_darwin.h"
//#include "contextmenustore_darwin.h"
//
//typedef struct {
// const char* ID;
// id nsmenu;
// Menu* menu;
//
// JsonNode* processedJSON;
//
// // Context menu data is given by the frontend when clicking a context menu.
// // We send this to the backend when an item is selected
// const char* contextMenuData;
//} ContextMenu;
//
//
//ContextMenu* NewContextMenu(const char* contextMenuJSON);
//
//ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
//void DeleteContextMenu(ContextMenu* contextMenu);
//int freeContextMenu(void *const context, struct hashmap_element_s *const e);
//
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
//
//#endif //CONTEXTMENU_DARWIN_H

View File

@@ -1,65 +1,65 @@
#include "contextmenus_darwin.h"
#include "contextmenustore_darwin.h"
ContextMenuStore* NewContextMenuStore() {
ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
// Allocate Context Menu Store
if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
}
return result;
}
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
//TODO: check if there is already an entry for this menu
hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
}
ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
// Get the current menu
return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
}
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
ContextMenu* newContextMenu = NewContextMenu(menuJSON);
// Get the current menu
ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
if ( currentMenu == NULL ) {
ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
}
hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
// Save the status bar reference
DeleteContextMenu(currentMenu);
hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
}
void DeleteContextMenuStore(ContextMenuStore* store) {
// Guard against NULLs
if( store == NULL ) {
return;
}
// Delete context menus
if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
}
}
// Free context menu hashmap
hashmap_destroy(&store->contextMenuMap);
}
//
//#include "contextmenus_darwin.h"
//#include "contextmenustore_darwin.h"
//
//ContextMenuStore* NewContextMenuStore() {
//
// ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
//
// // Allocate Context Menu Store
// if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
// ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
// }
//
// return result;
//}
//
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
// ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
//
// //TODO: check if there is already an entry for this menu
// hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
//}
//
//ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
// // Get the current menu
// return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
//}
//
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
// ContextMenu* newContextMenu = NewContextMenu(menuJSON);
//
// // Get the current menu
// ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
// if ( currentMenu == NULL ) {
// ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
// }
//
// hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
//
// // Save the status bar reference
// DeleteContextMenu(currentMenu);
//
// hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
//
//}
//
//
//void DeleteContextMenuStore(ContextMenuStore* store) {
//
// // Guard against NULLs
// if( store == NULL ) {
// return;
// }
//
// // Delete context menus
// if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
// if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
// ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
// }
// }
//
// // Free context menu hashmap
// hashmap_destroy(&store->contextMenuMap);
//
//}

View File

@@ -1,27 +1,27 @@
////
//// Created by Lea Anthony on 7/1/21.
////
//
// Created by Lea Anthony on 7/1/21.
//#ifndef CONTEXTMENUSTORE_DARWIN_H
//#define CONTEXTMENUSTORE_DARWIN_H
//
#ifndef CONTEXTMENUSTORE_DARWIN_H
#define CONTEXTMENUSTORE_DARWIN_H
#include "common.h"
typedef struct {
int dummy;
// This is our context menu store which keeps track
// of all instances of ContextMenus
struct hashmap_s contextMenuMap;
} ContextMenuStore;
ContextMenuStore* NewContextMenuStore();
void DeleteContextMenuStore(ContextMenuStore* store);
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
#endif //CONTEXTMENUSTORE_DARWIN_H
//#include "common.h"
//
//typedef struct {
//
// int dummy;
//
// // This is our context menu store which keeps track
// // of all instances of ContextMenus
// struct hashmap_s contextMenuMap;
//
//} ContextMenuStore;
//
//ContextMenuStore* NewContextMenuStore();
//
//void DeleteContextMenuStore(ContextMenuStore* store);
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
//
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
//
//#endif //CONTEXTMENUSTORE_DARWIN_H

View File

@@ -30,15 +30,15 @@ extern void Fullscreen(struct Application*);
extern void UnFullscreen(struct Application*);
extern void ToggleFullscreen(struct Application*);
extern void DisableFrame(struct Application*);
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void OpenDialog(struct Application*, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
extern void SaveDialog(struct Application*, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void MessageDialog(struct Application*, const char *callbackID, const char *type, const char *title, const char *message, const char *icon, const char *button1, const char *button2, const char *button3, const char *button4, const char *defaultButton, const char *cancelButton);
extern void DarkModeEnabled(struct Application*, const char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
extern void AddContextMenu(struct Application*, const char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, const char *contextMenuJSON);
#endif

View File

@@ -1,11 +1,10 @@
#ifdef FFENESTRI_DARWIN
#include "ffenestri.h"
#include "ffenestri_darwin.h"
#include "menu_darwin.h"
#include "contextmenus_darwin.h"
#include "traymenustore_darwin.h"
#include "traymenu_darwin.h"
#include "menu.h"
//#include "contextmenus_darwin.h"
//#include "traymenustore_darwin.h"
//#include "traymenu_darwin.h"
// References to assets
#include "assets.h"
@@ -14,6 +13,10 @@ extern const unsigned char runtime;
// Dialog icons
extern const unsigned char *defaultDialogIcons[];
#include "userdialogicons.h"
#include "menu_darwin.h"
extern void messageFromWindowCallback(const char *);
typedef void (*ffenestriCallback)(const char *);
// MAIN DEBUG FLAG
int debug;
@@ -49,9 +52,6 @@ void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
printf("}\n");
}
extern void messageFromWindowCallback(const char *);
typedef void (*ffenestriCallback)(const char *);
void HideMouse() {
msg(c("NSCursor"), s("hide"));
}
@@ -105,14 +105,16 @@ struct Application {
int hideToolbarSeparator;
int windowBackgroundIsTranslucent;
// Menu
Menu *applicationMenu;
// // Menu
// Menu *applicationMenu;
//
// // Tray
// TrayMenuStore* trayMenuStore;
// Tray
TrayMenuStore* trayMenuStore;
MenuManager* menuManager;
// Context Menus
ContextMenuStore *contextMenuStore;
// ContextMenuStore *contextMenuStore;
// Callback
ffenestriCallback sendMessageToBackend;
@@ -120,6 +122,9 @@ struct Application {
// Bindings
const char *bindings;
// Flags
bool running;
};
// Debug works like sprintf but mutes if the global debug flag is true
@@ -249,6 +254,9 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
Show(app);
}
// Setup initial trays
ShowTrayMenus(app->menuManager);
// TODO: Check this actually does reduce flicker
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
@@ -316,7 +324,8 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
const char* contextMenuData = STRCOPY(contextMenuDataNode->string_);
ON_MAIN_THREAD(
ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
//TODO: FIX UP
// ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
);
json_delete(contextMenuMessageJSON);
@@ -387,7 +396,7 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
}
void destroyContextMenus(struct Application *app) {
DeleteContextMenuStore(app->contextMenuStore);
// DeleteContextMenuStore(app->contextMenuStore);
}
void freeDialogIconCache(struct Application *app) {
@@ -420,19 +429,8 @@ void DestroyApplication(struct Application *app) {
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
}
// Delete the application menu if we have one
if( app->applicationMenu != NULL ) {
DeleteMenu(app->applicationMenu);
}
// Delete the tray menu store
DeleteTrayMenuStore(app->trayMenuStore);
// Delete the context menu store
DeleteContextMenuStore(app->contextMenuStore);
// Destroy the context menus
destroyContextMenus(app);
DeleteMenuManager(app->menuManager);
// Free dialog icon cache
freeDialogIconCache(app);
@@ -579,7 +577,7 @@ void SetPosition(struct Application *app, int x, int y) {
);
}
void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *defaultButton) {
void processDialogButton(id alert, const char *buttonTitle, const char *cancelButton, const char *defaultButton) {
// If this button is set
if( STR_HAS_CHARS(buttonTitle) ) {
id button = msg(alert, s("addButtonWithTitle:"), str(buttonTitle));
@@ -592,11 +590,11 @@ void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *
}
}
extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
extern void MessageDialog(struct Application *app, const char *callbackID, const char *type, const char *title, const char *message, const char *icon, const char *button1, const char *button2, const char *button3, const char *button4, const char *defaultButton, const char *cancelButton) {
ON_MAIN_THREAD(
id alert = ALLOC_INIT("NSAlert");
char *dialogType = type;
char *dialogIcon = type;
const char *dialogType = type;
const char *dialogIcon = type;
// Default to info type
if( dialogType == NULL ) {
@@ -674,7 +672,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
}
// Run modal
char *buttonPressed;
const char *buttonPressed;
int response = (int)msg(alert, s("runModal"));
if( response == NSAlertFirstButtonReturn ) {
buttonPressed = button1;
@@ -705,7 +703,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
}
// OpenDialog opens a dialog to select files/directories
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
void OpenDialog(struct Application *app, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
Debug(app, "OpenDialog Called with callback id: %s", callbackID);
// Create an open panel
@@ -793,7 +791,7 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
}
// SaveDialog opens a dialog to select files/directories
void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
void SaveDialog(struct Application *app, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
Debug(app, "SaveDialog Called with callback id: %s", callbackID);
// Create an open panel
@@ -908,7 +906,7 @@ void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
}
void SetDebug(void *applicationPointer, int flag) {
void SetDebug(struct Application *applicationPointer, int flag) {
debug = flag;
}
@@ -916,28 +914,31 @@ void SetDebug(void *applicationPointer, int flag) {
// AddContextMenu sets the context menu map for this application
void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
// AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
}
void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
// UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
}
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
AddTrayMenuToManager(app->menuManager, trayMenuJSON);
}
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
ON_MAIN_THREAD(
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
);
TrayMenu *menu = AddTrayMenuToManager(app->menuManager, trayMenuJSON);
if( app->running ) {
ON_MAIN_THREAD(
ShowTrayMenu(menu)
);
}
}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
ON_MAIN_THREAD(
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
);
}
//void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
// ON_MAIN_THREAD(
// UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
// );
//}
void SetBindings(struct Application *app, const char *bindings) {
@@ -1026,7 +1027,7 @@ void createDelegate(struct Application *app) {
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// All Menu Items use a common callback
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)PlatformMenuItemCallback, "v@:@");
// Script handler
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
@@ -1083,402 +1084,69 @@ const char* getInitialState(struct Application *app) {
return result;
}
void parseMenuRole(struct Application *app, 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 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 wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s(menuCallback), 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;
}
id parseCheckboxMenuItem(struct Application *app, id parentmenu, const char
*title, const char *menuid, bool disabled, bool checked, const char *key,
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
hashmap_put(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), s(checkboxCallbackFunction), 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 parseRadioMenuItem(struct Application *app, id parentmenu, const char *title,
const char *menuid, bool disabled, bool checked, const char *acceleratorkey,
struct hashmap_s *menuItemMap, const char *radioCallbackFunction) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
hashmap_put(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), s(radioCallbackFunction), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
return item;
}
void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item,
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
*radioCallbackFunction, const char *menuCallbackFunction) {
// 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 ) {
parseMenuRole(app, 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);
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenu) {
// Get item label
parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
}
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")) {
parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, menuCallbackFunction);
}
else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu);
}
else if ( STREQ(type->string_, "Checkbox")) {
// Get checked state
bool checked = false;
getJSONBool(item, "Checked", &checked);
parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, checkboxCallbackFunction);
}
else if ( STREQ(type->string_, "Radio")) {
// Get checked state
bool checked = false;
getJSONBool(item, "Checked", &checked);
parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, radioCallbackFunction);
}
if ( modifiers != NULL ) {
free(modifiers);
}
return;
}
}
void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) {
JsonNode *items = json_find_member(menu, "Items");
if( items == NULL ) {
// Parse error!
Fatal(app, "Unable to find Items in Menu");
return;
}
// Iterate items
JsonNode *item;
json_foreach(item, items) {
// Get item label
parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
}
}
void dumpMemberList(const char *name, id *memberList) {
void *member = memberList[0];
int count = 0;
printf("%s = %p -> [ ", name, memberList);
while( member != NULL ) {
printf("%p ", member);
count = count + 1;
member = memberList[count];
}
printf("]\n");
}
void processRadioGroup(JsonNode *radioGroup, struct hashmap_s *menuItemMap,
struct hashmap_s *radioGroupMap) {
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(menuItemMap, (char*)member->string_, strlen(member->string_));
// Save Member
memberList[count] = menuItem;
count = count + 1;
}
// Null terminate array
memberList[groupLength] = 0;
// dumpMemberList("memberList", memberList);
// 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(radioGroupMap, member->string_, strlen(member->string_), newMemberList);
}
}
// 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 (
DeleteMenu(app->applicationMenu);
Menu* newMenu = NewApplicationMenu(menuAsJSON);
id menu = GetMenu(newMenu);
app->applicationMenu = newMenu;
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
);
}
//void dumpMemberList(const char *name, id *memberList) {
// void *member = memberList[0];
// int count = 0;
// printf("%s = %p -> [ ", name, memberList);
// while( member != NULL ) {
// printf("%p ", member);
// count = count + 1;
// member = memberList[count];
// }
// printf("]\n");
//}
// SetApplicationMenu sets the initial menu for the application
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
if ( app->applicationMenu == NULL ) {
app->applicationMenu = NewApplicationMenu(menuAsJSON);
return;
}
//void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
// if ( app->applicationMenu == NULL ) {
// app->applicationMenu = NewApplicationMenu(menuAsJSON, (struct TrayMenuStore *) app->trayMenuStore);
// return;
// }
//
// // Update menu
// updateMenu(app, menuAsJSON);
//}
// Update menu
updateMenu(app, menuAsJSON);
}
void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
unsigned int count = 0;
while( 1 ) {
const unsigned char *name = dialogIcons[count++];
if( name == 0x00 ) {
break;
}
const unsigned char *lengthAsString = dialogIcons[count++];
if( name == 0x00 ) {
break;
}
const unsigned char *data = dialogIcons[count++];
if( data == 0x00 ) {
break;
}
int length = atoi((const char *)lengthAsString);
// Create the icon and add to the hashmap
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
id dialogImage = ALLOC("NSImage");
msg(dialogImage, s("initWithData:"), imageData);
hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
}
}
void processUserDialogIcons(struct Application *app) {
// Allocate the Dialog icon hashmap
if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
// Couldn't allocate map
Fatal(app, "Not enough memory to allocate dialogIconCache!");
return;
}
processDialogIcons(&dialogIconCache, defaultDialogIcons);
processDialogIcons(&dialogIconCache, userDialogIcons);
}
//void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
//
// unsigned int count = 0;
// while( 1 ) {
// const unsigned char *name = dialogIcons[count++];
// if( name == 0x00 ) {
// break;
// }
// const unsigned char *lengthAsString = dialogIcons[count++];
// if( name == 0x00 ) {
// break;
// }
// const unsigned char *data = dialogIcons[count++];
// if( data == 0x00 ) {
// break;
// }
// int length = atoi((const char *)lengthAsString);
//
// // Create the icon and add to the hashmap
// id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
// id dialogImage = ALLOC("NSImage");
// msg(dialogImage, s("initWithData:"), imageData);
// hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
// }
//
//}
//
//void processUserDialogIcons(struct Application *app) {
//
// // Allocate the Dialog icon hashmap
// if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
// // Couldn't allocate map
// Fatal(app, "Not enough memory to allocate dialogIconCache!");
// return;
// }
//
// processDialogIcons(&dialogIconCache, defaultDialogIcons);
// processDialogIcons(&dialogIconCache, userDialogIcons);
//
//}
void Run(struct Application *app, int argc, char **argv) {
@@ -1654,17 +1322,15 @@ 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 an application menu, process it
if( app->applicationMenu != NULL ) {
id menu = GetMenu(app->applicationMenu);
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
}
// Setup initial trays
ShowTrayMenusInStore(app->trayMenuStore);
// // If we have an application menu, process it
// if( app->applicationMenu != NULL ) {
// msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), app->applicationMenu->menu);
// }
// Process dialog icons
processUserDialogIcons(app);
// processUserDialogIcons(app);
app->running = true;
// Finally call run
Debug(app, "Run called");
@@ -1674,7 +1340,7 @@ void Run(struct Application *app, int argc, char **argv) {
}
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
struct Application* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
// Load the tray icons
LoadTrayIcons();
@@ -1715,13 +1381,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->delegate = NULL;
// Menu
result->applicationMenu = NULL;
// Tray
result->trayMenuStore = NewTrayMenuStore();
result->menuManager = NewMenuManager();
// Context Menus
result->contextMenuStore = NewContextMenuStore();
// result->contextMenuStore = NewContextMenuStore();
// Window Appearance
result->titlebarAppearsTransparent = 0;
@@ -1729,8 +1392,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
result->running = false;
return (void*) result;
}
#endif

View File

@@ -58,15 +58,15 @@ func (a *Application) processPlatformSettings() error {
C.WindowBackgroundIsTranslucent(a.app)
}
// Process menu
//applicationMenu := options.GetApplicationMenu(a.config)
applicationMenu := a.menuManager.GetApplicationMenuJSON()
if applicationMenu != "" {
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
}
//// Process menu
////applicationMenu := options.GetApplicationMenu(a.config)
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
//if applicationMenu != "" {
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
//}
// Process tray
trays, err := a.menuManager.GetTrayMenus()
trays, err := a.menuManager.GetTrayMenusAsJSON()
if err != nil {
return err
}
@@ -76,16 +76,16 @@ func (a *Application) processPlatformSettings() error {
}
}
// Process context menus
contextMenus, err := a.menuManager.GetContextMenus()
if err != nil {
return err
}
if contextMenus != nil {
for _, contextMenu := range contextMenus {
C.AddContextMenu(a.app, a.string2CString(contextMenu))
}
}
//// Process context menus
//contextMenus, err := a.menuManager.GetContextMenus()
//if err != nil {
// return err
//}
//if contextMenus != nil {
// for _, contextMenu := range contextMenus {
// C.AddContextMenu(a.app, a.string2CString(contextMenu))
// }
//}
return nil
}

View File

@@ -20,6 +20,7 @@
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg(input, s("UTF8String"))
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
#define RELEASE(input) if( input != NULL ) { msg(input, s("release")); }
#define ALLOC(classname) msg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
@@ -108,6 +109,5 @@ void WebviewIsTransparent(struct Application* app);
void WindowBackgroundIsTranslucent(struct Application* app);
void SetTray(struct Application* app, const char *, const char *, const char *);
//void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *);
#endif

View File

@@ -0,0 +1,276 @@
//
// Created by Lea Anthony on 18/1/21.
//
#include "menu.h"
Menu* NewMenu(struct JsonNode* menuData, struct JsonNode* radioData, MenuManager* manager) {
if( menuData == NULL ) return NULL;
if( manager == NULL ) return NULL;
Menu *result = NEW(Menu);
// No label by default
result->label = STRCOPY(getJSONStringDefault(menuData, "l", ""));
// Setup platform specific menu data
SetupMenuPlatformData(result);
// Init menu item list
vec_init(&result->menuItems);
// Get the menu items
JsonNode* items = getJSONObject(menuData, "i");
if( items != NULL ) {
// Process the menu data
JsonNode *item;
json_foreach(item, items) {
const char *ID = mustJSONString(item, "I");
MenuItem *menuItem = HASHMAP_GET(manager->menuItems, ID);
if (menuItem == NULL) {
// Process each menu item
menuItem = processMenuItem(result, item, manager);
// Filter out separators
if (menuItem->ID != NULL) {
HASHMAP_PUT(manager->menuItems, menuItem->ID, menuItem);
}
}
AddMenuItemToMenu(result, menuItem, manager);
}
if (radioData != NULL) {
// Iterate radio groups
JsonNode *radioGroup;
json_foreach(radioGroup, radioData) {
// Get item label
processRadioGroup(result, radioGroup, manager);
}
}
}
return result;
}
MenuItem* processMenuItem(Menu *menu, JsonNode *item, MenuManager *manager) {
// Get the role
const char *role = getJSONString(item, "r");
if( role != NULL ) {
// Roles override everything else
// return NewMenuItemForRole(role, menu, item, manager);
}
Menu* submenu = NULL;
// Check if this is a submenu
// JsonNode *submenuData = json_find_member(item, "S");
// if( submenuData != NULL ) {
// submenu = NewMenu(submenuData)
// // Get the label
// JsonNode *menuNameNode = json_find_member(item, "l");
// 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, "i");
// // 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;
// }
// Get the ID
const char *menuItemID = mustJSONString(item, "I");
// Get the label(s)
const char* label = getJSONStringDefault(item, "l", "");
const char* alternateLabel = getJSONString(item, "L");
bool checked = getJSONBool(item, "c");
bool hidden = getJSONBool(item, "h");
bool disabled = getJSONBool(item, "d");
const char* RGBA = getJSONString(item, "R");
const char* font = getJSONString(item, "F");
const char* image = getJSONString(item, "i");
int fontSize = 0;
getJSONInt(item, "F", &fontSize);
// Get the Accelerator
JsonNode *accelerator = json_find_member(item, "a");
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] = STRCOPY(modifier->string_);
count++;
}
// Null terminate the modifier list
modifiers[count] = NULL;
}
}
}
// has callback?
bool hasCallback = getJSONBool(item, "C");
// Get the Type
const char *type = mustJSONString(item, "t");
MenuItem* menuItem;
enum MenuItemType menuItemType;
if( STREQ(type, "t")) {
menuItemType = Text;
}
else if ( STREQ(type, "s")) {
menuItemType = Separator;
}
else if ( STREQ(type, "c")) {
menuItemType = Checkbox;
}
else if ( STREQ(type, "r")) {
menuItemType = Radio;
} else {
menuItemType = -1;
ABORT("Unknown Menu Item type '%s'!", type);
}
menuItem = NewMenuItem(menuItemType, menuItemID, label, alternateLabel, disabled, hidden, checked, RGBA, font, fontSize, image, acceleratorKey, modifiers, hasCallback, submenu);
return menuItem;
}
void DeleteMenu(Menu* menu) {
// NULL guard
if( menu == NULL ) return;
// Delete the platform specific menu data
DeleteMenuPlatformData(menu);
// Clean up other data
MEMFREE(menu->label);
// Clear the menu items vector
vec_deinit(&menu->menuItems);
}
const char* MenuAsJSON(Menu* menu) {
if( menu == NULL ) return NULL;
JsonNode *jsonObject = json_mkobject();
JSON_ADD_STRING(jsonObject, "Label", menu->label);
return json_encode(jsonObject);
}
JsonNode* MenuAsJSONObject(Menu* menu) {
if( menu == NULL ) return NULL;
JsonNode *jsonObject = json_mkobject();
JSON_ADD_STRING(jsonObject, "Label", menu->label);
if( vec_size(&menu->menuItems) > 0 ) {
JsonNode* items = json_mkarray();
int i; MenuItem *menuItem;
vec_foreach(&menu->menuItems, menuItem, i) {
JSON_ADD_ELEMENT(items, MenuItemAsJSONObject(menuItem));
}
JSON_ADD_OBJECT(jsonObject, "Items", items);
}
return jsonObject;
}
void processRadioGroup(Menu *menu, JsonNode *radioGroup, MenuManager* manager) {
JsonNode *members = json_find_member(radioGroup, "Members");
JsonNode *member;
int groupLength = json_array_length(radioGroup);
// Allocate array
size_t arrayLength = sizeof(id)*(groupLength+1);
MenuItem* memberList[arrayLength];
// Build the radio group items
int count=0;
json_foreach(member, members) {
// Get menu by id
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, (char*)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->radioGroups, member->string_, newMemberList);
}
}
void AddMenuItemToMenu(Menu* menu, MenuItem* menuItem, MenuManager* manager) {
vec_push(&menu->menuItems, menuItem);
PlatformAddMenuItemToMenu(menu, menuItem, manager);
}
// Creates a JSON message for the given menuItemID and data
const char* CreateMenuClickedMessage(const char *menuItemID, const char *data) {
JsonNode *jsonObject = json_mkobject();
if (menuItemID == NULL ) {
ABORT("Item ID NULL for menu!!\n");
}
json_append_member(jsonObject, "i", json_mkstring(menuItemID));
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;
}

View File

@@ -0,0 +1,178 @@
//
// Created by Lea Anthony on 18/1/21.
//
#ifndef MENU_H
#define MENU_H
#include "common.h"
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2, Separator = 3};
static const char *MenuItemTypeAsString[] = {
"Text", "Checkbox", "Radio", "Separator",
};
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu",
};
typedef struct {
// Menu label
const char *label;
// A list of menuItem IDs that make up this menu
vec_void_t menuItems;
// The platform specific menu data
void *platformData;
} Menu;
typedef struct {
// ID of the tray
const char *ID;
// The tray label
const char *Label;
// The icon name
const char *Icon;
// The menu
Menu* Menu;
// Platform specific data
void* platformData;
} TrayMenu;
typedef struct {
Menu* menu;
} ApplicationMenu;
typedef struct {
const char* ID;
Menu* menu;
} ContextMenu;
typedef struct {
// This is our menu item map using the menuItem ID as a key
// map[string]*MenuItem
struct hashmap_s menuItems;
// This is our context menu map using the context menu ID as a key
// map[string]*ContextMenu
struct hashmap_s contextMenus;
// This is our tray menu map using the tray menu ID as a key
// map[string]*TrayMenu
struct hashmap_s trayMenus;
// Application Menu
Menu* applicationMenu;
// Context menu data
const char* contextMenuData;
} MenuManager;
typedef struct {
MenuManager* manager;
const char *menuItemID;
enum MenuItemType menuItemType;
} MenuItemCallbackData;
typedef struct {
const char *ID;
const char* label;
const char* alternateLabel;
bool disabled;
bool hidden;
const char* colour;
const char* font;
int fontSize;
const char* image;
bool checked;
// Keyboard shortcut
const char* acceleratorKey;
const char** modifiers;
// Type of menuItem
enum MenuItemType type;
// Indicates if the menuItem has a callback
bool hasCallback;
// The platform specific menu data
void* platformData;
// Submenu
Menu* submenu;
// Radio group for this item
vec_void_t radioGroup;
// Data for handling callbacks
MenuItemCallbackData *callbackData;
} MenuItem;
// MenuItem
MenuItem* NewMenuItem(enum MenuItemType type, const char* ID, const char* label, const char* alternateLabel, bool disabled, bool hidden, bool checked, const char* colour, const char* font, int fontsize, const char* image, const char* acceleratorKey, const char** modifiers, bool hasCallback, Menu* submenu);
void DeleteMenuItem(MenuItem* menuItem);
JsonNode* MenuItemAsJSONObject(MenuItem* menuItem);
// Menu
Menu* NewMenu(JsonNode* menuData, JsonNode* radioData, MenuManager* menuManager);
void DeleteMenu(Menu* menu);
const char* MenuAsJSON(Menu* menu);
JsonNode* MenuAsJSONObject(Menu* menu);
void AddMenuItemToMenu(Menu* menu, MenuItem* menuItem, MenuManager* manager);
MenuItem* processMenuItem(Menu *menu, JsonNode *item, MenuManager *manager);
void processRadioGroup(Menu *menu, JsonNode *radioGroup, MenuManager* manager);
// Tray
TrayMenu* NewTrayMenu(JsonNode* trayJSON, MenuManager *manager);
void DeleteTrayMenu(TrayMenu *trayMenu);
const char* TrayMenuAsJSON(TrayMenu* menu);
// MenuManager
MenuManager* NewMenuManager();
void DeleteMenuManager(MenuManager* manager);
TrayMenu* AddTrayMenuToManager(MenuManager* manager, const char* trayMenuJSON);
void ShowTrayMenus(MenuManager* manager);
// Callbacks
MenuItemCallbackData* NewMenuItemCallbackData(MenuManager *manager, const char* menuItemID, enum MenuItemType menuItemType);
void DeleteMenuItemCallbackData(MenuItemCallbackData* callbackData);
const char* CreateMenuClickedMessage(const char *menuItemID, const char *data);
// Platform
void SetupMenuPlatformData(Menu* menu);
void DeleteMenuPlatformData(Menu* menu);
void SetupMenuItemPlatformData(MenuItem* menuItem);
void DeleteMenuItemPlatformData(MenuItem* menuItem);
void SetupTrayMenuPlatformData(TrayMenu* menu);
void DeleteTrayMenuPlatformData(TrayMenu* menu);
void PlatformAddMenuItemToMenu(Menu *menu, MenuItem* menuItem, MenuManager* manager);
void PlatformUpdateTrayIcon(TrayMenu *menu);
// Platform specific methods
void UnloadTrayIcons();
void LoadTrayIcons();
void ShowTrayMenu(TrayMenu* trayMenu);
#endif //MENU_H

View File

@@ -1,178 +1,35 @@
//
// Created by Lea Anthony on 6/1/21.
// Created by Lea Anthony on 18/1/21.
//
#include "ffenestri.h"
#include "ffenestri_darwin.h"
#include "menu_darwin.h"
#include "contextmenus_darwin.h"
#include "menu.h"
#include "trayicons.h"
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData) {
// A cache for all our tray menu icons
// Global because it's a singleton
struct hashmap_s trayIconCache;
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!");
}
// Init other members
result->menu = NULL;
result->parentData = NULL;
return result;
void SetupMenuPlatformData(Menu* menu) {
MacMenu* result = NEW(MacMenu);
result->Menu = ALLOC("NSMenu");
msg(result->Menu, s("initWithTitle:"), str(menu->label));
msg(result->Menu, s("setAutoenablesItems:"), NO);
menu->platformData = (void*)result;
}
Menu* NewApplicationMenu(const char *menuAsJSON) {
void DeleteMenuPlatformData(Menu* menu) {
// Parse the menu json
JsonNode *processedMenu = json_decode(menuAsJSON);
if( processedMenu == NULL ) {
// Parse error!
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
}
// Return if null
if( menu == NULL || menu->platformData == NULL) return;
Menu *result = NewMenu(processedMenu);
result->menuType = ApplicationMenuType;
return result;
MacMenu* macMenu = (MacMenu*) menu->platformData;
RELEASE(macMenu->Menu);
macMenu->Menu = NULL;
}
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
if (menu->processedMenu != NULL) {
json_delete(menu->processedMenu);
menu->processedMenu = NULL;
}
// Release the vector memory
vec_deinit(&menu->callbackDataCache);
// Free nsmenu if we have it
if ( menu->menu != NULL ) {
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, const char *parentID) {
JsonNode *jsonObject = json_mkobject();
if (menuItemID == NULL ) {
ABORT("Item ID NULL for menu!!\n");
}
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));
}
if (parentID != NULL) {
json_append_member(jsonObject, "parentID", json_mkstring(parentID));
}
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;
const char *parentID = NULL;
// Generate message to send to backend
if( menuType == ContextMenuType ) {
// Get the context menu data from the menu
ContextMenu* contextMenu = (ContextMenu*) callbackData->menu->parentData;
data = contextMenu->contextMenuData;
parentID = contextMenu->ID;
} else if ( menuType == TrayMenuType ) {
parentID = (const char*) callbackData->menu->parentData;
}
message = createMenuClickedMessage(menuID, data, menuType, parentID);
// Notify the backend
messageFromWindowCallback(message);
MEMFREE(message);
}
id processAcceleratorKey(const char *key) {
@@ -344,161 +201,6 @@ id processAcceleratorKey(const char *key) {
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) {
@@ -531,282 +233,286 @@ unsigned long parseModifiers(const char **modifiers) {
return result;
}
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) {
void SetupMenuItemPlatformData(MenuItem* menuItem) {
// Create the platform data
MacMenuItem *macMenuItem = NEW(MacMenuItem);
menuItem->platformData = macMenuItem;
// Create the NSMenuItem
id item = ALLOC("NSMenuItem");
macMenuItem->MenuItem = item;
// Store the item in the menu item map
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
// // TODO: Process ROLE
// if( menuItem->role != NULL ) {
//
// }
// 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),
id key = processAcceleratorKey(menuItem->acceleratorKey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(menuItem->label),
s("menuItemCallback:"), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("setEnabled:"), !menuItem->disabled);
msg(item, s("autorelease"));
// Process modifiers
if( modifiers != NULL ) {
unsigned long modifierFlags = parseModifiers(modifiers);
if( menuItem->modifiers != NULL ) {
unsigned long modifierFlags = parseModifiers(menuItem->modifiers);
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
menuItem->modifiers = NULL;
}
msg(parentMenu, s("addItem:"), item);
return item;
}
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
void DeleteMenuItemPlatformData(MenuItem* menuItem) {
// Check if this item is hidden and if so, exit early!
bool hidden = false;
getJSONBool(item, "Hidden", &hidden);
if( hidden ) {
if( menuItem == NULL || menuItem->platformData == NULL) return;
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
RELEASE(macMenuItem->MenuItem);
MEMFREE(macMenuItem);
}
void PlatformAddMenuItemToMenu(Menu *menu, MenuItem* menuItem, MenuManager* manager) {
// Return if null
if( menu == NULL || menu->platformData == NULL) return;
if( menuItem == NULL || menuItem->platformData == NULL) return;
// Don't add if the item is hidden
if( menuItem->hidden ) return;
// Setup callback
if( menuItem->hasCallback ) {
// Create a MenuItemCallbackData
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
menuItem->callbackData = NewMenuItemCallbackData(manager, menuItem->ID, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuItem->callbackData);
msg(macMenuItem->MenuItem, s("setRepresentedObject:"), wrappedId);
}
MacMenu* macMenu = (MacMenu*) menu->platformData;
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
msg(macMenu->Menu, s("addItem:"), macMenuItem->MenuItem);
}
void SetupTrayMenuPlatformData(TrayMenu* menu) {
MacTrayMenu* result = NEW(MacTrayMenu);
result->statusBarItem = NULL;
// TODO: Work out how to make this customisable
result->iconPosition = NSImageLeft;
menu->platformData = (void*)result;
}
void DeleteTrayMenuPlatformData(TrayMenu* menu) {
// Return if null
if( menu == NULL || menu->platformData == NULL) return;
MacTrayMenu* macMenu = (MacTrayMenu*) menu->platformData;
RELEASE(macMenu->statusBarItem);
macMenu->statusBarItem = NULL;
}
void PlatformUpdateTrayIcon(TrayMenu* trayMenu) {
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
// Empty icon means remove it
if (trayMenu->Icon == NULL || strlen(trayMenu->Icon) == 0) {
msg(statusBarButton, s("setImage:"), NULL);
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;
id trayImage = HASHMAP_GET(trayIconCache, trayMenu->Icon);
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
msg(statusBarButton, s("setImage:"), trayImage);
}
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!");
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
// Exit early if NULL
if( trayMenu->Label == NULL ) return;
// Update button label
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
msg(statusBarButton, s("setTitle:"), str(label));
}
void ShowTrayMenu(TrayMenu* trayMenu) {
if( trayMenu == NULL || trayMenu->platformData == NULL ) return;
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
// Create a status bar item if we don't have one
if( macTrayMenu->statusBarItem == NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
macTrayMenu->statusBarItem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(macTrayMenu->statusBarItem, s("retain"));
}
// Iterate items
JsonNode *item;
json_foreach(item, items) {
// Process each menu item
processMenuItem(menu, menu->menu, item);
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
// Update the icon if needed
PlatformUpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->Label);
// If we don't have a menu, return
if( trayMenu->Menu == NULL ) return;
// Update the menu
MacMenu* macMenu = (MacMenu*) trayMenu->Menu->platformData;
msg(macTrayMenu->statusBarItem, s("setMenu:"), macMenu->Menu);
}
void LoadTrayIcons() {
// Allocate the Tray Icons
if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) {
// Couldn't allocate map
ABORT("Not enough memory to allocate trayIconCache!");
}
unsigned int count = 0;
while( 1 ) {
const unsigned char *name = trayIcons[count++];
if( name == 0x00 ) {
break;
}
const unsigned char *lengthAsString = trayIcons[count++];
if( name == 0x00 ) {
break;
}
const unsigned char *data = trayIcons[count++];
if( data == 0x00 ) {
break;
}
char *c;
unsigned long length = strtol((const char *)lengthAsString, &c, 10);
// Create the icon and add to the hashmap
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
id trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
HASHMAP_PUT(trayIconCache, (const char *)name, trayImage);
}
}
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);
}
void UnloadTrayIcons() {
// Release the tray cache images
HASHMAP_ITERATE(trayIconCache, releaseNSObject, NULL);
HASHMAP_DESTROY(trayIconCache);
}
//
//// Callback for text menu items
//void menuItemCallback(id self, SEL cmd, id sender) {
// MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// if( callbackData == NULL) {
// return;
// }
//
// struct TrayMenuStore *store = callbackData->store;
// const char* menuItemID = callbackData->menuItemID;
// id nsmenu = GetMenuItemFromStore((TrayMenuStore *) store, menuItemID);
// if ( nsmenu == NULL ) {
// // The menu has been deleted!
// return;
// }
//
// const char *message;
//
// // Update checkbox / radio item
// if( callbackData->menuItemType == Checkbox) {
// // Toggle state
// bool state = msg(nsmenu, s("state"));
// msg(nsmenu, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
// } else if( callbackData->menuItemType == Radio ) {
// // Check the menu items' current state
// bool selected = msg(nsmenu, 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->menuItemID, strlen(callbackData->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(nsmenu, s("setState:"), NSControlStateValueOn);
// }
//
// message = createMenuClickedMessage(menuItemID, GetContextMenuDataFromStore((TrayMenuStore *) store));
//
// // Notify the backend
// messageFromWindowCallback(message);
// MEMFREE(message);
//}
id GetMenu(Menu *menu) {
void PlatformMenuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
const char *message;
// 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);
MenuManager* manager = callbackData->manager;
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, callbackData->menuItemID);
if( menuItem == NULL ) return;
MacMenuItem *macMenuItem = menuItem->platformData;
id nsMenuItem = macMenuItem->MenuItem;
// Update checkbox / radio item
if( callbackData->menuItemType == Checkbox) {
// Toggle state
bool state = msg(nsMenuItem, s("state"));
msg(nsMenuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
} else if( callbackData->menuItemType == Radio ) {
// Check the menu items' current state
bool selected = msg(nsMenuItem, s("state"));
// If it's already selected, exit early
if (selected) return;
int i=0; const char *groupMenuItemID;
vec_foreach(&menuItem->radioGroup, groupMenuItemID, i) {
// Get member
MenuItem* groupMember = HASHMAP_GET(manager->menuItems, groupMenuItemID);
MacMenuItem* groupMacMenuItem = (MacMenuItem*) groupMember->platformData;
msg(groupMacMenuItem->MenuItem, s("setState:"), NSControlStateValueOff);
}
// check the selected menu item
msg(nsMenuItem, s("setState:"), NSControlStateValueOn);
}
menu->menu = createMenu(str(""));
message = CreateMenuClickedMessage(callbackData->menuItemID, manager->contextMenuData);
// 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;
// Notify the backend
messageFromWindowCallback(message);
MEMFREE(message);
}

View File

@@ -1,100 +1,21 @@
//
// Created by Lea Anthony on 6/1/21.
// Created by Lea Anthony on 18/1/21.
//
#ifndef MENU_DARWIN_H
#define MENU_DARWIN_H
#include "common.h"
#include "ffenestri_darwin.h"
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu",
};
extern void messageFromWindowCallback(const char *);
#include "menu.h"
typedef struct {
const char *title;
/*** Internal ***/
// The decoded version of the Menu JSON
JsonNode *processedMenu;
struct hashmap_s menuItemMap;
struct hashmap_s radioGroupMap;
// Vector to keep track of callback data memory
vec_void_t callbackDataCache;
// 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;
// This indicates if we are an Application Menu, tray menu or context menu
enum MenuType menuType;
} Menu;
id Menu;
} MacMenu;
typedef struct {
id menuItem;
Menu *menu;
const char *menuID;
enum MenuItemType menuItemType;
} MenuItemCallbackData;
id MenuItem;
} MacMenuItem;
typedef struct {
id statusBarItem;
int iconPosition;
} MacTrayMenu;
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData);
Menu* NewApplicationMenu(const char *menuAsJSON);
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType);
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, const char *parentID);
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender);
id processAcceleratorKey(const char *key);
void addSeparator(id menu);
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
id createMenuItem(id title, const char *action, const char *key);
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
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);
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey);
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers);
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
void PlatformMenuItemCallback(id self, SEL cmd, id sender);

View File

@@ -0,0 +1,542 @@
//
// Created by Lea Anthony on 6/1/21.
//
#include "ffenestri_darwin.h"
#include "menu_darwin_old.h"
#include "contextmenus_darwin.h"
#include "traymenustore_darwin.h"
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData, JsonNode *radioGroups, struct TrayMenuStore *store) {
Menu *result = malloc(sizeof(Menu));
// 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!");
}
// Init other members
result->menu = NULL;
result->store = store;
// Process the menu
ProcessMenu(result, menuData, radioGroups);
return result;
}
Menu* NewApplicationMenu(const char *menuAsJSON, struct TrayMenuStore *store) {
// Parse the menu json
JsonNode *processedMenu = json_decode(menuAsJSON);
if( processedMenu == NULL ) {
// Parse error!
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
}
// TODO - fixup
Menu *result = NewMenu(processedMenu, NULL, store);
result->menuType = ApplicationMenuType;
return result;
}
//TODO: Put traymenu store in callback instead of menu as it'll never be null
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, const char *menuItemID, enum MenuItemType menuItemType) {
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
result->store = menu->store;
result->menuItemID = STRCOPY(menuItemID);
result->menuItemType = menuItemType;
// Store reference to this so we can destroy later
vec_push(&menu->callbackDataCache, result);
return result;
}
void DeleteMenu(Menu *menu) {
if( menu == NULL ) {
return;
}
// 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);
// Release the callback cache memory
int i; MenuItemCallbackData *callback;
vec_foreach(&menu->callbackDataCache, callback, i) {
MEMFREE(callback->menuItemID);
}
vec_deinit(&menu->callbackDataCache);
// Free nsmenu if we have it
if ( menu->menu != NULL ) {
msg(menu->menu, s("release"));
}
free(menu);
}
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;
}
}
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuItemID, bool disabled, bool checked, const char *acceleratorkey, bool hascallback) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
// hashmap_put(&menu->menuItemMap, (char*)menuItemID, strlen(menuItemID), item);
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
if( hascallback ) {
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, 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 *menuItemID, bool disabled, bool checked, const char *key, bool hascallback) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
// hashmap_put(&menu->menuItemMap, (char*)menuItemID, strlen(menuItemID), item);
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
if( hascallback ) {
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, 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 *menuItemID, bool disabled, const char *acceleratorkey, const char **modifiers, bool hascallback) {
id item = ALLOC("NSMenuItem");
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
if( hascallback ) {
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, 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 = getJSONBool(item, "h");
if( hidden ) {
return;
}
// Get the role
JsonNode *role = json_find_member(item, "r");
if( role != NULL ) {
processMenuRole(menu, parentMenu, role);
return;
}
// Check if this is a submenu
JsonNode *submenu = json_find_member(item, "S");
if( submenu != NULL ) {
// Get the label
JsonNode *menuNameNode = json_find_member(item, "l");
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, "i");
// 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, "l");
if ( label == NULL) {
label = "(empty)";
}
const char *menuItemID = getJSONString(item, "I");
if ( menuItemID == NULL) {
menuItemID = "";
}
bool disabled = getJSONBool(item, "d");
// Get the Accelerator
JsonNode *accelerator = json_find_member(item, "a");
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;
}
}
}
// has callback?
bool hascallback = getJSONBool(item, "C");
// Get the Type
const char *type = mustJSONString(item, "t");
if( STREQ(type, "t")) {
processTextMenuItem(menu, parentMenu, label, menuItemID, disabled, acceleratorkey, modifiers, hascallback);
}
else if ( STREQ(type, "s")) {
addSeparator(parentMenu);
}
else if ( STREQ(type, "c")) {
// Get checked state
bool checked = getJSONBool(item, "c");
processCheckboxMenuItem(menu, parentMenu, label, menuItemID, disabled, checked, "", hascallback);
}
else if ( STREQ(type, "r")) {
// Get checked state
bool checked = getJSONBool(item, "c");
processRadioMenuItem(menu, parentMenu, label, menuItemID, disabled, checked, "", hascallback);
}
if ( modifiers != NULL ) {
free(modifiers);
}
return;
}
void processMenuData(Menu *menu, JsonNode *menuData) {
// Iterate items
JsonNode *item;
json_foreach(item, menuData) {
// 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 ProcessMenu(Menu *menu, JsonNode *menuData, JsonNode *radioGroups) {
// exit if we have no meny
if( menuData == NULL ) {
return NULL;
}
menu->menu = createMenu(str(""));
// Process the menu data
processMenuData(menu, menuData);
if( radioGroups != NULL ) {
// Iterate radio groups
JsonNode *radioGroup;
json_foreach(radioGroup, radioGroups) {
// Get item label
processRadioGroupJSON(menu, radioGroup);
}
}
return menu->menu;
}

View File

@@ -0,0 +1,97 @@
//
// Created by Lea Anthony on 6/1/21.
//
#ifndef MENU_DARWIN_H
#define MENU_DARWIN_H
#include "common.h"
#include "ffenestri_darwin.h"
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu",
};
extern void messageFromWindowCallback(const char *);
struct TrayMenuStore;
typedef struct {
const char *title;
/*** Internal ***/
struct hashmap_s menuItemMap;
struct hashmap_s radioGroupMap;
// Vector to keep track of callback data memory
vec_void_t callbackDataCache;
// The NSMenu for this menu
id menu;
// A reference to the Menu store
struct TrayMenuStore *store;
// 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;
typedef struct {
struct TrayMenuStore *store;
Menu *menu;
const char *menuItemID;
enum MenuItemType menuItemType;
} MenuItemCallbackData;
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData, JsonNode *radioGroups, struct TrayMenuStore *store);
Menu* NewApplicationMenu(const char *menuAsJSON, struct TrayMenuStore *store);
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, const char *menuItemID, enum MenuItemType menuItemType);
void DeleteMenu(Menu *menu);
// Creates a JSON message for the given menuItemID and data
const char* createMenuClickedMessage(const char *menuItemID, const char *data);
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender);
id processAcceleratorKey(const char *key);
void addSeparator(id menu);
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
id createMenuItem(id title, const char *action, const char *key);
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
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);
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey, bool hasCallback);
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key, bool hasCallback);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, bool hasCallback);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
id ProcessMenu(Menu *menu, JsonNode *menuData, JsonNode *radioGroup);
#endif //ASSETS_C_MENU_DARWIN_H

View File

@@ -0,0 +1,101 @@
//
// Created by Lea Anthony on 18/1/21.
//
#include "menu.h"
MenuItem* NewMenuItem(enum MenuItemType type, const char* ID, const char* label, const char* alternateLabel, bool disabled, bool hidden, bool checked, const char* colour, const char* font, int fontsize, const char* image, const char* acceleratorKey, const char** modifiers, bool hasCallback, Menu* submenu) {
MenuItem *result = NEW(MenuItem);
// Setup
result->ID = STRCOPY(ID);
result->label = STRCOPY(label);
result->alternateLabel = STRCOPY(alternateLabel);
result->disabled = disabled;
result->hidden = hidden;
result->colour = STRCOPY(colour);
result->font = STRCOPY(font);
result->fontSize = fontsize;
result->image = STRCOPY(image);
result->acceleratorKey = STRCOPY(acceleratorKey);
result->modifiers = modifiers;
result->hasCallback = hasCallback;
result->type = type;
result->checked = checked;
result->submenu = submenu;
result->callbackData = NULL;
vec_init(&result->radioGroup);
SetupMenuItemPlatformData(result);
return result;
}
void DeleteMenuItem(MenuItem* menuItem) {
MEMFREE(menuItem->ID);
MEMFREE(menuItem->label);
MEMFREE(menuItem->alternateLabel);
MEMFREE(menuItem->colour);
MEMFREE(menuItem->font);
MEMFREE(menuItem->image);
MEMFREE(menuItem->acceleratorKey);
// Iterate the modifiers and free elements
if( menuItem->modifiers != NULL ) {
int i = 0;
const char *nextItem = menuItem->modifiers[0];
while (nextItem != NULL) {
MEMFREE(nextItem);
i++;
nextItem = menuItem->modifiers[i];
}
MEMFREE(menuItem->modifiers);
}
DeleteMenuItemCallbackData(menuItem->callbackData);
DeleteMenuItemPlatformData(menuItem);
MEMFREE(menuItem);
}
JsonNode* MenuItemAsJSONObject(MenuItem* menuItem) {
JsonNode* result = json_mkobject();
JSON_ADD_STRING(result, "ID", menuItem->ID);
JSON_ADD_STRING(result, "label", menuItem->label);
JSON_ADD_STRING(result, "alternateLabel", menuItem->alternateLabel);
JSON_ADD_BOOL(result, "disabled", menuItem->disabled);
JSON_ADD_BOOL(result, "hidden", menuItem->hidden);
JSON_ADD_STRING(result, "colour", menuItem->colour);
JSON_ADD_STRING(result, "font", menuItem->font);
JSON_ADD_NUMBER(result, "fontsize", menuItem->fontSize);
JSON_ADD_STRING(result, "image", menuItem->image);
JSON_ADD_STRING(result, "acceleratorKey", menuItem->acceleratorKey);
JSON_ADD_BOOL(result, "hasCallback", menuItem->hasCallback);
JSON_ADD_STRING(result, "type", MenuItemTypeAsString[menuItem->type]);
JSON_ADD_BOOL(result, "checked", menuItem->checked);
return result;
}
MenuItemCallbackData* NewMenuItemCallbackData(MenuManager *manager, const char* menuItemID, enum MenuItemType menuItemType) {
MenuItemCallbackData* result = NEW(MenuItemCallbackData);
result->manager = manager;
result->menuItemID = STRCOPY(menuItemID);
result->menuItemType = menuItemType;
return result;
}
void DeleteMenuItemCallbackData(MenuItemCallbackData* callbackData) {
if( callbackData == NULL ) return;
MEMFREE(callbackData->menuItemID);
MEMFREE(callbackData);
}

View File

@@ -0,0 +1,81 @@
//
// Created by Lea Anthony on 18/1/21.
//
#include "menu.h"
MenuManager* NewMenuManager() {
MenuManager* result = malloc(sizeof(MenuManager));
// Allocate Hashmaps
HASHMAP_INIT(result->menuItems, 32, "menuItems");
HASHMAP_INIT(result->contextMenus, 4, "contextMenus");
HASHMAP_INIT(result->trayMenus, 4, "trayMenus");
// Initialise other data
result->applicationMenu = NULL;
result->contextMenuData = NULL;
return result;
}
int deleteTrayMenu(void *const context, struct hashmap_element_s *const e) {
DeleteTrayMenu(e->data);
return -1; // Remove from hashmap
}
int deleteMenuItem(void *const context, struct hashmap_element_s *const e) {
DeleteMenuItem(e->data);
return -1; // Remove from hashmap
}
void DeleteMenuManager(MenuManager* manager) {
// Iterate hashmaps and delete items
HASHMAP_ITERATE(manager->trayMenus, deleteTrayMenu, NULL);
HASHMAP_ITERATE(manager->menuItems, deleteMenuItem, NULL);
// Delete applicationMenu
DeleteMenu(manager->applicationMenu);
// Delete Hashmaps
HASHMAP_DESTROY(manager->trayMenus);
HASHMAP_DESTROY(manager->contextMenus);
HASHMAP_DESTROY(manager->menuItems);
// Delete context menu data
MEMFREE(manager->contextMenuData);
}
TrayMenu* AddTrayMenuToManager(MenuManager* manager, const char* trayMenuJSON) {
// Parse JSON
struct JsonNode* parsedJSON = mustParseJSON(trayMenuJSON);
// Get the ID
const char *ID = mustJSONString(parsedJSON, "I");
// Check if there is already an entry for this menu
TrayMenu* existingTrayMenu = HASHMAP_GET(manager->trayMenus, ID);
if ( existingTrayMenu != NULL ) {
json_delete(parsedJSON);
return existingTrayMenu;
}
// Create new menu
TrayMenu* newMenu = NewTrayMenu(parsedJSON, manager);
HASHMAP_PUT(manager->trayMenus, newMenu->ID, newMenu);
return newMenu;
}
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
ShowTrayMenu(e->data);
// 0 to retain element, -1 to delete.
return 0;
}
void ShowTrayMenus(MenuManager* manager) {
HASHMAP_ITERATE(manager->trayMenus, showTrayMenu, NULL);
}

View File

@@ -0,0 +1,69 @@
//
// Created by Lea Anthony on 18/1/21.
//
#include "menu.h"
TrayMenu* NewTrayMenu(JsonNode* parsedJSON, MenuManager *manager) {
// NULL GUARD
if(parsedJSON == NULL) ABORT("[NewTrayMenu] parsedJSON == NULL");
// Create new tray menu
TrayMenu* result = NEW(TrayMenu);
// Initialise other data
result->ID = STRCOPY(mustJSONString(parsedJSON, "I"));
result->Label = STRCOPY(getJSONString(parsedJSON, "l"));
result->Icon = STRCOPY(getJSONString(parsedJSON, "i"));
// Process menu
struct JsonNode* menuJSON = getJSONObject(parsedJSON, "m");
struct JsonNode* radioJSON = getJSONObject(parsedJSON, "r");
result->Menu = NewMenu(menuJSON, radioJSON, manager);
// Setup platform data
SetupTrayMenuPlatformData(result);
return result;
}
void DeleteTrayMenu(TrayMenu *trayMenu) {
// NULL guard
if( trayMenu == NULL ) return;
// Free the strings
MEMFREE(trayMenu->ID);
MEMFREE(trayMenu->Label);
MEMFREE(trayMenu->Icon);
// Delete the menu
DeleteMenu(trayMenu->Menu);
// Delete the platform data
DeleteTrayMenuPlatformData(trayMenu);
// Free tray menu
MEMFREE(trayMenu);
}
const char* TrayMenuAsJSON(TrayMenu* menu) {
JsonNode *jsonObject = json_mkobject();
JSON_ADD_STRING(jsonObject, "ID", menu->ID);
JSON_ADD_STRING(jsonObject, "Label", menu->Label);
JSON_ADD_STRING(jsonObject, "Icon", menu->Icon);
JSON_ADD_OBJECT(jsonObject, "Menu", MenuAsJSONObject(menu->Menu));
return json_encode(jsonObject);
}
void UpdateTrayIcon(TrayMenu *trayMenu) {
// Exit early if NULL
if( trayMenu->Icon == NULL ) {
return;
}
PlatformUpdateTrayIcon(trayMenu);
}

View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.17)
project(test_app)
set(CMAKE_CXX_STANDARD 14)
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -g")
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -g")
add_executable(test_app test.c)
add_library(menus STATIC ../menu_manager.c ../menu.c ../menu_tray.c ../menu_item.c )
add_library(common STATIC ../common.c ../utf8.h)
add_library(json STATIC ../json.c)
add_library(vec STATIC ../vec.c)
if( CMAKE_HOST_APPLE )
find_library(WEBKIT WebKit)
add_library(runtime STATIC ../runtime_darwin.c)
add_library(ffenestri STATIC ../ffenestri_darwin.c)
add_library(defaulticons STATIC ../defaultdialogicons_darwin.c)
add_library(platform STATIC ../menu_darwin.c)
target_link_libraries(test_app objc ${WEBKIT} ffenestri platform runtime)
endif()
target_link_libraries(test_app vec json common menus)
include_directories(..)
include_directories(.)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.17)
project(test_menumanager)
set(CMAKE_CXX_STANDARD 14)
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -g")
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -g")
add_executable(test_menumanager test.c minunit.h)
add_library(menus STATIC ../menu_manager.c ../menu.c ../menu_tray.c ../menu_item.c)
add_library(common STATIC ../common.c ../utf8.h)
add_library(json STATIC ../json.c)
add_library(vec STATIC ../vec.c)
if( CMAKE_HOST_APPLE )
add_library(platform STATIC ../menu_darwin.c)
target_link_libraries(test_menumanager objc)
endif()
target_link_libraries(test_menumanager vec json common menus platform)
include_directories(..)
include_directories(.)

View File

@@ -0,0 +1,20 @@
Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,391 @@
/*
* Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MINUNIT_MINUNIT_H
#define MINUNIT_MINUNIT_H
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_WIN32)
#include <Windows.h>
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#define __func__ __FUNCTION__
#endif
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* Change POSIX C SOURCE version for pure c99 compilers */
#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif
#include <unistd.h> /* POSIX flags */
#include <time.h> /* clock_gettime(), time() */
#include <sys/time.h> /* gethrtime(), gettimeofday() */
#include <sys/resource.h>
#include <sys/times.h>
#include <string.h>
#if defined(__MACH__) && defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_time.h>
#endif
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
#define __func__ __extension__ __FUNCTION__
#endif
#else
#error "Unable to define timers for an unknown OS."
#endif
#include <stdio.h>
#include <math.h>
/* Maximum length of last message */
#define MINUNIT_MESSAGE_LEN 1024
/* Accuracy with which floats are compared */
#define MINUNIT_EPSILON 1E-12
/* Misc. counters */
static int minunit_run = 0;
static int minunit_assert = 0;
static int minunit_fail = 0;
static int minunit_status = 0;
/* Timers */
static double minunit_real_timer = 0;
static double minunit_proc_timer = 0;
/* Last message */
static char minunit_last_message[MINUNIT_MESSAGE_LEN];
/* Test setup and teardown function pointers */
static void (*minunit_setup)(void) = NULL;
static void (*minunit_teardown)(void) = NULL;
/* Definitions */
#define MU_TEST(method_name) static void method_name(void)
#define MU_TEST_SUITE(suite_name) static void suite_name(void)
#define MU__SAFE_BLOCK(block) do {\
block\
} while(0)
/* Run test suite and unset setup and teardown functions */
#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
suite_name();\
minunit_setup = NULL;\
minunit_teardown = NULL;\
)
/* Configure setup and teardown functions */
#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
minunit_setup = setup_fun;\
minunit_teardown = teardown_fun;\
)
/* Test runner */
#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
if (minunit_real_timer==0 && minunit_proc_timer==0) {\
minunit_real_timer = mu_timer_real();\
minunit_proc_timer = mu_timer_cpu();\
}\
if (minunit_setup) (*minunit_setup)();\
minunit_status = 0;\
test();\
minunit_run++;\
if (minunit_status) {\
minunit_fail++;\
printf("F");\
printf("\n%s\n", minunit_last_message);\
}\
fflush(stdout);\
if (minunit_teardown) (*minunit_teardown)();\
)
/* Report */
#define MU_REPORT() MU__SAFE_BLOCK(\
double minunit_end_real_timer;\
double minunit_end_proc_timer;\
printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
minunit_end_real_timer = mu_timer_real();\
minunit_end_proc_timer = mu_timer_cpu();\
printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
minunit_end_real_timer - minunit_real_timer,\
minunit_end_proc_timer - minunit_proc_timer);\
)
#define MU_EXIT_CODE minunit_fail
/* Assertions */
#define mu_check(test) MU__SAFE_BLOCK(\
minunit_assert++;\
if (!(test)) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_fail(message) MU__SAFE_BLOCK(\
minunit_assert++;\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
minunit_status = 1;\
return;\
)
#define mu_assert(test, message) MU__SAFE_BLOCK(\
minunit_assert++;\
if (!(test)) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
int minunit_tmp_e;\
int minunit_tmp_r;\
minunit_assert++;\
minunit_tmp_e = (expected);\
minunit_tmp_r = (result);\
if (minunit_tmp_e != minunit_tmp_r) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
double minunit_tmp_e;\
double minunit_tmp_r;\
minunit_assert++;\
minunit_tmp_e = (expected);\
minunit_tmp_r = (result);\
if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
const char* minunit_tmp_e = expected;\
const char* minunit_tmp_r = result;\
minunit_assert++;\
if (!minunit_tmp_e) {\
minunit_tmp_e = "<null pointer>";\
}\
if (!minunit_tmp_r) {\
minunit_tmp_r = "<null pointer>";\
}\
if(strcmp(minunit_tmp_e, minunit_tmp_r) != 0) {\
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
minunit_status = 1;\
return;\
} else {\
printf(".");\
}\
)
/*
* The following two functions were written by David Robert Nadeau
* from http://NadeauSoftware.com/ and distributed under the
* Creative Commons Attribution 3.0 Unported License
*/
/**
* Returns the real time, in seconds, or -1.0 if an error occurred.
*
* Time is measured since an arbitrary and OS-dependent start time.
* The returned real time is only useful for computing an elapsed time
* between two calls to this function.
*/
static double mu_timer_real(void)
{
#if defined(_WIN32)
/* Windows 2000 and later. ---------------------------------- */
LARGE_INTEGER Time;
LARGE_INTEGER Frequency;
QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&Time);
Time.QuadPart *= 1000000;
Time.QuadPart /= Frequency.QuadPart;
return (double)Time.QuadPart / 1000000.0;
#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
/* HP-UX, Solaris. ------------------------------------------ */
return (double)gethrtime( ) / 1000000000.0;
#elif defined(__MACH__) && defined(__APPLE__)
/* OSX. ----------------------------------------------------- */
static double timeConvert = 0.0;
if ( timeConvert == 0.0 )
{
mach_timebase_info_data_t timeBase;
(void)mach_timebase_info( &timeBase );
timeConvert = (double)timeBase.numer /
(double)timeBase.denom /
1000000000.0;
}
return (double)mach_absolute_time( ) * timeConvert;
#elif defined(_POSIX_VERSION)
/* POSIX. --------------------------------------------------- */
struct timeval tm;
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
{
struct timespec ts;
#if defined(CLOCK_MONOTONIC_PRECISE)
/* BSD. --------------------------------------------- */
const clockid_t id = CLOCK_MONOTONIC_PRECISE;
#elif defined(CLOCK_MONOTONIC_RAW)
/* Linux. ------------------------------------------- */
const clockid_t id = CLOCK_MONOTONIC_RAW;
#elif defined(CLOCK_HIGHRES)
/* Solaris. ----------------------------------------- */
const clockid_t id = CLOCK_HIGHRES;
#elif defined(CLOCK_MONOTONIC)
/* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
const clockid_t id = CLOCK_MONOTONIC;
#elif defined(CLOCK_REALTIME)
/* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
const clockid_t id = CLOCK_REALTIME;
#else
const clockid_t id = (clockid_t)-1; /* Unknown. */
#endif /* CLOCK_* */
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
/* Fall thru. */
}
#endif /* _POSIX_TIMERS */
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
gettimeofday( &tm, NULL );
return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
#else
return -1.0; /* Failed. */
#endif
}
/**
* Returns the amount of CPU time used by the current process,
* in seconds, or -1.0 if an error occurred.
*/
static double mu_timer_cpu(void)
{
#if defined(_WIN32)
/* Windows -------------------------------------------------- */
FILETIME createTime;
FILETIME exitTime;
FILETIME kernelTime;
FILETIME userTime;
/* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
if ( GetProcessTimes( GetCurrentProcess( ),
&createTime, &exitTime, &kernelTime, &userTime ) != 0 )
{
ULARGE_INTEGER userSystemTime;
memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
return (double)userSystemTime.QuadPart / 10000000.0;
}
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
/* Prefer high-res POSIX timers, when available. */
{
clockid_t id;
struct timespec ts;
#if _POSIX_CPUTIME > 0
/* Clock ids vary by OS. Query the id, if possible. */
if ( clock_getcpuclockid( 0, &id ) == -1 )
#endif
#if defined(CLOCK_PROCESS_CPUTIME_ID)
/* Use known clock id for AIX, Linux, or Solaris. */
id = CLOCK_PROCESS_CPUTIME_ID;
#elif defined(CLOCK_VIRTUAL)
/* Use known clock id for BSD or HP-UX. */
id = CLOCK_VIRTUAL;
#else
id = (clockid_t)-1;
#endif
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
}
#endif
#if defined(RUSAGE_SELF)
{
struct rusage rusage;
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
return (double)rusage.ru_utime.tv_sec +
(double)rusage.ru_utime.tv_usec / 1000000.0;
}
#endif
#if defined(_SC_CLK_TCK)
{
const double ticks = (double)sysconf( _SC_CLK_TCK );
struct tms tms;
if ( times( &tms ) != (clock_t)-1 )
return (double)tms.tms_utime / ticks;
}
#endif
#if defined(CLOCKS_PER_SEC)
{
clock_t cl = clock( );
if ( cl != (clock_t)-1 )
return (double)cl / (double)CLOCKS_PER_SEC;
}
#endif
#endif
return -1; /* Failed. */
}
#ifdef __cplusplus
}
#endif
#endif /* MINUNIT_MINUNIT_H */

View File

@@ -0,0 +1,71 @@
//
// Created by Lea Anthony on 12/1/21.
//
#include "minunit.h"
#include "menu.h"
#define empty "{\"I\":\"T1\"}"
#define emptyExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
#define labelOnly "{\"I\":\"T1\",\"l\":\"test\"}"
#define labelOnlyExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":null,\"Menu\":null}"
#define iconOnly "{\"I\":\"T1\",\"i\":\"svelte\"}"
#define iconOnlyExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":\"svelte\",\"Menu\":null}"
#define iconLabel "{\"I\":\"T1\",\"l\":\"test\",\"i\":\"svelte\"}"
#define iconLabelExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":\"svelte\",\"Menu\":null}"
#define blankLabel "{\"I\":\"T1\"}"
#define blankLabelExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
#define blankMenu "{\"I\":\"T1\"}"
#define blankMenuExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
#define menuTextItem "{\"I\":\"T1\",\"l\":\"test\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}"
#define menuTextItemExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":null,\"Menu\":{\"Label\":\"\",\"Items\":[{\"ID\":\"1\",\"label\":\"test\",\"alternateLabel\":null,\"disabled\":false,\"hidden\":false,\"colour\":null,\"font\":null,\"fontsize\":0,\"image\":null,\"acceleratorKey\":null,\"hasCallback\":false,\"type\":\"Text\",\"checked\":false}]}}"
#define checkboxItem "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"c\",\"c\":true}]}"
#define checkboxItemExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":{\"Label\":\"\",\"Items\":[{\"ID\":\"1\",\"label\":\"test\",\"alternateLabel\":null,\"disabled\":false,\"hidden\":false,\"colour\":null,\"font\":null,\"fontsize\":0,\"image\":null,\"acceleratorKey\":null,\"hasCallback\":false,\"type\":\"Checkbox\",\"checked\":true}]}}"
#define radioGroupItems "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"option 1\",\"t\":\"r\",\"c\":true},{\"I\":\"2\",\"l\":\"option 2\",\"t\":\"r\"},{\"I\":\"3\",\"l\":\"option 3\",\"t\":\"r\"}],\"r\":[{\"Members\":[\"1\",\"2\",\"3\"],\"Length\":3}]}"
#define radioGroupItemsExpected ""
#define callbackItem "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"Preferences\",\"t\":\"t\",\"C\":true}]}"
#define callbackItemExpected ""
const char* tests[] = {
empty, emptyExpected,
labelOnly, labelOnlyExpected,
iconOnly, iconOnlyExpected,
iconLabel, iconLabelExpected,
blankLabel, blankLabelExpected,
blankMenu, blankMenuExpected,
menuTextItem, menuTextItemExpected,
checkboxItem, checkboxItemExpected,
radioGroupItems, radioGroupItemsExpected,
callbackItem, callbackItemExpected,
};
MU_TEST(manager_creation) {
MenuManager* manager = NewMenuManager();
mu_assert(manager->applicationMenu == NULL, "app menu");
mu_assert(manager->contextMenuData == NULL, "context menu data");
mu_assert_int_eq(hashmap_num_entries(&manager->contextMenus), 0);
mu_assert_int_eq(hashmap_num_entries(&manager->trayMenus), 0);
mu_assert_int_eq(hashmap_num_entries(&manager->menuItems), 0);
DeleteMenuManager(manager);
}
MU_TEST(add_tray) {
for( int count = 0; count < sizeof(tests) / sizeof(tests[0]); count += 2 ) {
MenuManager* manager = NewMenuManager();
TrayMenu* tray = AddTrayMenu(manager, tests[count]);
const char* trayJSON = TrayMenuAsJSON(tray);
mu_assert_string_eq(tests[count+1], trayJSON);
DeleteMenuManager(manager);
}
}
MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(manager_creation);
MU_RUN_TEST(add_tray);
}
int main(int argc, char *argv[]) {
MU_RUN_SUITE(test_suite);
MU_REPORT();
return MU_EXIT_CODE;
}

View File

@@ -10,7 +10,7 @@
// Global because it's a singleton
struct hashmap_s trayIconCache;
TrayMenu* NewTrayMenu(const char* menuJSON) {
TrayMenu* NewTrayMenu(const char* menuJSON, struct TrayMenuStore* store) {
TrayMenu* result = malloc(sizeof(TrayMenu));
/*
@@ -21,26 +21,29 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
}
// Save reference to this json
result->processedJSON = processedJSON;
// TODO: Make this configurable
result->trayIconPosition = NSImageLeft;
result->ID = mustJSONString(processedJSON, "ID");
result->label = mustJSONString(processedJSON, "Label");
result->icon = mustJSONString(processedJSON, "Icon");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->ID = STRCOPY(mustJSONString(processedJSON, "I"));
result->label = STRCOPY(getJSONString(processedJSON, "l"));
result->icon = STRCOPY(getJSONString(processedJSON, "i"));
result->menu = NULL;
// Create the menu
result->menu = NewMenu(processedMenu);
JsonNode* menu = getJSONObject(processedJSON, "m");
if( menu != NULL ) {
JsonNode* radioGroups = getJSONObject(processedJSON, "r");
// Create the menu
result->menu = NewMenu(menu, radioGroups, store);
result->menu->menuType = TrayMenuType;
}
// Init tray status bar item
result->statusbaritem = NULL;
// Set the menu type and store the tray ID in the parent data
result->menu->menuType = TrayMenuType;
result->menu->parentData = (void*) result->ID;
// Free JSON
json_delete(processedJSON);
return result;
}
@@ -50,17 +53,6 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
}
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
return;
}
// Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(label));
}
void UpdateTrayIcon(TrayMenu *trayMenu) {
// Exit early if NULL
@@ -104,8 +96,9 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
UpdateTrayLabel(trayMenu, trayMenu->label);
// Update the menu
id menu = GetMenu(trayMenu->menu);
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
if (trayMenu->menu != NULL ) {
msg(trayMenu->statusbaritem, s("setMenu:"), trayMenu->menu->menu);
}
}
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
@@ -118,17 +111,11 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
// Set the new one
currentMenu->menu = newMenu->menu;
// Delete the old JSON
json_delete(currentMenu->processedJSON);
// Set the new JSON
currentMenu->processedJSON = newMenu->processedJSON;
// Copy the other data
currentMenu->ID = newMenu->ID;
currentMenu->label = newMenu->label;
currentMenu->ID = STRCOPY(newMenu->ID);
currentMenu->label = STRCOPY(newMenu->label);
currentMenu->trayIconPosition = newMenu->trayIconPosition;
currentMenu->icon = newMenu->icon;
currentMenu->icon = STRCOPY(newMenu->icon);
}
@@ -140,10 +127,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
// Delete the menu
DeleteMenu(trayMenu->menu);
// Free JSON
if (trayMenu->processedJSON != NULL ) {
json_delete(trayMenu->processedJSON);
}
// Free strings
MEMFREE(trayMenu->label);
MEMFREE(trayMenu->icon);
MEMFREE(trayMenu->ID);
// Free the status item
if ( trayMenu->statusbaritem != NULL ) {
@@ -189,14 +176,3 @@ void LoadTrayIcons() {
}
}
void UnloadTrayIcons() {
// Release the tray cache images
if( hashmap_num_entries(&trayIconCache) > 0 ) {
if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) {
ABORT("failed to release hashmap entries!");
}
}
//Free radio groups hashmap
hashmap_destroy(&trayIconCache);
}

View File

@@ -6,7 +6,7 @@
#define TRAYMENU_DARWIN_H
#include "common.h"
#include "menu_darwin.h"
#include "menu_darwin_old.h"
typedef struct {
@@ -18,12 +18,9 @@ typedef struct {
id statusbaritem;
int trayIconPosition;
JsonNode* processedJSON;
} TrayMenu;
TrayMenu* NewTrayMenu(const char *trayJSON);
TrayMenu* NewTrayMenu(const char *trayJSON, struct TrayMenuStore* store);
void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);

View File

@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
}
// Allocate menu item store
if( 0 != hashmap_create((const unsigned)8, &result->menuItemMap)) {
ABORT("[NewTrayMenuStore] Not enough memory to allocate menuItemMap!");
}
return result;
}
@@ -29,7 +34,7 @@ void DumpTrayMenuStore(TrayMenuStore* store) {
}
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
//TODO: check if there is already an entry for this menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
@@ -65,6 +70,9 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
// Destroy tray menu map
hashmap_destroy(&store->trayMenuMap);
// Destroy menu item map
hashmap_destroy(&store->menuItemMap);
}
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
@@ -81,6 +89,15 @@ TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
return result;
}
id GetMenuItemFromStore(TrayMenuStore* store, const char* menuItemID) {
return hashmap_get(&store->menuItemMap, menuItemID, strlen(menuItemID));
}
void SaveMenuItemInStore(TrayMenuStore* store, const char* menuItemID, id nsmenuitem) {
hashmap_put(&store->menuItemMap, menuItemID, strlen(menuItemID), nsmenuitem);
}
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Parse the JSON
JsonNode *parsedUpdate = mustParseJSON(JSON);
@@ -95,24 +112,19 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
}
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
DumpTrayMenu(newMenu);
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
// Get the current menu
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
// If we don't have a menu, we create one
if ( currentMenu == NULL ) {
printf(" currentMenu = NULL\n");
// Store the new menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
// Show it
ShowTrayMenu(newMenu);
return;
return newMenu;
}
DumpTrayMenu(currentMenu);
// Save the status bar reference
newMenu->statusbaritem = currentMenu->statusbaritem;
@@ -128,7 +140,11 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
// Show the updated menu
ShowTrayMenu(newMenu);
return newMenu;
}
const char* GetContextMenuDataFromStore(TrayMenuStore *store) {
return store->contextMenuData;
}

View File

@@ -5,6 +5,8 @@
#ifndef TRAYMENUSTORE_DARWIN_H
#define TRAYMENUSTORE_DARWIN_H
#include "traymenu_darwin.h"
typedef struct {
int dummy;
@@ -13,15 +15,26 @@ typedef struct {
// It maps tray IDs to TrayMenu*
struct hashmap_s trayMenuMap;
// This is our menu item map
// It maps menu Item IDs to NSMenuItems
struct hashmap_s menuItemMap;
const char* contextMenuData;
} TrayMenuStore;
TrayMenuStore* NewTrayMenuStore();
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON);
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
void ShowTrayMenusInStore(TrayMenuStore* store);
void DeleteTrayMenuStore(TrayMenuStore* store);
void SaveMenuItemInStore(TrayMenuStore* store, const char* menuItemID, id nsmenuitem);
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID);
id GetMenuItemFromStore(TrayMenuStore* store, const char* menuItemID);
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
const char* GetContextMenuDataFromStore(TrayMenuStore *store);
#endif //TRAYMENUSTORE_DARWIN_H

1292
v2/internal/ffenestri/utf8.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,8 @@
( vec_splice_(vec_unpack_(v), start, count),\
(v)->length -= (count) )
#define vec_size(v) \
(v)->length
#define vec_swapsplice(v, start, count)\
( vec_swapsplice_(vec_unpack_(v), start, count),\

View File

@@ -1,46 +0,0 @@
package menumanager
import "github.com/wailsapp/wails/v2/pkg/menu"
func (m *Manager) SetApplicationMenu(applicationMenu *menu.Menu) error {
if applicationMenu == nil {
return nil
}
m.applicationMenu = applicationMenu
// Reset the menu map
m.applicationMenuItemMap = NewMenuItemMap()
// Add the menu to the menu map
m.applicationMenuItemMap.AddMenu(applicationMenu)
return m.processApplicationMenu()
}
func (m *Manager) GetApplicationMenuJSON() string {
return m.applicationMenuJSON
}
// UpdateApplicationMenu reprocesses the application menu to pick up structure
// changes etc
// Returns the JSON representation of the updated menu
func (m *Manager) UpdateApplicationMenu() (string, error) {
m.applicationMenuItemMap = NewMenuItemMap()
m.applicationMenuItemMap.AddMenu(m.applicationMenu)
err := m.processApplicationMenu()
return m.applicationMenuJSON, err
}
func (m *Manager) processApplicationMenu() error {
// Process the menu
processedApplicationMenu := NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu)
applicationMenuJSON, err := processedApplicationMenu.AsJSON()
if err != nil {
return err
}
m.applicationMenuJSON = applicationMenuJSON
return nil
}

View File

@@ -1,60 +1,65 @@
package menumanager
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/menu"
)
type ContextMenu struct {
ID string
ProcessedMenu *WailsMenu
menuItemMap *MenuItemMap
menu *menu.Menu
}
func (t *ContextMenu) AsJSON() (string, error) {
data, err := json.Marshal(t)
if err != nil {
return "", err
}
return string(data), nil
}
func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
result := &ContextMenu{
ID: contextMenu.ID,
menu: contextMenu.Menu,
menuItemMap: NewMenuItemMap(),
}
result.menuItemMap.AddMenu(contextMenu.Menu)
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
return result
}
import "github.com/wailsapp/wails/v2/pkg/menu"
//
//import (
// "encoding/json"
// "fmt"
// "github.com/wailsapp/wails/v2/pkg/menu"
//)
//
//type ContextMenu struct {
// ID string
// ProcessedMenu *WailsMenu
// menuItemMap *MenuItemMap
// menu *menu.Menu
//}
//
//func (t *ContextMenu) AsJSON() (string, error) {
// data, err := json.Marshal(t)
// if err != nil {
// return "", err
// }
// return string(data), nil
//}
//
//func (m *Manager) NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
//
// result := &ContextMenu{
// ID: contextMenu.ID,
// menu: contextMenu.Menu,
// menuItemMap: NewMenuItemMap(),
// }
//
// result.menuItemMap.AddMenu(contextMenu.Menu)
// result.ProcessedMenu = m.NewWailsMenu(result.menuItemMap, result.menu)
//
// return result
//}
//
func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) {
newContextMenu := NewContextMenu(contextMenu)
// Save the references
m.contextMenus[contextMenu.ID] = newContextMenu
m.contextMenuPointers[contextMenu] = contextMenu.ID
//
// newContextMenu := m.NewContextMenu(contextMenu)
//
// // Save the references
// m.contextMenus[contextMenu.ID] = newContextMenu
// m.contextMenuPointers[contextMenu] = contextMenu.ID
}
//
func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
if !contextMenuKnown {
return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
}
// Create the updated context menu
updatedContextMenu := NewContextMenu(contextMenu)
// Save the reference
m.contextMenus[contextMenuID] = updatedContextMenu
return updatedContextMenu.AsJSON()
// contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
// if !contextMenuKnown {
// return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
// }
//
// // Create the updated context menu
// updatedContextMenu := m.NewContextMenu(contextMenu)
//
// // Save the reference
// m.contextMenus[contextMenuID] = updatedContextMenu
//
// return updatedContextMenu.AsJSON()
return "", nil
}

View File

@@ -1,75 +0,0 @@
package menumanager
import (
"fmt"
"github.com/wailsapp/wails/v2/pkg/menu"
"sync"
)
// MenuItemMap holds a mapping between menuIDs and menu items
type MenuItemMap struct {
idToMenuItemMap map[string]*menu.MenuItem
menuItemToIDMap map[*menu.MenuItem]string
// We use a simple counter to keep track of unique menu IDs
menuIDCounter int64
menuIDCounterMutex sync.Mutex
}
func NewMenuItemMap() *MenuItemMap {
result := &MenuItemMap{
idToMenuItemMap: make(map[string]*menu.MenuItem),
menuItemToIDMap: make(map[*menu.MenuItem]string),
}
return result
}
func (m *MenuItemMap) AddMenu(menu *menu.Menu) {
if menu == nil {
return
}
for _, item := range menu.Items {
m.processMenuItem(item)
}
}
func (m *MenuItemMap) Dump() {
println("idToMenuItemMap:")
for key, value := range m.idToMenuItemMap {
fmt.Printf(" %s\t%p\n", key, value)
}
println("\nmenuItemToIDMap")
for key, value := range m.menuItemToIDMap {
fmt.Printf(" %p\t%s\n", key, value)
}
}
// GenerateMenuID returns a unique string ID for a menu item
func (m *MenuItemMap) generateMenuID() string {
m.menuIDCounterMutex.Lock()
result := fmt.Sprintf("%d", m.menuIDCounter)
m.menuIDCounter++
m.menuIDCounterMutex.Unlock()
return result
}
func (m *MenuItemMap) processMenuItem(item *menu.MenuItem) {
if item.SubMenu != nil {
for _, submenuitem := range item.SubMenu.Items {
m.processMenuItem(submenuitem)
}
}
// Create a unique ID for this menu item
menuID := m.generateMenuID()
// Store references
m.idToMenuItemMap[menuID] = item
m.menuItemToIDMap[item] = menuID
}
func (m *MenuItemMap) getMenuItemByID(menuId string) *menu.MenuItem {
return m.idToMenuItemMap[menuId]
}

View File

@@ -2,89 +2,53 @@ package menumanager
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/counter"
"github.com/wailsapp/wails/v2/pkg/menu"
)
type Manager struct {
// The application menu.
applicationMenu *menu.Menu
applicationMenuJSON string
// MenuItemMap is a map of all menu items against a generated ID
menuItemMap map[string]*menu.MenuItem
menuItemIDCounter *counter.Counter
processedMenuItems map[*menu.MenuItem]*ProcessedMenuItem
// Our application menu mappings
applicationMenuItemMap *MenuItemMap
// Menus
menuIDCounter *counter.Counter
// Context menus
contextMenus map[string]*ContextMenu
contextMenuPointers map[*menu.ContextMenu]string
// Tray menu stores
trayMenus map[string]*TrayMenu
trayMenuPointers map[*menu.TrayMenu]string
// Map wails menus to internal menus
trayMenuMap map[*menu.TrayMenu]*TrayMenu
trayMenuIDCounter *counter.Counter
}
func NewManager() *Manager {
return &Manager{
applicationMenuItemMap: NewMenuItemMap(),
contextMenus: make(map[string]*ContextMenu),
contextMenuPointers: make(map[*menu.ContextMenu]string),
trayMenus: make(map[string]*TrayMenu),
trayMenuPointers: make(map[*menu.TrayMenu]string),
trayMenuMap: make(map[*menu.TrayMenu]*TrayMenu),
trayMenuIDCounter: counter.NewCounter(0),
menuIDCounter: counter.NewCounter(0),
menuItemMap: make(map[string]*menu.MenuItem),
menuItemIDCounter: counter.NewCounter(0),
processedMenuItems: make(map[*menu.MenuItem]*ProcessedMenuItem),
}
}
func (m *Manager) getMenuItemByID(menuMap *MenuItemMap, menuId string) *menu.MenuItem {
return menuMap.idToMenuItemMap[menuId]
}
func (m *Manager) ProcessClick(menuID string, data string, menuType string, parentID string) error {
var menuItemMap *MenuItemMap
switch menuType {
case "ApplicationMenu":
menuItemMap = m.applicationMenuItemMap
case "ContextMenu":
contextMenu := m.contextMenus[parentID]
if contextMenu == nil {
return fmt.Errorf("unknown context menu: %s", parentID)
}
menuItemMap = contextMenu.menuItemMap
case "TrayMenu":
trayMenu := m.trayMenus[parentID]
if trayMenu == nil {
return fmt.Errorf("unknown tray menu: %s", parentID)
}
menuItemMap = trayMenu.menuItemMap
default:
return fmt.Errorf("unknown menutype: %s", menuType)
}
// Get the menu item
menuItem := menuItemMap.getMenuItemByID(menuID)
func (m *Manager) ProcessClick(menuID string, data string) error {
// Get item from callback map
menuItem := m.menuItemMap[menuID]
if menuItem == nil {
return fmt.Errorf("Cannot process menuid %s - unknown", menuID)
}
// Is the menu item a checkbox?
if menuItem.Type == menu.CheckboxType {
// Toggle state
menuItem.Checked = !menuItem.Checked
return fmt.Errorf("menuItem doesn't exist")
}
if menuItem.Click == nil {
// No callback
return fmt.Errorf("No callback for menu '%s'", menuItem.Label)
return fmt.Errorf("menuItem 'does not have a callback")
}
// Create new Callback struct
callbackData := &menu.CallbackData{
MenuItem: menuItem,
ContextData: data,
}
// Call back!
// Callback!
go menuItem.Click(callbackData)
return nil
}

View File

@@ -2,72 +2,165 @@ package menumanager
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
)
type ProcessedMenuItem struct {
ID string
// Label is what appears as the menu text
Label string
// Role is a predefined menu type
Role menu.Role `json:"Role,omitempty"`
// Accelerator holds a representation of a key binding
Accelerator *keys.Accelerator `json:"Accelerator,omitempty"`
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
Type menu.Type
// Disabled makes the item unselectable
Disabled bool
// Hidden ensures that the item is not shown in the menu
Hidden bool
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
Checked bool
// Submenu contains a list of menu items that will be shown as a submenu
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
SubMenu *ProcessedMenu `json:"SubMenu,omitempty"`
type ProcessedMenu struct {
ID string `json:"I"`
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
Foreground int
// Background colour
Background int
Items []*ProcessedMenuItem `json:"i,omitempty"`
RadioGroups []*RadioGroup `json:"r,omitempty"`
}
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
func (m *Manager) ProcessMenu(menu *menu.Menu) *ProcessedMenu {
wm := m.NewWailsMenu(menu)
if len(wm.Menu) == 0 {
return nil
}
return &ProcessedMenu{
ID: m.generateMenuID(),
Items: wm.Menu,
RadioGroups: wm.RadioGroups,
}
}
type ProcessedMenuItem struct {
// ID of the menu item
ID string `json:"I"`
// Label is what appears as the menu text
Label string `json:"l,omitempty"`
// AlternateLabel is a secondary label (Used by Mac)
AlternateLabel string `json:"L,omitempty"`
// Role is a predefined menu type
Role menu.Role `json:"r,omitempty"`
// Accelerator holds a representation of a key binding
Accelerator *keys.Accelerator `json:"a,omitempty"`
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
Type menu.Type `json:"t,omitempty"`
// Font to use for the menu item
Font string `json:"f,omitempty"`
// Font to use for the menu item
FontSize int `json:"F,omitempty"`
// RGBA is the colour of the menu item
RGBA string `json:"R,omitempty"`
// Image is an image for the menu item (base64 string)
Image string `json:"i,omitempty"`
// Disabled makes the item unselectable
Disabled *bool `json:"d,omitempty"`
// Hidden ensures that the item is not shown in the menu
Hidden *bool `json:"h,omitempty"`
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
Checked *bool `json:"c,omitempty"`
// Submenu contains a list of menu items that will be shown as a submenu
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
SubMenu []*ProcessedMenuItem `json:"s,omitempty"`
// Indicates if this item has a callback
HasCallback *bool `json:"C,omitempty"`
}
func (m *Manager) generateMenuItemID() string {
return fmt.Sprintf("%d", m.menuItemIDCounter.Increment())
}
func (m *Manager) generateMenuID() string {
return fmt.Sprintf("%d", m.menuIDCounter.Increment())
}
func (m *Manager) NewProcessedMenuItem(menuItem *menu.MenuItem) *ProcessedMenuItem {
// Check if this menu has already been processed.
// This is to prevent duplicates.
existingMenuItem := m.processedMenuItems[menuItem]
if existingMenuItem != nil {
return &ProcessedMenuItem{ID: existingMenuItem.ID}
}
ID := m.generateMenuItemID()
ID := menuItemMap.menuItemToIDMap[menuItem]
result := &ProcessedMenuItem{
ID: ID,
Label: menuItem.Label,
Role: menuItem.Role,
Accelerator: menuItem.Accelerator,
Type: menuItem.Type,
Disabled: menuItem.Disabled,
Hidden: menuItem.Hidden,
Checked: menuItem.Checked,
Foreground: menuItem.Foreground,
Background: menuItem.Background,
ID: ID,
Label: menuItem.Label,
AlternateLabel: menuItem.AlternateLabel,
Role: menuItem.Role,
Accelerator: menuItem.Accelerator,
Type: menuItem.Type,
Font: menuItem.Font,
FontSize: menuItem.FontSize,
RGBA: menuItem.RGBA,
Image: menuItem.Image,
Disabled: nil,
Hidden: nil,
Checked: nil,
SubMenu: nil,
HasCallback: nil,
}
if menuItem.Hidden {
result.Hidden = new(bool)
*result.Hidden = true
}
if menuItem.Disabled {
result.Disabled = new(bool)
*result.Disabled = true
}
if menuItem.Checked {
result.Checked = new(bool)
*result.Checked = true
}
if menuItem.Click != nil {
result.HasCallback = new(bool)
*result.HasCallback = true
}
if menuItem.SubMenu != nil {
result.SubMenu = NewProcessedMenu(menuItemMap, menuItem.SubMenu)
result.SubMenu = m.NewProcessedMenu(menuItem.SubMenu)
}
// Add menu item to item map
m.menuItemMap[ID] = menuItem
// Add processed Item to processedMenuItems
m.processedMenuItems[menuItem] = result
return result
}
type ProcessedMenu struct {
Items []*ProcessedMenuItem
}
func (m *Manager) NewProcessedMenu(menu *menu.Menu) []*ProcessedMenuItem {
func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu {
if menu == nil {
return nil
}
result := &ProcessedMenu{}
if menu != nil {
for _, item := range menu.Items {
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
result.Items = append(result.Items, processedMenuItem)
}
if menu.Items == nil {
return nil
}
var result []*ProcessedMenuItem
for _, item := range menu.Items {
processedMenuItem := m.NewProcessedMenuItem(item)
result = append(result, processedMenuItem)
}
return result
@@ -76,8 +169,8 @@ func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu
// WailsMenu is the original menu with the addition
// of radio groups extracted from the menu data
type WailsMenu struct {
Menu *ProcessedMenu
RadioGroups []*RadioGroup
Menu []*ProcessedMenuItem `json:",omitempty"`
RadioGroups []*RadioGroup `json:",omitempty"`
currentRadioGroup []string
}
@@ -87,11 +180,12 @@ type RadioGroup struct {
Length int
}
func NewWailsMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *WailsMenu {
func (m *Manager) NewWailsMenu(menu *menu.Menu) *WailsMenu {
result := &WailsMenu{}
// Process the menus
result.Menu = NewProcessedMenu(menuItemMap, menu)
result.Menu = m.NewProcessedMenu(menu)
// Process the radio groups
result.processRadioGroups()
@@ -108,17 +202,7 @@ func (w *WailsMenu) AsJSON() (string, error) {
return string(menuAsJSON), nil
}
func (w *WailsMenu) processRadioGroups() {
// Loop over top level menus
for _, item := range w.Menu.Items {
// Process MenuItem
w.processMenuItem(item)
}
w.finaliseRadioGroup()
}
func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
func (w *WailsMenu) processMenuItemForRadioGroups(item *ProcessedMenuItem) {
switch item.Type {
@@ -129,8 +213,8 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
w.finaliseRadioGroup()
// Process each submenu item
for _, subitem := range item.SubMenu.Items {
w.processMenuItem(subitem)
for _, subitem := range item.SubMenu {
w.processMenuItemForRadioGroups(subitem)
}
case menu.RadioType:
// Add the item to the radio group
@@ -140,6 +224,20 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
}
}
func (w *WailsMenu) processRadioGroups() {
if w.Menu == nil {
return
}
// Loop over top level menus
for _, item := range w.Menu {
// Process MenuItem
w.processMenuItemForRadioGroups(item)
}
w.finaliseRadioGroup()
}
func (w *WailsMenu) finaliseRadioGroup() {
// If we were processing a radio group, fix up the references

View File

@@ -3,29 +3,14 @@ package menumanager
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/menu"
"sync"
)
var trayMenuID int
var trayMenuIDMutex sync.Mutex
func generateTrayID() string {
trayMenuIDMutex.Lock()
result := fmt.Sprintf("%d", trayMenuID)
trayMenuID++
trayMenuIDMutex.Unlock()
return result
}
type TrayMenu struct {
ID string
Label string
Icon string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
ID string `json:"I"`
Label string `json:"l,omitempty"`
Icon string `json:"i,omitempty"`
Menu *ProcessedMenu `json:"m,omitempty"`
}
func (t *TrayMenu) AsJSON() (string, error) {
@@ -36,55 +21,44 @@ func (t *TrayMenu) AsJSON() (string, error) {
return string(data), nil
}
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
func (m *Manager) newTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
result := &TrayMenu{
Label: trayMenu.Label,
Icon: trayMenu.Icon,
menu: trayMenu.Menu,
menuItemMap: NewMenuItemMap(),
result := TrayMenu{
ID: m.generateTrayID(),
Label: trayMenu.Label,
Icon: trayMenu.Icon,
Menu: m.ProcessMenu(trayMenu.Menu),
}
result.menuItemMap.AddMenu(trayMenu.Menu)
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
return &result
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
result := m.newTrayMenu(trayMenu)
// Add to map
m.trayMenuMap[trayMenu] = result
return result
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
newTrayMenu := NewTrayMenu(trayMenu)
// Hook up a new ID
trayID := generateTrayID()
newTrayMenu.ID = trayID
// Save the references
m.trayMenus[trayID] = newTrayMenu
m.trayMenuPointers[trayMenu] = trayID
return newTrayMenu.AsJSON()
func (m *Manager) generateTrayID() string {
return fmt.Sprintf("T%d", m.trayMenuIDCounter.Increment())
}
// SetTrayMenu updates or creates a menu
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
if !trayMenuKnown {
return m.AddTrayMenu(trayMenu)
func (m *Manager) GetTrayMenus() ([]*TrayMenu, error) {
var result []*TrayMenu
for _, trayMenu := range m.trayMenuMap {
result = append(result, trayMenu)
}
// Create the updated tray menu
updatedTrayMenu := NewTrayMenu(trayMenu)
updatedTrayMenu.ID = trayID
// Save the reference
m.trayMenus[trayID] = updatedTrayMenu
return updatedTrayMenu.AsJSON()
return result, nil
}
func (m *Manager) GetTrayMenus() ([]string, error) {
result := []string{}
for _, trayMenu := range m.trayMenus {
func (m *Manager) GetTrayMenusAsJSON() ([]string, error) {
var result []string
for _, trayMenu := range m.trayMenuMap {
JSON, err := trayMenu.AsJSON()
if err != nil {
return nil, err
@@ -95,40 +69,11 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
return result, nil
}
// SetTrayMenu updates or creates a menu
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
return "", nil
}
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
if !trayMenuKnown {
return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
}
type LabelUpdate struct {
ID string
Label string
}
update := &LabelUpdate{
ID: trayID,
Label: trayMenu.Label,
}
data, err := json.Marshal(update)
if err != nil {
return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
}
return string(data), nil
}
func (m *Manager) GetContextMenus() ([]string, error) {
result := []string{}
for _, contextMenu := range m.contextMenus {
JSON, err := contextMenu.AsJSON()
if err != nil {
return nil, err
}
result = append(result, JSON)
}
return result, nil
return "", nil
}

View File

@@ -0,0 +1,99 @@
package menumanager
import (
"github.com/matryer/is"
"github.com/wailsapp/wails/v2/pkg/menu"
"testing"
)
func TestManager_AddTrayMenu(t *testing.T) {
is := is.New(t)
simpleLabel := menu.Text("test", nil, nil)
checkbox := menu.Checkbox("test", true, nil, nil)
radioGroup1 := menu.Radio("option 1", true, nil, nil)
radioGroup2 := menu.Radio("option 2", false, nil, nil)
radioGroup3 := menu.Radio("option 3", false, nil, nil)
callback := menu.Text("Preferences", nil, func(_ *menu.CallbackData) {})
empty := &menu.TrayMenu{}
labelOnly := &menu.TrayMenu{Label: "test"}
iconOnly := &menu.TrayMenu{Icon: "svelte"}
iconLabel := &menu.TrayMenu{Icon: "svelte", Label: "test"}
blankLabel := &menu.TrayMenu{Label: ""}
blankMenu := &menu.TrayMenu{Menu: menu.NewMenu()}
menuTextItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabel)}
checkboxItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(checkbox)}
radioGroupItems := &menu.TrayMenu{Menu: menu.NewMenuFromItems(radioGroup1, radioGroup2, radioGroup3)}
callbackItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(callback)}
tests := []struct {
trayMenu *menu.TrayMenu
want string
}{
{empty, "{\"I\":\"T1\"}"},
{labelOnly, "{\"I\":\"T1\",\"l\":\"test\"}"},
{iconOnly, "{\"I\":\"T1\",\"i\":\"svelte\"}"},
{iconLabel, "{\"I\":\"T1\",\"l\":\"test\",\"i\":\"svelte\"}"},
{blankLabel, "{\"I\":\"T1\"}"},
{blankMenu, "{\"I\":\"T1\"}"},
{menuTextItem, "{\"I\":\"T1\",\"m\":{\"I\":\"1\",\"i\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}}"},
{checkboxItem, "{\"I\":\"T1\",\"m\":{\"I\":\"1\",\"i\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"c\",\"c\":true}]}}"},
{radioGroupItems, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"option 1\",\"t\":\"r\",\"c\":true},{\"I\":\"2\",\"l\":\"option 2\",\"t\":\"r\"},{\"I\":\"3\",\"l\":\"option 3\",\"t\":\"r\"}],\"r\":[{\"Members\":[\"1\",\"2\",\"3\"],\"Length\":3}]}"},
{callbackItem, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"Preferences\",\"t\":\"t\",\"C\":true}]}"},
}
for _, tt := range tests {
m := NewManager()
got := m.AddTrayMenu(tt.trayMenu)
JSON, err := got.AsJSON()
is.NoErr(err)
is.Equal(JSON, tt.want)
}
}
func TestManager_CallbackMap(t *testing.T) {
is := is.New(t)
simpleLabel := menu.Text("test", nil, nil)
simpleLabelWithCallback := menu.Text("test", nil, func(_ *menu.CallbackData) {})
checkboxWithCallback := menu.Checkbox("test", true, nil, func(_ *menu.CallbackData) {})
submenu := menu.SubMenu("test", menu.NewMenuFromItems(checkboxWithCallback))
blankMenu := &menu.TrayMenu{Menu: menu.NewMenu()}
noCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabel)}
oneMenuWithCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback)}
duplicateCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, simpleLabelWithCallback)}
twoMenusWithCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, checkboxWithCallback)}
duplicateMenusWithCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, checkboxWithCallback, simpleLabelWithCallback, checkboxWithCallback)}
submenuWithCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(submenu)}
duplicateSubmenus := &menu.TrayMenu{Menu: menu.NewMenuFromItems(submenu, submenu)}
tests := []struct {
trayMenu *menu.TrayMenu
trays int
menuItems int
JSON string
}{
{blankMenu, 1, 0, "{\"I\":\"T1\"}"},
{noCallback, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}"},
{oneMenuWithCallback, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true}]}"},
{duplicateCallbacks, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"1\"}]}"},
{twoMenusWithCallbacks, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]}"},
{duplicateMenusWithCallbacks, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true},{\"I\":\"1\"},{\"I\":\"2\"}]}"},
{submenuWithCallback, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"S\",\"s\":[{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]}]}"},
{duplicateSubmenus, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"S\",\"s\":[{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]},{\"I\":\"1\"}]}"},
}
for _, test := range tests {
m := NewManager()
tm := m.AddTrayMenu(test.trayMenu)
is.Equal(len(m.trayMenuMap), test.trays)
is.Equal(len(m.menuItemMap), test.menuItems)
JSON, err := tm.AsJSON()
is.NoErr(err)
is.Equal(JSON, test.JSON)
}
}

View File

@@ -26,6 +26,7 @@ func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Proces
// Start the process
func (p *Process) Start() error {
p.cmd.
err := p.cmd.Start()
if err != nil {
return err

View File

@@ -80,9 +80,7 @@ func (m *Menu) Start() error {
type ClickCallbackMessage struct {
MenuItemID string `json:"menuItemID"`
MenuType string `json:"menuType"`
Data string `json:"data"`
ParentID string `json:"parentID"`
}
var callbackData ClickCallbackMessage
@@ -93,7 +91,7 @@ func (m *Menu) Start() error {
return
}
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data, callbackData.MenuType, callbackData.ParentID)
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data)
if err != nil {
m.logger.Trace("%s", err.Error())
}

View File

@@ -9,6 +9,8 @@ import (
type MenuItem struct {
// Label is what appears as the menu text
Label string
// AlternateLabel is a secondary label (Used by Mac)
AlternateLabel string
// Role is a predefined menu type
Role Role `json:"Role,omitempty"`
// Accelerator holds a representation of a key binding
@@ -21,6 +23,14 @@ type MenuItem struct {
Hidden bool
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
Checked bool
// Font to use for the menu item
Font string
// Font to use for the menu item
FontSize int
// RGBA is the colour of the menu item
RGBA string
// Image is an image for the menu item (base64 string)
Image string
// Submenu contains a list of menu items that will be shown as a submenu
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
SubMenu *Menu `json:"SubMenu,omitempty"`

View File

@@ -5,13 +5,13 @@ type Type string
const (
// TextType is the text menuitem type
TextType Type = "Text"
TextType Type = "t"
// SeparatorType is the Separator menuitem type
SeparatorType Type = "Separator"
SeparatorType Type = "s"
// SubmenuType is the Submenu menuitem type
SubmenuType Type = "Submenu"
SubmenuType Type = "S"
// CheckboxType is the Checkbox menuitem type
CheckboxType Type = "Checkbox"
CheckboxType Type = "c"
// RadioType is the Radio menuitem type
RadioType Type = "Radio"
RadioType Type = "r"
)

View File

@@ -13,10 +13,8 @@ var Default = &App{
DevTools: false,
RGBA: 0xFFFFFFFF,
Mac: &mac.Options{
TitleBar: mac.TitleBarDefault(),
Appearance: mac.DefaultAppearance,
WebviewIsTransparent: false,
WindowBackgroundIsTranslucent: false,
TitleBar: mac.TitleBarDefault(),
Appearance: mac.DefaultAppearance,
},
Logger: logger.NewDefaultLogger(),
LogLevel: logger.INFO,

View File

@@ -58,6 +58,7 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=