mirror of
https://github.com/taigrr/wails.git
synced 2026-04-05 06:32:42 -07:00
Compare commits
13 Commits
feature/v2
...
feature/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
480dcb7895 | ||
|
|
c8d89cf002 | ||
|
|
fc669ede37 | ||
|
|
47bca0be88 | ||
|
|
7ac8cc6b8b | ||
|
|
b435ec1217 | ||
|
|
688d4fee6a | ||
|
|
29ffeaa9f3 | ||
|
|
742e4ba2cb | ||
|
|
0a0063de1f | ||
|
|
1b7d1e61cc | ||
|
|
15a273458e | ||
|
|
eef8eb756f |
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.6"
|
||||
var version = "v2.0.0-alpha.10"
|
||||
|
||||
@@ -43,6 +43,10 @@ type App struct {
|
||||
// Application Stores
|
||||
loglevelStore *runtime.Store
|
||||
appconfigStore *runtime.Store
|
||||
|
||||
// Startup/Shutdown
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
}
|
||||
|
||||
// Create App
|
||||
@@ -76,11 +80,13 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
|
||||
|
||||
result := &App{
|
||||
window: window,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
menuManager: menuManager,
|
||||
window: window,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
menuManager: menuManager,
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
}
|
||||
|
||||
result.options = appoptions
|
||||
@@ -112,7 +118,7 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -34,26 +34,6 @@ func (b *Bindings) Add(structPtr interface{}) error {
|
||||
structName := splitName[1]
|
||||
methodName := splitName[2]
|
||||
|
||||
// Is this WailsInit?
|
||||
if method.IsWailsInit() {
|
||||
err := b.db.AddWailsInit(method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.logger.Trace("Registered WailsInit method: %s", method.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Is this WailsShutdown?
|
||||
if method.IsWailsShutdown() {
|
||||
err := b.db.AddWailsShutdown(method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.logger.Trace("Registered WailsShutdown method: %s", method.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add it as a regular method
|
||||
b.db.AddMethod(packageName, structName, methodName, method)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BoundMethod defines all the data related to a Go method that is
|
||||
@@ -17,58 +16,6 @@ type BoundMethod struct {
|
||||
Method reflect.Value `json:"-"`
|
||||
}
|
||||
|
||||
// IsWailsInit returns true if the method name is "WailsInit"
|
||||
func (b *BoundMethod) IsWailsInit() bool {
|
||||
return strings.HasSuffix(b.Name, "WailsInit")
|
||||
}
|
||||
|
||||
// IsWailsShutdown returns true if the method name is "WailsShutdown"
|
||||
func (b *BoundMethod) IsWailsShutdown() bool {
|
||||
return strings.HasSuffix(b.Name, "WailsShutdown")
|
||||
}
|
||||
|
||||
// VerifyWailsInit checks if the WailsInit signature is correct
|
||||
func (b *BoundMethod) VerifyWailsInit() error {
|
||||
// Must only have 1 input
|
||||
if b.InputCount() != 1 {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Check input type
|
||||
if !b.Inputs[0].IsType("*runtime.Runtime") {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Must only have 1 output
|
||||
if b.OutputCount() != 1 {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Check output type
|
||||
if !b.Outputs[0].IsError() {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Input must be of type Runtime
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
|
||||
func (b *BoundMethod) VerifyWailsShutdown() error {
|
||||
// Must have no inputs
|
||||
if b.InputCount() != 0 {
|
||||
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
|
||||
}
|
||||
|
||||
// Must have no outputs
|
||||
if b.OutputCount() != 0 {
|
||||
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
|
||||
}
|
||||
|
||||
// Input must be of type Runtime
|
||||
return nil
|
||||
}
|
||||
|
||||
// InputCount returns the number of inputs this bound method has
|
||||
func (b *BoundMethod) InputCount() int {
|
||||
return len(b.Inputs)
|
||||
|
||||
@@ -15,10 +15,6 @@ type DB struct {
|
||||
// It used for performance gains at runtime
|
||||
methodMap map[string]*BoundMethod
|
||||
|
||||
// These are slices of methods registered using WailsInit and WailsShutdown
|
||||
wailsInitMethods []*BoundMethod
|
||||
wailsShutdownMethods []*BoundMethod
|
||||
|
||||
// Lock to ensure sync access to the data
|
||||
lock sync.RWMutex
|
||||
}
|
||||
@@ -94,38 +90,6 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string,
|
||||
|
||||
}
|
||||
|
||||
// AddWailsInit checks the given method is a WailsInit method and if it
|
||||
// is, adds it to the list of WailsInit methods
|
||||
func (d *DB) AddWailsInit(method *BoundMethod) error {
|
||||
err := method.VerifyWailsInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.wailsInitMethods = append(d.wailsInitMethods, method)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWailsShutdown checks the given method is a WailsInit method and if it
|
||||
// is, adds it to the list of WailsShutdown methods
|
||||
func (d *DB) AddWailsShutdown(method *BoundMethod) error {
|
||||
err := method.VerifyWailsShutdown()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToJSON converts the method map to JSON
|
||||
func (d *DB) ToJSON() (string, error) {
|
||||
|
||||
@@ -138,13 +102,3 @@ func (d *DB) ToJSON() (string, error) {
|
||||
// Return zero copy string as this string will be read only
|
||||
return *(*string)(unsafe.Pointer(&bytes)), err
|
||||
}
|
||||
|
||||
// WailsInitMethods returns the list of registered WailsInit methods
|
||||
func (d *DB) WailsInitMethods() []*BoundMethod {
|
||||
return d.wailsInitMethods
|
||||
}
|
||||
|
||||
// WailsShutdownMethods returns the list of registered WailsInit methods
|
||||
func (d *DB) WailsShutdownMethods() []*BoundMethod {
|
||||
return d.wailsShutdownMethods
|
||||
}
|
||||
|
||||
40
v2/internal/counter/counter.go
Normal file
40
v2/internal/counter/counter.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package counter
|
||||
|
||||
import "sync"
|
||||
|
||||
type Counter struct {
|
||||
initialValue uint64
|
||||
value uint64
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewCounter(initialValue uint64) *Counter {
|
||||
return &Counter{
|
||||
initialValue: initialValue,
|
||||
value: initialValue,
|
||||
}
|
||||
}
|
||||
|
||||
// SetValue sets the value to the given value
|
||||
func (c *Counter) SetValue(value uint64) {
|
||||
c.lock.Lock()
|
||||
c.value = value
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Increment adds 1 to the counter and returns its value
|
||||
func (c *Counter) Increment() uint64 {
|
||||
var result uint64
|
||||
c.lock.Lock()
|
||||
c.value++
|
||||
result = c.value
|
||||
c.lock.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// Reset the value to the initial value
|
||||
func (c *Counter) Reset() {
|
||||
c.lock.Lock()
|
||||
c.value = c.initialValue
|
||||
c.lock.Unlock()
|
||||
}
|
||||
@@ -10,4 +10,8 @@ License: http://git.ozlabs.org/?p=ccan;a=blob;f=licenses/BSD-MIT;h=89de354795ec7
|
||||
|
||||
## hashmap
|
||||
Homepage: https://github.com/sheredom/hashmap.h
|
||||
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
|
||||
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
|
||||
|
||||
## utf8.h
|
||||
Homepage: https://github.com/sheredom/utf8.h
|
||||
License: https://github.com/sheredom/utf8.h/blob/master/LICENSE
|
||||
@@ -38,21 +38,31 @@ int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
|
||||
const char* getJSONString(JsonNode *item, const char* key) {
|
||||
// Get key
|
||||
JsonNode *node = json_find_member(item, key);
|
||||
const char *result = "";
|
||||
const char *result = NULL;
|
||||
if ( node != NULL && node->tag == JSON_STRING) {
|
||||
result = node->string_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* getJSONStringDefault(JsonNode *item, const char* key, const char* defaultValue) {
|
||||
const char* result = getJSONString(item, key);
|
||||
if ( result == NULL ) result = defaultValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ABORT_JSON(JsonNode *node, const char* key) {
|
||||
ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node));
|
||||
}
|
||||
|
||||
const char* mustJSONString(JsonNode *node, const char* key) {
|
||||
const char* result = getJSONString(node, key);
|
||||
if ( result == NULL ) {
|
||||
ABORT_JSON(node, key);
|
||||
const char* mustJSONString(JsonNode *item, const char* key) {
|
||||
JsonNode *member = json_find_member(item, key);
|
||||
if ( member == NULL ) {
|
||||
ABORT_JSON(item, key);
|
||||
}
|
||||
const char *result = "";
|
||||
if ( member != NULL && member->tag == JSON_STRING) {
|
||||
result = member->string_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -68,15 +78,23 @@ JsonNode* getJSONObject(JsonNode* node, const char* key) {
|
||||
return json_find_member(node, key);
|
||||
}
|
||||
|
||||
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
|
||||
JsonNode *node = json_find_member(item, key);
|
||||
if ( node != NULL && node->tag == JSON_BOOL) {
|
||||
*result = node->bool_;
|
||||
return true;
|
||||
bool getJSONBool(JsonNode *node, const char* key) {
|
||||
JsonNode *result = json_find_member(node, key);
|
||||
if ( result != NULL && result->tag == JSON_BOOL) {
|
||||
return result->bool_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int mustJSONInt(JsonNode *node, const char* key) {
|
||||
JsonNode *result = json_find_member(node, key);
|
||||
if ( result == NULL || result->tag != JSON_NUMBER) {
|
||||
ABORT_JSON(result, key);
|
||||
}
|
||||
|
||||
return (int) result->number_;
|
||||
}
|
||||
|
||||
bool getJSONInt(JsonNode *item, const char* key, int *result) {
|
||||
JsonNode *node = json_find_member(item, key);
|
||||
if ( node != NULL && node->tag == JSON_NUMBER) {
|
||||
@@ -86,3 +104,10 @@ bool getJSONInt(JsonNode *item, const char* key, int *result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonNode* mustParseJSON(const char* JSON) {
|
||||
JsonNode* parsedUpdate = json_decode(JSON);
|
||||
if ( parsedUpdate == NULL ) {
|
||||
ABORT("Unable to decode JSON: %s\n", JSON);
|
||||
}
|
||||
return parsedUpdate;
|
||||
}
|
||||
@@ -18,21 +18,45 @@
|
||||
|
||||
#define STREQ(a,b) strcmp(a, b) == 0
|
||||
#define STREMPTY(string) strlen(string) == 0
|
||||
#define STRCOPY(a) concat(a, "")
|
||||
#define STRCOPY(str) (str == NULL ? NULL : concat(str, ""))
|
||||
#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0
|
||||
#define MEMFREE(input) free((void*)input); input = NULL;
|
||||
#define MEMFREE(input) if(input != NULL) { free((void*)input); input = NULL; }
|
||||
#define FREE_AND_SET(variable, value) if( variable != NULL ) { MEMFREE(variable); } variable = value
|
||||
#define NEW(struct) malloc(sizeof(struct));
|
||||
|
||||
#define HASHMAP_INIT(hashmap, initialSize, name) \
|
||||
if( 0 != hashmap_create((const unsigned)initialSize, &hashmap)) { \
|
||||
ABORT("Not enough memory to allocate %s!\n", name); \
|
||||
}
|
||||
|
||||
#define HASHMAP_PUT(hashmap, key, value) hashmap_put(&hashmap, key, strlen(key), value);
|
||||
#define HASHMAP_GET(hashmap, key) hashmap_get(&hashmap, key, strlen(key));
|
||||
#define HASHMAP_DESTROY(hashmap) hashmap_destroy(&hashmap);
|
||||
#define HASHMAP_SIZE(hashmap) hashmap_num_entries(&hashmap)
|
||||
#define HASHMAP_ITERATE(hashmap, function, context) if( hashmap_num_entries(&hashmap) > 0 ) { \
|
||||
if (0!=hashmap_iterate_pairs(&hashmap, function, context)) { \
|
||||
ABORT("failed to iterate hashmap entries!"); \
|
||||
} \
|
||||
}
|
||||
#define JSON_ADD_STRING(jsonObject, key, value) if( value != NULL ) { json_append_member(jsonObject, (char*)key, json_mkstring(value)); }
|
||||
#define JSON_ADD_NUMBER(jsonObject, key, value) json_append_member(jsonObject, (char*)key, json_mknumber(value));
|
||||
#define JSON_ADD_OBJECT(jsonObject, key, value) if( value != NULL ) { json_append_member(jsonObject, (char*)key, value); }
|
||||
#define JSON_ADD_BOOL(jsonObject, key, value) json_append_member(jsonObject, (char*)key, json_mkbool(value));
|
||||
#define JSON_ADD_ELEMENT(jsonObject, value) json_append_element(jsonObject, value);
|
||||
|
||||
// Credit: https://stackoverflow.com/a/8465083
|
||||
char* concat(const char *string1, const char *string2);
|
||||
void ABORT(const char *message, ...);
|
||||
int freeHashmapItem(void *const context, struct hashmap_element_s *const e);
|
||||
int freeHashmapItem(void *context, struct hashmap_element_s *e);
|
||||
const char* getJSONString(JsonNode *item, const char* key);
|
||||
const char* getJSONStringDefault(JsonNode *item, const char* key, const char* defaultValue);
|
||||
const char* mustJSONString(JsonNode *node, const char* key);
|
||||
JsonNode* getJSONObject(JsonNode* node, const char* key);
|
||||
JsonNode* mustJSONObject(JsonNode *node, const char* key);
|
||||
|
||||
bool getJSONBool(JsonNode *item, const char* key, bool *result);
|
||||
bool getJSONBool(JsonNode *item, const char* key);
|
||||
bool getJSONInt(JsonNode *item, const char* key, int *result);
|
||||
int mustJSONInt(JsonNode *node, const char* key);
|
||||
JsonNode* mustParseJSON(const char* JSON);
|
||||
|
||||
#endif //ASSETS_C_COMMON_H
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
////
|
||||
//// Created by Lea Anthony on 6/1/21.
|
||||
//////
|
||||
////// Created by Lea Anthony on 6/1/21.
|
||||
//////
|
||||
////
|
||||
//
|
||||
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "common.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
|
||||
ContextMenu* NewContextMenu(const char* contextMenuJSON) {
|
||||
ContextMenu* result = malloc(sizeof(ContextMenu));
|
||||
|
||||
JsonNode* processedJSON = json_decode(contextMenuJSON);
|
||||
if( processedJSON == NULL ) {
|
||||
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
|
||||
}
|
||||
// Save reference to this json
|
||||
result->processedJSON = processedJSON;
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
|
||||
result->menu = NewMenu(processedMenu);
|
||||
result->nsmenu = NULL;
|
||||
result->menu->menuType = ContextMenuType;
|
||||
result->menu->parentData = result;
|
||||
result->contextMenuData = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
|
||||
return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||
}
|
||||
|
||||
void DeleteContextMenu(ContextMenu* contextMenu) {
|
||||
// Free Menu
|
||||
DeleteMenu(contextMenu->menu);
|
||||
|
||||
// Delete any context menu data we may have stored
|
||||
if( contextMenu->contextMenuData != NULL ) {
|
||||
MEMFREE(contextMenu->contextMenuData);
|
||||
}
|
||||
|
||||
// Free JSON
|
||||
if (contextMenu->processedJSON != NULL ) {
|
||||
json_delete(contextMenu->processedJSON);
|
||||
contextMenu->processedJSON = NULL;
|
||||
}
|
||||
|
||||
// Free context menu
|
||||
free(contextMenu);
|
||||
}
|
||||
|
||||
int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteContextMenu(e->data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
|
||||
|
||||
// If no context menu ID was given, abort
|
||||
if( contextMenuID == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
|
||||
|
||||
// We don't need the ID now
|
||||
MEMFREE(contextMenuID);
|
||||
|
||||
if( contextMenu == NULL ) {
|
||||
// Free context menu data
|
||||
if( contextMenuData != NULL ) {
|
||||
MEMFREE(contextMenuData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to store the context menu data. Free existing data if we have it
|
||||
// and set to the new value.
|
||||
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||
|
||||
// Grab the content view and show the menu
|
||||
id contentView = msg(mainWindow, s("contentView"));
|
||||
|
||||
// Get the triggering event
|
||||
id menuEvent = msg(mainWindow, s("currentEvent"));
|
||||
|
||||
if( contextMenu->nsmenu == NULL ) {
|
||||
// GetMenu creates the NSMenu
|
||||
contextMenu->nsmenu = GetMenu(contextMenu->menu);
|
||||
}
|
||||
|
||||
// Show popup
|
||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||
|
||||
}
|
||||
|
||||
//#include "ffenestri_darwin.h"
|
||||
//#include "common.h"
|
||||
//#include "contextmenus_darwin.h"
|
||||
//#include "menu_darwin.h"
|
||||
//
|
||||
//ContextMenu* NewContextMenu(const char* contextMenuJSON, struct TrayMenuStore *store) {
|
||||
// ContextMenu* result = malloc(sizeof(ContextMenu));
|
||||
//
|
||||
// JsonNode* processedJSON = json_decode(contextMenuJSON);
|
||||
// if( processedJSON == NULL ) {
|
||||
// ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
|
||||
// }
|
||||
// // Save reference to this json
|
||||
// result->processedJSON = processedJSON;
|
||||
//
|
||||
// result->ID = mustJSONString(processedJSON, "ID");
|
||||
// JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
//
|
||||
//// result->menu = NewMenu(processedMenu);
|
||||
// result->nsmenu = NULL;
|
||||
// result->menu->menuType = ContextMenuType;
|
||||
// result->menu->store = store;
|
||||
// result->contextMenuData = NULL;
|
||||
// return result;
|
||||
//}
|
||||
//
|
||||
//ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
|
||||
// return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||
//}
|
||||
//
|
||||
//void DeleteContextMenu(ContextMenu* contextMenu) {
|
||||
// // Free Menu
|
||||
// DeleteMenu(contextMenu->menu);
|
||||
//
|
||||
// // Delete any context menu data we may have stored
|
||||
// if( contextMenu->contextMenuData != NULL ) {
|
||||
// MEMFREE(contextMenu->contextMenuData);
|
||||
// }
|
||||
//
|
||||
// // Free JSON
|
||||
// if (contextMenu->processedJSON != NULL ) {
|
||||
// json_delete(contextMenu->processedJSON);
|
||||
// contextMenu->processedJSON = NULL;
|
||||
// }
|
||||
//
|
||||
// // Free context menu
|
||||
// free(contextMenu);
|
||||
//}
|
||||
//
|
||||
//int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
// DeleteContextMenu(e->data);
|
||||
// return -1;
|
||||
//}
|
||||
//
|
||||
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
|
||||
//
|
||||
// // If no context menu ID was given, abort
|
||||
// if( contextMenuID == NULL ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
|
||||
//
|
||||
// // We don't need the ID now
|
||||
// MEMFREE(contextMenuID);
|
||||
//
|
||||
// if( contextMenu == NULL ) {
|
||||
// // Free context menu data
|
||||
// if( contextMenuData != NULL ) {
|
||||
// MEMFREE(contextMenuData);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // We need to store the context menu data. Free existing data if we have it
|
||||
// // and set to the new value.
|
||||
// FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||
//
|
||||
// // Grab the content view and show the menu
|
||||
// id contentView = msg(mainWindow, s("contentView"));
|
||||
//
|
||||
// // Get the triggering event
|
||||
// id menuEvent = msg(mainWindow, s("currentEvent"));
|
||||
//
|
||||
//// if( contextMenu->nsmenu == NULL ) {
|
||||
//// // GetMenu creates the NSMenu
|
||||
//// contextMenu->nsmenu = GetMenu(contextMenu->menu);
|
||||
//// }
|
||||
//
|
||||
// // Show popup
|
||||
// msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||
//
|
||||
//}
|
||||
//
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
//////
|
||||
////// Created by Lea Anthony on 6/1/21.
|
||||
//////
|
||||
////
|
||||
//// Created by Lea Anthony on 6/1/21.
|
||||
////
|
||||
//#ifndef CONTEXTMENU_DARWIN_H
|
||||
//#define CONTEXTMENU_DARWIN_H
|
||||
//
|
||||
#ifndef CONTEXTMENU_DARWIN_H
|
||||
#define CONTEXTMENU_DARWIN_H
|
||||
|
||||
#include "json.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenustore_darwin.h"
|
||||
|
||||
typedef struct {
|
||||
const char* ID;
|
||||
id nsmenu;
|
||||
Menu* menu;
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
// Context menu data is given by the frontend when clicking a context menu.
|
||||
// We send this to the backend when an item is selected
|
||||
const char* contextMenuData;
|
||||
} ContextMenu;
|
||||
|
||||
|
||||
ContextMenu* NewContextMenu(const char* contextMenuJSON);
|
||||
|
||||
ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
|
||||
void DeleteContextMenu(ContextMenu* contextMenu);
|
||||
int freeContextMenu(void *const context, struct hashmap_element_s *const e);
|
||||
|
||||
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
|
||||
|
||||
#endif //CONTEXTMENU_DARWIN_H
|
||||
//#include "json.h"
|
||||
//#include "menu_darwin.h"
|
||||
//#include "contextmenustore_darwin.h"
|
||||
//
|
||||
//typedef struct {
|
||||
// const char* ID;
|
||||
// id nsmenu;
|
||||
// Menu* menu;
|
||||
//
|
||||
// JsonNode* processedJSON;
|
||||
//
|
||||
// // Context menu data is given by the frontend when clicking a context menu.
|
||||
// // We send this to the backend when an item is selected
|
||||
// const char* contextMenuData;
|
||||
//} ContextMenu;
|
||||
//
|
||||
//
|
||||
//ContextMenu* NewContextMenu(const char* contextMenuJSON);
|
||||
//
|
||||
//ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
|
||||
//void DeleteContextMenu(ContextMenu* contextMenu);
|
||||
//int freeContextMenu(void *const context, struct hashmap_element_s *const e);
|
||||
//
|
||||
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
|
||||
//
|
||||
//#endif //CONTEXTMENU_DARWIN_H
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "contextmenustore_darwin.h"
|
||||
|
||||
ContextMenuStore* NewContextMenuStore() {
|
||||
|
||||
ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
|
||||
|
||||
// Allocate Context Menu Store
|
||||
if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
|
||||
ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
|
||||
ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
|
||||
|
||||
//TODO: check if there is already an entry for this menu
|
||||
hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
}
|
||||
|
||||
ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
|
||||
}
|
||||
|
||||
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
|
||||
ContextMenu* newContextMenu = NewContextMenu(menuJSON);
|
||||
|
||||
// Get the current menu
|
||||
ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
|
||||
if ( currentMenu == NULL ) {
|
||||
ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
|
||||
}
|
||||
|
||||
hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
|
||||
|
||||
// Save the status bar reference
|
||||
DeleteContextMenu(currentMenu);
|
||||
|
||||
hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void DeleteContextMenuStore(ContextMenuStore* store) {
|
||||
|
||||
// Guard against NULLs
|
||||
if( store == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete context menus
|
||||
if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
|
||||
if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
|
||||
ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
|
||||
}
|
||||
}
|
||||
|
||||
// Free context menu hashmap
|
||||
hashmap_destroy(&store->contextMenuMap);
|
||||
|
||||
}
|
||||
//
|
||||
//#include "contextmenus_darwin.h"
|
||||
//#include "contextmenustore_darwin.h"
|
||||
//
|
||||
//ContextMenuStore* NewContextMenuStore() {
|
||||
//
|
||||
// ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
|
||||
//
|
||||
// // Allocate Context Menu Store
|
||||
// if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
|
||||
// ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
|
||||
// }
|
||||
//
|
||||
// return result;
|
||||
//}
|
||||
//
|
||||
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
|
||||
// ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
|
||||
//
|
||||
// //TODO: check if there is already an entry for this menu
|
||||
// hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
//}
|
||||
//
|
||||
//ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
|
||||
// // Get the current menu
|
||||
// return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
|
||||
//}
|
||||
//
|
||||
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
|
||||
// ContextMenu* newContextMenu = NewContextMenu(menuJSON);
|
||||
//
|
||||
// // Get the current menu
|
||||
// ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
|
||||
// if ( currentMenu == NULL ) {
|
||||
// ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
|
||||
// }
|
||||
//
|
||||
// hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
|
||||
//
|
||||
// // Save the status bar reference
|
||||
// DeleteContextMenu(currentMenu);
|
||||
//
|
||||
// hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
|
||||
//
|
||||
//}
|
||||
//
|
||||
//
|
||||
//void DeleteContextMenuStore(ContextMenuStore* store) {
|
||||
//
|
||||
// // Guard against NULLs
|
||||
// if( store == NULL ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Delete context menus
|
||||
// if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
|
||||
// if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
|
||||
// ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Free context menu hashmap
|
||||
// hashmap_destroy(&store->contextMenuMap);
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
////
|
||||
//// Created by Lea Anthony on 7/1/21.
|
||||
////
|
||||
//
|
||||
// Created by Lea Anthony on 7/1/21.
|
||||
//#ifndef CONTEXTMENUSTORE_DARWIN_H
|
||||
//#define CONTEXTMENUSTORE_DARWIN_H
|
||||
//
|
||||
|
||||
#ifndef CONTEXTMENUSTORE_DARWIN_H
|
||||
#define CONTEXTMENUSTORE_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
|
||||
// This is our context menu store which keeps track
|
||||
// of all instances of ContextMenus
|
||||
struct hashmap_s contextMenuMap;
|
||||
|
||||
} ContextMenuStore;
|
||||
|
||||
ContextMenuStore* NewContextMenuStore();
|
||||
|
||||
void DeleteContextMenuStore(ContextMenuStore* store);
|
||||
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
|
||||
|
||||
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
|
||||
|
||||
#endif //CONTEXTMENUSTORE_DARWIN_H
|
||||
//#include "common.h"
|
||||
//
|
||||
//typedef struct {
|
||||
//
|
||||
// int dummy;
|
||||
//
|
||||
// // This is our context menu store which keeps track
|
||||
// // of all instances of ContextMenus
|
||||
// struct hashmap_s contextMenuMap;
|
||||
//
|
||||
//} ContextMenuStore;
|
||||
//
|
||||
//ContextMenuStore* NewContextMenuStore();
|
||||
//
|
||||
//void DeleteContextMenuStore(ContextMenuStore* store);
|
||||
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
|
||||
//
|
||||
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
|
||||
//
|
||||
//#endif //CONTEXTMENUSTORE_DARWIN_H
|
||||
|
||||
@@ -30,14 +30,15 @@ extern void Fullscreen(struct Application*);
|
||||
extern void UnFullscreen(struct Application*);
|
||||
extern void ToggleFullscreen(struct Application*);
|
||||
extern void DisableFrame(struct Application*);
|
||||
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
|
||||
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
|
||||
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
|
||||
extern void DarkModeEnabled(struct Application*, char *callbackID);
|
||||
extern void OpenDialog(struct Application*, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
|
||||
extern void SaveDialog(struct Application*, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
|
||||
extern void MessageDialog(struct Application*, const char *callbackID, const char *type, const char *title, const char *message, const char *icon, const char *button1, const char *button2, const char *button3, const char *button4, const char *defaultButton, const char *cancelButton);
|
||||
extern void DarkModeEnabled(struct Application*, const char *callbackID);
|
||||
extern void SetApplicationMenu(struct Application*, const char *);
|
||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void UpdateTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||
extern void AddContextMenu(struct Application*, const char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, const char *contextMenuJSON);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,10 +12,10 @@ package ffenestri
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
// Client is our implentation of messageDispatcher.Client
|
||||
@@ -120,7 +120,7 @@ func (c *Client) WindowSetColour(colour int) {
|
||||
}
|
||||
|
||||
// OpenDialog will open a dialog with the given title and filter
|
||||
func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
|
||||
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
C.OpenDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
@@ -138,7 +138,7 @@ func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string
|
||||
}
|
||||
|
||||
// SaveDialog will open a dialog with the given title and filter
|
||||
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
|
||||
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
|
||||
C.SaveDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
@@ -152,7 +152,7 @@ func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string
|
||||
}
|
||||
|
||||
// MessageDialog will open a message dialog with the given options
|
||||
func (c *Client) MessageDialog(dialogOptions *options.MessageDialog, callbackID string) {
|
||||
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
|
||||
|
||||
// Sanity check button length
|
||||
if len(dialogOptions.Buttons) > 4 {
|
||||
@@ -188,8 +188,12 @@ func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
|
||||
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateTrayMenu(trayMenuJSON string) {
|
||||
C.UpdateTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
|
||||
func (c *Client) SetTrayMenu(trayMenuJSON string) {
|
||||
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
||||
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
#ifdef FFENESTRI_DARWIN
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "traymenustore_darwin.h"
|
||||
#include "traymenu_darwin.h"
|
||||
#include "menu.h"
|
||||
//#include "contextmenus_darwin.h"
|
||||
//#include "traymenustore_darwin.h"
|
||||
//#include "traymenu_darwin.h"
|
||||
|
||||
// References to assets
|
||||
#include "assets.h"
|
||||
@@ -14,6 +13,10 @@ extern const unsigned char runtime;
|
||||
// Dialog icons
|
||||
extern const unsigned char *defaultDialogIcons[];
|
||||
#include "userdialogicons.h"
|
||||
#include "menu_darwin.h"
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
typedef void (*ffenestriCallback)(const char *);
|
||||
|
||||
// MAIN DEBUG FLAG
|
||||
int debug;
|
||||
@@ -21,10 +24,6 @@ int debug;
|
||||
// A cache for all our dialog icons
|
||||
struct hashmap_s dialogIconCache;
|
||||
|
||||
// Context menu data is given by the frontend when clicking a context menu.
|
||||
// We send this to the backend when an item is selected;
|
||||
const char *contextMenuData;
|
||||
|
||||
// Dispatch Method
|
||||
typedef void (^dispatchMethod)(void);
|
||||
|
||||
@@ -53,9 +52,6 @@ void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
typedef void (*ffenestriCallback)(const char *);
|
||||
|
||||
void HideMouse() {
|
||||
msg(c("NSCursor"), s("hide"));
|
||||
}
|
||||
@@ -109,14 +105,16 @@ struct Application {
|
||||
int hideToolbarSeparator;
|
||||
int windowBackgroundIsTranslucent;
|
||||
|
||||
// Menu
|
||||
Menu *applicationMenu;
|
||||
// // Menu
|
||||
// Menu *applicationMenu;
|
||||
//
|
||||
// // Tray
|
||||
// TrayMenuStore* trayMenuStore;
|
||||
|
||||
// Tray
|
||||
TrayMenuStore* trayMenuStore;
|
||||
MenuManager* menuManager;
|
||||
|
||||
// Context Menus
|
||||
ContextMenuStore *contextMenuStore;
|
||||
// ContextMenuStore *contextMenuStore;
|
||||
|
||||
// Callback
|
||||
ffenestriCallback sendMessageToBackend;
|
||||
@@ -124,6 +122,9 @@ struct Application {
|
||||
// Bindings
|
||||
const char *bindings;
|
||||
|
||||
// Flags
|
||||
bool running;
|
||||
|
||||
};
|
||||
|
||||
// Debug works like sprintf but mutes if the global debug flag is true
|
||||
@@ -253,8 +254,15 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
Show(app);
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenus(app->menuManager);
|
||||
|
||||
// TODO: Check this actually does reduce flicker
|
||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
||||
|
||||
// Notify backend we are ready (system startup)
|
||||
app->sendMessageToBackend("SS");
|
||||
|
||||
} else if( strcmp(name, "windowDrag") == 0 ) {
|
||||
// Guard against null events
|
||||
if( app->mouseEvent != NULL ) {
|
||||
@@ -316,7 +324,8 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
const char* contextMenuData = STRCOPY(contextMenuDataNode->string_);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
|
||||
//TODO: FIX UP
|
||||
// ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
|
||||
);
|
||||
|
||||
json_delete(contextMenuMessageJSON);
|
||||
@@ -387,7 +396,7 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void destroyContextMenus(struct Application *app) {
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
// DeleteContextMenuStore(app->contextMenuStore);
|
||||
}
|
||||
|
||||
void freeDialogIconCache(struct Application *app) {
|
||||
@@ -420,19 +429,8 @@ void DestroyApplication(struct Application *app) {
|
||||
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
|
||||
}
|
||||
|
||||
// Delete the application menu if we have one
|
||||
if( app->applicationMenu != NULL ) {
|
||||
DeleteMenu(app->applicationMenu);
|
||||
}
|
||||
|
||||
// Delete the tray menu store
|
||||
DeleteTrayMenuStore(app->trayMenuStore);
|
||||
|
||||
// Delete the context menu store
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
|
||||
// Destroy the context menus
|
||||
destroyContextMenus(app);
|
||||
DeleteMenuManager(app->menuManager);
|
||||
|
||||
// Free dialog icon cache
|
||||
freeDialogIconCache(app);
|
||||
@@ -579,7 +577,7 @@ void SetPosition(struct Application *app, int x, int y) {
|
||||
);
|
||||
}
|
||||
|
||||
void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *defaultButton) {
|
||||
void processDialogButton(id alert, const char *buttonTitle, const char *cancelButton, const char *defaultButton) {
|
||||
// If this button is set
|
||||
if( STR_HAS_CHARS(buttonTitle) ) {
|
||||
id button = msg(alert, s("addButtonWithTitle:"), str(buttonTitle));
|
||||
@@ -592,11 +590,11 @@ void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *
|
||||
}
|
||||
}
|
||||
|
||||
extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
|
||||
extern void MessageDialog(struct Application *app, const char *callbackID, const char *type, const char *title, const char *message, const char *icon, const char *button1, const char *button2, const char *button3, const char *button4, const char *defaultButton, const char *cancelButton) {
|
||||
ON_MAIN_THREAD(
|
||||
id alert = ALLOC_INIT("NSAlert");
|
||||
char *dialogType = type;
|
||||
char *dialogIcon = type;
|
||||
const char *dialogType = type;
|
||||
const char *dialogIcon = type;
|
||||
|
||||
// Default to info type
|
||||
if( dialogType == NULL ) {
|
||||
@@ -633,6 +631,8 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
dialogIcon = icon;
|
||||
}
|
||||
|
||||
// TODO: move dialog icons + methods to own file
|
||||
|
||||
// Determine what dialog icon we are looking for
|
||||
id dialogImage = NULL;
|
||||
// Look for `name-theme2x` first
|
||||
@@ -672,7 +672,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
}
|
||||
|
||||
// Run modal
|
||||
char *buttonPressed;
|
||||
const char *buttonPressed;
|
||||
int response = (int)msg(alert, s("runModal"));
|
||||
if( response == NSAlertFirstButtonReturn ) {
|
||||
buttonPressed = button1;
|
||||
@@ -703,7 +703,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
}
|
||||
|
||||
// OpenDialog opens a dialog to select files/directories
|
||||
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||
void OpenDialog(struct Application *app, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||
Debug(app, "OpenDialog Called with callback id: %s", callbackID);
|
||||
|
||||
// Create an open panel
|
||||
@@ -791,7 +791,7 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
|
||||
}
|
||||
|
||||
// SaveDialog opens a dialog to select files/directories
|
||||
void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||
void SaveDialog(struct Application *app, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||
Debug(app, "SaveDialog Called with callback id: %s", callbackID);
|
||||
|
||||
// Create an open panel
|
||||
@@ -906,28 +906,41 @@ void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
|
||||
}
|
||||
|
||||
|
||||
void SetDebug(void *applicationPointer, int flag) {
|
||||
void SetDebug(struct Application *applicationPointer, int flag) {
|
||||
debug = flag;
|
||||
}
|
||||
|
||||
|
||||
// SetContextMenus 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) {
|
||||
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||
// AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||
}
|
||||
|
||||
void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
|
||||
UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
|
||||
// UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
|
||||
}
|
||||
|
||||
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
||||
AddTrayMenuToManager(app->menuManager, trayMenuJSON);
|
||||
}
|
||||
|
||||
void UpdateTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
TrayMenu *menu = AddTrayMenuToManager(app->menuManager, trayMenuJSON);
|
||||
if( app->running ) {
|
||||
ON_MAIN_THREAD(
|
||||
ShowTrayMenu(menu)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
// ON_MAIN_THREAD(
|
||||
// UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||
// );
|
||||
//}
|
||||
|
||||
|
||||
void SetBindings(struct Application *app, const char *bindings) {
|
||||
const char* temp = concat("window.wailsbindings = \"", bindings);
|
||||
const char* jscall = concat(temp, "\";");
|
||||
@@ -1014,7 +1027,7 @@ void createDelegate(struct Application *app) {
|
||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// All Menu Items use a common callback
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)PlatformMenuItemCallback, "v@:@");
|
||||
|
||||
// Script handler
|
||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
@@ -1071,402 +1084,69 @@ const char* getInitialState(struct Application *app) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
|
||||
const char *roleName = item->string_;
|
||||
|
||||
if ( STREQ(roleName, "appMenu") ) {
|
||||
createDefaultAppMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "editMenu")) {
|
||||
createDefaultEditMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hide")) {
|
||||
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "front")) {
|
||||
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "undo")) {
|
||||
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "redo")) {
|
||||
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "cut")) {
|
||||
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "copy")) {
|
||||
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "paste")) {
|
||||
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "delete")) {
|
||||
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "minimize")) {
|
||||
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "zoom")) {
|
||||
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s(menuCallback), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
id parseCheckboxMenuItem(struct Application *app, id parentmenu, const char
|
||||
*title, const char *menuid, bool disabled, bool checked, const char *key,
|
||||
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(checkboxCallbackFunction), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id parseRadioMenuItem(struct Application *app, id parentmenu, const char *title,
|
||||
const char *menuid, bool disabled, bool checked, const char *acceleratorkey,
|
||||
struct hashmap_s *menuItemMap, const char *radioCallbackFunction) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(radioCallbackFunction), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item,
|
||||
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
||||
*radioCallbackFunction, const char *menuCallbackFunction) {
|
||||
|
||||
// Check if this item is hidden and if so, exit early!
|
||||
bool hidden = false;
|
||||
getJSONBool(item, "Hidden", &hidden);
|
||||
if( hidden ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the role
|
||||
JsonNode *role = json_find_member(item, "Role");
|
||||
if( role != NULL ) {
|
||||
parseMenuRole(app, parentMenu, role);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenu) {
|
||||
// Get item label
|
||||
parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "Label");
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
}
|
||||
|
||||
bool disabled = false;
|
||||
getJSONBool(item, "Disabled", &disabled);
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "Accelerator");
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorkey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = modifier->string_;
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get the Type
|
||||
JsonNode *type = json_find_member(item, "Type");
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, menuCallbackFunction);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Checkbox")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, checkboxCallbackFunction);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Radio")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, radioCallbackFunction);
|
||||
}
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
free(modifiers);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) {
|
||||
JsonNode *items = json_find_member(menu, "Items");
|
||||
if( items == NULL ) {
|
||||
// Parse error!
|
||||
Fatal(app, "Unable to find Items in Menu");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate items
|
||||
JsonNode *item;
|
||||
json_foreach(item, items) {
|
||||
// Get item label
|
||||
parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpMemberList(const char *name, id *memberList) {
|
||||
void *member = memberList[0];
|
||||
int count = 0;
|
||||
printf("%s = %p -> [ ", name, memberList);
|
||||
while( member != NULL ) {
|
||||
printf("%p ", member);
|
||||
count = count + 1;
|
||||
member = memberList[count];
|
||||
}
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
void processRadioGroup(JsonNode *radioGroup, struct hashmap_s *menuItemMap,
|
||||
struct hashmap_s *radioGroupMap) {
|
||||
|
||||
int groupLength;
|
||||
getJSONInt(radioGroup, "Length", &groupLength);
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
id memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
id menuItem = (id)hashmap_get(menuItemMap, (char*)member->string_, strlen(member->string_));
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// dumpMemberList("memberList", memberList);
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
hashmap_put(radioGroupMap, member->string_, strlen(member->string_), newMemberList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// updateMenu replaces the current menu with the given one
|
||||
void updateMenu(struct Application *app, const char *menuAsJSON) {
|
||||
Debug(app, "Menu is now: %s", menuAsJSON);
|
||||
ON_MAIN_THREAD (
|
||||
DeleteMenu(app->applicationMenu);
|
||||
Menu* newMenu = NewApplicationMenu(menuAsJSON);
|
||||
id menu = GetMenu(newMenu);
|
||||
app->applicationMenu = newMenu;
|
||||
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
|
||||
);
|
||||
}
|
||||
//void dumpMemberList(const char *name, id *memberList) {
|
||||
// void *member = memberList[0];
|
||||
// int count = 0;
|
||||
// printf("%s = %p -> [ ", name, memberList);
|
||||
// while( member != NULL ) {
|
||||
// printf("%p ", member);
|
||||
// count = count + 1;
|
||||
// member = memberList[count];
|
||||
// }
|
||||
// printf("]\n");
|
||||
//}
|
||||
|
||||
// SetApplicationMenu sets the initial menu for the application
|
||||
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
||||
if ( app->applicationMenu == NULL ) {
|
||||
app->applicationMenu = NewApplicationMenu(menuAsJSON);
|
||||
return;
|
||||
}
|
||||
//void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
||||
// if ( app->applicationMenu == NULL ) {
|
||||
// app->applicationMenu = NewApplicationMenu(menuAsJSON, (struct TrayMenuStore *) app->trayMenuStore);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Update menu
|
||||
// updateMenu(app, menuAsJSON);
|
||||
//}
|
||||
|
||||
// Update menu
|
||||
updateMenu(app, menuAsJSON);
|
||||
}
|
||||
|
||||
void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
|
||||
|
||||
unsigned int count = 0;
|
||||
while( 1 ) {
|
||||
const unsigned char *name = dialogIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *lengthAsString = dialogIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *data = dialogIcons[count++];
|
||||
if( data == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
int length = atoi((const char *)lengthAsString);
|
||||
|
||||
// Create the icon and add to the hashmap
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
id dialogImage = ALLOC("NSImage");
|
||||
msg(dialogImage, s("initWithData:"), imageData);
|
||||
hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processUserDialogIcons(struct Application *app) {
|
||||
|
||||
// Allocate the Dialog icon hashmap
|
||||
if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
|
||||
// Couldn't allocate map
|
||||
Fatal(app, "Not enough memory to allocate dialogIconCache!");
|
||||
return;
|
||||
}
|
||||
|
||||
processDialogIcons(&dialogIconCache, defaultDialogIcons);
|
||||
processDialogIcons(&dialogIconCache, userDialogIcons);
|
||||
|
||||
}
|
||||
//void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
|
||||
//
|
||||
// unsigned int count = 0;
|
||||
// while( 1 ) {
|
||||
// const unsigned char *name = dialogIcons[count++];
|
||||
// if( name == 0x00 ) {
|
||||
// break;
|
||||
// }
|
||||
// const unsigned char *lengthAsString = dialogIcons[count++];
|
||||
// if( name == 0x00 ) {
|
||||
// break;
|
||||
// }
|
||||
// const unsigned char *data = dialogIcons[count++];
|
||||
// if( data == 0x00 ) {
|
||||
// break;
|
||||
// }
|
||||
// int length = atoi((const char *)lengthAsString);
|
||||
//
|
||||
// // Create the icon and add to the hashmap
|
||||
// id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
// id dialogImage = ALLOC("NSImage");
|
||||
// msg(dialogImage, s("initWithData:"), imageData);
|
||||
// hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//void processUserDialogIcons(struct Application *app) {
|
||||
//
|
||||
// // Allocate the Dialog icon hashmap
|
||||
// if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
|
||||
// // Couldn't allocate map
|
||||
// Fatal(app, "Not enough memory to allocate dialogIconCache!");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// processDialogIcons(&dialogIconCache, defaultDialogIcons);
|
||||
// processDialogIcons(&dialogIconCache, userDialogIcons);
|
||||
//
|
||||
//}
|
||||
|
||||
|
||||
void Run(struct Application *app, int argc, char **argv) {
|
||||
@@ -1501,6 +1181,9 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
makeWindowBackgroundTranslucent(app);
|
||||
}
|
||||
|
||||
// We set it to be invisible by default. It will become visible when everything has initialised
|
||||
msg(app->mainWindow, s("setIsVisible:"), NO);
|
||||
|
||||
// Setup webview
|
||||
id config = msg(c("WKWebViewConfiguration"), s("new"));
|
||||
msg(config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 1), str("suppressesIncrementalRendering"));
|
||||
@@ -1639,20 +1322,15 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
|
||||
}
|
||||
|
||||
// If we have an application menu, process it
|
||||
if( app->applicationMenu != NULL ) {
|
||||
id menu = GetMenu(app->applicationMenu);
|
||||
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenusInStore(app->trayMenuStore);
|
||||
// // If we have an application menu, process it
|
||||
// if( app->applicationMenu != NULL ) {
|
||||
// msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), app->applicationMenu->menu);
|
||||
// }
|
||||
|
||||
// Process dialog icons
|
||||
processUserDialogIcons(app);
|
||||
// processUserDialogIcons(app);
|
||||
|
||||
// We set it to be invisible by default. It will become visible when everything has initialised
|
||||
msg(app->mainWindow, s("setIsVisible:"), NO);
|
||||
app->running = true;
|
||||
|
||||
// Finally call run
|
||||
Debug(app, "Run called");
|
||||
@@ -1662,7 +1340,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
}
|
||||
|
||||
|
||||
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
||||
struct Application* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
||||
|
||||
// Load the tray icons
|
||||
LoadTrayIcons();
|
||||
@@ -1703,13 +1381,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->delegate = NULL;
|
||||
|
||||
// Menu
|
||||
result->applicationMenu = NULL;
|
||||
|
||||
// Tray
|
||||
result->trayMenuStore = NewTrayMenuStore();
|
||||
result->menuManager = NewMenuManager();
|
||||
|
||||
// Context Menus
|
||||
result->contextMenuStore = NewContextMenuStore();
|
||||
// result->contextMenuStore = NewContextMenuStore();
|
||||
|
||||
// Window Appearance
|
||||
result->titlebarAppearsTransparent = 0;
|
||||
@@ -1717,8 +1392,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
|
||||
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
|
||||
|
||||
result->running = false;
|
||||
|
||||
return (void*) result;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -58,15 +58,15 @@ func (a *Application) processPlatformSettings() error {
|
||||
C.WindowBackgroundIsTranslucent(a.app)
|
||||
}
|
||||
|
||||
// Process menu
|
||||
//applicationMenu := options.GetApplicationMenu(a.config)
|
||||
applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||
if applicationMenu != "" {
|
||||
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||
}
|
||||
//// Process menu
|
||||
////applicationMenu := options.GetApplicationMenu(a.config)
|
||||
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||
//if applicationMenu != "" {
|
||||
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||
//}
|
||||
|
||||
// Process tray
|
||||
trays, err := a.menuManager.GetTrayMenus()
|
||||
trays, err := a.menuManager.GetTrayMenusAsJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,16 +76,16 @@ func (a *Application) processPlatformSettings() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Process context menus
|
||||
contextMenus, err := a.menuManager.GetContextMenus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contextMenus != nil {
|
||||
for _, contextMenu := range contextMenus {
|
||||
C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
||||
}
|
||||
}
|
||||
//// Process context menus
|
||||
//contextMenus, err := a.menuManager.GetContextMenus()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if contextMenus != nil {
|
||||
// for _, contextMenu := range contextMenus {
|
||||
// C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
||||
// }
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
||||
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
|
||||
#define RELEASE(input) if( input != NULL ) { msg(input, s("release")); }
|
||||
|
||||
#define ALLOC(classname) msg(c(classname), s("alloc"))
|
||||
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
|
||||
@@ -107,7 +108,6 @@ void SetAppearance(struct Application* app, const char *);
|
||||
void WebviewIsTransparent(struct Application* app);
|
||||
void WindowBackgroundIsTranslucent(struct Application* app);
|
||||
void SetTray(struct Application* app, const char *, const char *, const char *);
|
||||
void SetContextMenus(struct Application* app, const char *);
|
||||
void AddTrayMenu(struct Application* app, const char *);
|
||||
//void SetContextMenus(struct Application* app, const char *);
|
||||
|
||||
#endif
|
||||
276
v2/internal/ffenestri/menu.c
Normal file
276
v2/internal/ffenestri/menu.c
Normal file
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
Menu* NewMenu(struct JsonNode* menuData, struct JsonNode* radioData, MenuManager* manager) {
|
||||
|
||||
if( menuData == NULL ) return NULL;
|
||||
if( manager == NULL ) return NULL;
|
||||
|
||||
Menu *result = NEW(Menu);
|
||||
|
||||
// No label by default
|
||||
result->label = STRCOPY(getJSONStringDefault(menuData, "l", ""));
|
||||
|
||||
// Setup platform specific menu data
|
||||
SetupMenuPlatformData(result);
|
||||
|
||||
// Init menu item list
|
||||
vec_init(&result->menuItems);
|
||||
|
||||
// Get the menu items
|
||||
JsonNode* items = getJSONObject(menuData, "i");
|
||||
if( items != NULL ) {
|
||||
|
||||
// Process the menu data
|
||||
JsonNode *item;
|
||||
json_foreach(item, items) {
|
||||
const char *ID = mustJSONString(item, "I");
|
||||
MenuItem *menuItem = HASHMAP_GET(manager->menuItems, ID);
|
||||
if (menuItem == NULL) {
|
||||
// Process each menu item
|
||||
menuItem = processMenuItem(result, item, manager);
|
||||
|
||||
// Filter out separators
|
||||
if (menuItem->ID != NULL) {
|
||||
HASHMAP_PUT(manager->menuItems, menuItem->ID, menuItem);
|
||||
}
|
||||
}
|
||||
AddMenuItemToMenu(result, menuItem, manager);
|
||||
}
|
||||
|
||||
if (radioData != NULL) {
|
||||
// Iterate radio groups
|
||||
JsonNode *radioGroup;
|
||||
json_foreach(radioGroup, radioData) {
|
||||
// Get item label
|
||||
processRadioGroup(result, radioGroup, manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MenuItem* processMenuItem(Menu *menu, JsonNode *item, MenuManager *manager) {
|
||||
|
||||
|
||||
// Get the role
|
||||
const char *role = getJSONString(item, "r");
|
||||
if( role != NULL ) {
|
||||
// Roles override everything else
|
||||
// return NewMenuItemForRole(role, menu, item, manager);
|
||||
}
|
||||
|
||||
Menu* submenu = NULL;
|
||||
|
||||
// Check if this is a submenu
|
||||
// JsonNode *submenuData = json_find_member(item, "S");
|
||||
// if( submenuData != NULL ) {
|
||||
// submenu = NewMenu(submenuData)
|
||||
// // Get the label
|
||||
// JsonNode *menuNameNode = json_find_member(item, "l");
|
||||
// const char *name = "";
|
||||
// if ( menuNameNode != NULL) {
|
||||
// name = menuNameNode->string_;
|
||||
// }
|
||||
//
|
||||
// id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
// id thisMenu = createMenu(str(name));
|
||||
//
|
||||
// msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
// msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
//
|
||||
// JsonNode *submenuItems = json_find_member(submenu, "i");
|
||||
// // If we have no items, just return
|
||||
// if ( submenuItems == NULL ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Loop over submenu items
|
||||
// JsonNode *item;
|
||||
// json_foreach(item, submenuItems) {
|
||||
// // Get item label
|
||||
// processMenuItem(menu, thisMenu, item);
|
||||
// }
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// Get the ID
|
||||
const char *menuItemID = mustJSONString(item, "I");
|
||||
|
||||
// Get the label(s)
|
||||
const char* label = getJSONStringDefault(item, "l", "");
|
||||
const char* alternateLabel = getJSONString(item, "L");
|
||||
|
||||
bool checked = getJSONBool(item, "c");
|
||||
bool hidden = getJSONBool(item, "h");
|
||||
bool disabled = getJSONBool(item, "d");
|
||||
const char* RGBA = getJSONString(item, "R");
|
||||
const char* font = getJSONString(item, "F");
|
||||
const char* image = getJSONString(item, "i");
|
||||
|
||||
int fontSize = 0;
|
||||
getJSONInt(item, "F", &fontSize);
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "a");
|
||||
const char *acceleratorKey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorKey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = STRCOPY(modifier->string_);
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// has callback?
|
||||
bool hasCallback = getJSONBool(item, "C");
|
||||
|
||||
// Get the Type
|
||||
const char *type = mustJSONString(item, "t");
|
||||
|
||||
MenuItem* menuItem;
|
||||
enum MenuItemType menuItemType;
|
||||
if( STREQ(type, "t")) {
|
||||
menuItemType = Text;
|
||||
}
|
||||
else if ( STREQ(type, "s")) {
|
||||
menuItemType = Separator;
|
||||
}
|
||||
else if ( STREQ(type, "c")) {
|
||||
menuItemType = Checkbox;
|
||||
}
|
||||
else if ( STREQ(type, "r")) {
|
||||
menuItemType = Radio;
|
||||
} else {
|
||||
menuItemType = -1;
|
||||
ABORT("Unknown Menu Item type '%s'!", type);
|
||||
}
|
||||
menuItem = NewMenuItem(menuItemType, menuItemID, label, alternateLabel, disabled, hidden, checked, RGBA, font, fontSize, image, acceleratorKey, modifiers, hasCallback, submenu);
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
void DeleteMenu(Menu* menu) {
|
||||
|
||||
// NULL guard
|
||||
if( menu == NULL ) return;
|
||||
|
||||
// Delete the platform specific menu data
|
||||
DeleteMenuPlatformData(menu);
|
||||
|
||||
// Clean up other data
|
||||
MEMFREE(menu->label);
|
||||
|
||||
// Clear the menu items vector
|
||||
vec_deinit(&menu->menuItems);
|
||||
}
|
||||
|
||||
const char* MenuAsJSON(Menu* menu) {
|
||||
|
||||
if( menu == NULL ) return NULL;
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
JSON_ADD_STRING(jsonObject, "Label", menu->label);
|
||||
return json_encode(jsonObject);
|
||||
}
|
||||
|
||||
JsonNode* MenuAsJSONObject(Menu* menu) {
|
||||
|
||||
if( menu == NULL ) return NULL;
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
JSON_ADD_STRING(jsonObject, "Label", menu->label);
|
||||
if( vec_size(&menu->menuItems) > 0 ) {
|
||||
JsonNode* items = json_mkarray();
|
||||
int i; MenuItem *menuItem;
|
||||
vec_foreach(&menu->menuItems, menuItem, i) {
|
||||
JSON_ADD_ELEMENT(items, MenuItemAsJSONObject(menuItem));
|
||||
}
|
||||
JSON_ADD_OBJECT(jsonObject, "Items", items);
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
void processRadioGroup(Menu *menu, JsonNode *radioGroup, MenuManager* manager) {
|
||||
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
int groupLength = json_array_length(radioGroup);
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
MenuItem* memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, (char*)member->string_);
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
HASHMAP_PUT(menu->radioGroups, member->string_, newMemberList);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AddMenuItemToMenu(Menu* menu, MenuItem* menuItem, MenuManager* manager) {
|
||||
vec_push(&menu->menuItems, menuItem);
|
||||
PlatformAddMenuItemToMenu(menu, menuItem, manager);
|
||||
}
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* CreateMenuClickedMessage(const char *menuItemID, const char *data) {
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
if (menuItemID == NULL ) {
|
||||
ABORT("Item ID NULL for menu!!\n");
|
||||
}
|
||||
json_append_member(jsonObject, "i", json_mkstring(menuItemID));
|
||||
if (data != NULL) {
|
||||
json_append_member(jsonObject, "data", json_mkstring(data));
|
||||
}
|
||||
const char *payload = json_encode(jsonObject);
|
||||
json_delete(jsonObject);
|
||||
const char *result = concat("MC", payload);
|
||||
MEMFREE(payload);
|
||||
return result;
|
||||
}
|
||||
178
v2/internal/ffenestri/menu.h
Normal file
178
v2/internal/ffenestri/menu.h
Normal file
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2, Separator = 3};
|
||||
static const char *MenuItemTypeAsString[] = {
|
||||
"Text", "Checkbox", "Radio", "Separator",
|
||||
};
|
||||
|
||||
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
|
||||
static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
// Menu label
|
||||
const char *label;
|
||||
|
||||
// A list of menuItem IDs that make up this menu
|
||||
vec_void_t menuItems;
|
||||
|
||||
// The platform specific menu data
|
||||
void *platformData;
|
||||
|
||||
} Menu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
// ID of the tray
|
||||
const char *ID;
|
||||
|
||||
// The tray label
|
||||
const char *Label;
|
||||
|
||||
// The icon name
|
||||
const char *Icon;
|
||||
|
||||
// The menu
|
||||
Menu* Menu;
|
||||
|
||||
// Platform specific data
|
||||
void* platformData;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Menu* menu;
|
||||
|
||||
} ApplicationMenu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char* ID;
|
||||
|
||||
Menu* menu;
|
||||
|
||||
} ContextMenu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
// This is our menu item map using the menuItem ID as a key
|
||||
// map[string]*MenuItem
|
||||
struct hashmap_s menuItems;
|
||||
|
||||
// This is our context menu map using the context menu ID as a key
|
||||
// map[string]*ContextMenu
|
||||
struct hashmap_s contextMenus;
|
||||
|
||||
// This is our tray menu map using the tray menu ID as a key
|
||||
// map[string]*TrayMenu
|
||||
struct hashmap_s trayMenus;
|
||||
|
||||
// Application Menu
|
||||
Menu* applicationMenu;
|
||||
|
||||
// Context menu data
|
||||
const char* contextMenuData;
|
||||
|
||||
} MenuManager;
|
||||
|
||||
|
||||
typedef struct {
|
||||
MenuManager* manager;
|
||||
const char *menuItemID;
|
||||
enum MenuItemType menuItemType;
|
||||
} MenuItemCallbackData;
|
||||
|
||||
typedef struct {
|
||||
const char *ID;
|
||||
const char* label;
|
||||
const char* alternateLabel;
|
||||
bool disabled;
|
||||
bool hidden;
|
||||
const char* colour;
|
||||
const char* font;
|
||||
int fontSize;
|
||||
const char* image;
|
||||
bool checked;
|
||||
|
||||
// Keyboard shortcut
|
||||
const char* acceleratorKey;
|
||||
const char** modifiers;
|
||||
|
||||
// Type of menuItem
|
||||
enum MenuItemType type;
|
||||
|
||||
// Indicates if the menuItem has a callback
|
||||
bool hasCallback;
|
||||
|
||||
// The platform specific menu data
|
||||
void* platformData;
|
||||
|
||||
// Submenu
|
||||
Menu* submenu;
|
||||
|
||||
// Radio group for this item
|
||||
vec_void_t radioGroup;
|
||||
|
||||
// Data for handling callbacks
|
||||
MenuItemCallbackData *callbackData;
|
||||
|
||||
} MenuItem;
|
||||
|
||||
// MenuItem
|
||||
MenuItem* NewMenuItem(enum MenuItemType type, const char* ID, const char* label, const char* alternateLabel, bool disabled, bool hidden, bool checked, const char* colour, const char* font, int fontsize, const char* image, const char* acceleratorKey, const char** modifiers, bool hasCallback, Menu* submenu);
|
||||
void DeleteMenuItem(MenuItem* menuItem);
|
||||
JsonNode* MenuItemAsJSONObject(MenuItem* menuItem);
|
||||
|
||||
// Menu
|
||||
Menu* NewMenu(JsonNode* menuData, JsonNode* radioData, MenuManager* menuManager);
|
||||
void DeleteMenu(Menu* menu);
|
||||
const char* MenuAsJSON(Menu* menu);
|
||||
JsonNode* MenuAsJSONObject(Menu* menu);
|
||||
void AddMenuItemToMenu(Menu* menu, MenuItem* menuItem, MenuManager* manager);
|
||||
MenuItem* processMenuItem(Menu *menu, JsonNode *item, MenuManager *manager);
|
||||
void processRadioGroup(Menu *menu, JsonNode *radioGroup, MenuManager* manager);
|
||||
|
||||
// Tray
|
||||
TrayMenu* NewTrayMenu(JsonNode* trayJSON, MenuManager *manager);
|
||||
void DeleteTrayMenu(TrayMenu *trayMenu);
|
||||
const char* TrayMenuAsJSON(TrayMenu* menu);
|
||||
|
||||
// MenuManager
|
||||
MenuManager* NewMenuManager();
|
||||
void DeleteMenuManager(MenuManager* manager);
|
||||
TrayMenu* AddTrayMenuToManager(MenuManager* manager, const char* trayMenuJSON);
|
||||
void ShowTrayMenus(MenuManager* manager);
|
||||
|
||||
// Callbacks
|
||||
MenuItemCallbackData* NewMenuItemCallbackData(MenuManager *manager, const char* menuItemID, enum MenuItemType menuItemType);
|
||||
void DeleteMenuItemCallbackData(MenuItemCallbackData* callbackData);
|
||||
const char* CreateMenuClickedMessage(const char *menuItemID, const char *data);
|
||||
|
||||
// Platform
|
||||
void SetupMenuPlatformData(Menu* menu);
|
||||
void DeleteMenuPlatformData(Menu* menu);
|
||||
void SetupMenuItemPlatformData(MenuItem* menuItem);
|
||||
void DeleteMenuItemPlatformData(MenuItem* menuItem);
|
||||
void SetupTrayMenuPlatformData(TrayMenu* menu);
|
||||
void DeleteTrayMenuPlatformData(TrayMenu* menu);
|
||||
void PlatformAddMenuItemToMenu(Menu *menu, MenuItem* menuItem, MenuManager* manager);
|
||||
void PlatformUpdateTrayIcon(TrayMenu *menu);
|
||||
|
||||
// Platform specific methods
|
||||
void UnloadTrayIcons();
|
||||
void LoadTrayIcons();
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
|
||||
#endif //MENU_H
|
||||
@@ -1,174 +1,35 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "menu.h"
|
||||
#include "trayicons.h"
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData) {
|
||||
// A cache for all our tray menu icons
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
|
||||
Menu *result = malloc(sizeof(Menu));
|
||||
|
||||
result->processedMenu = menuData;
|
||||
|
||||
// No title by default
|
||||
result->title = "";
|
||||
|
||||
// Initialise menuCallbackDataCache
|
||||
vec_init(&result->callbackDataCache);
|
||||
|
||||
// Allocate MenuItem Map
|
||||
if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
|
||||
}
|
||||
// Allocate the Radio Group Map
|
||||
if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!");
|
||||
}
|
||||
|
||||
// Init other members
|
||||
result->menu = NULL;
|
||||
result->parentData = NULL;
|
||||
|
||||
return result;
|
||||
void SetupMenuPlatformData(Menu* menu) {
|
||||
MacMenu* result = NEW(MacMenu);
|
||||
result->Menu = ALLOC("NSMenu");
|
||||
msg(result->Menu, s("initWithTitle:"), str(menu->label));
|
||||
msg(result->Menu, s("setAutoenablesItems:"), NO);
|
||||
menu->platformData = (void*)result;
|
||||
}
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON) {
|
||||
void DeleteMenuPlatformData(Menu* menu) {
|
||||
|
||||
// Parse the menu json
|
||||
JsonNode *processedMenu = json_decode(menuAsJSON);
|
||||
if( processedMenu == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
|
||||
}
|
||||
// Return if null
|
||||
if( menu == NULL || menu->platformData == NULL) return;
|
||||
|
||||
Menu *result = NewMenu(processedMenu);
|
||||
result->menuType = ApplicationMenuType;
|
||||
return result;
|
||||
MacMenu* macMenu = (MacMenu*) menu->platformData;
|
||||
RELEASE(macMenu->Menu);
|
||||
macMenu->Menu = NULL;
|
||||
}
|
||||
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType) {
|
||||
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
|
||||
|
||||
result->menu = menu;
|
||||
result->menuID = menuID;
|
||||
result->menuItem = menuItem;
|
||||
result->menuItemType = menuItemType;
|
||||
|
||||
// Store reference to this so we can destroy later
|
||||
vec_push(&menu->callbackDataCache, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DeleteMenu(Menu *menu) {
|
||||
|
||||
// Free menu item hashmap
|
||||
hashmap_destroy(&menu->menuItemMap);
|
||||
|
||||
// Free radio group members
|
||||
if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) {
|
||||
if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) {
|
||||
ABORT("[DeleteMenu] Failed to release radioGroupMap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
// Free radio groups hashmap
|
||||
hashmap_destroy(&menu->radioGroupMap);
|
||||
|
||||
// Free up the processed menu memory
|
||||
if (menu->processedMenu != NULL) {
|
||||
json_delete(menu->processedMenu);
|
||||
menu->processedMenu = NULL;
|
||||
}
|
||||
|
||||
// Release the vector memory
|
||||
vec_deinit(&menu->callbackDataCache);
|
||||
|
||||
// Free nsmenu if we have it
|
||||
if ( menu->menu != NULL ) {
|
||||
msg(menu->menu, s("release"));
|
||||
}
|
||||
|
||||
free(menu);
|
||||
}
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
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) {
|
||||
|
||||
@@ -340,161 +201,6 @@ id processAcceleratorKey(const char *key) {
|
||||
return str(key);
|
||||
}
|
||||
|
||||
|
||||
void addSeparator(id menu) {
|
||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
||||
msg(menu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg(item, s("autorelease"));
|
||||
return item;
|
||||
}
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
||||
id item = createMenuItem(str(title), action, key);
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(menu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenu(id title) {
|
||||
id menu = ALLOC("NSMenu");
|
||||
msg(menu, s("initWithTitle:"), title);
|
||||
msg(menu, s("setAutoenablesItems:"), NO);
|
||||
// msg(menu, s("autorelease"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
void createDefaultAppMenu(id parentMenu) {
|
||||
// App Menu
|
||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||
id appMenu = createMenu(appName);
|
||||
|
||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
||||
msg(parentMenu, s("addItem:"), appMenuItem);
|
||||
|
||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
||||
id item = createMenuItem(title, "hide:", "h");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
|
||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
|
||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
|
||||
addSeparator(appMenu);
|
||||
|
||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
||||
item = createMenuItem(title, "terminate:", "q");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
void createDefaultEditMenu(id parentMenu) {
|
||||
// Edit Menu
|
||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||
id editMenu = createMenu(str("Edit"));
|
||||
|
||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
||||
msg(parentMenu, s("addItem:"), editMenuItem);
|
||||
|
||||
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
||||
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
||||
addSeparator(editMenu);
|
||||
addMenuItem(editMenu, "Cut", "cut:", "x", FALSE);
|
||||
addMenuItem(editMenu, "Copy", "copy:", "c", FALSE);
|
||||
addMenuItem(editMenu, "Paste", "paste:", "v", FALSE);
|
||||
addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
}
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
const char *roleName = item->string_;
|
||||
|
||||
if ( STREQ(roleName, "appMenu") ) {
|
||||
createDefaultAppMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "editMenu")) {
|
||||
createDefaultEditMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hide")) {
|
||||
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "front")) {
|
||||
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "undo")) {
|
||||
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "redo")) {
|
||||
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "cut")) {
|
||||
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "copy")) {
|
||||
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "paste")) {
|
||||
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "delete")) {
|
||||
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "minimize")) {
|
||||
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "zoom")) {
|
||||
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This converts a string array of modifiers into the
|
||||
// equivalent MacOS Modifier Flags
|
||||
unsigned long parseModifiers(const char **modifiers) {
|
||||
@@ -527,282 +233,286 @@ unsigned long parseModifiers(const char **modifiers) {
|
||||
return result;
|
||||
}
|
||||
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) {
|
||||
|
||||
|
||||
void SetupMenuItemPlatformData(MenuItem* menuItem) {
|
||||
|
||||
// Create the platform data
|
||||
MacMenuItem *macMenuItem = NEW(MacMenuItem);
|
||||
|
||||
menuItem->platformData = macMenuItem;
|
||||
|
||||
// Create the NSMenuItem
|
||||
id item = ALLOC("NSMenuItem");
|
||||
macMenuItem->MenuItem = item;
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
// // TODO: Process ROLE
|
||||
// if( menuItem->role != NULL ) {
|
||||
//
|
||||
// }
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key) {
|
||||
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
id key = processAcceleratorKey(menuItem->acceleratorKey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(menuItem->label),
|
||||
s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("setEnabled:"), !menuItem->disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
if( menuItem->modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(menuItem->modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
menuItem->modifiers = NULL;
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
void DeleteMenuItemPlatformData(MenuItem* menuItem) {
|
||||
|
||||
// Check if this item is hidden and if so, exit early!
|
||||
bool hidden = false;
|
||||
getJSONBool(item, "Hidden", &hidden);
|
||||
if( hidden ) {
|
||||
if( menuItem == NULL || menuItem->platformData == NULL) return;
|
||||
|
||||
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||
RELEASE(macMenuItem->MenuItem);
|
||||
|
||||
MEMFREE(macMenuItem);
|
||||
|
||||
}
|
||||
|
||||
void PlatformAddMenuItemToMenu(Menu *menu, MenuItem* menuItem, MenuManager* manager) {
|
||||
|
||||
// Return if null
|
||||
if( menu == NULL || menu->platformData == NULL) return;
|
||||
if( menuItem == NULL || menuItem->platformData == NULL) return;
|
||||
|
||||
// Don't add if the item is hidden
|
||||
if( menuItem->hidden ) return;
|
||||
|
||||
// Setup callback
|
||||
if( menuItem->hasCallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||
menuItem->callbackData = NewMenuItemCallbackData(manager, menuItem->ID, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuItem->callbackData);
|
||||
msg(macMenuItem->MenuItem, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
MacMenu* macMenu = (MacMenu*) menu->platformData;
|
||||
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||
|
||||
msg(macMenu->Menu, s("addItem:"), macMenuItem->MenuItem);
|
||||
|
||||
}
|
||||
|
||||
void SetupTrayMenuPlatformData(TrayMenu* menu) {
|
||||
MacTrayMenu* result = NEW(MacTrayMenu);
|
||||
result->statusBarItem = NULL;
|
||||
|
||||
// TODO: Work out how to make this customisable
|
||||
result->iconPosition = NSImageLeft;
|
||||
|
||||
menu->platformData = (void*)result;
|
||||
}
|
||||
|
||||
void DeleteTrayMenuPlatformData(TrayMenu* menu) {
|
||||
|
||||
// Return if null
|
||||
if( menu == NULL || menu->platformData == NULL) return;
|
||||
|
||||
MacTrayMenu* macMenu = (MacTrayMenu*) menu->platformData;
|
||||
RELEASE(macMenu->statusBarItem);
|
||||
macMenu->statusBarItem = NULL;
|
||||
}
|
||||
|
||||
|
||||
void PlatformUpdateTrayIcon(TrayMenu* trayMenu) {
|
||||
|
||||
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
|
||||
|
||||
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||
|
||||
// Empty icon means remove it
|
||||
if (trayMenu->Icon == NULL || strlen(trayMenu->Icon) == 0) {
|
||||
msg(statusBarButton, s("setImage:"), NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the role
|
||||
JsonNode *role = json_find_member(item, "Role");
|
||||
if( role != NULL ) {
|
||||
processMenuRole(menu, parentMenu, role);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
||||
// If we have no items, just return
|
||||
if ( submenuItems == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenuItems) {
|
||||
// Get item label
|
||||
processMenuItem(menu, thisMenu, item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "Label");
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
}
|
||||
|
||||
bool disabled = false;
|
||||
getJSONBool(item, "Disabled", &disabled);
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "Accelerator");
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorkey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = modifier->string_;
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the Type
|
||||
JsonNode *type = json_find_member(item, "Type");
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Checkbox")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
processCheckboxMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
||||
}
|
||||
else if ( STREQ(type->string_, "Radio")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
free(modifiers);
|
||||
}
|
||||
|
||||
return;
|
||||
id trayImage = HASHMAP_GET(trayIconCache, trayMenu->Icon);
|
||||
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
}
|
||||
|
||||
void processMenuData(Menu *menu, JsonNode *menuData) {
|
||||
JsonNode *items = json_find_member(menuData, "Items");
|
||||
if( items == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to find 'Items' in menu JSON!");
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->Label == NULL ) return;
|
||||
|
||||
// Update button label
|
||||
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
|
||||
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(label));
|
||||
}
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
if( trayMenu == NULL || trayMenu->platformData == NULL ) return;
|
||||
|
||||
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
if( macTrayMenu->statusBarItem == NULL ) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
macTrayMenu->statusBarItem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(macTrayMenu->statusBarItem, s("retain"));
|
||||
}
|
||||
|
||||
// Iterate items
|
||||
JsonNode *item;
|
||||
json_foreach(item, items) {
|
||||
// Process each menu item
|
||||
processMenuItem(menu, menu->menu, item);
|
||||
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
PlatformUpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->Label);
|
||||
|
||||
// If we don't have a menu, return
|
||||
if( trayMenu->Menu == NULL ) return;
|
||||
|
||||
// Update the menu
|
||||
MacMenu* macMenu = (MacMenu*) trayMenu->Menu->platformData;
|
||||
msg(macTrayMenu->statusBarItem, s("setMenu:"), macMenu->Menu);
|
||||
}
|
||||
|
||||
void LoadTrayIcons() {
|
||||
|
||||
// Allocate the Tray Icons
|
||||
if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) {
|
||||
// Couldn't allocate map
|
||||
ABORT("Not enough memory to allocate trayIconCache!");
|
||||
}
|
||||
|
||||
unsigned int count = 0;
|
||||
while( 1 ) {
|
||||
const unsigned char *name = trayIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *lengthAsString = trayIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *data = trayIcons[count++];
|
||||
if( data == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
char *c;
|
||||
unsigned long length = strtol((const char *)lengthAsString, &c, 10);
|
||||
|
||||
// Create the icon and add to the hashmap
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
id trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
HASHMAP_PUT(trayIconCache, (const char *)name, trayImage);
|
||||
}
|
||||
}
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
|
||||
|
||||
int groupLength;
|
||||
getJSONInt(radioGroup, "Length", &groupLength);
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
id memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
|
||||
}
|
||||
|
||||
void UnloadTrayIcons() {
|
||||
// Release the tray cache images
|
||||
HASHMAP_ITERATE(trayIconCache, releaseNSObject, NULL);
|
||||
HASHMAP_DESTROY(trayIconCache);
|
||||
}
|
||||
//
|
||||
//// Callback for text menu items
|
||||
//void menuItemCallback(id self, SEL cmd, id sender) {
|
||||
// MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
// if( callbackData == NULL) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// struct TrayMenuStore *store = callbackData->store;
|
||||
// const char* menuItemID = callbackData->menuItemID;
|
||||
// id nsmenu = GetMenuItemFromStore((TrayMenuStore *) store, menuItemID);
|
||||
// if ( nsmenu == NULL ) {
|
||||
// // The menu has been deleted!
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const char *message;
|
||||
//
|
||||
// // Update checkbox / radio item
|
||||
// if( callbackData->menuItemType == Checkbox) {
|
||||
// // Toggle state
|
||||
// bool state = msg(nsmenu, s("state"));
|
||||
// msg(nsmenu, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
// } else if( callbackData->menuItemType == Radio ) {
|
||||
// // Check the menu items' current state
|
||||
// bool selected = msg(nsmenu, s("state"));
|
||||
//
|
||||
// // If it's already selected, exit early
|
||||
// if (selected) return;
|
||||
//
|
||||
// // Get this item's radio group members and turn them off
|
||||
// id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuItemID, strlen(callbackData->menuItemID));
|
||||
//
|
||||
// // Uncheck all members of the group
|
||||
// id thisMember = members[0];
|
||||
// int count = 0;
|
||||
// while(thisMember != NULL) {
|
||||
// msg(thisMember, s("setState:"), NSControlStateValueOff);
|
||||
// count = count + 1;
|
||||
// thisMember = members[count];
|
||||
// }
|
||||
//
|
||||
// // check the selected menu item
|
||||
// msg(nsmenu, s("setState:"), NSControlStateValueOn);
|
||||
// }
|
||||
//
|
||||
// message = createMenuClickedMessage(menuItemID, GetContextMenuDataFromStore((TrayMenuStore *) store));
|
||||
//
|
||||
// // Notify the backend
|
||||
// messageFromWindowCallback(message);
|
||||
// MEMFREE(message);
|
||||
//}
|
||||
|
||||
id GetMenu(Menu *menu) {
|
||||
void PlatformMenuItemCallback(id self, SEL cmd, id sender) {
|
||||
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *message;
|
||||
|
||||
// Pull out the menu data
|
||||
JsonNode *menuData = json_find_member(menu->processedMenu, "Menu");
|
||||
if( menuData == NULL ) {
|
||||
ABORT("Unable to find Menu data: %s", menu->processedMenu);
|
||||
MenuManager* manager = callbackData->manager;
|
||||
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, callbackData->menuItemID);
|
||||
|
||||
if( menuItem == NULL ) return;
|
||||
|
||||
MacMenuItem *macMenuItem = menuItem->platformData;
|
||||
id nsMenuItem = macMenuItem->MenuItem;
|
||||
|
||||
// Update checkbox / radio item
|
||||
if( callbackData->menuItemType == Checkbox) {
|
||||
// Toggle state
|
||||
bool state = msg(nsMenuItem, s("state"));
|
||||
msg(nsMenuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
} else if( callbackData->menuItemType == Radio ) {
|
||||
// Check the menu items' current state
|
||||
bool selected = msg(nsMenuItem, s("state"));
|
||||
|
||||
// If it's already selected, exit early
|
||||
if (selected) return;
|
||||
|
||||
int i=0; const char *groupMenuItemID;
|
||||
vec_foreach(&menuItem->radioGroup, groupMenuItemID, i) {
|
||||
// Get member
|
||||
MenuItem* groupMember = HASHMAP_GET(manager->menuItems, groupMenuItemID);
|
||||
MacMenuItem* groupMacMenuItem = (MacMenuItem*) groupMember->platformData;
|
||||
msg(groupMacMenuItem->MenuItem, s("setState:"), NSControlStateValueOff);
|
||||
}
|
||||
|
||||
// check the selected menu item
|
||||
msg(nsMenuItem, s("setState:"), NSControlStateValueOn);
|
||||
}
|
||||
|
||||
menu->menu = createMenu(str(""));
|
||||
message = CreateMenuClickedMessage(callbackData->menuItemID, manager->contextMenuData);
|
||||
|
||||
// Process the menu data
|
||||
processMenuData(menu, menuData);
|
||||
|
||||
// Create the radiogroup cache
|
||||
JsonNode *radioGroups = json_find_member(menu->processedMenu, "RadioGroups");
|
||||
if( radioGroups == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to find RadioGroups data: %s", menu->processedMenu);
|
||||
}
|
||||
|
||||
// Iterate radio groups
|
||||
JsonNode *radioGroup;
|
||||
json_foreach(radioGroup, radioGroups) {
|
||||
// Get item label
|
||||
processRadioGroupJSON(menu, radioGroup);
|
||||
}
|
||||
|
||||
return menu->menu;
|
||||
// Notify the backend
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +1,21 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#ifndef MENU_DARWIN_H
|
||||
#define MENU_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
|
||||
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
|
||||
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
|
||||
static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
#include "menu.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char *title;
|
||||
|
||||
/*** Internal ***/
|
||||
|
||||
// The decoded version of the Menu JSON
|
||||
JsonNode *processedMenu;
|
||||
|
||||
struct hashmap_s menuItemMap;
|
||||
struct hashmap_s radioGroupMap;
|
||||
|
||||
// Vector to keep track of callback data memory
|
||||
vec_void_t callbackDataCache;
|
||||
|
||||
// The NSMenu for this menu
|
||||
id menu;
|
||||
|
||||
// The parent data, eg ContextMenuStore or Tray
|
||||
void *parentData;
|
||||
|
||||
// The commands for the menu callbacks
|
||||
const char *callbackCommand;
|
||||
|
||||
// This indicates if we are an Application Menu, tray menu or context menu
|
||||
enum MenuType menuType;
|
||||
|
||||
|
||||
} Menu;
|
||||
|
||||
id Menu;
|
||||
} MacMenu;
|
||||
|
||||
typedef struct {
|
||||
id menuItem;
|
||||
Menu *menu;
|
||||
const char *menuID;
|
||||
enum MenuItemType menuItemType;
|
||||
} MenuItemCallbackData;
|
||||
id MenuItem;
|
||||
} MacMenuItem;
|
||||
|
||||
typedef struct {
|
||||
id statusBarItem;
|
||||
int iconPosition;
|
||||
} MacTrayMenu;
|
||||
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData);
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON);
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType);
|
||||
|
||||
void DeleteMenu(Menu *menu);
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID);
|
||||
// Callback for text menu items
|
||||
void menuItemCallback(id self, SEL cmd, id sender);
|
||||
id processAcceleratorKey(const char *key);
|
||||
|
||||
|
||||
void addSeparator(id menu);
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key);
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
|
||||
|
||||
id createMenu(id title);
|
||||
void createDefaultAppMenu(id parentMenu);
|
||||
void createDefaultEditMenu(id parentMenu);
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item);
|
||||
// This converts a string array of modifiers into the
|
||||
// equivalent MacOS Modifier Flags
|
||||
unsigned long parseModifiers(const char **modifiers);
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey);
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers);
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
id GetMenu(Menu *menu);
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
void PlatformMenuItemCallback(id self, SEL cmd, id sender);
|
||||
|
||||
542
v2/internal/ffenestri/menu_darwin_old.c
Normal file
542
v2/internal/ffenestri/menu_darwin_old.c
Normal file
@@ -0,0 +1,542 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
//
|
||||
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin_old.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "traymenustore_darwin.h"
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData, JsonNode *radioGroups, struct TrayMenuStore *store) {
|
||||
|
||||
Menu *result = malloc(sizeof(Menu));
|
||||
|
||||
// No title by default
|
||||
result->title = "";
|
||||
|
||||
// Initialise menuCallbackDataCache
|
||||
vec_init(&result->callbackDataCache);
|
||||
|
||||
// Allocate MenuItem Map
|
||||
if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
|
||||
}
|
||||
// Allocate the Radio Group Map
|
||||
if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!");
|
||||
}
|
||||
|
||||
// Init other members
|
||||
result->menu = NULL;
|
||||
result->store = store;
|
||||
|
||||
// Process the menu
|
||||
ProcessMenu(result, menuData, radioGroups);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON, struct TrayMenuStore *store) {
|
||||
|
||||
// Parse the menu json
|
||||
JsonNode *processedMenu = json_decode(menuAsJSON);
|
||||
if( processedMenu == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
|
||||
}
|
||||
|
||||
// TODO - fixup
|
||||
Menu *result = NewMenu(processedMenu, NULL, store);
|
||||
result->menuType = ApplicationMenuType;
|
||||
return result;
|
||||
}
|
||||
|
||||
//TODO: Put traymenu store in callback instead of menu as it'll never be null
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, const char *menuItemID, enum MenuItemType menuItemType) {
|
||||
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
|
||||
|
||||
result->store = menu->store;
|
||||
result->menuItemID = STRCOPY(menuItemID);
|
||||
result->menuItemType = menuItemType;
|
||||
|
||||
// Store reference to this so we can destroy later
|
||||
vec_push(&menu->callbackDataCache, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DeleteMenu(Menu *menu) {
|
||||
|
||||
if( menu == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Free menu item hashmap
|
||||
hashmap_destroy(&menu->menuItemMap);
|
||||
|
||||
// Free radio group members
|
||||
if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) {
|
||||
if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) {
|
||||
ABORT("[DeleteMenu] Failed to release radioGroupMap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
// Free radio groups hashmap
|
||||
hashmap_destroy(&menu->radioGroupMap);
|
||||
|
||||
// Release the callback cache memory
|
||||
int i; MenuItemCallbackData *callback;
|
||||
vec_foreach(&menu->callbackDataCache, callback, i) {
|
||||
MEMFREE(callback->menuItemID);
|
||||
}
|
||||
vec_deinit(&menu->callbackDataCache);
|
||||
|
||||
// Free nsmenu if we have it
|
||||
if ( menu->menu != NULL ) {
|
||||
msg(menu->menu, s("release"));
|
||||
}
|
||||
|
||||
free(menu);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void addSeparator(id menu) {
|
||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
||||
msg(menu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg(item, s("autorelease"));
|
||||
return item;
|
||||
}
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
||||
id item = createMenuItem(str(title), action, key);
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(menu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenu(id title) {
|
||||
id menu = ALLOC("NSMenu");
|
||||
msg(menu, s("initWithTitle:"), title);
|
||||
msg(menu, s("setAutoenablesItems:"), NO);
|
||||
// msg(menu, s("autorelease"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
void createDefaultAppMenu(id parentMenu) {
|
||||
// App Menu
|
||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||
id appMenu = createMenu(appName);
|
||||
|
||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
||||
msg(parentMenu, s("addItem:"), appMenuItem);
|
||||
|
||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
||||
id item = createMenuItem(title, "hide:", "h");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
|
||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
|
||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
|
||||
addSeparator(appMenu);
|
||||
|
||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
||||
item = createMenuItem(title, "terminate:", "q");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
void createDefaultEditMenu(id parentMenu) {
|
||||
// Edit Menu
|
||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||
id editMenu = createMenu(str("Edit"));
|
||||
|
||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
||||
msg(parentMenu, s("addItem:"), editMenuItem);
|
||||
|
||||
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
||||
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
||||
addSeparator(editMenu);
|
||||
addMenuItem(editMenu, "Cut", "cut:", "x", FALSE);
|
||||
addMenuItem(editMenu, "Copy", "copy:", "c", FALSE);
|
||||
addMenuItem(editMenu, "Paste", "paste:", "v", FALSE);
|
||||
addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
}
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
const char *roleName = item->string_;
|
||||
|
||||
if ( STREQ(roleName, "appMenu") ) {
|
||||
createDefaultAppMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "editMenu")) {
|
||||
createDefaultEditMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hide")) {
|
||||
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "front")) {
|
||||
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "undo")) {
|
||||
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "redo")) {
|
||||
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "cut")) {
|
||||
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "copy")) {
|
||||
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "paste")) {
|
||||
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "delete")) {
|
||||
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "minimize")) {
|
||||
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "zoom")) {
|
||||
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuItemID, bool disabled, bool checked, const char *acceleratorkey, bool hascallback) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
// hashmap_put(&menu->menuItemMap, (char*)menuItemID, strlen(menuItemID), item);
|
||||
|
||||
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
|
||||
|
||||
if( hascallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, Radio);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuItemID, bool disabled, bool checked, const char *key, bool hascallback) {
|
||||
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
// hashmap_put(&menu->menuItemMap, (char*)menuItemID, strlen(menuItemID), item);
|
||||
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
|
||||
|
||||
if( hascallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, Checkbox);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuItemID, bool disabled, const char *acceleratorkey, const char **modifiers, bool hascallback) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
|
||||
|
||||
if( hascallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
|
||||
// Check if this item is hidden and if so, exit early!
|
||||
bool hidden = getJSONBool(item, "h");
|
||||
if( hidden ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the role
|
||||
JsonNode *role = json_find_member(item, "r");
|
||||
if( role != NULL ) {
|
||||
processMenuRole(menu, parentMenu, role);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "S");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "l");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
JsonNode *submenuItems = json_find_member(submenu, "i");
|
||||
// If we have no items, just return
|
||||
if ( submenuItems == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenuItems) {
|
||||
// Get item label
|
||||
processMenuItem(menu, thisMenu, item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "l");
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
const char *menuItemID = getJSONString(item, "I");
|
||||
if ( menuItemID == NULL) {
|
||||
menuItemID = "";
|
||||
}
|
||||
|
||||
bool disabled = getJSONBool(item, "d");
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "a");
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorkey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = modifier->string_;
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// has callback?
|
||||
bool hascallback = getJSONBool(item, "C");
|
||||
|
||||
// Get the Type
|
||||
const char *type = mustJSONString(item, "t");
|
||||
|
||||
if( STREQ(type, "t")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuItemID, disabled, acceleratorkey, modifiers, hascallback);
|
||||
}
|
||||
else if ( STREQ(type, "s")) {
|
||||
addSeparator(parentMenu);
|
||||
}
|
||||
else if ( STREQ(type, "c")) {
|
||||
// Get checked state
|
||||
bool checked = getJSONBool(item, "c");
|
||||
|
||||
processCheckboxMenuItem(menu, parentMenu, label, menuItemID, disabled, checked, "", hascallback);
|
||||
}
|
||||
else if ( STREQ(type, "r")) {
|
||||
// Get checked state
|
||||
bool checked = getJSONBool(item, "c");
|
||||
|
||||
processRadioMenuItem(menu, parentMenu, label, menuItemID, disabled, checked, "", hascallback);
|
||||
}
|
||||
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
free(modifiers);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void processMenuData(Menu *menu, JsonNode *menuData) {
|
||||
// Iterate items
|
||||
JsonNode *item;
|
||||
json_foreach(item, menuData) {
|
||||
// Process each menu item
|
||||
processMenuItem(menu, menu->menu, item);
|
||||
}
|
||||
}
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
|
||||
|
||||
int groupLength;
|
||||
getJSONInt(radioGroup, "Length", &groupLength);
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
id memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
id ProcessMenu(Menu *menu, JsonNode *menuData, JsonNode *radioGroups) {
|
||||
|
||||
// exit if we have no meny
|
||||
if( menuData == NULL ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
menu->menu = createMenu(str(""));
|
||||
|
||||
// Process the menu data
|
||||
processMenuData(menu, menuData);
|
||||
|
||||
if( radioGroups != NULL ) {
|
||||
// Iterate radio groups
|
||||
JsonNode *radioGroup;
|
||||
json_foreach(radioGroup, radioGroups) {
|
||||
// Get item label
|
||||
processRadioGroupJSON(menu, radioGroup);
|
||||
}
|
||||
}
|
||||
|
||||
return menu->menu;
|
||||
}
|
||||
|
||||
97
v2/internal/ffenestri/menu_darwin_old.h
Normal file
97
v2/internal/ffenestri/menu_darwin_old.h
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
//
|
||||
|
||||
#ifndef MENU_DARWIN_H
|
||||
#define MENU_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
|
||||
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
|
||||
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
|
||||
static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
|
||||
struct TrayMenuStore;
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char *title;
|
||||
|
||||
/*** Internal ***/
|
||||
|
||||
struct hashmap_s menuItemMap;
|
||||
struct hashmap_s radioGroupMap;
|
||||
|
||||
// Vector to keep track of callback data memory
|
||||
vec_void_t callbackDataCache;
|
||||
|
||||
// The NSMenu for this menu
|
||||
id menu;
|
||||
|
||||
// A reference to the Menu store
|
||||
struct TrayMenuStore *store;
|
||||
|
||||
// The commands for the menu callbacks
|
||||
const char *callbackCommand;
|
||||
|
||||
// This indicates if we are an Application Menu, tray menu or context menu
|
||||
enum MenuType menuType;
|
||||
|
||||
|
||||
} Menu;
|
||||
|
||||
typedef struct {
|
||||
struct TrayMenuStore *store;
|
||||
Menu *menu;
|
||||
const char *menuItemID;
|
||||
enum MenuItemType menuItemType;
|
||||
} MenuItemCallbackData;
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData, JsonNode *radioGroups, struct TrayMenuStore *store);
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON, struct TrayMenuStore *store);
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, const char *menuItemID, enum MenuItemType menuItemType);
|
||||
|
||||
void DeleteMenu(Menu *menu);
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data);
|
||||
|
||||
// Callback for text menu items
|
||||
void menuItemCallback(id self, SEL cmd, id sender);
|
||||
id processAcceleratorKey(const char *key);
|
||||
|
||||
|
||||
void addSeparator(id menu);
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key);
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
|
||||
|
||||
id createMenu(id title);
|
||||
void createDefaultAppMenu(id parentMenu);
|
||||
void createDefaultEditMenu(id parentMenu);
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item);
|
||||
// This converts a string array of modifiers into the
|
||||
// equivalent MacOS Modifier Flags
|
||||
unsigned long parseModifiers(const char **modifiers);
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey, bool hasCallback);
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key, bool hasCallback);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, bool hasCallback);
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
id ProcessMenu(Menu *menu, JsonNode *menuData, JsonNode *radioGroup);
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
101
v2/internal/ffenestri/menu_item.c
Normal file
101
v2/internal/ffenestri/menu_item.c
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
MenuItem* NewMenuItem(enum MenuItemType type, const char* ID, const char* label, const char* alternateLabel, bool disabled, bool hidden, bool checked, const char* colour, const char* font, int fontsize, const char* image, const char* acceleratorKey, const char** modifiers, bool hasCallback, Menu* submenu) {
|
||||
|
||||
MenuItem *result = NEW(MenuItem);
|
||||
|
||||
// Setup
|
||||
result->ID = STRCOPY(ID);
|
||||
result->label = STRCOPY(label);
|
||||
result->alternateLabel = STRCOPY(alternateLabel);
|
||||
result->disabled = disabled;
|
||||
result->hidden = hidden;
|
||||
result->colour = STRCOPY(colour);
|
||||
result->font = STRCOPY(font);
|
||||
result->fontSize = fontsize;
|
||||
result->image = STRCOPY(image);
|
||||
result->acceleratorKey = STRCOPY(acceleratorKey);
|
||||
result->modifiers = modifiers;
|
||||
result->hasCallback = hasCallback;
|
||||
result->type = type;
|
||||
result->checked = checked;
|
||||
result->submenu = submenu;
|
||||
|
||||
result->callbackData = NULL;
|
||||
|
||||
vec_init(&result->radioGroup);
|
||||
|
||||
SetupMenuItemPlatformData(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteMenuItem(MenuItem* menuItem) {
|
||||
|
||||
MEMFREE(menuItem->ID);
|
||||
MEMFREE(menuItem->label);
|
||||
MEMFREE(menuItem->alternateLabel);
|
||||
MEMFREE(menuItem->colour);
|
||||
MEMFREE(menuItem->font);
|
||||
MEMFREE(menuItem->image);
|
||||
MEMFREE(menuItem->acceleratorKey);
|
||||
|
||||
// Iterate the modifiers and free elements
|
||||
if( menuItem->modifiers != NULL ) {
|
||||
int i = 0;
|
||||
const char *nextItem = menuItem->modifiers[0];
|
||||
while (nextItem != NULL) {
|
||||
MEMFREE(nextItem);
|
||||
i++;
|
||||
nextItem = menuItem->modifiers[i];
|
||||
}
|
||||
MEMFREE(menuItem->modifiers);
|
||||
}
|
||||
|
||||
DeleteMenuItemCallbackData(menuItem->callbackData);
|
||||
|
||||
DeleteMenuItemPlatformData(menuItem);
|
||||
|
||||
MEMFREE(menuItem);
|
||||
}
|
||||
|
||||
JsonNode* MenuItemAsJSONObject(MenuItem* menuItem) {
|
||||
|
||||
JsonNode* result = json_mkobject();
|
||||
JSON_ADD_STRING(result, "ID", menuItem->ID);
|
||||
JSON_ADD_STRING(result, "label", menuItem->label);
|
||||
JSON_ADD_STRING(result, "alternateLabel", menuItem->alternateLabel);
|
||||
JSON_ADD_BOOL(result, "disabled", menuItem->disabled);
|
||||
JSON_ADD_BOOL(result, "hidden", menuItem->hidden);
|
||||
JSON_ADD_STRING(result, "colour", menuItem->colour);
|
||||
JSON_ADD_STRING(result, "font", menuItem->font);
|
||||
JSON_ADD_NUMBER(result, "fontsize", menuItem->fontSize);
|
||||
JSON_ADD_STRING(result, "image", menuItem->image);
|
||||
JSON_ADD_STRING(result, "acceleratorKey", menuItem->acceleratorKey);
|
||||
JSON_ADD_BOOL(result, "hasCallback", menuItem->hasCallback);
|
||||
JSON_ADD_STRING(result, "type", MenuItemTypeAsString[menuItem->type]);
|
||||
JSON_ADD_BOOL(result, "checked", menuItem->checked);
|
||||
return result;
|
||||
}
|
||||
|
||||
MenuItemCallbackData* NewMenuItemCallbackData(MenuManager *manager, const char* menuItemID, enum MenuItemType menuItemType) {
|
||||
MenuItemCallbackData* result = NEW(MenuItemCallbackData);
|
||||
|
||||
result->manager = manager;
|
||||
result->menuItemID = STRCOPY(menuItemID);
|
||||
result->menuItemType = menuItemType;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteMenuItemCallbackData(MenuItemCallbackData* callbackData) {
|
||||
|
||||
if( callbackData == NULL ) return;
|
||||
|
||||
MEMFREE(callbackData->menuItemID);
|
||||
MEMFREE(callbackData);
|
||||
}
|
||||
81
v2/internal/ffenestri/menu_manager.c
Normal file
81
v2/internal/ffenestri/menu_manager.c
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
MenuManager* NewMenuManager() {
|
||||
|
||||
MenuManager* result = malloc(sizeof(MenuManager));
|
||||
|
||||
// Allocate Hashmaps
|
||||
HASHMAP_INIT(result->menuItems, 32, "menuItems");
|
||||
HASHMAP_INIT(result->contextMenus, 4, "contextMenus");
|
||||
HASHMAP_INIT(result->trayMenus, 4, "trayMenus");
|
||||
|
||||
// Initialise other data
|
||||
result->applicationMenu = NULL;
|
||||
result->contextMenuData = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int deleteTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteTrayMenu(e->data);
|
||||
return -1; // Remove from hashmap
|
||||
}
|
||||
|
||||
int deleteMenuItem(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteMenuItem(e->data);
|
||||
return -1; // Remove from hashmap
|
||||
}
|
||||
|
||||
void DeleteMenuManager(MenuManager* manager) {
|
||||
|
||||
// Iterate hashmaps and delete items
|
||||
HASHMAP_ITERATE(manager->trayMenus, deleteTrayMenu, NULL);
|
||||
HASHMAP_ITERATE(manager->menuItems, deleteMenuItem, NULL);
|
||||
|
||||
// Delete applicationMenu
|
||||
DeleteMenu(manager->applicationMenu);
|
||||
|
||||
// Delete Hashmaps
|
||||
HASHMAP_DESTROY(manager->trayMenus);
|
||||
HASHMAP_DESTROY(manager->contextMenus);
|
||||
HASHMAP_DESTROY(manager->menuItems);
|
||||
|
||||
// Delete context menu data
|
||||
MEMFREE(manager->contextMenuData);
|
||||
}
|
||||
|
||||
TrayMenu* AddTrayMenuToManager(MenuManager* manager, const char* trayMenuJSON) {
|
||||
|
||||
// Parse JSON
|
||||
struct JsonNode* parsedJSON = mustParseJSON(trayMenuJSON);
|
||||
|
||||
// Get the ID
|
||||
const char *ID = mustJSONString(parsedJSON, "I");
|
||||
|
||||
// Check if there is already an entry for this menu
|
||||
TrayMenu* existingTrayMenu = HASHMAP_GET(manager->trayMenus, ID);
|
||||
if ( existingTrayMenu != NULL ) {
|
||||
json_delete(parsedJSON);
|
||||
return existingTrayMenu;
|
||||
}
|
||||
|
||||
// Create new menu
|
||||
TrayMenu* newMenu = NewTrayMenu(parsedJSON, manager);
|
||||
HASHMAP_PUT(manager->trayMenus, newMenu->ID, newMenu);
|
||||
|
||||
return newMenu;
|
||||
}
|
||||
|
||||
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
ShowTrayMenu(e->data);
|
||||
// 0 to retain element, -1 to delete.
|
||||
return 0;
|
||||
}
|
||||
void ShowTrayMenus(MenuManager* manager) {
|
||||
HASHMAP_ITERATE(manager->trayMenus, showTrayMenu, NULL);
|
||||
}
|
||||
|
||||
69
v2/internal/ffenestri/menu_tray.c
Normal file
69
v2/internal/ffenestri/menu_tray.c
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
TrayMenu* NewTrayMenu(JsonNode* parsedJSON, MenuManager *manager) {
|
||||
|
||||
// NULL GUARD
|
||||
if(parsedJSON == NULL) ABORT("[NewTrayMenu] parsedJSON == NULL");
|
||||
|
||||
// Create new tray menu
|
||||
TrayMenu* result = NEW(TrayMenu);
|
||||
|
||||
// Initialise other data
|
||||
result->ID = STRCOPY(mustJSONString(parsedJSON, "I"));
|
||||
result->Label = STRCOPY(getJSONString(parsedJSON, "l"));
|
||||
result->Icon = STRCOPY(getJSONString(parsedJSON, "i"));
|
||||
|
||||
// Process menu
|
||||
struct JsonNode* menuJSON = getJSONObject(parsedJSON, "m");
|
||||
struct JsonNode* radioJSON = getJSONObject(parsedJSON, "r");
|
||||
result->Menu = NewMenu(menuJSON, radioJSON, manager);
|
||||
|
||||
// Setup platform data
|
||||
SetupTrayMenuPlatformData(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteTrayMenu(TrayMenu *trayMenu) {
|
||||
|
||||
// NULL guard
|
||||
if( trayMenu == NULL ) return;
|
||||
|
||||
// Free the strings
|
||||
MEMFREE(trayMenu->ID);
|
||||
MEMFREE(trayMenu->Label);
|
||||
MEMFREE(trayMenu->Icon);
|
||||
|
||||
// Delete the menu
|
||||
DeleteMenu(trayMenu->Menu);
|
||||
|
||||
// Delete the platform data
|
||||
DeleteTrayMenuPlatformData(trayMenu);
|
||||
|
||||
// Free tray menu
|
||||
MEMFREE(trayMenu);
|
||||
}
|
||||
|
||||
const char* TrayMenuAsJSON(TrayMenu* menu) {
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
JSON_ADD_STRING(jsonObject, "ID", menu->ID);
|
||||
JSON_ADD_STRING(jsonObject, "Label", menu->Label);
|
||||
JSON_ADD_STRING(jsonObject, "Icon", menu->Icon);
|
||||
JSON_ADD_OBJECT(jsonObject, "Menu", MenuAsJSONObject(menu->Menu));
|
||||
return json_encode(jsonObject);
|
||||
}
|
||||
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->Icon == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformUpdateTrayIcon(trayMenu);
|
||||
}
|
||||
25
v2/internal/ffenestri/test_app/CMakeLists.txt
Normal file
25
v2/internal/ffenestri/test_app/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
project(test_app)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -g")
|
||||
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -g")
|
||||
|
||||
add_executable(test_app test.c)
|
||||
add_library(menus STATIC ../menu_manager.c ../menu.c ../menu_tray.c ../menu_item.c )
|
||||
add_library(common STATIC ../common.c ../utf8.h)
|
||||
add_library(json STATIC ../json.c)
|
||||
add_library(vec STATIC ../vec.c)
|
||||
|
||||
if( CMAKE_HOST_APPLE )
|
||||
find_library(WEBKIT WebKit)
|
||||
add_library(runtime STATIC ../runtime_darwin.c)
|
||||
add_library(ffenestri STATIC ../ffenestri_darwin.c)
|
||||
add_library(defaulticons STATIC ../defaultdialogicons_darwin.c)
|
||||
add_library(platform STATIC ../menu_darwin.c)
|
||||
target_link_libraries(test_app objc ${WEBKIT} ffenestri platform runtime)
|
||||
endif()
|
||||
|
||||
target_link_libraries(test_app vec json common menus)
|
||||
include_directories(..)
|
||||
include_directories(.)
|
||||
9
v2/internal/ffenestri/test_app/assets.h
Normal file
9
v2/internal/ffenestri/test_app/assets.h
Normal file
File diff suppressed because one or more lines are too long
81
v2/internal/ffenestri/test_app/test.c
Normal file
81
v2/internal/ffenestri/test_app/test.c
Normal file
File diff suppressed because one or more lines are too long
21
v2/internal/ffenestri/test_menumanager/CMakeLists.txt
Normal file
21
v2/internal/ffenestri/test_menumanager/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
project(test_menumanager)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -g")
|
||||
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -g")
|
||||
|
||||
add_executable(test_menumanager test.c minunit.h)
|
||||
add_library(menus STATIC ../menu_manager.c ../menu.c ../menu_tray.c ../menu_item.c)
|
||||
add_library(common STATIC ../common.c ../utf8.h)
|
||||
add_library(json STATIC ../json.c)
|
||||
add_library(vec STATIC ../vec.c)
|
||||
|
||||
if( CMAKE_HOST_APPLE )
|
||||
add_library(platform STATIC ../menu_darwin.c)
|
||||
target_link_libraries(test_menumanager objc)
|
||||
endif()
|
||||
|
||||
target_link_libraries(test_menumanager vec json common menus platform)
|
||||
include_directories(..)
|
||||
include_directories(.)
|
||||
20
v2/internal/ffenestri/test_menumanager/minunit.LICENSE.txt
Normal file
20
v2/internal/ffenestri/test_menumanager/minunit.LICENSE.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
391
v2/internal/ffenestri/test_menumanager/minunit.h
Normal file
391
v2/internal/ffenestri/test_menumanager/minunit.h
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef MINUNIT_MINUNIT_H
|
||||
#define MINUNIT_MINUNIT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
||||
|
||||
/* Change POSIX C SOURCE version for pure c99 compilers */
|
||||
#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#endif
|
||||
|
||||
#include <unistd.h> /* POSIX flags */
|
||||
#include <time.h> /* clock_gettime(), time() */
|
||||
#include <sys/time.h> /* gethrtime(), gettimeofday() */
|
||||
#include <sys/resource.h>
|
||||
#include <sys/times.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__MACH__) && defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
|
||||
#define __func__ __extension__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "Unable to define timers for an unknown OS."
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Maximum length of last message */
|
||||
#define MINUNIT_MESSAGE_LEN 1024
|
||||
/* Accuracy with which floats are compared */
|
||||
#define MINUNIT_EPSILON 1E-12
|
||||
|
||||
/* Misc. counters */
|
||||
static int minunit_run = 0;
|
||||
static int minunit_assert = 0;
|
||||
static int minunit_fail = 0;
|
||||
static int minunit_status = 0;
|
||||
|
||||
/* Timers */
|
||||
static double minunit_real_timer = 0;
|
||||
static double minunit_proc_timer = 0;
|
||||
|
||||
/* Last message */
|
||||
static char minunit_last_message[MINUNIT_MESSAGE_LEN];
|
||||
|
||||
/* Test setup and teardown function pointers */
|
||||
static void (*minunit_setup)(void) = NULL;
|
||||
static void (*minunit_teardown)(void) = NULL;
|
||||
|
||||
/* Definitions */
|
||||
#define MU_TEST(method_name) static void method_name(void)
|
||||
#define MU_TEST_SUITE(suite_name) static void suite_name(void)
|
||||
|
||||
#define MU__SAFE_BLOCK(block) do {\
|
||||
block\
|
||||
} while(0)
|
||||
|
||||
/* Run test suite and unset setup and teardown functions */
|
||||
#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
|
||||
suite_name();\
|
||||
minunit_setup = NULL;\
|
||||
minunit_teardown = NULL;\
|
||||
)
|
||||
|
||||
/* Configure setup and teardown functions */
|
||||
#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
|
||||
minunit_setup = setup_fun;\
|
||||
minunit_teardown = teardown_fun;\
|
||||
)
|
||||
|
||||
/* Test runner */
|
||||
#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
|
||||
if (minunit_real_timer==0 && minunit_proc_timer==0) {\
|
||||
minunit_real_timer = mu_timer_real();\
|
||||
minunit_proc_timer = mu_timer_cpu();\
|
||||
}\
|
||||
if (minunit_setup) (*minunit_setup)();\
|
||||
minunit_status = 0;\
|
||||
test();\
|
||||
minunit_run++;\
|
||||
if (minunit_status) {\
|
||||
minunit_fail++;\
|
||||
printf("F");\
|
||||
printf("\n%s\n", minunit_last_message);\
|
||||
}\
|
||||
fflush(stdout);\
|
||||
if (minunit_teardown) (*minunit_teardown)();\
|
||||
)
|
||||
|
||||
/* Report */
|
||||
#define MU_REPORT() MU__SAFE_BLOCK(\
|
||||
double minunit_end_real_timer;\
|
||||
double minunit_end_proc_timer;\
|
||||
printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
|
||||
minunit_end_real_timer = mu_timer_real();\
|
||||
minunit_end_proc_timer = mu_timer_cpu();\
|
||||
printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
|
||||
minunit_end_real_timer - minunit_real_timer,\
|
||||
minunit_end_proc_timer - minunit_proc_timer);\
|
||||
)
|
||||
#define MU_EXIT_CODE minunit_fail
|
||||
|
||||
/* Assertions */
|
||||
#define mu_check(test) MU__SAFE_BLOCK(\
|
||||
minunit_assert++;\
|
||||
if (!(test)) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_fail(message) MU__SAFE_BLOCK(\
|
||||
minunit_assert++;\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
)
|
||||
|
||||
#define mu_assert(test, message) MU__SAFE_BLOCK(\
|
||||
minunit_assert++;\
|
||||
if (!(test)) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
|
||||
int minunit_tmp_e;\
|
||||
int minunit_tmp_r;\
|
||||
minunit_assert++;\
|
||||
minunit_tmp_e = (expected);\
|
||||
minunit_tmp_r = (result);\
|
||||
if (minunit_tmp_e != minunit_tmp_r) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
|
||||
double minunit_tmp_e;\
|
||||
double minunit_tmp_r;\
|
||||
minunit_assert++;\
|
||||
minunit_tmp_e = (expected);\
|
||||
minunit_tmp_r = (result);\
|
||||
if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
|
||||
int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
|
||||
const char* minunit_tmp_e = expected;\
|
||||
const char* minunit_tmp_r = result;\
|
||||
minunit_assert++;\
|
||||
if (!minunit_tmp_e) {\
|
||||
minunit_tmp_e = "<null pointer>";\
|
||||
}\
|
||||
if (!minunit_tmp_r) {\
|
||||
minunit_tmp_r = "<null pointer>";\
|
||||
}\
|
||||
if(strcmp(minunit_tmp_e, minunit_tmp_r) != 0) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
/*
|
||||
* The following two functions were written by David Robert Nadeau
|
||||
* from http://NadeauSoftware.com/ and distributed under the
|
||||
* Creative Commons Attribution 3.0 Unported License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the real time, in seconds, or -1.0 if an error occurred.
|
||||
*
|
||||
* Time is measured since an arbitrary and OS-dependent start time.
|
||||
* The returned real time is only useful for computing an elapsed time
|
||||
* between two calls to this function.
|
||||
*/
|
||||
static double mu_timer_real(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* Windows 2000 and later. ---------------------------------- */
|
||||
LARGE_INTEGER Time;
|
||||
LARGE_INTEGER Frequency;
|
||||
|
||||
QueryPerformanceFrequency(&Frequency);
|
||||
QueryPerformanceCounter(&Time);
|
||||
|
||||
Time.QuadPart *= 1000000;
|
||||
Time.QuadPart /= Frequency.QuadPart;
|
||||
|
||||
return (double)Time.QuadPart / 1000000.0;
|
||||
|
||||
#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
|
||||
/* HP-UX, Solaris. ------------------------------------------ */
|
||||
return (double)gethrtime( ) / 1000000000.0;
|
||||
|
||||
#elif defined(__MACH__) && defined(__APPLE__)
|
||||
/* OSX. ----------------------------------------------------- */
|
||||
static double timeConvert = 0.0;
|
||||
if ( timeConvert == 0.0 )
|
||||
{
|
||||
mach_timebase_info_data_t timeBase;
|
||||
(void)mach_timebase_info( &timeBase );
|
||||
timeConvert = (double)timeBase.numer /
|
||||
(double)timeBase.denom /
|
||||
1000000000.0;
|
||||
}
|
||||
return (double)mach_absolute_time( ) * timeConvert;
|
||||
|
||||
#elif defined(_POSIX_VERSION)
|
||||
/* POSIX. --------------------------------------------------- */
|
||||
struct timeval tm;
|
||||
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
|
||||
{
|
||||
struct timespec ts;
|
||||
#if defined(CLOCK_MONOTONIC_PRECISE)
|
||||
/* BSD. --------------------------------------------- */
|
||||
const clockid_t id = CLOCK_MONOTONIC_PRECISE;
|
||||
#elif defined(CLOCK_MONOTONIC_RAW)
|
||||
/* Linux. ------------------------------------------- */
|
||||
const clockid_t id = CLOCK_MONOTONIC_RAW;
|
||||
#elif defined(CLOCK_HIGHRES)
|
||||
/* Solaris. ----------------------------------------- */
|
||||
const clockid_t id = CLOCK_HIGHRES;
|
||||
#elif defined(CLOCK_MONOTONIC)
|
||||
/* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
|
||||
const clockid_t id = CLOCK_MONOTONIC;
|
||||
#elif defined(CLOCK_REALTIME)
|
||||
/* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
|
||||
const clockid_t id = CLOCK_REALTIME;
|
||||
#else
|
||||
const clockid_t id = (clockid_t)-1; /* Unknown. */
|
||||
#endif /* CLOCK_* */
|
||||
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
|
||||
return (double)ts.tv_sec +
|
||||
(double)ts.tv_nsec / 1000000000.0;
|
||||
/* Fall thru. */
|
||||
}
|
||||
#endif /* _POSIX_TIMERS */
|
||||
|
||||
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
|
||||
gettimeofday( &tm, NULL );
|
||||
return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
|
||||
#else
|
||||
return -1.0; /* Failed. */
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of CPU time used by the current process,
|
||||
* in seconds, or -1.0 if an error occurred.
|
||||
*/
|
||||
static double mu_timer_cpu(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* Windows -------------------------------------------------- */
|
||||
FILETIME createTime;
|
||||
FILETIME exitTime;
|
||||
FILETIME kernelTime;
|
||||
FILETIME userTime;
|
||||
|
||||
/* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
|
||||
if ( GetProcessTimes( GetCurrentProcess( ),
|
||||
&createTime, &exitTime, &kernelTime, &userTime ) != 0 )
|
||||
{
|
||||
ULARGE_INTEGER userSystemTime;
|
||||
memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
|
||||
return (double)userSystemTime.QuadPart / 10000000.0;
|
||||
}
|
||||
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
||||
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
|
||||
|
||||
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
|
||||
/* Prefer high-res POSIX timers, when available. */
|
||||
{
|
||||
clockid_t id;
|
||||
struct timespec ts;
|
||||
#if _POSIX_CPUTIME > 0
|
||||
/* Clock ids vary by OS. Query the id, if possible. */
|
||||
if ( clock_getcpuclockid( 0, &id ) == -1 )
|
||||
#endif
|
||||
#if defined(CLOCK_PROCESS_CPUTIME_ID)
|
||||
/* Use known clock id for AIX, Linux, or Solaris. */
|
||||
id = CLOCK_PROCESS_CPUTIME_ID;
|
||||
#elif defined(CLOCK_VIRTUAL)
|
||||
/* Use known clock id for BSD or HP-UX. */
|
||||
id = CLOCK_VIRTUAL;
|
||||
#else
|
||||
id = (clockid_t)-1;
|
||||
#endif
|
||||
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
|
||||
return (double)ts.tv_sec +
|
||||
(double)ts.tv_nsec / 1000000000.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(RUSAGE_SELF)
|
||||
{
|
||||
struct rusage rusage;
|
||||
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
|
||||
return (double)rusage.ru_utime.tv_sec +
|
||||
(double)rusage.ru_utime.tv_usec / 1000000.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_SC_CLK_TCK)
|
||||
{
|
||||
const double ticks = (double)sysconf( _SC_CLK_TCK );
|
||||
struct tms tms;
|
||||
if ( times( &tms ) != (clock_t)-1 )
|
||||
return (double)tms.tms_utime / ticks;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CLOCKS_PER_SEC)
|
||||
{
|
||||
clock_t cl = clock( );
|
||||
if ( cl != (clock_t)-1 )
|
||||
return (double)cl / (double)CLOCKS_PER_SEC;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
return -1; /* Failed. */
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MINUNIT_MINUNIT_H */
|
||||
71
v2/internal/ffenestri/test_menumanager/test.c
Normal file
71
v2/internal/ffenestri/test_menumanager/test.c
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by Lea Anthony on 12/1/21.
|
||||
//
|
||||
|
||||
#include "minunit.h"
|
||||
#include "menu.h"
|
||||
|
||||
#define empty "{\"I\":\"T1\"}"
|
||||
#define emptyExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
|
||||
#define labelOnly "{\"I\":\"T1\",\"l\":\"test\"}"
|
||||
#define labelOnlyExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":null,\"Menu\":null}"
|
||||
#define iconOnly "{\"I\":\"T1\",\"i\":\"svelte\"}"
|
||||
#define iconOnlyExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":\"svelte\",\"Menu\":null}"
|
||||
#define iconLabel "{\"I\":\"T1\",\"l\":\"test\",\"i\":\"svelte\"}"
|
||||
#define iconLabelExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":\"svelte\",\"Menu\":null}"
|
||||
#define blankLabel "{\"I\":\"T1\"}"
|
||||
#define blankLabelExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
|
||||
#define blankMenu "{\"I\":\"T1\"}"
|
||||
#define blankMenuExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
|
||||
#define menuTextItem "{\"I\":\"T1\",\"l\":\"test\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}"
|
||||
#define menuTextItemExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":null,\"Menu\":{\"Label\":\"\",\"Items\":[{\"ID\":\"1\",\"label\":\"test\",\"alternateLabel\":null,\"disabled\":false,\"hidden\":false,\"colour\":null,\"font\":null,\"fontsize\":0,\"image\":null,\"acceleratorKey\":null,\"hasCallback\":false,\"type\":\"Text\",\"checked\":false}]}}"
|
||||
#define checkboxItem "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"c\",\"c\":true}]}"
|
||||
#define checkboxItemExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":{\"Label\":\"\",\"Items\":[{\"ID\":\"1\",\"label\":\"test\",\"alternateLabel\":null,\"disabled\":false,\"hidden\":false,\"colour\":null,\"font\":null,\"fontsize\":0,\"image\":null,\"acceleratorKey\":null,\"hasCallback\":false,\"type\":\"Checkbox\",\"checked\":true}]}}"
|
||||
#define radioGroupItems "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"option 1\",\"t\":\"r\",\"c\":true},{\"I\":\"2\",\"l\":\"option 2\",\"t\":\"r\"},{\"I\":\"3\",\"l\":\"option 3\",\"t\":\"r\"}],\"r\":[{\"Members\":[\"1\",\"2\",\"3\"],\"Length\":3}]}"
|
||||
#define radioGroupItemsExpected ""
|
||||
#define callbackItem "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"Preferences\",\"t\":\"t\",\"C\":true}]}"
|
||||
#define callbackItemExpected ""
|
||||
|
||||
const char* tests[] = {
|
||||
empty, emptyExpected,
|
||||
labelOnly, labelOnlyExpected,
|
||||
iconOnly, iconOnlyExpected,
|
||||
iconLabel, iconLabelExpected,
|
||||
blankLabel, blankLabelExpected,
|
||||
blankMenu, blankMenuExpected,
|
||||
menuTextItem, menuTextItemExpected,
|
||||
checkboxItem, checkboxItemExpected,
|
||||
radioGroupItems, radioGroupItemsExpected,
|
||||
callbackItem, callbackItemExpected,
|
||||
};
|
||||
|
||||
MU_TEST(manager_creation) {
|
||||
MenuManager* manager = NewMenuManager();
|
||||
mu_assert(manager->applicationMenu == NULL, "app menu");
|
||||
mu_assert(manager->contextMenuData == NULL, "context menu data");
|
||||
mu_assert_int_eq(hashmap_num_entries(&manager->contextMenus), 0);
|
||||
mu_assert_int_eq(hashmap_num_entries(&manager->trayMenus), 0);
|
||||
mu_assert_int_eq(hashmap_num_entries(&manager->menuItems), 0);
|
||||
DeleteMenuManager(manager);
|
||||
}
|
||||
|
||||
MU_TEST(add_tray) {
|
||||
for( int count = 0; count < sizeof(tests) / sizeof(tests[0]); count += 2 ) {
|
||||
MenuManager* manager = NewMenuManager();
|
||||
TrayMenu* tray = AddTrayMenu(manager, tests[count]);
|
||||
const char* trayJSON = TrayMenuAsJSON(tray);
|
||||
mu_assert_string_eq(tests[count+1], trayJSON);
|
||||
DeleteMenuManager(manager);
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_suite) {
|
||||
MU_RUN_TEST(manager_creation);
|
||||
MU_RUN_TEST(add_tray);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
MU_RUN_SUITE(test_suite);
|
||||
MU_REPORT();
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
TrayMenu* NewTrayMenu(const char* menuJSON, struct TrayMenuStore* store) {
|
||||
TrayMenu* result = malloc(sizeof(TrayMenu));
|
||||
|
||||
/*
|
||||
@@ -21,26 +21,29 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
|
||||
}
|
||||
|
||||
// Save reference to this json
|
||||
result->processedJSON = processedJSON;
|
||||
|
||||
// TODO: Make this configurable
|
||||
result->trayIconPosition = NSImageLeft;
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
result->label = mustJSONString(processedJSON, "Label");
|
||||
result->icon = mustJSONString(processedJSON, "Icon");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->ID = STRCOPY(mustJSONString(processedJSON, "I"));
|
||||
result->label = STRCOPY(getJSONString(processedJSON, "l"));
|
||||
result->icon = STRCOPY(getJSONString(processedJSON, "i"));
|
||||
result->menu = NULL;
|
||||
|
||||
// Create the menu
|
||||
result->menu = NewMenu(processedMenu);
|
||||
JsonNode* menu = getJSONObject(processedJSON, "m");
|
||||
if( menu != NULL ) {
|
||||
|
||||
JsonNode* radioGroups = getJSONObject(processedJSON, "r");
|
||||
|
||||
// Create the menu
|
||||
result->menu = NewMenu(menu, radioGroups, store);
|
||||
result->menu->menuType = TrayMenuType;
|
||||
}
|
||||
|
||||
// Init tray status bar item
|
||||
result->statusbaritem = NULL;
|
||||
|
||||
// Set the menu type and store the tray ID in the parent data
|
||||
result->menu->menuType = TrayMenuType;
|
||||
result->menu->parentData = (void*) result->ID;
|
||||
// Free JSON
|
||||
json_delete(processedJSON);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -49,42 +52,8 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem );
|
||||
}
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
if( trayMenu->statusbaritem == NULL ) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(trayMenu->statusbaritem, s("retain"));
|
||||
|
||||
}
|
||||
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
UpdateTrayMenuIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayMenuLabel(trayMenu);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
}
|
||||
|
||||
void UpdateTrayMenuLabel(TrayMenu *trayMenu) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->label == NULL ) {
|
||||
return;
|
||||
}
|
||||
// We don't check for a
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(trayMenu->label));
|
||||
}
|
||||
|
||||
void UpdateTrayMenuIcon(TrayMenu *trayMenu) {
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->icon == NULL ) {
|
||||
@@ -105,6 +74,33 @@ void UpdateTrayMenuIcon(TrayMenu *trayMenu) {
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
if( trayMenu->statusbaritem == NULL ) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(trayMenu->statusbaritem, s("retain"));
|
||||
|
||||
}
|
||||
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
UpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
||||
|
||||
// Update the menu
|
||||
if (trayMenu->menu != NULL ) {
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), trayMenu->menu->menu);
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
||||
// updated with the data from the new menu.
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
||||
@@ -115,17 +111,11 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
||||
// Set the new one
|
||||
currentMenu->menu = newMenu->menu;
|
||||
|
||||
// Delete the old JSON
|
||||
json_delete(currentMenu->processedJSON);
|
||||
|
||||
// Set the new JSON
|
||||
currentMenu->processedJSON = newMenu->processedJSON;
|
||||
|
||||
// Copy the other data
|
||||
currentMenu->ID = newMenu->ID;
|
||||
currentMenu->label = newMenu->label;
|
||||
currentMenu->ID = STRCOPY(newMenu->ID);
|
||||
currentMenu->label = STRCOPY(newMenu->label);
|
||||
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
||||
currentMenu->icon = newMenu->icon;
|
||||
currentMenu->icon = STRCOPY(newMenu->icon);
|
||||
|
||||
}
|
||||
|
||||
@@ -137,10 +127,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
||||
// Delete the menu
|
||||
DeleteMenu(trayMenu->menu);
|
||||
|
||||
// Free JSON
|
||||
if (trayMenu->processedJSON != NULL ) {
|
||||
json_delete(trayMenu->processedJSON);
|
||||
}
|
||||
// Free strings
|
||||
MEMFREE(trayMenu->label);
|
||||
MEMFREE(trayMenu->icon);
|
||||
MEMFREE(trayMenu->ID);
|
||||
|
||||
// Free the status item
|
||||
if ( trayMenu->statusbaritem != NULL ) {
|
||||
@@ -186,14 +176,3 @@ void LoadTrayIcons() {
|
||||
}
|
||||
}
|
||||
|
||||
void UnloadTrayIcons() {
|
||||
// Release the tray cache images
|
||||
if( hashmap_num_entries(&trayIconCache) > 0 ) {
|
||||
if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) {
|
||||
ABORT("failed to release hashmap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
//Free radio groups hashmap
|
||||
hashmap_destroy(&trayIconCache);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
#define TRAYMENU_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "menu_darwin_old.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -18,17 +18,14 @@ typedef struct {
|
||||
|
||||
id statusbaritem;
|
||||
int trayIconPosition;
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON, struct TrayMenuStore* store);
|
||||
void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
void UpdateTrayMenuIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayMenuLabel(TrayMenu *trayMenu);
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
|
||||
|
||||
void LoadTrayIcons();
|
||||
void UnloadTrayIcons();
|
||||
|
||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
||||
}
|
||||
|
||||
// Allocate menu item store
|
||||
if( 0 != hashmap_create((const unsigned)8, &result->menuItemMap)) {
|
||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate menuItemMap!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -29,7 +34,7 @@ void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||
}
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
|
||||
|
||||
//TODO: check if there is already an entry for this menu
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
@@ -65,6 +70,9 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
||||
|
||||
// Destroy tray menu map
|
||||
hashmap_destroy(&store->trayMenuMap);
|
||||
|
||||
// Destroy menu item map
|
||||
hashmap_destroy(&store->menuItemMap);
|
||||
}
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
@@ -72,13 +80,50 @@ TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
}
|
||||
|
||||
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
if (result == NULL ) {
|
||||
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
id GetMenuItemFromStore(TrayMenuStore* store, const char* menuItemID) {
|
||||
return hashmap_get(&store->menuItemMap, menuItemID, strlen(menuItemID));
|
||||
}
|
||||
|
||||
void SaveMenuItemInStore(TrayMenuStore* store, const char* menuItemID, id nsmenuitem) {
|
||||
hashmap_put(&store->menuItemMap, menuItemID, strlen(menuItemID), nsmenuitem);
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
// Parse the JSON
|
||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||
|
||||
// Get the data out
|
||||
const char* ID = mustJSONString(parsedUpdate, "ID");
|
||||
const char* Label = mustJSONString(parsedUpdate, "Label");
|
||||
|
||||
// Check we have this menu
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
UpdateTrayLabel(menu, Label);
|
||||
|
||||
}
|
||||
|
||||
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
|
||||
|
||||
// Get the current menu
|
||||
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
|
||||
|
||||
// If we don't have a menu, we create one
|
||||
if ( currentMenu == NULL ) {
|
||||
ABORT("Attempted to update unknown tray menu with ID '%s'.", newMenu->ID);
|
||||
// Store the new menu
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
|
||||
return newMenu;
|
||||
}
|
||||
|
||||
// Save the status bar reference
|
||||
@@ -90,18 +135,16 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
DeleteMenu(currentMenu->menu);
|
||||
currentMenu->menu = NULL;
|
||||
|
||||
// Free JSON
|
||||
if (currentMenu->processedJSON != NULL ) {
|
||||
json_delete(currentMenu->processedJSON);
|
||||
currentMenu->processedJSON = NULL;
|
||||
}
|
||||
|
||||
// Free the tray menu memory
|
||||
MEMFREE(currentMenu);
|
||||
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
|
||||
// Show the updated menu
|
||||
ShowTrayMenu(newMenu);
|
||||
return newMenu;
|
||||
|
||||
}
|
||||
|
||||
const char* GetContextMenuDataFromStore(TrayMenuStore *store) {
|
||||
return store->contextMenuData;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||
#define TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
#include "traymenu_darwin.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
@@ -13,13 +15,26 @@ typedef struct {
|
||||
// It maps tray IDs to TrayMenu*
|
||||
struct hashmap_s trayMenuMap;
|
||||
|
||||
// This is our menu item map
|
||||
// It maps menu Item IDs to NSMenuItems
|
||||
struct hashmap_s menuItemMap;
|
||||
|
||||
const char* contextMenuData;
|
||||
|
||||
} TrayMenuStore;
|
||||
|
||||
TrayMenuStore* NewTrayMenuStore();
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||
|
||||
void SaveMenuItemInStore(TrayMenuStore* store, const char* menuItemID, id nsmenuitem);
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID);
|
||||
id GetMenuItemFromStore(TrayMenuStore* store, const char* menuItemID);
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||
const char* GetContextMenuDataFromStore(TrayMenuStore *store);
|
||||
|
||||
#endif //TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
1292
v2/internal/ffenestri/utf8.h
Normal file
1292
v2/internal/ffenestri/utf8.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,8 @@
|
||||
( vec_splice_(vec_unpack_(v), start, count),\
|
||||
(v)->length -= (count) )
|
||||
|
||||
#define vec_size(v) \
|
||||
(v)->length
|
||||
|
||||
#define vec_swapsplice(v, start, count)\
|
||||
( vec_swapsplice_(vec_unpack_(v), start, count),\
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package menumanager
|
||||
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
func (m *Manager) SetApplicationMenu(applicationMenu *menu.Menu) error {
|
||||
|
||||
if applicationMenu == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.applicationMenu = applicationMenu
|
||||
|
||||
// Reset the menu map
|
||||
m.applicationMenuItemMap = NewMenuItemMap()
|
||||
|
||||
// Add the menu to the menu map
|
||||
m.applicationMenuItemMap.AddMenu(applicationMenu)
|
||||
|
||||
return m.processApplicationMenu()
|
||||
}
|
||||
|
||||
func (m *Manager) GetApplicationMenuJSON() string {
|
||||
return m.applicationMenuJSON
|
||||
}
|
||||
|
||||
// UpdateApplicationMenu reprocesses the application menu to pick up structure
|
||||
// changes etc
|
||||
// Returns the JSON representation of the updated menu
|
||||
func (m *Manager) UpdateApplicationMenu() (string, error) {
|
||||
m.applicationMenuItemMap = NewMenuItemMap()
|
||||
m.applicationMenuItemMap.AddMenu(m.applicationMenu)
|
||||
err := m.processApplicationMenu()
|
||||
return m.applicationMenuJSON, err
|
||||
}
|
||||
|
||||
func (m *Manager) processApplicationMenu() error {
|
||||
|
||||
// Process the menu
|
||||
processedApplicationMenu := NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu)
|
||||
applicationMenuJSON, err := processedApplicationMenu.AsJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.applicationMenuJSON = applicationMenuJSON
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +1,65 @@
|
||||
package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
type ContextMenu struct {
|
||||
ID string
|
||||
ProcessedMenu *WailsMenu
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
}
|
||||
|
||||
func (t *ContextMenu) AsJSON() (string, error) {
|
||||
data, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
|
||||
|
||||
result := &ContextMenu{
|
||||
ID: contextMenu.ID,
|
||||
menu: contextMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(contextMenu.Menu)
|
||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
|
||||
return result
|
||||
}
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
//
|
||||
//import (
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
//)
|
||||
//
|
||||
//type ContextMenu struct {
|
||||
// ID string
|
||||
// ProcessedMenu *WailsMenu
|
||||
// menuItemMap *MenuItemMap
|
||||
// menu *menu.Menu
|
||||
//}
|
||||
//
|
||||
//func (t *ContextMenu) AsJSON() (string, error) {
|
||||
// data, err := json.Marshal(t)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// return string(data), nil
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
|
||||
//
|
||||
// result := &ContextMenu{
|
||||
// ID: contextMenu.ID,
|
||||
// menu: contextMenu.Menu,
|
||||
// menuItemMap: NewMenuItemMap(),
|
||||
// }
|
||||
//
|
||||
// result.menuItemMap.AddMenu(contextMenu.Menu)
|
||||
// result.ProcessedMenu = m.NewWailsMenu(result.menuItemMap, result.menu)
|
||||
//
|
||||
// return result
|
||||
//}
|
||||
//
|
||||
func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) {
|
||||
|
||||
newContextMenu := NewContextMenu(contextMenu)
|
||||
|
||||
// Save the references
|
||||
m.contextMenus[contextMenu.ID] = newContextMenu
|
||||
m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||
//
|
||||
// newContextMenu := m.NewContextMenu(contextMenu)
|
||||
//
|
||||
// // Save the references
|
||||
// m.contextMenus[contextMenu.ID] = newContextMenu
|
||||
// m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||
}
|
||||
|
||||
//
|
||||
func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
|
||||
contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||
if !contextMenuKnown {
|
||||
return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||
}
|
||||
|
||||
// Create the updated context menu
|
||||
updatedContextMenu := NewContextMenu(contextMenu)
|
||||
|
||||
// Save the reference
|
||||
m.contextMenus[contextMenuID] = updatedContextMenu
|
||||
|
||||
return updatedContextMenu.AsJSON()
|
||||
// contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||
// if !contextMenuKnown {
|
||||
// return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||
// }
|
||||
//
|
||||
// // Create the updated context menu
|
||||
// updatedContextMenu := m.NewContextMenu(contextMenu)
|
||||
//
|
||||
// // Save the reference
|
||||
// m.contextMenus[contextMenuID] = updatedContextMenu
|
||||
//
|
||||
// return updatedContextMenu.AsJSON()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,72 +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) {
|
||||
for _, item := range menu.Items {
|
||||
m.processMenuItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) Dump() {
|
||||
println("idToMenuItemMap:")
|
||||
for key, value := range m.idToMenuItemMap {
|
||||
fmt.Printf(" %s\t%p\n", key, value)
|
||||
}
|
||||
println("\nmenuItemToIDMap")
|
||||
for key, value := range m.menuItemToIDMap {
|
||||
fmt.Printf(" %p\t%s\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateMenuID returns a unique string ID for a menu item
|
||||
func (m *MenuItemMap) generateMenuID() string {
|
||||
m.menuIDCounterMutex.Lock()
|
||||
result := fmt.Sprintf("%d", m.menuIDCounter)
|
||||
m.menuIDCounter++
|
||||
m.menuIDCounterMutex.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) processMenuItem(item *menu.MenuItem) {
|
||||
|
||||
if item.SubMenu != nil {
|
||||
for _, submenuitem := range item.SubMenu.Items {
|
||||
m.processMenuItem(submenuitem)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a unique ID for this menu item
|
||||
menuID := m.generateMenuID()
|
||||
|
||||
// Store references
|
||||
m.idToMenuItemMap[menuID] = item
|
||||
m.menuItemToIDMap[item] = menuID
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) getMenuItemByID(menuId string) *menu.MenuItem {
|
||||
return m.idToMenuItemMap[menuId]
|
||||
}
|
||||
@@ -2,89 +2,53 @@ package menumanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/internal/counter"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
|
||||
// The application menu.
|
||||
applicationMenu *menu.Menu
|
||||
applicationMenuJSON string
|
||||
// MenuItemMap is a map of all menu items against a generated ID
|
||||
menuItemMap map[string]*menu.MenuItem
|
||||
menuItemIDCounter *counter.Counter
|
||||
processedMenuItems map[*menu.MenuItem]*ProcessedMenuItem
|
||||
|
||||
// Our application menu mappings
|
||||
applicationMenuItemMap *MenuItemMap
|
||||
// Menus
|
||||
menuIDCounter *counter.Counter
|
||||
|
||||
// Context menus
|
||||
contextMenus map[string]*ContextMenu
|
||||
contextMenuPointers map[*menu.ContextMenu]string
|
||||
|
||||
// Tray menu stores
|
||||
trayMenus map[string]*TrayMenu
|
||||
trayMenuPointers map[*menu.TrayMenu]string
|
||||
// Map wails menus to internal menus
|
||||
trayMenuMap map[*menu.TrayMenu]*TrayMenu
|
||||
trayMenuIDCounter *counter.Counter
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
applicationMenuItemMap: NewMenuItemMap(),
|
||||
contextMenus: make(map[string]*ContextMenu),
|
||||
contextMenuPointers: make(map[*menu.ContextMenu]string),
|
||||
trayMenus: make(map[string]*TrayMenu),
|
||||
trayMenuPointers: make(map[*menu.TrayMenu]string),
|
||||
trayMenuMap: make(map[*menu.TrayMenu]*TrayMenu),
|
||||
trayMenuIDCounter: counter.NewCounter(0),
|
||||
menuIDCounter: counter.NewCounter(0),
|
||||
menuItemMap: make(map[string]*menu.MenuItem),
|
||||
menuItemIDCounter: counter.NewCounter(0),
|
||||
processedMenuItems: make(map[*menu.MenuItem]*ProcessedMenuItem),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) getMenuItemByID(menuMap *MenuItemMap, menuId string) *menu.MenuItem {
|
||||
return menuMap.idToMenuItemMap[menuId]
|
||||
}
|
||||
|
||||
func (m *Manager) ProcessClick(menuID string, data string, menuType string, parentID string) error {
|
||||
|
||||
var menuItemMap *MenuItemMap
|
||||
|
||||
switch menuType {
|
||||
case "ApplicationMenu":
|
||||
menuItemMap = m.applicationMenuItemMap
|
||||
case "ContextMenu":
|
||||
contextMenu := m.contextMenus[parentID]
|
||||
if contextMenu == nil {
|
||||
return fmt.Errorf("unknown context menu: %s", parentID)
|
||||
}
|
||||
menuItemMap = contextMenu.menuItemMap
|
||||
case "TrayMenu":
|
||||
trayMenu := m.trayMenus[parentID]
|
||||
if trayMenu == nil {
|
||||
return fmt.Errorf("unknown tray menu: %s", parentID)
|
||||
}
|
||||
menuItemMap = trayMenu.menuItemMap
|
||||
default:
|
||||
return fmt.Errorf("unknown menutype: %s", menuType)
|
||||
}
|
||||
|
||||
// Get the menu item
|
||||
menuItem := menuItemMap.getMenuItemByID(menuID)
|
||||
func (m *Manager) ProcessClick(menuID string, data string) error {
|
||||
// Get item from callback map
|
||||
menuItem := m.menuItemMap[menuID]
|
||||
if menuItem == nil {
|
||||
return fmt.Errorf("Cannot process menuid %s - unknown", menuID)
|
||||
}
|
||||
|
||||
// Is the menu item a checkbox?
|
||||
if menuItem.Type == menu.CheckboxType {
|
||||
// Toggle state
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
return fmt.Errorf("menuItem doesn't exist")
|
||||
}
|
||||
|
||||
if menuItem.Click == nil {
|
||||
// No callback
|
||||
return fmt.Errorf("No callback for menu '%s'", menuItem.Label)
|
||||
return fmt.Errorf("menuItem 'does not have a callback")
|
||||
}
|
||||
|
||||
// Create new Callback struct
|
||||
callbackData := &menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
ContextData: data,
|
||||
}
|
||||
|
||||
// Call back!
|
||||
// Callback!
|
||||
go menuItem.Click(callbackData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,70 +2,165 @@ package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
type ProcessedMenuItem struct {
|
||||
ID string
|
||||
// Label is what appears as the menu text
|
||||
Label string
|
||||
// Role is a predefined menu type
|
||||
Role menu.Role `json:"Role,omitempty"`
|
||||
// Accelerator holds a representation of a key binding
|
||||
Accelerator *keys.Accelerator `json:"Accelerator,omitempty"`
|
||||
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
|
||||
Type menu.Type
|
||||
// Disabled makes the item unselectable
|
||||
Disabled bool
|
||||
// Hidden ensures that the item is not shown in the menu
|
||||
Hidden bool
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked bool
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu *ProcessedMenu `json:"SubMenu,omitempty"`
|
||||
type ProcessedMenu struct {
|
||||
ID string `json:"I"`
|
||||
|
||||
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
|
||||
Foreground int
|
||||
|
||||
// Background colour
|
||||
Background int
|
||||
Items []*ProcessedMenuItem `json:"i,omitempty"`
|
||||
RadioGroups []*RadioGroup `json:"r,omitempty"`
|
||||
}
|
||||
|
||||
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
func (m *Manager) ProcessMenu(menu *menu.Menu) *ProcessedMenu {
|
||||
wm := m.NewWailsMenu(menu)
|
||||
|
||||
if len(wm.Menu) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProcessedMenu{
|
||||
ID: m.generateMenuID(),
|
||||
Items: wm.Menu,
|
||||
RadioGroups: wm.RadioGroups,
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessedMenuItem struct {
|
||||
// ID of the menu item
|
||||
ID string `json:"I"`
|
||||
|
||||
// Label is what appears as the menu text
|
||||
Label string `json:"l,omitempty"`
|
||||
|
||||
// AlternateLabel is a secondary label (Used by Mac)
|
||||
AlternateLabel string `json:"L,omitempty"`
|
||||
|
||||
// Role is a predefined menu type
|
||||
Role menu.Role `json:"r,omitempty"`
|
||||
|
||||
// Accelerator holds a representation of a key binding
|
||||
Accelerator *keys.Accelerator `json:"a,omitempty"`
|
||||
|
||||
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
|
||||
Type menu.Type `json:"t,omitempty"`
|
||||
|
||||
// Font to use for the menu item
|
||||
Font string `json:"f,omitempty"`
|
||||
|
||||
// Font to use for the menu item
|
||||
FontSize int `json:"F,omitempty"`
|
||||
|
||||
// RGBA is the colour of the menu item
|
||||
RGBA string `json:"R,omitempty"`
|
||||
|
||||
// Image is an image for the menu item (base64 string)
|
||||
Image string `json:"i,omitempty"`
|
||||
|
||||
// Disabled makes the item unselectable
|
||||
Disabled *bool `json:"d,omitempty"`
|
||||
|
||||
// Hidden ensures that the item is not shown in the menu
|
||||
Hidden *bool `json:"h,omitempty"`
|
||||
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked *bool `json:"c,omitempty"`
|
||||
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu []*ProcessedMenuItem `json:"s,omitempty"`
|
||||
|
||||
// Indicates if this item has a callback
|
||||
HasCallback *bool `json:"C,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Manager) generateMenuItemID() string {
|
||||
return fmt.Sprintf("%d", m.menuItemIDCounter.Increment())
|
||||
}
|
||||
|
||||
func (m *Manager) generateMenuID() string {
|
||||
return fmt.Sprintf("%d", m.menuIDCounter.Increment())
|
||||
}
|
||||
|
||||
func (m *Manager) NewProcessedMenuItem(menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
|
||||
// Check if this menu has already been processed.
|
||||
// This is to prevent duplicates.
|
||||
existingMenuItem := m.processedMenuItems[menuItem]
|
||||
if existingMenuItem != nil {
|
||||
return &ProcessedMenuItem{ID: existingMenuItem.ID}
|
||||
}
|
||||
|
||||
ID := m.generateMenuItemID()
|
||||
|
||||
ID := menuItemMap.menuItemToIDMap[menuItem]
|
||||
result := &ProcessedMenuItem{
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
Role: menuItem.Role,
|
||||
Accelerator: menuItem.Accelerator,
|
||||
Type: menuItem.Type,
|
||||
Disabled: menuItem.Disabled,
|
||||
Hidden: menuItem.Hidden,
|
||||
Checked: menuItem.Checked,
|
||||
Foreground: menuItem.Foreground,
|
||||
Background: menuItem.Background,
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
AlternateLabel: menuItem.AlternateLabel,
|
||||
Role: menuItem.Role,
|
||||
Accelerator: menuItem.Accelerator,
|
||||
Type: menuItem.Type,
|
||||
Font: menuItem.Font,
|
||||
FontSize: menuItem.FontSize,
|
||||
RGBA: menuItem.RGBA,
|
||||
Image: menuItem.Image,
|
||||
Disabled: nil,
|
||||
Hidden: nil,
|
||||
Checked: nil,
|
||||
SubMenu: nil,
|
||||
HasCallback: nil,
|
||||
}
|
||||
|
||||
if menuItem.Hidden {
|
||||
result.Hidden = new(bool)
|
||||
*result.Hidden = true
|
||||
}
|
||||
|
||||
if menuItem.Disabled {
|
||||
result.Disabled = new(bool)
|
||||
*result.Disabled = true
|
||||
}
|
||||
|
||||
if menuItem.Checked {
|
||||
result.Checked = new(bool)
|
||||
*result.Checked = true
|
||||
}
|
||||
|
||||
if menuItem.Click != nil {
|
||||
result.HasCallback = new(bool)
|
||||
*result.HasCallback = true
|
||||
}
|
||||
|
||||
if menuItem.SubMenu != nil {
|
||||
result.SubMenu = NewProcessedMenu(menuItemMap, menuItem.SubMenu)
|
||||
result.SubMenu = m.NewProcessedMenu(menuItem.SubMenu)
|
||||
}
|
||||
|
||||
// Add menu item to item map
|
||||
m.menuItemMap[ID] = menuItem
|
||||
|
||||
// Add processed Item to processedMenuItems
|
||||
m.processedMenuItems[menuItem] = result
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type ProcessedMenu struct {
|
||||
Items []*ProcessedMenuItem
|
||||
}
|
||||
func (m *Manager) NewProcessedMenu(menu *menu.Menu) []*ProcessedMenuItem {
|
||||
|
||||
func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu {
|
||||
if menu == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if menu.Items == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []*ProcessedMenuItem
|
||||
|
||||
result := &ProcessedMenu{}
|
||||
for _, item := range menu.Items {
|
||||
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
|
||||
result.Items = append(result.Items, processedMenuItem)
|
||||
processedMenuItem := m.NewProcessedMenuItem(item)
|
||||
result = append(result, processedMenuItem)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -74,8 +169,8 @@ func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu
|
||||
// WailsMenu is the original menu with the addition
|
||||
// of radio groups extracted from the menu data
|
||||
type WailsMenu struct {
|
||||
Menu *ProcessedMenu
|
||||
RadioGroups []*RadioGroup
|
||||
Menu []*ProcessedMenuItem `json:",omitempty"`
|
||||
RadioGroups []*RadioGroup `json:",omitempty"`
|
||||
currentRadioGroup []string
|
||||
}
|
||||
|
||||
@@ -85,11 +180,12 @@ type RadioGroup struct {
|
||||
Length int
|
||||
}
|
||||
|
||||
func NewWailsMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *WailsMenu {
|
||||
func (m *Manager) NewWailsMenu(menu *menu.Menu) *WailsMenu {
|
||||
|
||||
result := &WailsMenu{}
|
||||
|
||||
// Process the menus
|
||||
result.Menu = NewProcessedMenu(menuItemMap, menu)
|
||||
result.Menu = m.NewProcessedMenu(menu)
|
||||
|
||||
// Process the radio groups
|
||||
result.processRadioGroups()
|
||||
@@ -106,17 +202,7 @@ func (w *WailsMenu) AsJSON() (string, error) {
|
||||
return string(menuAsJSON), nil
|
||||
}
|
||||
|
||||
func (w *WailsMenu) processRadioGroups() {
|
||||
// Loop over top level menus
|
||||
for _, item := range w.Menu.Items {
|
||||
// Process MenuItem
|
||||
w.processMenuItem(item)
|
||||
}
|
||||
|
||||
w.finaliseRadioGroup()
|
||||
}
|
||||
|
||||
func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
||||
func (w *WailsMenu) processMenuItemForRadioGroups(item *ProcessedMenuItem) {
|
||||
|
||||
switch item.Type {
|
||||
|
||||
@@ -127,8 +213,8 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
||||
w.finaliseRadioGroup()
|
||||
|
||||
// Process each submenu item
|
||||
for _, subitem := range item.SubMenu.Items {
|
||||
w.processMenuItem(subitem)
|
||||
for _, subitem := range item.SubMenu {
|
||||
w.processMenuItemForRadioGroups(subitem)
|
||||
}
|
||||
case menu.RadioType:
|
||||
// Add the item to the radio group
|
||||
@@ -138,6 +224,20 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WailsMenu) processRadioGroups() {
|
||||
|
||||
if w.Menu == nil {
|
||||
return
|
||||
}
|
||||
// Loop over top level menus
|
||||
for _, item := range w.Menu {
|
||||
// Process MenuItem
|
||||
w.processMenuItemForRadioGroups(item)
|
||||
}
|
||||
|
||||
w.finaliseRadioGroup()
|
||||
}
|
||||
|
||||
func (w *WailsMenu) finaliseRadioGroup() {
|
||||
|
||||
// If we were processing a radio group, fix up the references
|
||||
|
||||
@@ -4,27 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var trayMenuID int
|
||||
var trayMenuIDMutex sync.Mutex
|
||||
|
||||
func generateTrayID() string {
|
||||
trayMenuIDMutex.Lock()
|
||||
result := fmt.Sprintf("%d", trayMenuID)
|
||||
trayMenuID++
|
||||
trayMenuIDMutex.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
type TrayMenu struct {
|
||||
ID string
|
||||
Label string
|
||||
Icon string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
ID string `json:"I"`
|
||||
Label string `json:"l,omitempty"`
|
||||
Icon string `json:"i,omitempty"`
|
||||
Menu *ProcessedMenu `json:"m,omitempty"`
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -35,52 +21,44 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
func (m *Manager) newTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
result := &TrayMenu{
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
menu: trayMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
result := TrayMenu{
|
||||
ID: m.generateTrayID(),
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
Menu: m.ProcessMenu(trayMenu.Menu),
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
return &result
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
result := m.newTrayMenu(trayMenu)
|
||||
|
||||
// Add to map
|
||||
m.trayMenuMap[trayMenu] = result
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
// Hook up a new ID
|
||||
trayID := generateTrayID()
|
||||
newTrayMenu.ID = trayID
|
||||
|
||||
// Save the references
|
||||
m.trayMenus[trayID] = newTrayMenu
|
||||
m.trayMenuPointers[trayMenu] = trayID
|
||||
func (m *Manager) generateTrayID() string {
|
||||
return fmt.Sprintf("T%d", m.trayMenuIDCounter.Increment())
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
if !trayMenuKnown {
|
||||
return "", fmt.Errorf("unknown Tray Menu '%s'. Please add the tray menu using AddTrayMenu()", trayMenu.Label)
|
||||
func (m *Manager) GetTrayMenus() ([]*TrayMenu, error) {
|
||||
var result []*TrayMenu
|
||||
for _, trayMenu := range m.trayMenuMap {
|
||||
result = append(result, trayMenu)
|
||||
}
|
||||
|
||||
// Create the updated tray menu
|
||||
updatedTrayMenu := NewTrayMenu(trayMenu)
|
||||
updatedTrayMenu.ID = trayID
|
||||
|
||||
// Save the reference
|
||||
m.trayMenus[trayID] = updatedTrayMenu
|
||||
|
||||
return updatedTrayMenu.AsJSON()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
result := []string{}
|
||||
for _, trayMenu := range m.trayMenus {
|
||||
func (m *Manager) GetTrayMenusAsJSON() ([]string, error) {
|
||||
var result []string
|
||||
for _, trayMenu := range m.trayMenuMap {
|
||||
JSON, err := trayMenu.AsJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -91,15 +69,11 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
return result, 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
|
||||
// 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) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
99
v2/internal/menumanager/traymenu_test.go
Normal file
99
v2/internal/menumanager/traymenu_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package menumanager
|
||||
|
||||
import (
|
||||
"github.com/matryer/is"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestManager_AddTrayMenu(t *testing.T) {
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
simpleLabel := menu.Text("test", nil, nil)
|
||||
checkbox := menu.Checkbox("test", true, nil, nil)
|
||||
radioGroup1 := menu.Radio("option 1", true, nil, nil)
|
||||
radioGroup2 := menu.Radio("option 2", false, nil, nil)
|
||||
radioGroup3 := menu.Radio("option 3", false, nil, nil)
|
||||
callback := menu.Text("Preferences", nil, func(_ *menu.CallbackData) {})
|
||||
|
||||
empty := &menu.TrayMenu{}
|
||||
labelOnly := &menu.TrayMenu{Label: "test"}
|
||||
iconOnly := &menu.TrayMenu{Icon: "svelte"}
|
||||
iconLabel := &menu.TrayMenu{Icon: "svelte", Label: "test"}
|
||||
blankLabel := &menu.TrayMenu{Label: ""}
|
||||
blankMenu := &menu.TrayMenu{Menu: menu.NewMenu()}
|
||||
menuTextItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabel)}
|
||||
checkboxItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(checkbox)}
|
||||
radioGroupItems := &menu.TrayMenu{Menu: menu.NewMenuFromItems(radioGroup1, radioGroup2, radioGroup3)}
|
||||
callbackItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(callback)}
|
||||
|
||||
tests := []struct {
|
||||
trayMenu *menu.TrayMenu
|
||||
want string
|
||||
}{
|
||||
{empty, "{\"I\":\"T1\"}"},
|
||||
{labelOnly, "{\"I\":\"T1\",\"l\":\"test\"}"},
|
||||
{iconOnly, "{\"I\":\"T1\",\"i\":\"svelte\"}"},
|
||||
{iconLabel, "{\"I\":\"T1\",\"l\":\"test\",\"i\":\"svelte\"}"},
|
||||
{blankLabel, "{\"I\":\"T1\"}"},
|
||||
{blankMenu, "{\"I\":\"T1\"}"},
|
||||
{menuTextItem, "{\"I\":\"T1\",\"m\":{\"I\":\"1\",\"i\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}}"},
|
||||
{checkboxItem, "{\"I\":\"T1\",\"m\":{\"I\":\"1\",\"i\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"c\",\"c\":true}]}}"},
|
||||
{radioGroupItems, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"option 1\",\"t\":\"r\",\"c\":true},{\"I\":\"2\",\"l\":\"option 2\",\"t\":\"r\"},{\"I\":\"3\",\"l\":\"option 3\",\"t\":\"r\"}],\"r\":[{\"Members\":[\"1\",\"2\",\"3\"],\"Length\":3}]}"},
|
||||
{callbackItem, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"Preferences\",\"t\":\"t\",\"C\":true}]}"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
m := NewManager()
|
||||
got := m.AddTrayMenu(tt.trayMenu)
|
||||
JSON, err := got.AsJSON()
|
||||
is.NoErr(err)
|
||||
is.Equal(JSON, tt.want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestManager_CallbackMap(t *testing.T) {
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
simpleLabel := menu.Text("test", nil, nil)
|
||||
simpleLabelWithCallback := menu.Text("test", nil, func(_ *menu.CallbackData) {})
|
||||
checkboxWithCallback := menu.Checkbox("test", true, nil, func(_ *menu.CallbackData) {})
|
||||
submenu := menu.SubMenu("test", menu.NewMenuFromItems(checkboxWithCallback))
|
||||
|
||||
blankMenu := &menu.TrayMenu{Menu: menu.NewMenu()}
|
||||
noCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabel)}
|
||||
oneMenuWithCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback)}
|
||||
duplicateCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, simpleLabelWithCallback)}
|
||||
twoMenusWithCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, checkboxWithCallback)}
|
||||
duplicateMenusWithCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, checkboxWithCallback, simpleLabelWithCallback, checkboxWithCallback)}
|
||||
submenuWithCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(submenu)}
|
||||
duplicateSubmenus := &menu.TrayMenu{Menu: menu.NewMenuFromItems(submenu, submenu)}
|
||||
|
||||
tests := []struct {
|
||||
trayMenu *menu.TrayMenu
|
||||
trays int
|
||||
menuItems int
|
||||
JSON string
|
||||
}{
|
||||
{blankMenu, 1, 0, "{\"I\":\"T1\"}"},
|
||||
{noCallback, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}"},
|
||||
{oneMenuWithCallback, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true}]}"},
|
||||
{duplicateCallbacks, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"1\"}]}"},
|
||||
{twoMenusWithCallbacks, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]}"},
|
||||
{duplicateMenusWithCallbacks, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true},{\"I\":\"1\"},{\"I\":\"2\"}]}"},
|
||||
{submenuWithCallback, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"S\",\"s\":[{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]}]}"},
|
||||
{duplicateSubmenus, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"S\",\"s\":[{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]},{\"I\":\"1\"}]}"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
m := NewManager()
|
||||
tm := m.AddTrayMenu(test.trayMenu)
|
||||
is.Equal(len(m.trayMenuMap), test.trays)
|
||||
is.Equal(len(m.menuItemMap), test.menuItems)
|
||||
JSON, err := tm.AsJSON()
|
||||
is.NoErr(err)
|
||||
is.Equal(JSON, test.JSON)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
// Client defines what a frontend client can do
|
||||
@@ -13,9 +13,9 @@ type Client interface {
|
||||
Quit()
|
||||
NotifyEvent(message string)
|
||||
CallResult(message string)
|
||||
OpenDialog(dialogOptions *options.OpenDialog, callbackID string)
|
||||
SaveDialog(dialogOptions *options.SaveDialog, callbackID string)
|
||||
MessageDialog(dialogOptions *options.MessageDialog, callbackID string)
|
||||
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
|
||||
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
|
||||
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string)
|
||||
WindowSetTitle(title string)
|
||||
WindowShow()
|
||||
WindowHide()
|
||||
@@ -31,7 +31,8 @@ type Client interface {
|
||||
WindowSetColour(colour int)
|
||||
DarkModeEnabled(callbackID string)
|
||||
SetApplicationMenu(menuJSON string)
|
||||
UpdateTrayMenu(trayMenuJSON string)
|
||||
SetTrayMenu(trayMenuJSON string)
|
||||
UpdateTrayMenuLabel(JSON string)
|
||||
UpdateContextMenu(contextMenuJSON string)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
// systemMessageParser does what it says on the tin!
|
||||
func systemMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// Sanity check: system messages must be at least 4 bytes
|
||||
if len(message) < 4 {
|
||||
// Sanity check: system messages must be at least 2 bytes
|
||||
if len(message) < 2 {
|
||||
return nil, fmt.Errorf("system message was an invalid length")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ func systemMessageParser(message string) (*parsedMessage, error) {
|
||||
// Format of system response messages: S<command><callbackID>|<payload>
|
||||
// DarkModeEnabled
|
||||
case 'D':
|
||||
if len(message) < 4 {
|
||||
return nil, fmt.Errorf("system message was an invalid length")
|
||||
}
|
||||
message = message[1:]
|
||||
idx := strings.IndexByte(message, '|')
|
||||
if idx < 0 {
|
||||
@@ -34,6 +37,10 @@ func systemMessageParser(message string) (*parsedMessage, error) {
|
||||
topic := "systemresponse:" + callbackID
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: payloadData == "T"}
|
||||
|
||||
// This is our startup hook - the frontend is now ready
|
||||
case 'S':
|
||||
topic := "hooks:startup"
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: nil}
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package messagedispatcher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -10,7 +11,6 @@ import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
// Dispatcher translates messages received from the frontend
|
||||
@@ -370,7 +370,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
|
||||
dialogType := splitTopic[2]
|
||||
switch dialogType {
|
||||
case "open":
|
||||
dialogOptions, ok := result.Data().(*options.OpenDialog)
|
||||
dialogOptions, ok := result.Data().(*dialog.OpenDialog)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
|
||||
return
|
||||
@@ -384,7 +384,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
|
||||
client.frontend.OpenDialog(dialogOptions, callbackID)
|
||||
}
|
||||
case "save":
|
||||
dialogOptions, ok := result.Data().(*options.SaveDialog)
|
||||
dialogOptions, ok := result.Data().(*dialog.SaveDialog)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
|
||||
return
|
||||
@@ -398,7 +398,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
|
||||
client.frontend.SaveDialog(dialogOptions, callbackID)
|
||||
}
|
||||
case "message":
|
||||
dialogOptions, ok := result.Data().(*options.MessageDialog)
|
||||
dialogOptions, ok := result.Data().(*dialog.MessageDialog)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'dialog:select:message' : %#v", result.Data())
|
||||
return
|
||||
@@ -445,10 +445,10 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
client.frontend.SetApplicationMenu(updatedMenu)
|
||||
}
|
||||
|
||||
case "updatetraymenu":
|
||||
updatedTrayMenu, ok := result.Data().(string)
|
||||
case "settraymenu":
|
||||
trayMenuJSON, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenu' : %#v",
|
||||
d.logger.Error("Invalid data for 'menufrontend:settraymenu' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
@@ -456,8 +456,9 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenu(updatedTrayMenu)
|
||||
client.frontend.SetTrayMenu(trayMenuJSON)
|
||||
}
|
||||
|
||||
case "updatecontextmenu":
|
||||
updatedContextMenu, ok := result.Data().(string)
|
||||
if !ok {
|
||||
@@ -472,6 +473,20 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
client.frontend.UpdateContextMenu(updatedContextMenu)
|
||||
}
|
||||
|
||||
case "updatetraymenulabel":
|
||||
updatedTrayMenuLabel, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var internalMethods = slicer.String([]string{"WailsInit", "Wails Shutdown"})
|
||||
|
||||
var structCache = make(map[string]*ParsedStruct)
|
||||
var boundStructs = make(map[string]*ParsedStruct)
|
||||
var boundMethods = []string{}
|
||||
@@ -49,7 +47,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo}
|
||||
pkgs, err := packages.Load(cfg, projectPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "load: %v\n", err)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "load: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if packages.PrintErrors(pkgs) > 0 {
|
||||
@@ -203,10 +201,6 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
// This is a struct pointer method
|
||||
i, ok := se.X.(*ast.Ident)
|
||||
if ok {
|
||||
// We want to ignore Internal functions
|
||||
if internalMethods.Contains(x.Name.Name) {
|
||||
continue
|
||||
}
|
||||
// If we haven't already found this struct,
|
||||
// Create a placeholder in the cache
|
||||
parsedStruct := structCache[i.Name]
|
||||
@@ -437,4 +431,6 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
println()
|
||||
println("}")
|
||||
println()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Proces
|
||||
// Start the process
|
||||
func (p *Process) Start() error {
|
||||
|
||||
p.cmd.
|
||||
err := p.cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
dialogoptions "github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
// Dialog defines all Dialog related operations
|
||||
type Dialog interface {
|
||||
Open(dialogOptions *options.OpenDialog) []string
|
||||
Save(dialogOptions *options.SaveDialog) string
|
||||
Message(dialogOptions *options.MessageDialog) string
|
||||
Open(dialogOptions *dialogoptions.OpenDialog) []string
|
||||
Save(dialogOptions *dialogoptions.SaveDialog) string
|
||||
Message(dialogOptions *dialogoptions.MessageDialog) string
|
||||
}
|
||||
|
||||
// dialog exposes the Dialog interface
|
||||
@@ -45,7 +45,7 @@ func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
|
||||
}
|
||||
|
||||
// Open prompts the user to select a file
|
||||
func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
|
||||
func (r *dialog) Open(dialogOptions *dialogoptions.OpenDialog) []string {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
@@ -70,7 +70,7 @@ func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
|
||||
}
|
||||
|
||||
// Save prompts the user to select a file
|
||||
func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
|
||||
func (r *dialog) Save(dialogOptions *dialogoptions.SaveDialog) string {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
@@ -95,7 +95,7 @@ func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
|
||||
}
|
||||
|
||||
// Message show a message to the user
|
||||
func (r *dialog) Message(dialogOptions *options.MessageDialog) string {
|
||||
func (r *dialog) Message(dialogOptions *dialogoptions.MessageDialog) string {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
type Menu interface {
|
||||
UpdateApplicationMenu()
|
||||
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
||||
UpdateTrayMenu(trayMenu *menu.TrayMenu)
|
||||
SetTrayMenu(trayMenu *menu.TrayMenu)
|
||||
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
||||
}
|
||||
|
||||
type menuRuntime struct {
|
||||
@@ -31,6 +32,10 @@ func (m *menuRuntime) UpdateContextMenu(contextMenu *menu.ContextMenu) {
|
||||
m.bus.Publish("menu:updatecontextmenu", contextMenu)
|
||||
}
|
||||
|
||||
func (m *menuRuntime) UpdateTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:updatetraymenu", trayMenu)
|
||||
func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:settraymenu", trayMenu)
|
||||
}
|
||||
|
||||
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ func (r *system) IsDarkMode() bool {
|
||||
systemResponseChannel, err := r.bus.Subscribe(responseTopic)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
message := "system:isdarkmode:" + uniqueCallback
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Binding is the Binding subsystem. It manages all service bus messages
|
||||
@@ -26,13 +24,6 @@ type Binding struct {
|
||||
runtime *runtime.Runtime
|
||||
}
|
||||
|
||||
func showError(err error) {
|
||||
// Add a slight delay so log buffer clears
|
||||
time.Sleep(1 * time.Second)
|
||||
println("\n\n\n\n\n\n")
|
||||
println("Fatal Error in WailsInit(): " + err.Error())
|
||||
}
|
||||
|
||||
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference.
|
||||
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
|
||||
|
||||
@@ -56,16 +47,6 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
// Call WailsInit methods once the frontend is loaded
|
||||
runtime.Events.On("wails:loaded", func(...interface{}) {
|
||||
result.logger.Trace("Calling WailsInit() methods")
|
||||
err := result.CallWailsInit()
|
||||
if err != nil {
|
||||
showError(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -94,34 +75,6 @@ func (b *Binding) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CallWailsInit will callback to the registered WailsInit
|
||||
// methods with the runtime object
|
||||
func (b *Binding) CallWailsInit() error {
|
||||
for _, wailsinit := range b.bindings.DB().WailsInitMethods() {
|
||||
_, err := wailsinit.Call([]interface{}{b.runtime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CallWailsShutdown will callback to the registered WailsShutdown
|
||||
// methods with the runtime object
|
||||
func (b *Binding) CallWailsShutdown() error {
|
||||
for _, wailsshutdown := range b.bindings.DB().WailsShutdownMethods() {
|
||||
_, err := wailsshutdown.Call([]interface{}{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Binding) shutdown() {
|
||||
err := b.CallWailsShutdown()
|
||||
if err != nil {
|
||||
showError(err)
|
||||
}
|
||||
b.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package subsystem
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
@@ -10,7 +11,6 @@ import (
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
// Call is the Call subsystem. It manages all service bus messages
|
||||
@@ -131,7 +131,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
|
||||
darkModeEnabled := c.runtime.System.IsDarkMode()
|
||||
c.sendResult(darkModeEnabled, payload, clientID)
|
||||
case "Dialog.Open":
|
||||
dialogOptions := new(options.OpenDialog)
|
||||
dialogOptions := new(dialog.OpenDialog)
|
||||
err := json.Unmarshal(payload.Args[0], dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
@@ -139,7 +139,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
|
||||
result := c.runtime.Dialog.Open(dialogOptions)
|
||||
c.sendResult(result, payload, clientID)
|
||||
case "Dialog.Save":
|
||||
dialogOptions := new(options.SaveDialog)
|
||||
dialogOptions := new(dialog.SaveDialog)
|
||||
err := json.Unmarshal(payload.Args[0], dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
@@ -147,7 +147,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
|
||||
result := c.runtime.Dialog.Save(dialogOptions)
|
||||
c.sendResult(result, payload, clientID)
|
||||
case "Dialog.Message":
|
||||
dialogOptions := new(options.MessageDialog)
|
||||
dialogOptions := new(dialog.MessageDialog)
|
||||
err := json.Unmarshal(payload.Args[0], dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
|
||||
@@ -80,9 +80,7 @@ func (m *Menu) Start() error {
|
||||
|
||||
type ClickCallbackMessage struct {
|
||||
MenuItemID string `json:"menuItemID"`
|
||||
MenuType string `json:"menuType"`
|
||||
Data string `json:"data"`
|
||||
ParentID string `json:"parentID"`
|
||||
}
|
||||
|
||||
var callbackData ClickCallbackMessage
|
||||
@@ -93,7 +91,7 @@ func (m *Menu) Start() error {
|
||||
return
|
||||
}
|
||||
|
||||
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data, callbackData.MenuType, callbackData.ParentID)
|
||||
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
}
|
||||
@@ -120,16 +118,27 @@ func (m *Menu) Start() error {
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:updatecontextmenu", updatedMenu)
|
||||
|
||||
case "updatetraymenu":
|
||||
case "settraymenu":
|
||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||
updatedMenu, err := m.menuManager.UpdateTrayMenu(trayMenu)
|
||||
updatedMenu, err := m.menuManager.SetTrayMenu(trayMenu)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:updatetraymenu", updatedMenu)
|
||||
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
||||
|
||||
case "updatetraymenulabel":
|
||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:updatetraymenulabel", updatedLabel)
|
||||
|
||||
default:
|
||||
m.logger.Error("unknown menu message: %+v", menuMessage)
|
||||
|
||||
@@ -14,7 +14,13 @@ import (
|
||||
type Runtime struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
runtimeChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// The hooks channel allows us to hook into frontend startup
|
||||
hooksChannel <-chan *servicebus.Message
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
|
||||
running bool
|
||||
|
||||
logger logger.CustomLogger
|
||||
|
||||
@@ -23,7 +29,7 @@ type Runtime struct {
|
||||
}
|
||||
|
||||
// NewRuntime creates a new runtime subsystem
|
||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, error) {
|
||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
@@ -37,11 +43,20 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to log messages
|
||||
hooksChannel, err := bus.Subscribe("hooks:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &Runtime{
|
||||
quitChannel: quitChannel,
|
||||
runtimeChannel: runtimeChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus),
|
||||
quitChannel: quitChannel,
|
||||
runtimeChannel: runtimeChannel,
|
||||
hooksChannel: hooksChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus),
|
||||
startupCallback: startupCallback,
|
||||
shutdownCallback: shutdownCallback,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -59,6 +74,21 @@ func (r *Runtime) Start() error {
|
||||
case <-r.quitChannel:
|
||||
r.running = false
|
||||
break
|
||||
case hooksMessage := <-r.hooksChannel:
|
||||
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
|
||||
messageSlice := strings.Split(hooksMessage.Topic(), ":")
|
||||
hook := messageSlice[1]
|
||||
switch hook {
|
||||
case "startup":
|
||||
if r.startupCallback != nil {
|
||||
go r.startupCallback(r.runtime)
|
||||
} else {
|
||||
r.logger.Error("no startup callback registered!")
|
||||
}
|
||||
default:
|
||||
r.logger.Error("unknown hook message: %+v", hooksMessage)
|
||||
continue
|
||||
}
|
||||
case runtimeMessage := <-r.runtimeChannel:
|
||||
r.logger.Trace(fmt.Sprintf("Received message: %+v", runtimeMessage))
|
||||
// Topics have the format: "runtime:category:call"
|
||||
@@ -99,6 +129,9 @@ func (r *Runtime) GoRuntime() *runtime.Runtime {
|
||||
}
|
||||
|
||||
func (r *Runtime) shutdown() {
|
||||
if r.shutdownCallback != nil {
|
||||
go r.shutdownCallback()
|
||||
}
|
||||
r.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package webserver
|
||||
import (
|
||||
"context"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -20,11 +20,27 @@ type WebClient struct {
|
||||
running bool
|
||||
}
|
||||
|
||||
func (wc *WebClient) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
|
||||
func (wc *WebClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
|
||||
func (wc *WebClient) SetApplicationMenu(menuJSON string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) UpdateTrayMenu(trayMenuJSON string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) UpdateContextMenu(contextMenuJSON string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
@@ -84,10 +100,6 @@ func (wc *WebClient) UpdateTrayIcon(name string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) UpdateContextMenus(contextMenus *menu.ContextMenus) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
// Quit terminates the webclient session
|
||||
func (wc *WebClient) Quit() {
|
||||
wc.running = false
|
||||
|
||||
@@ -63,6 +63,14 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
|
||||
return err
|
||||
}
|
||||
|
||||
// Make dir if it doesn't exist
|
||||
if !fs.DirExists(assetDir) {
|
||||
err := fs.Mkdir(assetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Dump assets as C
|
||||
assetsFile, err := assets.WriteToCFile(assetDir)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,14 @@ func (m *Menu) Append(item *MenuItem) {
|
||||
m.Items = append(m.Items, item)
|
||||
}
|
||||
|
||||
// Merge will append the items in the given menu
|
||||
// into this menu
|
||||
func (m *Menu) Merge(menu *Menu) {
|
||||
for _, item := range menu.Items {
|
||||
m.Items = append(m.Items, item)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) Prepend(item *MenuItem) {
|
||||
m.Items = append([]*MenuItem{item}, m.Items...)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
type MenuItem struct {
|
||||
// Label is what appears as the menu text
|
||||
Label string
|
||||
// AlternateLabel is a secondary label (Used by Mac)
|
||||
AlternateLabel string
|
||||
// Role is a predefined menu type
|
||||
Role Role `json:"Role,omitempty"`
|
||||
// Accelerator holds a representation of a key binding
|
||||
@@ -21,6 +23,14 @@ type MenuItem struct {
|
||||
Hidden bool
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked bool
|
||||
// Font to use for the menu item
|
||||
Font string
|
||||
// Font to use for the menu item
|
||||
FontSize int
|
||||
// RGBA is the colour of the menu item
|
||||
RGBA string
|
||||
// Image is an image for the menu item (base64 string)
|
||||
Image string
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu *Menu `json:"SubMenu,omitempty"`
|
||||
|
||||
@@ -5,13 +5,13 @@ type Type string
|
||||
|
||||
const (
|
||||
// TextType is the text menuitem type
|
||||
TextType Type = "Text"
|
||||
TextType Type = "t"
|
||||
// SeparatorType is the Separator menuitem type
|
||||
SeparatorType Type = "Separator"
|
||||
SeparatorType Type = "s"
|
||||
// SubmenuType is the Submenu menuitem type
|
||||
SubmenuType Type = "Submenu"
|
||||
SubmenuType Type = "S"
|
||||
// CheckboxType is the Checkbox menuitem type
|
||||
CheckboxType Type = "Checkbox"
|
||||
CheckboxType Type = "c"
|
||||
// RadioType is the Radio menuitem type
|
||||
RadioType Type = "Radio"
|
||||
RadioType Type = "r"
|
||||
)
|
||||
|
||||
@@ -13,10 +13,8 @@ var Default = &App{
|
||||
DevTools: false,
|
||||
RGBA: 0xFFFFFFFF,
|
||||
Mac: &mac.Options{
|
||||
TitleBar: mac.TitleBarDefault(),
|
||||
Appearance: mac.DefaultAppearance,
|
||||
WebviewIsTransparent: false,
|
||||
WindowBackgroundIsTranslucent: false,
|
||||
TitleBar: mac.TitleBarDefault(),
|
||||
Appearance: mac.DefaultAppearance,
|
||||
},
|
||||
Logger: logger.NewDefaultLogger(),
|
||||
LogLevel: logger.INFO,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package options
|
||||
package dialog
|
||||
|
||||
// OpenDialog contains the options for the OpenDialog runtime method
|
||||
type OpenDialog struct {
|
||||
@@ -1,6 +1,7 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
wailsruntime "github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"log"
|
||||
"runtime"
|
||||
@@ -30,6 +31,8 @@ type App struct {
|
||||
Mac *mac.Options
|
||||
Logger logger.Logger `json:"-"`
|
||||
LogLevel logger.LogLevel
|
||||
Startup func(*wailsruntime.Runtime) `json:"-"`
|
||||
Shutdown func() `json:"-"`
|
||||
}
|
||||
|
||||
// MergeDefaults will set the minimum default values for an application
|
||||
|
||||
@@ -44,11 +44,6 @@ func (p *Parser) parseStructMethods(boundStruct *Struct) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// We want to ignore Internal functions
|
||||
if funcDecl.Name.Name == "WailsInit" || funcDecl.Name.Name == "WailsShutdown" {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this method is not Public, ignore
|
||||
if string(funcDecl.Name.Name[0]) != strings.ToUpper((string(funcDecl.Name.Name[0]))) {
|
||||
continue
|
||||
|
||||
10
v2/pkg/str/str.go
Normal file
10
v2/pkg/str/str.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package str
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func UnixNow() string {
|
||||
return fmt.Sprintf("%+v", time.Now().Unix())
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
// Dialog struct
|
||||
@@ -18,16 +18,16 @@ func (l *Dialog) WailsInit(runtime *wails.Runtime) error {
|
||||
}
|
||||
|
||||
// Open Dialog
|
||||
func (l *Dialog) Open(options *options.OpenDialog) []string {
|
||||
func (l *Dialog) Open(options *dialog.OpenDialog) []string {
|
||||
return l.runtime.Dialog.Open(options)
|
||||
}
|
||||
|
||||
// Save Dialog
|
||||
func (l *Dialog) Save(options *options.SaveDialog) string {
|
||||
func (l *Dialog) Save(options *dialog.SaveDialog) string {
|
||||
return l.runtime.Dialog.Save(options)
|
||||
}
|
||||
|
||||
// Message Dialog
|
||||
func (l *Dialog) Message(options *options.MessageDialog) string {
|
||||
func (l *Dialog) Message(options *dialog.MessageDialog) string {
|
||||
return l.runtime.Dialog.Message(options)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
type Notepad struct {
|
||||
@@ -21,7 +21,7 @@ func (n *Notepad) WailsInit(runtime *wails.Runtime) error {
|
||||
// successful save.
|
||||
func (n *Notepad) SaveNotes(notes string) (bool, error) {
|
||||
|
||||
selectedFile := n.runtime.Dialog.Save(&options.SaveDialog{
|
||||
selectedFile := n.runtime.Dialog.Save(&dialog.SaveDialog{
|
||||
DefaultFilename: "notes.md",
|
||||
Filters: "*.md",
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
type Notepad struct {
|
||||
@@ -18,7 +18,7 @@ func (n *Notepad) WailsInit(runtime *wails.Runtime) error {
|
||||
|
||||
func (n *Notepad) LoadNotes() (string, error) {
|
||||
|
||||
selectedFiles := n.runtime.Dialog.Open(&options.OpenDialog{
|
||||
selectedFiles := n.runtime.Dialog.Open(&dialog.OpenDialog{
|
||||
DefaultFilename: "notes.md",
|
||||
Filters: "*.md",
|
||||
AllowFiles: true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
type Notepad struct {
|
||||
@@ -21,7 +21,7 @@ func (n *Notepad) WailsInit(runtime *wails.Runtime) error {
|
||||
// successful save.
|
||||
func (n *Notepad) SaveNotes(notes string) (bool, error) {
|
||||
|
||||
selectedFile := n.runtime.Dialog.Save(&options.SaveDialog{
|
||||
selectedFile := n.runtime.Dialog.Save(&dialog.SaveDialog{
|
||||
DefaultFilename: "notes.md",
|
||||
Filters: "*.md",
|
||||
})
|
||||
|
||||
@@ -2,8 +2,6 @@ module test
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/wailsapp/wails/v2 v2.0.0-alpha
|
||||
)
|
||||
require github.com/wailsapp/wails/v2 v2.0.0-alpha
|
||||
|
||||
replace github.com/wailsapp/wails/v2 v2.0.0-alpha => ../../../v2
|
||||
|
||||
@@ -1,112 +1,90 @@
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
|
||||
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/colors v1.2.0/go.mod h1:miw1R2JIE19cclPxsXqNdzLZsk4DP4iF+m88bRc7kfM=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||
github.com/leaanthony/mewn v0.10.7/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ=
|
||||
github.com/leaanthony/slicer v1.4.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leaanthony/spinner v0.5.3/go.mod h1:oHlrvWicr++CVV7ALWYi+qHk/XNA91D9IJ48IqmpVUo=
|
||||
github.com/leaanthony/synx v0.1.0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs=
|
||||
github.com/leaanthony/wincursor v0.1.0/go.mod h1:7TVwwrzSH/2Y9gLOGH+VhA+bZhoWXBRgbGNTMk+yimE=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
|
||||
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
|
||||
github.com/tdewolff/minify/v2 v2.9.5/go.mod h1:jshtBj/uUJH6JX1fuxTLnnHOA1RVJhF5MM+leJzDKb4=
|
||||
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
|
||||
github.com/tdewolff/parse/v2 v2.5.3/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/wailsapp/wails v1.8.0 h1:gnQhpwoGM8s2GD5PZrgMKU1PO3pQ9cdKKJgwtkNz2f4=
|
||||
github.com/wailsapp/wails v1.8.0/go.mod h1:XFZunea+USOCMMgBlz0A0JHLL3oWrRhnOl4baZlRpxo=
|
||||
github.com/wailsapp/wails v1.9.1 h1:ez/TK8YpU9lvOZ9nkgzUXsWu+xOPFVO57zTy0n5w3hc=
|
||||
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -123,12 +101,14 @@ golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82u
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.4/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
|
||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
|
||||
@@ -34,6 +34,8 @@ func main() {
|
||||
TrayMenus: Tray.createTrayMenus(),
|
||||
},
|
||||
LogLevel: logger.TRACE,
|
||||
Startup: Tray.start,
|
||||
Shutdown: Tray.shutdown,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -21,8 +21,8 @@ type Tray struct {
|
||||
done bool
|
||||
}
|
||||
|
||||
// WailsInit is called at application startup
|
||||
func (t *Tray) WailsInit(runtime *wails.Runtime) error {
|
||||
// This is called at application startup
|
||||
func (t *Tray) start(runtime *wails.Runtime) {
|
||||
// Perform your setup here
|
||||
t.runtime = runtime
|
||||
|
||||
@@ -36,7 +36,6 @@ func (t *Tray) WailsInit(runtime *wails.Runtime) error {
|
||||
// t.runtime.Tray.SetIcon("dark")
|
||||
//})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tray) showWindow(_ *menu.CallbackData) {
|
||||
@@ -55,7 +54,7 @@ func (t *Tray) minimiseWindow(_ *menu.CallbackData) {
|
||||
t.runtime.Window.Minimise()
|
||||
}
|
||||
|
||||
func (t *Tray) WailsShutdown() {
|
||||
func (t *Tray) shutdown() {
|
||||
t.done = true
|
||||
}
|
||||
|
||||
@@ -71,19 +70,19 @@ func (t *Tray) decrementcounter() int {
|
||||
|
||||
func (t *Tray) SvelteIcon(_ *menu.CallbackData) {
|
||||
t.secondTrayMenu.Icon = "svelte"
|
||||
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
|
||||
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
|
||||
}
|
||||
func (t *Tray) NoIcon(_ *menu.CallbackData) {
|
||||
t.secondTrayMenu.Icon = ""
|
||||
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
|
||||
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
|
||||
}
|
||||
func (t *Tray) LightIcon(_ *menu.CallbackData) {
|
||||
t.secondTrayMenu.Icon = "light"
|
||||
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
|
||||
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
|
||||
}
|
||||
func (t *Tray) DarkIcon(_ *menu.CallbackData) {
|
||||
t.secondTrayMenu.Icon = "dark"
|
||||
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
|
||||
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
|
||||
}
|
||||
|
||||
//func (t *Tray) removeMenu(_ *menu.MenuItem) {
|
||||
@@ -146,7 +145,7 @@ func (t *Tray) createTrayMenus() []*menu.TrayMenu {
|
||||
counter := t.incrementcounter()
|
||||
trayLabel := "Updated Label " + strconv.Itoa(counter)
|
||||
secondTrayMenu.Label = trayLabel
|
||||
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
|
||||
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
|
||||
}),
|
||||
menu.SubMenu("Select Icon", menu.NewMenuFromItems(
|
||||
menu.Text("Svelte", nil, t.SvelteIcon),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
// RuntimeTest to test the runtimes
|
||||
@@ -75,7 +75,7 @@ func (r *RuntimeTest) SetColour(colour int) {
|
||||
|
||||
// OpenFileDialog will call the Runtime.Dialog.OpenDialog method requesting File selection
|
||||
func (r *RuntimeTest) OpenFileDialog(title string, filter string) []string {
|
||||
dialogOptions := &options.OpenDialog{
|
||||
dialogOptions := &dialog.OpenDialog{
|
||||
Title: title,
|
||||
Filters: filter,
|
||||
AllowFiles: true,
|
||||
@@ -85,7 +85,7 @@ func (r *RuntimeTest) OpenFileDialog(title string, filter string) []string {
|
||||
|
||||
// OpenDirectoryDialog will call the Runtime.Dialog.OpenDialog method requesting File selection
|
||||
func (r *RuntimeTest) OpenDirectoryDialog(title string, filter string) []string {
|
||||
dialogOptions := &options.OpenDialog{
|
||||
dialogOptions := &dialog.OpenDialog{
|
||||
Title: title,
|
||||
Filters: filter,
|
||||
AllowDirectories: true,
|
||||
@@ -95,7 +95,7 @@ func (r *RuntimeTest) OpenDirectoryDialog(title string, filter string) []string
|
||||
|
||||
// OpenDialog will call the Runtime.Dialog.OpenDialog method requesting both Files and Directories
|
||||
func (r *RuntimeTest) OpenDialog(title string, filter string) []string {
|
||||
dialogOptions := &options.OpenDialog{
|
||||
dialogOptions := &dialog.OpenDialog{
|
||||
Title: title,
|
||||
Filters: filter,
|
||||
AllowDirectories: true,
|
||||
@@ -106,7 +106,7 @@ func (r *RuntimeTest) OpenDialog(title string, filter string) []string {
|
||||
|
||||
// OpenDialogMultiple will call the Runtime.Dialog.OpenDialog method allowing multiple selection
|
||||
func (r *RuntimeTest) OpenDialogMultiple(title string, filter string) []string {
|
||||
dialogOptions := &options.OpenDialog{
|
||||
dialogOptions := &dialog.OpenDialog{
|
||||
Title: title,
|
||||
Filters: filter,
|
||||
AllowDirectories: true,
|
||||
@@ -118,7 +118,7 @@ func (r *RuntimeTest) OpenDialogMultiple(title string, filter string) []string {
|
||||
|
||||
// OpenDialogAllOptions will call the Runtime.Dialog.OpenDialog method allowing multiple selection
|
||||
func (r *RuntimeTest) OpenDialogAllOptions(filter string, defaultDir string, defaultFilename string) []string {
|
||||
dialogOptions := &options.OpenDialog{
|
||||
dialogOptions := &dialog.OpenDialog{
|
||||
DefaultDirectory: defaultDir,
|
||||
DefaultFilename: defaultFilename,
|
||||
Filters: filter,
|
||||
@@ -134,7 +134,7 @@ func (r *RuntimeTest) OpenDialogAllOptions(filter string, defaultDir string, def
|
||||
|
||||
// SaveFileDialog will call the Runtime.Dialog.SaveDialog method requesting a File selection
|
||||
func (r *RuntimeTest) SaveFileDialog(title string, filter string) string {
|
||||
dialogOptions := &options.SaveDialog{
|
||||
dialogOptions := &dialog.SaveDialog{
|
||||
Title: title,
|
||||
Filters: filter,
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (r *RuntimeTest) SaveFileDialog(title string, filter string) string {
|
||||
|
||||
// SaveDialogAllOptions will call the Runtime.Dialog.SaveDialog method allowing multiple selection
|
||||
func (r *RuntimeTest) SaveDialogAllOptions(filter string, defaultDir string, defaultFilename string) string {
|
||||
dialogOptions := &options.SaveDialog{
|
||||
dialogOptions := &dialog.SaveDialog{
|
||||
DefaultDirectory: defaultDir,
|
||||
DefaultFilename: defaultFilename,
|
||||
Filters: filter,
|
||||
|
||||
Reference in New Issue
Block a user