mirror of
https://github.com/taigrr/wails.git
synced 2026-04-14 10:50:53 -07:00
Compare commits
3 Commits
feature/v2
...
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
|
## hashmap
|
||||||
Homepage: https://github.com/sheredom/hashmap.h
|
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) {
|
const char* getJSONString(JsonNode *item, const char* key) {
|
||||||
// Get key
|
// Get key
|
||||||
JsonNode *node = json_find_member(item, key);
|
JsonNode *node = json_find_member(item, key);
|
||||||
const char *result = "";
|
const char *result = NULL;
|
||||||
if ( node != NULL && node->tag == JSON_STRING) {
|
if ( node != NULL && node->tag == JSON_STRING) {
|
||||||
result = node->string_;
|
result = node->string_;
|
||||||
}
|
}
|
||||||
return result;
|
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) {
|
void ABORT_JSON(JsonNode *node, const char* key) {
|
||||||
ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node));
|
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* mustJSONString(JsonNode *item, const char* key) {
|
||||||
const char* result = getJSONString(node, key);
|
JsonNode *member = json_find_member(item, key);
|
||||||
if ( result == NULL ) {
|
if ( member == NULL ) {
|
||||||
ABORT_JSON(node, key);
|
ABORT_JSON(item, key);
|
||||||
|
}
|
||||||
|
const char *result = "";
|
||||||
|
if ( member != NULL && member->tag == JSON_STRING) {
|
||||||
|
result = member->string_;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -68,15 +78,23 @@ JsonNode* getJSONObject(JsonNode* node, const char* key) {
|
|||||||
return json_find_member(node, key);
|
return json_find_member(node, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
|
bool getJSONBool(JsonNode *node, const char* key) {
|
||||||
JsonNode *node = json_find_member(item, key);
|
JsonNode *result = json_find_member(node, key);
|
||||||
if ( node != NULL && node->tag == JSON_BOOL) {
|
if ( result != NULL && result->tag == JSON_BOOL) {
|
||||||
*result = node->bool_;
|
return result->bool_;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
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) {
|
bool getJSONInt(JsonNode *item, const char* key, int *result) {
|
||||||
JsonNode *node = json_find_member(item, key);
|
JsonNode *node = json_find_member(item, key);
|
||||||
if ( node != NULL && node->tag == JSON_NUMBER) {
|
if ( node != NULL && node->tag == JSON_NUMBER) {
|
||||||
|
|||||||
@@ -18,23 +18,45 @@
|
|||||||
|
|
||||||
#define STREQ(a,b) strcmp(a, b) == 0
|
#define STREQ(a,b) strcmp(a, b) == 0
|
||||||
#define STREMPTY(string) strlen(string) == 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 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 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
|
// Credit: https://stackoverflow.com/a/8465083
|
||||||
char* concat(const char *string1, const char *string2);
|
char* concat(const char *string1, const char *string2);
|
||||||
void ABORT(const char *message, ...);
|
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* 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);
|
const char* mustJSONString(JsonNode *node, const char* key);
|
||||||
JsonNode* getJSONObject(JsonNode* node, const char* key);
|
JsonNode* getJSONObject(JsonNode* node, const char* key);
|
||||||
JsonNode* mustJSONObject(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);
|
bool getJSONInt(JsonNode *item, const char* key, int *result);
|
||||||
|
int mustJSONInt(JsonNode *node, const char* key);
|
||||||
JsonNode* mustParseJSON(const char* JSON);
|
JsonNode* mustParseJSON(const char* JSON);
|
||||||
|
|
||||||
#endif //ASSETS_C_COMMON_H
|
#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 "ffenestri_darwin.h"
|
//#include "common.h"
|
||||||
#include "common.h"
|
//#include "contextmenus_darwin.h"
|
||||||
#include "contextmenus_darwin.h"
|
//#include "menu_darwin.h"
|
||||||
#include "menu_darwin.h"
|
//
|
||||||
|
//ContextMenu* NewContextMenu(const char* contextMenuJSON, struct TrayMenuStore *store) {
|
||||||
ContextMenu* NewContextMenu(const char* contextMenuJSON) {
|
// ContextMenu* result = malloc(sizeof(ContextMenu));
|
||||||
ContextMenu* result = malloc(sizeof(ContextMenu));
|
//
|
||||||
|
// JsonNode* processedJSON = json_decode(contextMenuJSON);
|
||||||
JsonNode* processedJSON = json_decode(contextMenuJSON);
|
// if( processedJSON == NULL ) {
|
||||||
if( processedJSON == NULL ) {
|
// ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
|
||||||
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
|
// }
|
||||||
}
|
// // Save reference to this json
|
||||||
// Save reference to this json
|
// result->processedJSON = processedJSON;
|
||||||
result->processedJSON = processedJSON;
|
//
|
||||||
|
// result->ID = mustJSONString(processedJSON, "ID");
|
||||||
result->ID = mustJSONString(processedJSON, "ID");
|
// JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
//
|
||||||
|
//// result->menu = NewMenu(processedMenu);
|
||||||
result->menu = NewMenu(processedMenu);
|
// result->nsmenu = NULL;
|
||||||
result->nsmenu = NULL;
|
// result->menu->menuType = ContextMenuType;
|
||||||
result->menu->menuType = ContextMenuType;
|
// result->menu->store = store;
|
||||||
result->menu->parentData = result;
|
// result->contextMenuData = NULL;
|
||||||
result->contextMenuData = NULL;
|
// return result;
|
||||||
return result;
|
//}
|
||||||
}
|
//
|
||||||
|
//ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
|
||||||
ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
|
// return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||||
return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
//}
|
||||||
}
|
//
|
||||||
|
//void DeleteContextMenu(ContextMenu* contextMenu) {
|
||||||
void DeleteContextMenu(ContextMenu* contextMenu) {
|
// // Free Menu
|
||||||
// Free Menu
|
// DeleteMenu(contextMenu->menu);
|
||||||
DeleteMenu(contextMenu->menu);
|
//
|
||||||
|
// // Delete any context menu data we may have stored
|
||||||
// Delete any context menu data we may have stored
|
// if( contextMenu->contextMenuData != NULL ) {
|
||||||
if( contextMenu->contextMenuData != NULL ) {
|
// MEMFREE(contextMenu->contextMenuData);
|
||||||
MEMFREE(contextMenu->contextMenuData);
|
// }
|
||||||
}
|
//
|
||||||
|
// // Free JSON
|
||||||
// Free JSON
|
// if (contextMenu->processedJSON != NULL ) {
|
||||||
if (contextMenu->processedJSON != NULL ) {
|
// json_delete(contextMenu->processedJSON);
|
||||||
json_delete(contextMenu->processedJSON);
|
// contextMenu->processedJSON = NULL;
|
||||||
contextMenu->processedJSON = NULL;
|
// }
|
||||||
}
|
//
|
||||||
|
// // Free context menu
|
||||||
// Free context menu
|
// free(contextMenu);
|
||||||
free(contextMenu);
|
//}
|
||||||
}
|
//
|
||||||
|
//int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
|
||||||
int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
|
// DeleteContextMenu(e->data);
|
||||||
DeleteContextMenu(e->data);
|
// return -1;
|
||||||
return -1;
|
//}
|
||||||
}
|
//
|
||||||
|
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
|
||||||
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
|
//
|
||||||
|
// // If no context menu ID was given, abort
|
||||||
// If no context menu ID was given, abort
|
// if( contextMenuID == NULL ) {
|
||||||
if( contextMenuID == NULL ) {
|
// return;
|
||||||
return;
|
// }
|
||||||
}
|
//
|
||||||
|
// ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
|
||||||
ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
|
//
|
||||||
|
// // We don't need the ID now
|
||||||
// We don't need the ID now
|
// MEMFREE(contextMenuID);
|
||||||
MEMFREE(contextMenuID);
|
//
|
||||||
|
// if( contextMenu == NULL ) {
|
||||||
if( contextMenu == NULL ) {
|
// // Free context menu data
|
||||||
// Free context menu data
|
// if( contextMenuData != NULL ) {
|
||||||
if( contextMenuData != NULL ) {
|
// MEMFREE(contextMenuData);
|
||||||
MEMFREE(contextMenuData);
|
// return;
|
||||||
return;
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// // We need to store the context menu data. Free existing data if we have it
|
||||||
// We need to store the context menu data. Free existing data if we have it
|
// // and set to the new value.
|
||||||
// and set to the new value.
|
// FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||||
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
//
|
||||||
|
// // Grab the content view and show the menu
|
||||||
// Grab the content view and show the menu
|
// id contentView = msg(mainWindow, s("contentView"));
|
||||||
id contentView = msg(mainWindow, s("contentView"));
|
//
|
||||||
|
// // Get the triggering event
|
||||||
// Get the triggering event
|
// id menuEvent = msg(mainWindow, s("currentEvent"));
|
||||||
id menuEvent = msg(mainWindow, s("currentEvent"));
|
//
|
||||||
|
//// if( contextMenu->nsmenu == NULL ) {
|
||||||
if( contextMenu->nsmenu == NULL ) {
|
//// // GetMenu creates the NSMenu
|
||||||
// GetMenu creates the NSMenu
|
//// contextMenu->nsmenu = GetMenu(contextMenu->menu);
|
||||||
contextMenu->nsmenu = GetMenu(contextMenu->menu);
|
//// }
|
||||||
}
|
//
|
||||||
|
// // Show popup
|
||||||
// Show popup
|
// msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||||
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
|
//#include "json.h"
|
||||||
#define CONTEXTMENU_DARWIN_H
|
//#include "menu_darwin.h"
|
||||||
|
//#include "contextmenustore_darwin.h"
|
||||||
#include "json.h"
|
//
|
||||||
#include "menu_darwin.h"
|
//typedef struct {
|
||||||
#include "contextmenustore_darwin.h"
|
// const char* ID;
|
||||||
|
// id nsmenu;
|
||||||
typedef struct {
|
// Menu* menu;
|
||||||
const char* ID;
|
//
|
||||||
id nsmenu;
|
// JsonNode* processedJSON;
|
||||||
Menu* menu;
|
//
|
||||||
|
// // Context menu data is given by the frontend when clicking a context menu.
|
||||||
JsonNode* processedJSON;
|
// // We send this to the backend when an item is selected
|
||||||
|
// const char* contextMenuData;
|
||||||
// Context menu data is given by the frontend when clicking a context menu.
|
//} ContextMenu;
|
||||||
// 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);
|
||||||
ContextMenu* NewContextMenu(const char* contextMenuJSON);
|
//void DeleteContextMenu(ContextMenu* contextMenu);
|
||||||
|
//int freeContextMenu(void *const context, struct hashmap_element_s *const e);
|
||||||
ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
|
//
|
||||||
void DeleteContextMenu(ContextMenu* contextMenu);
|
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
|
||||||
int freeContextMenu(void *const context, struct hashmap_element_s *const e);
|
//
|
||||||
|
//#endif //CONTEXTMENU_DARWIN_H
|
||||||
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 "contextmenus_darwin.h"
|
||||||
#include "contextmenustore_darwin.h"
|
//#include "contextmenustore_darwin.h"
|
||||||
|
//
|
||||||
ContextMenuStore* NewContextMenuStore() {
|
//ContextMenuStore* NewContextMenuStore() {
|
||||||
|
//
|
||||||
ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
|
// ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
|
||||||
|
//
|
||||||
// Allocate Context Menu Store
|
// // Allocate Context Menu Store
|
||||||
if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
|
// if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
|
||||||
ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
|
// ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return result;
|
// return result;
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
|
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
|
||||||
ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
|
// ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
|
||||||
|
//
|
||||||
//TODO: check if there is already an entry for this menu
|
// //TODO: check if there is already an entry for this menu
|
||||||
hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
// hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
|
//ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
|
||||||
// Get the current menu
|
// // Get the current menu
|
||||||
return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
|
// return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
|
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
|
||||||
ContextMenu* newContextMenu = NewContextMenu(menuJSON);
|
// ContextMenu* newContextMenu = NewContextMenu(menuJSON);
|
||||||
|
//
|
||||||
// Get the current menu
|
// // Get the current menu
|
||||||
ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
|
// ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
|
||||||
if ( currentMenu == NULL ) {
|
// if ( currentMenu == NULL ) {
|
||||||
ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
|
// ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
|
// hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
|
||||||
|
//
|
||||||
// Save the status bar reference
|
// // Save the status bar reference
|
||||||
DeleteContextMenu(currentMenu);
|
// DeleteContextMenu(currentMenu);
|
||||||
|
//
|
||||||
hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
|
// hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
void DeleteContextMenuStore(ContextMenuStore* store) {
|
//void DeleteContextMenuStore(ContextMenuStore* store) {
|
||||||
|
//
|
||||||
// Guard against NULLs
|
// // Guard against NULLs
|
||||||
if( store == NULL ) {
|
// if( store == NULL ) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Delete context menus
|
// // Delete context menus
|
||||||
if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
|
// if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
|
||||||
if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
|
// if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
|
||||||
ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
|
// ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Free context menu hashmap
|
// // Free context menu hashmap
|
||||||
hashmap_destroy(&store->contextMenuMap);
|
// 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
|
||||||
//
|
//
|
||||||
|
//#include "common.h"
|
||||||
#ifndef CONTEXTMENUSTORE_DARWIN_H
|
//
|
||||||
#define CONTEXTMENUSTORE_DARWIN_H
|
//typedef struct {
|
||||||
|
//
|
||||||
#include "common.h"
|
// int dummy;
|
||||||
|
//
|
||||||
typedef struct {
|
// // This is our context menu store which keeps track
|
||||||
|
// // of all instances of ContextMenus
|
||||||
int dummy;
|
// struct hashmap_s contextMenuMap;
|
||||||
|
//
|
||||||
// This is our context menu store which keeps track
|
//} ContextMenuStore;
|
||||||
// of all instances of ContextMenus
|
//
|
||||||
struct hashmap_s contextMenuMap;
|
//ContextMenuStore* NewContextMenuStore();
|
||||||
|
//
|
||||||
} ContextMenuStore;
|
//void DeleteContextMenuStore(ContextMenuStore* store);
|
||||||
|
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
|
||||||
ContextMenuStore* NewContextMenuStore();
|
//
|
||||||
|
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
|
||||||
void DeleteContextMenuStore(ContextMenuStore* store);
|
//
|
||||||
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
|
//#endif //CONTEXTMENUSTORE_DARWIN_H
|
||||||
|
|
||||||
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 UnFullscreen(struct Application*);
|
||||||
extern void ToggleFullscreen(struct Application*);
|
extern void ToggleFullscreen(struct Application*);
|
||||||
extern void DisableFrame(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 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*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, 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*, 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*, 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*, char *callbackID);
|
extern void DarkModeEnabled(struct Application*, const char *callbackID);
|
||||||
extern void SetApplicationMenu(struct Application*, const char *);
|
extern void SetApplicationMenu(struct Application*, const char *);
|
||||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
extern void AddContextMenu(struct Application*, const char *contextMenuJSON);
|
||||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
extern void UpdateContextMenu(struct Application*, const char *contextMenuJSON);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
|
||||||
#ifdef FFENESTRI_DARWIN
|
#include "ffenestri.h"
|
||||||
|
|
||||||
#include "ffenestri_darwin.h"
|
#include "ffenestri_darwin.h"
|
||||||
#include "menu_darwin.h"
|
#include "menu.h"
|
||||||
#include "contextmenus_darwin.h"
|
//#include "contextmenus_darwin.h"
|
||||||
#include "traymenustore_darwin.h"
|
//#include "traymenustore_darwin.h"
|
||||||
#include "traymenu_darwin.h"
|
//#include "traymenu_darwin.h"
|
||||||
|
|
||||||
// References to assets
|
// References to assets
|
||||||
#include "assets.h"
|
#include "assets.h"
|
||||||
@@ -14,6 +13,10 @@ extern const unsigned char runtime;
|
|||||||
// Dialog icons
|
// Dialog icons
|
||||||
extern const unsigned char *defaultDialogIcons[];
|
extern const unsigned char *defaultDialogIcons[];
|
||||||
#include "userdialogicons.h"
|
#include "userdialogicons.h"
|
||||||
|
#include "menu_darwin.h"
|
||||||
|
|
||||||
|
extern void messageFromWindowCallback(const char *);
|
||||||
|
typedef void (*ffenestriCallback)(const char *);
|
||||||
|
|
||||||
// MAIN DEBUG FLAG
|
// MAIN DEBUG FLAG
|
||||||
int debug;
|
int debug;
|
||||||
@@ -49,9 +52,6 @@ void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
|||||||
printf("}\n");
|
printf("}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void messageFromWindowCallback(const char *);
|
|
||||||
typedef void (*ffenestriCallback)(const char *);
|
|
||||||
|
|
||||||
void HideMouse() {
|
void HideMouse() {
|
||||||
msg(c("NSCursor"), s("hide"));
|
msg(c("NSCursor"), s("hide"));
|
||||||
}
|
}
|
||||||
@@ -105,14 +105,16 @@ struct Application {
|
|||||||
int hideToolbarSeparator;
|
int hideToolbarSeparator;
|
||||||
int windowBackgroundIsTranslucent;
|
int windowBackgroundIsTranslucent;
|
||||||
|
|
||||||
// Menu
|
// // Menu
|
||||||
Menu *applicationMenu;
|
// Menu *applicationMenu;
|
||||||
|
//
|
||||||
|
// // Tray
|
||||||
|
// TrayMenuStore* trayMenuStore;
|
||||||
|
|
||||||
// Tray
|
MenuManager* menuManager;
|
||||||
TrayMenuStore* trayMenuStore;
|
|
||||||
|
|
||||||
// Context Menus
|
// Context Menus
|
||||||
ContextMenuStore *contextMenuStore;
|
// ContextMenuStore *contextMenuStore;
|
||||||
|
|
||||||
// Callback
|
// Callback
|
||||||
ffenestriCallback sendMessageToBackend;
|
ffenestriCallback sendMessageToBackend;
|
||||||
@@ -120,6 +122,9 @@ struct Application {
|
|||||||
// Bindings
|
// Bindings
|
||||||
const char *bindings;
|
const char *bindings;
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
bool running;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug works like sprintf but mutes if the global debug flag is true
|
// Debug works like sprintf but mutes if the global debug flag is true
|
||||||
@@ -249,6 +254,9 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
Show(app);
|
Show(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup initial trays
|
||||||
|
ShowTrayMenus(app->menuManager);
|
||||||
|
|
||||||
// TODO: Check this actually does reduce flicker
|
// TODO: Check this actually does reduce flicker
|
||||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
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_);
|
const char* contextMenuData = STRCOPY(contextMenuDataNode->string_);
|
||||||
|
|
||||||
ON_MAIN_THREAD(
|
ON_MAIN_THREAD(
|
||||||
ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
|
//TODO: FIX UP
|
||||||
|
// ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
|
||||||
);
|
);
|
||||||
|
|
||||||
json_delete(contextMenuMessageJSON);
|
json_delete(contextMenuMessageJSON);
|
||||||
@@ -387,7 +396,7 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void destroyContextMenus(struct Application *app) {
|
void destroyContextMenus(struct Application *app) {
|
||||||
DeleteContextMenuStore(app->contextMenuStore);
|
// DeleteContextMenuStore(app->contextMenuStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
void freeDialogIconCache(struct Application *app) {
|
void freeDialogIconCache(struct Application *app) {
|
||||||
@@ -420,19 +429,8 @@ void DestroyApplication(struct Application *app) {
|
|||||||
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
|
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
|
// Delete the tray menu store
|
||||||
DeleteTrayMenuStore(app->trayMenuStore);
|
DeleteMenuManager(app->menuManager);
|
||||||
|
|
||||||
// Delete the context menu store
|
|
||||||
DeleteContextMenuStore(app->contextMenuStore);
|
|
||||||
|
|
||||||
// Destroy the context menus
|
|
||||||
destroyContextMenus(app);
|
|
||||||
|
|
||||||
// Free dialog icon cache
|
// Free dialog icon cache
|
||||||
freeDialogIconCache(app);
|
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 this button is set
|
||||||
if( STR_HAS_CHARS(buttonTitle) ) {
|
if( STR_HAS_CHARS(buttonTitle) ) {
|
||||||
id button = msg(alert, s("addButtonWithTitle:"), str(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(
|
ON_MAIN_THREAD(
|
||||||
id alert = ALLOC_INIT("NSAlert");
|
id alert = ALLOC_INIT("NSAlert");
|
||||||
char *dialogType = type;
|
const char *dialogType = type;
|
||||||
char *dialogIcon = type;
|
const char *dialogIcon = type;
|
||||||
|
|
||||||
// Default to info type
|
// Default to info type
|
||||||
if( dialogType == NULL ) {
|
if( dialogType == NULL ) {
|
||||||
@@ -674,7 +672,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run modal
|
// Run modal
|
||||||
char *buttonPressed;
|
const char *buttonPressed;
|
||||||
int response = (int)msg(alert, s("runModal"));
|
int response = (int)msg(alert, s("runModal"));
|
||||||
if( response == NSAlertFirstButtonReturn ) {
|
if( response == NSAlertFirstButtonReturn ) {
|
||||||
buttonPressed = button1;
|
buttonPressed = button1;
|
||||||
@@ -705,7 +703,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenDialog opens a dialog to select files/directories
|
// 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);
|
Debug(app, "OpenDialog Called with callback id: %s", callbackID);
|
||||||
|
|
||||||
// Create an open panel
|
// 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
|
// 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);
|
Debug(app, "SaveDialog Called with callback id: %s", callbackID);
|
||||||
|
|
||||||
// Create an open panel
|
// 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;
|
debug = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,28 +914,31 @@ void SetDebug(void *applicationPointer, int flag) {
|
|||||||
|
|
||||||
// AddContextMenu sets the context menu map for this application
|
// AddContextMenu sets the context menu map for this application
|
||||||
void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
|
void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
|
||||||
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
// AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateContextMenu(struct Application *app, const char* 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) {
|
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
||||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
AddTrayMenuToManager(app->menuManager, trayMenuJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||||
ON_MAIN_THREAD(
|
TrayMenu *menu = AddTrayMenuToManager(app->menuManager, trayMenuJSON);
|
||||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
if( app->running ) {
|
||||||
);
|
ON_MAIN_THREAD(
|
||||||
|
ShowTrayMenu(menu)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
//void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||||
ON_MAIN_THREAD(
|
// ON_MAIN_THREAD(
|
||||||
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
// UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
void SetBindings(struct Application *app, const char *bindings) {
|
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@:@");
|
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||||
|
|
||||||
// All Menu Items use a common callback
|
// 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
|
// Script handler
|
||||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||||
@@ -1083,402 +1084,69 @@ const char* getInitialState(struct Application *app) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
|
//void dumpMemberList(const char *name, id *memberList) {
|
||||||
const char *roleName = item->string_;
|
// void *member = memberList[0];
|
||||||
|
// int count = 0;
|
||||||
if ( STREQ(roleName, "appMenu") ) {
|
// printf("%s = %p -> [ ", name, memberList);
|
||||||
createDefaultAppMenu(parentMenu);
|
// while( member != NULL ) {
|
||||||
return;
|
// printf("%p ", member);
|
||||||
}
|
// count = count + 1;
|
||||||
if ( STREQ(roleName, "editMenu")) {
|
// member = memberList[count];
|
||||||
createDefaultEditMenu(parentMenu);
|
// }
|
||||||
return;
|
// printf("]\n");
|
||||||
}
|
//}
|
||||||
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);
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetApplicationMenu sets the initial menu for the application
|
// SetApplicationMenu sets the initial menu for the application
|
||||||
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
//void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
||||||
if ( app->applicationMenu == NULL ) {
|
// if ( app->applicationMenu == NULL ) {
|
||||||
app->applicationMenu = NewApplicationMenu(menuAsJSON);
|
// app->applicationMenu = NewApplicationMenu(menuAsJSON, (struct TrayMenuStore *) app->trayMenuStore);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// // Update menu
|
||||||
|
// updateMenu(app, menuAsJSON);
|
||||||
|
//}
|
||||||
|
|
||||||
// Update menu
|
//void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
|
||||||
updateMenu(app, menuAsJSON);
|
//
|
||||||
}
|
// unsigned int count = 0;
|
||||||
|
// while( 1 ) {
|
||||||
void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
|
// const unsigned char *name = dialogIcons[count++];
|
||||||
|
// if( name == 0x00 ) {
|
||||||
unsigned int count = 0;
|
// break;
|
||||||
while( 1 ) {
|
// }
|
||||||
const unsigned char *name = dialogIcons[count++];
|
// const unsigned char *lengthAsString = dialogIcons[count++];
|
||||||
if( name == 0x00 ) {
|
// if( name == 0x00 ) {
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
const unsigned char *lengthAsString = dialogIcons[count++];
|
// const unsigned char *data = dialogIcons[count++];
|
||||||
if( name == 0x00 ) {
|
// if( data == 0x00 ) {
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
const unsigned char *data = dialogIcons[count++];
|
// int length = atoi((const char *)lengthAsString);
|
||||||
if( data == 0x00 ) {
|
//
|
||||||
break;
|
// // Create the icon and add to the hashmap
|
||||||
}
|
// id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||||
int length = atoi((const char *)lengthAsString);
|
// id dialogImage = ALLOC("NSImage");
|
||||||
|
// msg(dialogImage, s("initWithData:"), imageData);
|
||||||
// Create the icon and add to the hashmap
|
// hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
|
||||||
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)) {
|
||||||
void processUserDialogIcons(struct Application *app) {
|
// // Couldn't allocate map
|
||||||
|
// Fatal(app, "Not enough memory to allocate dialogIconCache!");
|
||||||
// Allocate the Dialog icon hashmap
|
// return;
|
||||||
if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
|
// }
|
||||||
// Couldn't allocate map
|
//
|
||||||
Fatal(app, "Not enough memory to allocate dialogIconCache!");
|
// processDialogIcons(&dialogIconCache, defaultDialogIcons);
|
||||||
return;
|
// processDialogIcons(&dialogIconCache, userDialogIcons);
|
||||||
}
|
//
|
||||||
|
//}
|
||||||
processDialogIcons(&dialogIconCache, defaultDialogIcons);
|
|
||||||
processDialogIcons(&dialogIconCache, userDialogIcons);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Run(struct Application *app, int argc, char **argv) {
|
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"));
|
msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have an application menu, process it
|
// // If we have an application menu, process it
|
||||||
if( app->applicationMenu != NULL ) {
|
// if( app->applicationMenu != NULL ) {
|
||||||
id menu = GetMenu(app->applicationMenu);
|
// msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), app->applicationMenu->menu);
|
||||||
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// Setup initial trays
|
|
||||||
ShowTrayMenusInStore(app->trayMenuStore);
|
|
||||||
|
|
||||||
// Process dialog icons
|
// Process dialog icons
|
||||||
processUserDialogIcons(app);
|
// processUserDialogIcons(app);
|
||||||
|
|
||||||
|
app->running = true;
|
||||||
|
|
||||||
// Finally call run
|
// Finally call run
|
||||||
Debug(app, "Run called");
|
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
|
// Load the tray icons
|
||||||
LoadTrayIcons();
|
LoadTrayIcons();
|
||||||
@@ -1715,13 +1381,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
|||||||
result->delegate = NULL;
|
result->delegate = NULL;
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
result->applicationMenu = NULL;
|
result->menuManager = NewMenuManager();
|
||||||
|
|
||||||
// Tray
|
|
||||||
result->trayMenuStore = NewTrayMenuStore();
|
|
||||||
|
|
||||||
// Context Menus
|
// Context Menus
|
||||||
result->contextMenuStore = NewContextMenuStore();
|
// result->contextMenuStore = NewContextMenuStore();
|
||||||
|
|
||||||
// Window Appearance
|
// Window Appearance
|
||||||
result->titlebarAppearsTransparent = 0;
|
result->titlebarAppearsTransparent = 0;
|
||||||
@@ -1729,8 +1392,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
|||||||
|
|
||||||
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
|
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
|
||||||
|
|
||||||
|
result->running = false;
|
||||||
|
|
||||||
return (void*) result;
|
return (void*) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -58,15 +58,15 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
C.WindowBackgroundIsTranslucent(a.app)
|
C.WindowBackgroundIsTranslucent(a.app)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process menu
|
//// Process menu
|
||||||
//applicationMenu := options.GetApplicationMenu(a.config)
|
////applicationMenu := options.GetApplicationMenu(a.config)
|
||||||
applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||||
if applicationMenu != "" {
|
//if applicationMenu != "" {
|
||||||
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Process tray
|
// Process tray
|
||||||
trays, err := a.menuManager.GetTrayMenus()
|
trays, err := a.menuManager.GetTrayMenusAsJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -76,16 +76,16 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process context menus
|
//// Process context menus
|
||||||
contextMenus, err := a.menuManager.GetContextMenus()
|
//contextMenus, err := a.menuManager.GetContextMenus()
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
//}
|
||||||
if contextMenus != nil {
|
//if contextMenus != nil {
|
||||||
for _, contextMenu := range contextMenus {
|
// for _, contextMenu := range contextMenus {
|
||||||
C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
// C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||||
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
||||||
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
|
#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(classname) msg(c(classname), s("alloc"))
|
||||||
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
|
#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 WindowBackgroundIsTranslucent(struct Application* app);
|
||||||
void SetTray(struct Application* app, const char *, const char *, const char *);
|
void SetTray(struct Application* app, const char *, const char *, const char *);
|
||||||
//void SetContextMenus(struct Application* app, const char *);
|
//void SetContextMenus(struct Application* app, const char *);
|
||||||
void AddTrayMenu(struct Application* app, const char *);
|
|
||||||
|
|
||||||
#endif
|
#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 "ffenestri_darwin.h"
|
||||||
#include "menu_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
|
// A cache for all our tray menu icons
|
||||||
Menu* NewMenu(JsonNode *menuData) {
|
// Global because it's a singleton
|
||||||
|
struct hashmap_s trayIconCache;
|
||||||
|
|
||||||
Menu *result = malloc(sizeof(Menu));
|
void SetupMenuPlatformData(Menu* menu) {
|
||||||
|
MacMenu* result = NEW(MacMenu);
|
||||||
result->processedMenu = menuData;
|
result->Menu = ALLOC("NSMenu");
|
||||||
|
msg(result->Menu, s("initWithTitle:"), str(menu->label));
|
||||||
// No title by default
|
msg(result->Menu, s("setAutoenablesItems:"), NO);
|
||||||
result->title = "";
|
menu->platformData = (void*)result;
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu* NewApplicationMenu(const char *menuAsJSON) {
|
void DeleteMenuPlatformData(Menu* menu) {
|
||||||
|
|
||||||
// Parse the menu json
|
// Return if null
|
||||||
JsonNode *processedMenu = json_decode(menuAsJSON);
|
if( menu == NULL || menu->platformData == NULL) return;
|
||||||
if( processedMenu == NULL ) {
|
|
||||||
// Parse error!
|
|
||||||
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu *result = NewMenu(processedMenu);
|
MacMenu* macMenu = (MacMenu*) menu->platformData;
|
||||||
result->menuType = ApplicationMenuType;
|
RELEASE(macMenu->Menu);
|
||||||
return result;
|
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) {
|
id processAcceleratorKey(const char *key) {
|
||||||
|
|
||||||
@@ -344,161 +201,6 @@ id processAcceleratorKey(const char *key) {
|
|||||||
return str(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
|
// This converts a string array of modifiers into the
|
||||||
// equivalent MacOS Modifier Flags
|
// equivalent MacOS Modifier Flags
|
||||||
unsigned long parseModifiers(const char **modifiers) {
|
unsigned long parseModifiers(const char **modifiers) {
|
||||||
@@ -531,282 +233,286 @@ unsigned long parseModifiers(const char **modifiers) {
|
|||||||
return result;
|
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");
|
id item = ALLOC("NSMenuItem");
|
||||||
|
macMenuItem->MenuItem = item;
|
||||||
|
|
||||||
// Store the item in the menu item map
|
// // TODO: Process ROLE
|
||||||
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
|
// if( menuItem->role != NULL ) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
// Create a MenuItemCallbackData
|
id key = processAcceleratorKey(menuItem->acceleratorKey);
|
||||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
|
msg(item, s("initWithTitle:action:keyEquivalent:"), str(menuItem->label),
|
||||||
|
|
||||||
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),
|
|
||||||
s("menuItemCallback:"), key);
|
s("menuItemCallback:"), key);
|
||||||
|
|
||||||
msg(item, s("setEnabled:"), !disabled);
|
msg(item, s("setEnabled:"), !menuItem->disabled);
|
||||||
msg(item, s("autorelease"));
|
msg(item, s("autorelease"));
|
||||||
|
|
||||||
// Process modifiers
|
// Process modifiers
|
||||||
if( modifiers != NULL ) {
|
if( menuItem->modifiers != NULL ) {
|
||||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
unsigned long modifierFlags = parseModifiers(menuItem->modifiers);
|
||||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
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!
|
if( menuItem == NULL || menuItem->platformData == NULL) return;
|
||||||
bool hidden = false;
|
|
||||||
getJSONBool(item, "Hidden", &hidden);
|
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||||
if( hidden ) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the role
|
id trayImage = HASHMAP_GET(trayIconCache, trayMenu->Icon);
|
||||||
JsonNode *role = json_find_member(item, "Role");
|
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
|
||||||
if( role != NULL ) {
|
msg(statusBarButton, s("setImage:"), trayImage);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void processMenuData(Menu *menu, JsonNode *menuData) {
|
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||||
JsonNode *items = json_find_member(menuData, "Items");
|
|
||||||
if( items == NULL ) {
|
// Exit early if NULL
|
||||||
// Parse error!
|
if( trayMenu->Label == NULL ) return;
|
||||||
ABORT("Unable to find 'Items' in menu JSON!");
|
|
||||||
|
// 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
|
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||||
JsonNode *item;
|
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
|
||||||
json_foreach(item, items) {
|
|
||||||
// Process each menu item
|
// Update the icon if needed
|
||||||
processMenuItem(menu, menu->menu, item);
|
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) {
|
void UnloadTrayIcons() {
|
||||||
|
// Release the tray cache images
|
||||||
int groupLength;
|
HASHMAP_ITERATE(trayIconCache, releaseNSObject, NULL);
|
||||||
getJSONInt(radioGroup, "Length", &groupLength);
|
HASHMAP_DESTROY(trayIconCache);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
//// 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
|
MenuManager* manager = callbackData->manager;
|
||||||
JsonNode *menuData = json_find_member(menu->processedMenu, "Menu");
|
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, callbackData->menuItemID);
|
||||||
if( menuData == NULL ) {
|
|
||||||
ABORT("Unable to find Menu data: %s", menu->processedMenu);
|
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
|
// Notify the backend
|
||||||
processMenuData(menu, menuData);
|
messageFromWindowCallback(message);
|
||||||
|
MEMFREE(message);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 "common.h"
|
||||||
#include "ffenestri_darwin.h"
|
#include "menu.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 *);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
id Menu;
|
||||||
const char *title;
|
} MacMenu;
|
||||||
|
|
||||||
/*** 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;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
id menuItem;
|
id MenuItem;
|
||||||
Menu *menu;
|
} MacMenuItem;
|
||||||
const char *menuID;
|
|
||||||
enum MenuItemType menuItemType;
|
|
||||||
} MenuItemCallbackData;
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
id statusBarItem;
|
||||||
|
int iconPosition;
|
||||||
|
} MacTrayMenu;
|
||||||
|
|
||||||
|
void PlatformMenuItemCallback(id self, SEL cmd, id sender);
|
||||||
// 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
|
|
||||||
|
|||||||
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
|
// Global because it's a singleton
|
||||||
struct hashmap_s trayIconCache;
|
struct hashmap_s trayIconCache;
|
||||||
|
|
||||||
TrayMenu* NewTrayMenu(const char* menuJSON) {
|
TrayMenu* NewTrayMenu(const char* menuJSON, struct TrayMenuStore* store) {
|
||||||
TrayMenu* result = malloc(sizeof(TrayMenu));
|
TrayMenu* result = malloc(sizeof(TrayMenu));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -21,26 +21,29 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
|||||||
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
|
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save reference to this json
|
|
||||||
result->processedJSON = processedJSON;
|
|
||||||
|
|
||||||
// TODO: Make this configurable
|
// TODO: Make this configurable
|
||||||
result->trayIconPosition = NSImageLeft;
|
result->trayIconPosition = NSImageLeft;
|
||||||
|
|
||||||
result->ID = mustJSONString(processedJSON, "ID");
|
result->ID = STRCOPY(mustJSONString(processedJSON, "I"));
|
||||||
result->label = mustJSONString(processedJSON, "Label");
|
result->label = STRCOPY(getJSONString(processedJSON, "l"));
|
||||||
result->icon = mustJSONString(processedJSON, "Icon");
|
result->icon = STRCOPY(getJSONString(processedJSON, "i"));
|
||||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
result->menu = NULL;
|
||||||
|
|
||||||
// Create the menu
|
JsonNode* menu = getJSONObject(processedJSON, "m");
|
||||||
result->menu = NewMenu(processedMenu);
|
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
|
// Init tray status bar item
|
||||||
result->statusbaritem = NULL;
|
result->statusbaritem = NULL;
|
||||||
|
|
||||||
// Set the menu type and store the tray ID in the parent data
|
// Free JSON
|
||||||
result->menu->menuType = TrayMenuType;
|
json_delete(processedJSON);
|
||||||
result->menu->parentData = (void*) result->ID;
|
|
||||||
|
|
||||||
return result;
|
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) {
|
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||||
|
|
||||||
// Exit early if NULL
|
// Exit early if NULL
|
||||||
@@ -104,8 +96,9 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
|||||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
UpdateTrayLabel(trayMenu, trayMenu->label);
|
||||||
|
|
||||||
// Update the menu
|
// Update the menu
|
||||||
id menu = GetMenu(trayMenu->menu);
|
if (trayMenu->menu != NULL ) {
|
||||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
msg(trayMenu->statusbaritem, s("setMenu:"), trayMenu->menu->menu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
||||||
@@ -118,17 +111,11 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
|||||||
// Set the new one
|
// Set the new one
|
||||||
currentMenu->menu = newMenu->menu;
|
currentMenu->menu = newMenu->menu;
|
||||||
|
|
||||||
// Delete the old JSON
|
|
||||||
json_delete(currentMenu->processedJSON);
|
|
||||||
|
|
||||||
// Set the new JSON
|
|
||||||
currentMenu->processedJSON = newMenu->processedJSON;
|
|
||||||
|
|
||||||
// Copy the other data
|
// Copy the other data
|
||||||
currentMenu->ID = newMenu->ID;
|
currentMenu->ID = STRCOPY(newMenu->ID);
|
||||||
currentMenu->label = newMenu->label;
|
currentMenu->label = STRCOPY(newMenu->label);
|
||||||
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
||||||
currentMenu->icon = newMenu->icon;
|
currentMenu->icon = STRCOPY(newMenu->icon);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,10 +127,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
|||||||
// Delete the menu
|
// Delete the menu
|
||||||
DeleteMenu(trayMenu->menu);
|
DeleteMenu(trayMenu->menu);
|
||||||
|
|
||||||
// Free JSON
|
// Free strings
|
||||||
if (trayMenu->processedJSON != NULL ) {
|
MEMFREE(trayMenu->label);
|
||||||
json_delete(trayMenu->processedJSON);
|
MEMFREE(trayMenu->icon);
|
||||||
}
|
MEMFREE(trayMenu->ID);
|
||||||
|
|
||||||
// Free the status item
|
// Free the status item
|
||||||
if ( trayMenu->statusbaritem != NULL ) {
|
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
|
#define TRAYMENU_DARWIN_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "menu_darwin.h"
|
#include "menu_darwin_old.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
@@ -18,12 +18,9 @@ typedef struct {
|
|||||||
|
|
||||||
id statusbaritem;
|
id statusbaritem;
|
||||||
int trayIconPosition;
|
int trayIconPosition;
|
||||||
|
|
||||||
JsonNode* processedJSON;
|
|
||||||
|
|
||||||
} TrayMenu;
|
} TrayMenu;
|
||||||
|
|
||||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
TrayMenu* NewTrayMenu(const char *trayJSON, struct TrayMenuStore* store);
|
||||||
void DumpTrayMenu(TrayMenu* trayMenu);
|
void DumpTrayMenu(TrayMenu* trayMenu);
|
||||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
|||||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +34,7 @@ void DumpTrayMenuStore(TrayMenuStore* store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
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
|
//TODO: check if there is already an entry for this menu
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
@@ -65,6 +70,9 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
|||||||
|
|
||||||
// Destroy tray menu map
|
// Destroy tray menu map
|
||||||
hashmap_destroy(&store->trayMenuMap);
|
hashmap_destroy(&store->trayMenuMap);
|
||||||
|
|
||||||
|
// Destroy menu item map
|
||||||
|
hashmap_destroy(&store->menuItemMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||||
@@ -81,6 +89,15 @@ TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
|||||||
return result;
|
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) {
|
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||||
// Parse the JSON
|
// Parse the JSON
|
||||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||||
@@ -95,24 +112,19 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
|
||||||
DumpTrayMenu(newMenu);
|
|
||||||
|
|
||||||
// Get the current menu
|
// Get the current menu
|
||||||
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
|
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
|
||||||
|
|
||||||
// If we don't have a menu, we create one
|
// If we don't have a menu, we create one
|
||||||
if ( currentMenu == NULL ) {
|
if ( currentMenu == NULL ) {
|
||||||
printf(" currentMenu = NULL\n");
|
|
||||||
// Store the new menu
|
// Store the new menu
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
|
||||||
// Show it
|
return newMenu;
|
||||||
ShowTrayMenu(newMenu);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
DumpTrayMenu(currentMenu);
|
|
||||||
|
|
||||||
// Save the status bar reference
|
// Save the status bar reference
|
||||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
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);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
|
||||||
// Show the updated menu
|
return newMenu;
|
||||||
ShowTrayMenu(newMenu);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* GetContextMenuDataFromStore(TrayMenuStore *store) {
|
||||||
|
return store->contextMenuData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||||
#define TRAYMENUSTORE_DARWIN_H
|
#define TRAYMENUSTORE_DARWIN_H
|
||||||
|
|
||||||
|
#include "traymenu_darwin.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
int dummy;
|
int dummy;
|
||||||
@@ -13,15 +15,26 @@ typedef struct {
|
|||||||
// It maps tray IDs to TrayMenu*
|
// It maps tray IDs to TrayMenu*
|
||||||
struct hashmap_s trayMenuMap;
|
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;
|
||||||
|
|
||||||
TrayMenuStore* NewTrayMenuStore();
|
TrayMenuStore* NewTrayMenuStore();
|
||||||
|
|
||||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON);
|
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 ShowTrayMenusInStore(TrayMenuStore* store);
|
||||||
void DeleteTrayMenuStore(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);
|
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||||
|
const char* GetContextMenuDataFromStore(TrayMenuStore *store);
|
||||||
|
|
||||||
#endif //TRAYMENUSTORE_DARWIN_H
|
#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),\
|
( vec_splice_(vec_unpack_(v), start, count),\
|
||||||
(v)->length -= (count) )
|
(v)->length -= (count) )
|
||||||
|
|
||||||
|
#define vec_size(v) \
|
||||||
|
(v)->length
|
||||||
|
|
||||||
#define vec_swapsplice(v, start, count)\
|
#define vec_swapsplice(v, start, count)\
|
||||||
( vec_swapsplice_(vec_unpack_(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
|
package menumanager
|
||||||
|
|
||||||
import (
|
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"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 (
|
||||||
|
// "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) {
|
func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) {
|
||||||
|
//
|
||||||
newContextMenu := NewContextMenu(contextMenu)
|
// newContextMenu := m.NewContextMenu(contextMenu)
|
||||||
|
//
|
||||||
// Save the references
|
// // Save the references
|
||||||
m.contextMenus[contextMenu.ID] = newContextMenu
|
// m.contextMenus[contextMenu.ID] = newContextMenu
|
||||||
m.contextMenuPointers[contextMenu] = contextMenu.ID
|
// m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
|
func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
|
||||||
contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
// contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||||
if !contextMenuKnown {
|
// if !contextMenuKnown {
|
||||||
return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
// return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Create the updated context menu
|
// // Create the updated context menu
|
||||||
updatedContextMenu := NewContextMenu(contextMenu)
|
// updatedContextMenu := m.NewContextMenu(contextMenu)
|
||||||
|
//
|
||||||
// Save the reference
|
// // Save the reference
|
||||||
m.contextMenus[contextMenuID] = updatedContextMenu
|
// m.contextMenus[contextMenuID] = updatedContextMenu
|
||||||
|
//
|
||||||
return updatedContextMenu.AsJSON()
|
// 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/counter"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
|
||||||
// The application menu.
|
// MenuItemMap is a map of all menu items against a generated ID
|
||||||
applicationMenu *menu.Menu
|
menuItemMap map[string]*menu.MenuItem
|
||||||
applicationMenuJSON string
|
menuItemIDCounter *counter.Counter
|
||||||
|
processedMenuItems map[*menu.MenuItem]*ProcessedMenuItem
|
||||||
|
|
||||||
// Our application menu mappings
|
// Menus
|
||||||
applicationMenuItemMap *MenuItemMap
|
menuIDCounter *counter.Counter
|
||||||
|
|
||||||
// Context menus
|
// Map wails menus to internal menus
|
||||||
contextMenus map[string]*ContextMenu
|
trayMenuMap map[*menu.TrayMenu]*TrayMenu
|
||||||
contextMenuPointers map[*menu.ContextMenu]string
|
trayMenuIDCounter *counter.Counter
|
||||||
|
|
||||||
// Tray menu stores
|
|
||||||
trayMenus map[string]*TrayMenu
|
|
||||||
trayMenuPointers map[*menu.TrayMenu]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
applicationMenuItemMap: NewMenuItemMap(),
|
trayMenuMap: make(map[*menu.TrayMenu]*TrayMenu),
|
||||||
contextMenus: make(map[string]*ContextMenu),
|
trayMenuIDCounter: counter.NewCounter(0),
|
||||||
contextMenuPointers: make(map[*menu.ContextMenu]string),
|
menuIDCounter: counter.NewCounter(0),
|
||||||
trayMenus: make(map[string]*TrayMenu),
|
menuItemMap: make(map[string]*menu.MenuItem),
|
||||||
trayMenuPointers: make(map[*menu.TrayMenu]string),
|
menuItemIDCounter: counter.NewCounter(0),
|
||||||
|
processedMenuItems: make(map[*menu.MenuItem]*ProcessedMenuItem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) getMenuItemByID(menuMap *MenuItemMap, menuId string) *menu.MenuItem {
|
func (m *Manager) ProcessClick(menuID string, data string) error {
|
||||||
return menuMap.idToMenuItemMap[menuId]
|
// Get item from callback map
|
||||||
}
|
menuItem := m.menuItemMap[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)
|
|
||||||
if menuItem == nil {
|
if menuItem == nil {
|
||||||
return fmt.Errorf("Cannot process menuid %s - unknown", menuID)
|
return fmt.Errorf("menuItem doesn't exist")
|
||||||
}
|
|
||||||
|
|
||||||
// Is the menu item a checkbox?
|
|
||||||
if menuItem.Type == menu.CheckboxType {
|
|
||||||
// Toggle state
|
|
||||||
menuItem.Checked = !menuItem.Checked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if menuItem.Click == nil {
|
if menuItem.Click == nil {
|
||||||
// No callback
|
return fmt.Errorf("menuItem 'does not have a callback")
|
||||||
return fmt.Errorf("No callback for menu '%s'", menuItem.Label)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new Callback struct
|
|
||||||
callbackData := &menu.CallbackData{
|
callbackData := &menu.CallbackData{
|
||||||
MenuItem: menuItem,
|
MenuItem: menuItem,
|
||||||
ContextData: data,
|
ContextData: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call back!
|
// Callback!
|
||||||
go menuItem.Click(callbackData)
|
go menuItem.Click(callbackData)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,72 +2,165 @@ package menumanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProcessedMenuItem struct {
|
type ProcessedMenu struct {
|
||||||
ID string
|
ID string `json:"I"`
|
||||||
// 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"`
|
|
||||||
|
|
||||||
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
|
Items []*ProcessedMenuItem `json:"i,omitempty"`
|
||||||
Foreground int
|
RadioGroups []*RadioGroup `json:"r,omitempty"`
|
||||||
|
|
||||||
// Background colour
|
|
||||||
Background int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
result := &ProcessedMenuItem{
|
||||||
ID: ID,
|
ID: ID,
|
||||||
Label: menuItem.Label,
|
Label: menuItem.Label,
|
||||||
Role: menuItem.Role,
|
AlternateLabel: menuItem.AlternateLabel,
|
||||||
Accelerator: menuItem.Accelerator,
|
Role: menuItem.Role,
|
||||||
Type: menuItem.Type,
|
Accelerator: menuItem.Accelerator,
|
||||||
Disabled: menuItem.Disabled,
|
Type: menuItem.Type,
|
||||||
Hidden: menuItem.Hidden,
|
Font: menuItem.Font,
|
||||||
Checked: menuItem.Checked,
|
FontSize: menuItem.FontSize,
|
||||||
Foreground: menuItem.Foreground,
|
RGBA: menuItem.RGBA,
|
||||||
Background: menuItem.Background,
|
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 {
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProcessedMenu struct {
|
func (m *Manager) NewProcessedMenu(menu *menu.Menu) []*ProcessedMenuItem {
|
||||||
Items []*ProcessedMenuItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu {
|
if menu == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
result := &ProcessedMenu{}
|
if menu.Items == nil {
|
||||||
if menu != nil {
|
return nil
|
||||||
for _, item := range menu.Items {
|
}
|
||||||
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
|
|
||||||
result.Items = append(result.Items, processedMenuItem)
|
var result []*ProcessedMenuItem
|
||||||
}
|
|
||||||
|
for _, item := range menu.Items {
|
||||||
|
processedMenuItem := m.NewProcessedMenuItem(item)
|
||||||
|
result = append(result, processedMenuItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -76,8 +169,8 @@ func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu
|
|||||||
// WailsMenu is the original menu with the addition
|
// WailsMenu is the original menu with the addition
|
||||||
// of radio groups extracted from the menu data
|
// of radio groups extracted from the menu data
|
||||||
type WailsMenu struct {
|
type WailsMenu struct {
|
||||||
Menu *ProcessedMenu
|
Menu []*ProcessedMenuItem `json:",omitempty"`
|
||||||
RadioGroups []*RadioGroup
|
RadioGroups []*RadioGroup `json:",omitempty"`
|
||||||
currentRadioGroup []string
|
currentRadioGroup []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +180,12 @@ type RadioGroup struct {
|
|||||||
Length int
|
Length int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWailsMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *WailsMenu {
|
func (m *Manager) NewWailsMenu(menu *menu.Menu) *WailsMenu {
|
||||||
|
|
||||||
result := &WailsMenu{}
|
result := &WailsMenu{}
|
||||||
|
|
||||||
// Process the menus
|
// Process the menus
|
||||||
result.Menu = NewProcessedMenu(menuItemMap, menu)
|
result.Menu = m.NewProcessedMenu(menu)
|
||||||
|
|
||||||
// Process the radio groups
|
// Process the radio groups
|
||||||
result.processRadioGroups()
|
result.processRadioGroups()
|
||||||
@@ -108,17 +202,7 @@ func (w *WailsMenu) AsJSON() (string, error) {
|
|||||||
return string(menuAsJSON), nil
|
return string(menuAsJSON), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WailsMenu) processRadioGroups() {
|
func (w *WailsMenu) processMenuItemForRadioGroups(item *ProcessedMenuItem) {
|
||||||
// Loop over top level menus
|
|
||||||
for _, item := range w.Menu.Items {
|
|
||||||
// Process MenuItem
|
|
||||||
w.processMenuItem(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.finaliseRadioGroup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
|
||||||
|
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
|
|
||||||
@@ -129,8 +213,8 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
|||||||
w.finaliseRadioGroup()
|
w.finaliseRadioGroup()
|
||||||
|
|
||||||
// Process each submenu item
|
// Process each submenu item
|
||||||
for _, subitem := range item.SubMenu.Items {
|
for _, subitem := range item.SubMenu {
|
||||||
w.processMenuItem(subitem)
|
w.processMenuItemForRadioGroups(subitem)
|
||||||
}
|
}
|
||||||
case menu.RadioType:
|
case menu.RadioType:
|
||||||
// Add the item to the radio group
|
// 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() {
|
func (w *WailsMenu) finaliseRadioGroup() {
|
||||||
|
|
||||||
// If we were processing a radio group, fix up the references
|
// If we were processing a radio group, fix up the references
|
||||||
|
|||||||
@@ -3,29 +3,14 @@ package menumanager
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"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 {
|
type TrayMenu struct {
|
||||||
ID string
|
ID string `json:"I"`
|
||||||
Label string
|
Label string `json:"l,omitempty"`
|
||||||
Icon string
|
Icon string `json:"i,omitempty"`
|
||||||
menuItemMap *MenuItemMap
|
Menu *ProcessedMenu `json:"m,omitempty"`
|
||||||
menu *menu.Menu
|
|
||||||
ProcessedMenu *WailsMenu
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TrayMenu) AsJSON() (string, error) {
|
func (t *TrayMenu) AsJSON() (string, error) {
|
||||||
@@ -36,55 +21,44 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
|||||||
return string(data), nil
|
return string(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
func (m *Manager) newTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||||
|
|
||||||
result := &TrayMenu{
|
result := TrayMenu{
|
||||||
Label: trayMenu.Label,
|
ID: m.generateTrayID(),
|
||||||
Icon: trayMenu.Icon,
|
Label: trayMenu.Label,
|
||||||
menu: trayMenu.Menu,
|
Icon: trayMenu.Icon,
|
||||||
menuItemMap: NewMenuItemMap(),
|
Menu: m.ProcessMenu(trayMenu.Menu),
|
||||||
}
|
}
|
||||||
|
|
||||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
return &result
|
||||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
}
|
||||||
|
|
||||||
|
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||||
|
|
||||||
|
result := m.newTrayMenu(trayMenu)
|
||||||
|
|
||||||
|
// Add to map
|
||||||
|
m.trayMenuMap[trayMenu] = result
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
func (m *Manager) generateTrayID() string {
|
||||||
newTrayMenu := NewTrayMenu(trayMenu)
|
return fmt.Sprintf("T%d", m.trayMenuIDCounter.Increment())
|
||||||
|
|
||||||
// Hook up a new ID
|
|
||||||
trayID := generateTrayID()
|
|
||||||
newTrayMenu.ID = trayID
|
|
||||||
|
|
||||||
// Save the references
|
|
||||||
m.trayMenus[trayID] = newTrayMenu
|
|
||||||
m.trayMenuPointers[trayMenu] = trayID
|
|
||||||
|
|
||||||
return newTrayMenu.AsJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTrayMenu updates or creates a menu
|
func (m *Manager) GetTrayMenus() ([]*TrayMenu, error) {
|
||||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
var result []*TrayMenu
|
||||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
for _, trayMenu := range m.trayMenuMap {
|
||||||
if !trayMenuKnown {
|
result = append(result, trayMenu)
|
||||||
return m.AddTrayMenu(trayMenu)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the updated tray menu
|
return result, nil
|
||||||
updatedTrayMenu := NewTrayMenu(trayMenu)
|
|
||||||
updatedTrayMenu.ID = trayID
|
|
||||||
|
|
||||||
// Save the reference
|
|
||||||
m.trayMenus[trayID] = updatedTrayMenu
|
|
||||||
|
|
||||||
return updatedTrayMenu.AsJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetTrayMenus() ([]string, error) {
|
func (m *Manager) GetTrayMenusAsJSON() ([]string, error) {
|
||||||
result := []string{}
|
var result []string
|
||||||
for _, trayMenu := range m.trayMenus {
|
for _, trayMenu := range m.trayMenuMap {
|
||||||
JSON, err := trayMenu.AsJSON()
|
JSON, err := trayMenu.AsJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -95,40 +69,11 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
|
|||||||
return result, nil
|
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) {
|
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
return "", nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
// Start the process
|
||||||
func (p *Process) Start() error {
|
func (p *Process) Start() error {
|
||||||
|
|
||||||
|
p.cmd.
|
||||||
err := p.cmd.Start()
|
err := p.cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -80,9 +80,7 @@ func (m *Menu) Start() error {
|
|||||||
|
|
||||||
type ClickCallbackMessage struct {
|
type ClickCallbackMessage struct {
|
||||||
MenuItemID string `json:"menuItemID"`
|
MenuItemID string `json:"menuItemID"`
|
||||||
MenuType string `json:"menuType"`
|
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
ParentID string `json:"parentID"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var callbackData ClickCallbackMessage
|
var callbackData ClickCallbackMessage
|
||||||
@@ -93,7 +91,7 @@ func (m *Menu) Start() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data, callbackData.MenuType, callbackData.ParentID)
|
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Trace("%s", err.Error())
|
m.logger.Trace("%s", err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
type MenuItem struct {
|
type MenuItem struct {
|
||||||
// Label is what appears as the menu text
|
// Label is what appears as the menu text
|
||||||
Label string
|
Label string
|
||||||
|
// AlternateLabel is a secondary label (Used by Mac)
|
||||||
|
AlternateLabel string
|
||||||
// Role is a predefined menu type
|
// Role is a predefined menu type
|
||||||
Role Role `json:"Role,omitempty"`
|
Role Role `json:"Role,omitempty"`
|
||||||
// Accelerator holds a representation of a key binding
|
// Accelerator holds a representation of a key binding
|
||||||
@@ -21,6 +23,14 @@ type MenuItem struct {
|
|||||||
Hidden bool
|
Hidden bool
|
||||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||||
Checked bool
|
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 contains a list of menu items that will be shown as a submenu
|
||||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||||
SubMenu *Menu `json:"SubMenu,omitempty"`
|
SubMenu *Menu `json:"SubMenu,omitempty"`
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ type Type string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// TextType is the text menuitem type
|
// TextType is the text menuitem type
|
||||||
TextType Type = "Text"
|
TextType Type = "t"
|
||||||
// SeparatorType is the Separator menuitem type
|
// SeparatorType is the Separator menuitem type
|
||||||
SeparatorType Type = "Separator"
|
SeparatorType Type = "s"
|
||||||
// SubmenuType is the Submenu menuitem type
|
// SubmenuType is the Submenu menuitem type
|
||||||
SubmenuType Type = "Submenu"
|
SubmenuType Type = "S"
|
||||||
// CheckboxType is the Checkbox menuitem type
|
// CheckboxType is the Checkbox menuitem type
|
||||||
CheckboxType Type = "Checkbox"
|
CheckboxType Type = "c"
|
||||||
// RadioType is the Radio menuitem type
|
// RadioType is the Radio menuitem type
|
||||||
RadioType Type = "Radio"
|
RadioType Type = "r"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ var Default = &App{
|
|||||||
DevTools: false,
|
DevTools: false,
|
||||||
RGBA: 0xFFFFFFFF,
|
RGBA: 0xFFFFFFFF,
|
||||||
Mac: &mac.Options{
|
Mac: &mac.Options{
|
||||||
TitleBar: mac.TitleBarDefault(),
|
TitleBar: mac.TitleBarDefault(),
|
||||||
Appearance: mac.DefaultAppearance,
|
Appearance: mac.DefaultAppearance,
|
||||||
WebviewIsTransparent: false,
|
|
||||||
WindowBackgroundIsTranslucent: false,
|
|
||||||
},
|
},
|
||||||
Logger: logger.NewDefaultLogger(),
|
Logger: logger.NewDefaultLogger(),
|
||||||
LogLevel: logger.INFO,
|
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
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/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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|||||||
Reference in New Issue
Block a user