mirror of
https://github.com/taigrr/wails.git
synced 2026-04-03 05:38:56 -07:00
Compare commits
3 Commits
patch-1
...
feature/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
480dcb7895 | ||
|
|
c8d89cf002 | ||
|
|
fc669ede37 |
40
v2/internal/counter/counter.go
Normal file
40
v2/internal/counter/counter.go
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
//
|
||||
//}
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
276
v2/internal/ffenestri/menu.c
Normal file
276
v2/internal/ffenestri/menu.c
Normal 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;
|
||||
}
|
||||
178
v2/internal/ffenestri/menu.h
Normal file
178
v2/internal/ffenestri/menu.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
542
v2/internal/ffenestri/menu_darwin_old.c
Normal file
542
v2/internal/ffenestri/menu_darwin_old.c
Normal 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;
|
||||
}
|
||||
|
||||
97
v2/internal/ffenestri/menu_darwin_old.h
Normal file
97
v2/internal/ffenestri/menu_darwin_old.h
Normal 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
|
||||
101
v2/internal/ffenestri/menu_item.c
Normal file
101
v2/internal/ffenestri/menu_item.c
Normal 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);
|
||||
}
|
||||
81
v2/internal/ffenestri/menu_manager.c
Normal file
81
v2/internal/ffenestri/menu_manager.c
Normal 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);
|
||||
}
|
||||
|
||||
69
v2/internal/ffenestri/menu_tray.c
Normal file
69
v2/internal/ffenestri/menu_tray.c
Normal 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);
|
||||
}
|
||||
25
v2/internal/ffenestri/test_app/CMakeLists.txt
Normal file
25
v2/internal/ffenestri/test_app/CMakeLists.txt
Normal 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(.)
|
||||
9
v2/internal/ffenestri/test_app/assets.h
Normal file
9
v2/internal/ffenestri/test_app/assets.h
Normal file
File diff suppressed because one or more lines are too long
81
v2/internal/ffenestri/test_app/test.c
Normal file
81
v2/internal/ffenestri/test_app/test.c
Normal file
File diff suppressed because one or more lines are too long
21
v2/internal/ffenestri/test_menumanager/CMakeLists.txt
Normal file
21
v2/internal/ffenestri/test_menumanager/CMakeLists.txt
Normal 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(.)
|
||||
20
v2/internal/ffenestri/test_menumanager/minunit.LICENSE.txt
Normal file
20
v2/internal/ffenestri/test_menumanager/minunit.LICENSE.txt
Normal 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.
|
||||
391
v2/internal/ffenestri/test_menumanager/minunit.h
Normal file
391
v2/internal/ffenestri/test_menumanager/minunit.h
Normal 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 */
|
||||
71
v2/internal/ffenestri/test_menumanager/test.c
Normal file
71
v2/internal/ffenestri/test_menumanager/test.c
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
1292
v2/internal/ffenestri/utf8.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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),\
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
99
v2/internal/menumanager/traymenu_test.go
Normal file
99
v2/internal/menumanager/traymenu_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user