mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 00:41:59 +08:00
Support context menu data
Support StartHidden
This commit is contained in:
parent
34ac62e4ac
commit
a8995c5377
@ -25,7 +25,7 @@ type App struct {
|
||||
}
|
||||
|
||||
// CreateApp returns a null application
|
||||
func CreateApp(options *options.App) *App {
|
||||
func CreateApp(_ *options.App) *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
@ -37,6 +37,5 @@ func (a *App) Run() error {
|
||||
}
|
||||
|
||||
// Bind the dummy interface
|
||||
func (a *App) Bind(dummy interface{}) error {
|
||||
return nil
|
||||
func (a *App) Bind(_ interface{}) {
|
||||
}
|
||||
|
@ -23,14 +23,15 @@ type App struct {
|
||||
options *options.App
|
||||
|
||||
// Subsystems
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
tray *subsystem.Tray
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
tray *subsystem.Tray
|
||||
contextmenus *subsystem.ContextMenus
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
// Indicates if the app is in debug mode
|
||||
debug bool
|
||||
@ -94,8 +95,9 @@ func (a *App) Run() error {
|
||||
// Start the runtime
|
||||
applicationMenu := options.GetApplicationMenu(a.options)
|
||||
trayMenu := options.GetTrayMenu(a.options)
|
||||
contextMenus := options.GetContextMenus(a.options)
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu, contextMenus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -179,6 +181,19 @@ func (a *App) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally start the context menu subsystem
|
||||
if contextMenus != nil {
|
||||
contextmenussubsystem, err := subsystem.NewContextMenus(contextMenus, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.contextmenus = contextmenussubsystem
|
||||
err = a.contextmenus.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Start the call subsystem
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
|
@ -34,4 +34,6 @@ extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *fi
|
||||
extern void DarkModeEnabled(void *appPointer, char *callbackID);
|
||||
extern void UpdateMenu(void *app, char *menuAsJSON);
|
||||
extern void UpdateTray(void *app, char *menuAsJSON);
|
||||
extern void UpdateContextMenus(void *app, char *contextMenusAsJSON);
|
||||
|
||||
#endif
|
||||
|
@ -13,6 +13,7 @@ import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"strconv"
|
||||
|
||||
@ -188,3 +189,19 @@ func (c *Client) UpdateTray(menu *menu.Menu) {
|
||||
}
|
||||
C.UpdateTray(c.app.app, c.app.string2CString(string(trayMenuJSON)))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateContextMenus(contextMenus *menu.ContextMenus) {
|
||||
|
||||
// Guard against nil contextMenus
|
||||
if contextMenus == nil {
|
||||
return
|
||||
}
|
||||
// Process the menu
|
||||
contextMenusJSON, err := json.Marshal(contextMenus)
|
||||
fmt.Printf("\n\nUPDATED CONTEXT MENUS:\n %+v\n\n", string(contextMenusJSON))
|
||||
if err != nil {
|
||||
c.app.logger.Error("Error processing updated Context Menus: %s", err.Error())
|
||||
return
|
||||
}
|
||||
C.UpdateContextMenus(c.app.app, c.app.string2CString(string(contextMenusJSON)))
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#define STREQ(a,b) strcmp(a, b) == 0
|
||||
#define STRCOPY(a) concat(a, "")
|
||||
#define MEMFREE(input) free((void*)input); input = NULL;
|
||||
|
||||
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
||||
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
||||
@ -107,6 +108,10 @@ struct hashmap_s menuItemMapForContextMenus;
|
||||
// RadioGroup map for the context menus. Maps a menuitem id with its associated radio group items
|
||||
struct hashmap_s radioGroupMapForContextMenus;
|
||||
|
||||
// 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);
|
||||
|
||||
@ -253,7 +258,7 @@ void Debug(struct Application *app, const char *message, ... ) {
|
||||
va_start(args, message);
|
||||
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||
app->sendMessageToBackend(&logbuffer[0]);
|
||||
free((void*)temp);
|
||||
MEMFREE(temp);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
@ -264,7 +269,7 @@ void Fatal(struct Application *app, const char *message, ... ) {
|
||||
va_start(args, message);
|
||||
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||
app->sendMessageToBackend(&logbuffer[0]);
|
||||
free((void*)temp);
|
||||
MEMFREE(temp);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@ -323,21 +328,33 @@ void showContextMenu(struct Application *app, const char *contextMenuID) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("contextMenuID = %s\n", contextMenuID);
|
||||
|
||||
ON_MAIN_THREAD (
|
||||
// Look for the context menu for this ID
|
||||
id contextMenu = (id)hashmap_get(&contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||
|
||||
// Look for the context menu for this ID
|
||||
id contextMenu = (id)hashmap_get(&contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||
printf("CONTEXT MENU = %p\n", contextMenu);
|
||||
|
||||
// Grab the content view and show the menu
|
||||
id contentView = msg(app->mainWindow, s("contentView"));
|
||||
// Free menu id
|
||||
MEMFREE(contextMenuID);
|
||||
|
||||
// Get the triggering event
|
||||
id menuEvent = msg(app->mainWindow, s("currentEvent"));
|
||||
if( contextMenu == NULL ) {
|
||||
printf("\n\n\n\n\n\n\n");
|
||||
dumpHashmap("contextMenuMap", &contextMenuMap);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the content view and show the menu
|
||||
id contentView = msg(app->mainWindow, s("contentView"));
|
||||
printf("contentView = %p\n", contentView);
|
||||
|
||||
// Get the triggering event
|
||||
id menuEvent = msg(app->mainWindow, s("currentEvent"));
|
||||
printf("menuEvent = %p\n", menuEvent);
|
||||
|
||||
// Show popup
|
||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu, menuEvent, contentView);
|
||||
|
||||
// Show popup
|
||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu, menuEvent, contentView);
|
||||
);
|
||||
}
|
||||
|
||||
void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
|
||||
@ -353,13 +370,13 @@ void FullSizeContent(struct Application *app) {
|
||||
app->fullSizeContent = 1;
|
||||
}
|
||||
|
||||
void Hide(struct Application *app) {
|
||||
void Hide(struct Application *app) {
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->application, s("hide:"))
|
||||
);
|
||||
}
|
||||
|
||||
void Show(struct Application *app) {
|
||||
void Show(struct Application *app) {
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL);
|
||||
msg(app->application, s("activateIgnoringOtherApps:"), YES);
|
||||
@ -378,7 +395,9 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
if( strcmp(name, "completed") == 0) {
|
||||
// Delete handler
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
||||
Show(app);
|
||||
if (app->startHidden == 0) {
|
||||
Show(app);
|
||||
}
|
||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
||||
} else if( strcmp(name, "windowDrag") == 0 ) {
|
||||
// Guard against null events
|
||||
@ -397,6 +416,11 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
|
||||
const char *contextMenuMessage = cstr(msg(message, s("body")));
|
||||
|
||||
if( contextMenuMessage == NULL ) {
|
||||
printf("EMPTY CONTEXT MENU MESSAGE!!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the message
|
||||
JsonNode *contextMenuMessageJSON = json_decode(contextMenuMessage);
|
||||
if( contextMenuMessageJSON == NULL ) {
|
||||
@ -407,42 +431,34 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
// Get menu ID
|
||||
JsonNode *contextMenuIDNode = json_find_member(contextMenuMessageJSON, "id");
|
||||
if( contextMenuIDNode == NULL ) {
|
||||
Debug(app, "Error decoding context menu ID: %s", contextMenuMessage);
|
||||
return;
|
||||
}
|
||||
if( contextMenuIDNode->tag != JSON_STRING ) {
|
||||
Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage);
|
||||
return;
|
||||
}
|
||||
// TODO: Use the X & Y coordinates of the menu to programmatically open
|
||||
// the context menu at that point rather than relying on the current NSEvent.
|
||||
// I got it mostly working (IE not crashing) but the menu was invisible...
|
||||
// Revisit later
|
||||
Debug(app, "Error decoding context menu ID: %s", contextMenuMessage);
|
||||
return;
|
||||
}
|
||||
if( contextMenuIDNode->tag != JSON_STRING ) {
|
||||
Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// // Get menu X
|
||||
// JsonNode *contextMenuXNode = json_find_member(contextMenuMessageJSON, "x");
|
||||
// if( contextMenuXNode == NULL ) {
|
||||
// Debug(app, "Error decoding context menu X: %s", contextMenuMessage);
|
||||
// return;
|
||||
// }
|
||||
// if( contextMenuXNode->tag != JSON_NUMBER ) {
|
||||
// Debug(app, "Error decoding context menu X (Not a number): %s", contextMenuMessage);
|
||||
// return;
|
||||
// }
|
||||
// // Get menu Y
|
||||
// JsonNode *contextMenuYNode = json_find_member(contextMenuMessageJSON, "y");
|
||||
// if( contextMenuYNode == NULL ) {
|
||||
// Debug(app, "Error decoding context menu Y: %s", contextMenuMessage);
|
||||
// return;
|
||||
// }
|
||||
// if( contextMenuYNode->tag != JSON_NUMBER ) {
|
||||
// Debug(app, "Error decoding context menu Y (Not a number): %s", contextMenuMessage);
|
||||
// return;
|
||||
// }
|
||||
// Get menu Data
|
||||
JsonNode *contextMenuDataNode = json_find_member(contextMenuMessageJSON, "data");
|
||||
if( contextMenuDataNode == NULL ) {
|
||||
Debug(app, "Error decoding context menu data: %s", contextMenuMessage);
|
||||
return;
|
||||
}
|
||||
if( contextMenuDataNode->tag != JSON_STRING ) {
|
||||
Debug(app, "Error decoding context menu data (Not a string): %s", contextMenuMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
showContextMenu(app, contextMenuIDNode->string_);
|
||||
);
|
||||
// Save a copy of the context menu data
|
||||
if ( contextMenuData != NULL ) {
|
||||
MEMFREE(contextMenuData);
|
||||
}
|
||||
contextMenuData = STRCOPY(contextMenuDataNode->string_);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
showContextMenu(app, contextMenuIDNode->string_);
|
||||
);
|
||||
|
||||
} else {
|
||||
// const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String"));
|
||||
@ -451,40 +467,52 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createContextMenuMessage(const char *menuItemID, const char *contextMenuData) {
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
|
||||
json_append_member(jsonObject, "data", json_mkstring(contextMenuData));
|
||||
const char *result = json_encode(jsonObject);
|
||||
json_delete(jsonObject);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Callback for menu items
|
||||
void menuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
// Notify the backend
|
||||
const char *message = concat("MC", menuID);
|
||||
const char *message = concat("MC", menuItemID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
// Callback for tray items
|
||||
void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
// Notify the backend
|
||||
const char *message = concat("TC", menuID);
|
||||
const char *message = concat("TC", menuItemID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
|
||||
// Callback for context menu items
|
||||
void menuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
// Notify the backend
|
||||
const char *message = concat("XC", menuID);
|
||||
const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData);
|
||||
const char *message = concat("XC", contextMenuMessage);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
MEMFREE(contextMenuMessage);
|
||||
}
|
||||
|
||||
// Callback for menu items
|
||||
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
|
||||
// Get the menu item from the menu item map
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuID, strlen(menuID));
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Get the current state
|
||||
bool state = msg(menuItem, s("state"));
|
||||
@ -493,17 +521,17 @@ void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, stru
|
||||
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
|
||||
// Notify the backend
|
||||
const char *message = concat("MC", menuID);
|
||||
const char *message = concat("MC", menuItemID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
// Callback for tray menu items
|
||||
void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
|
||||
// Get the menu item from the menu item map
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuID, strlen(menuID));
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Get the current state
|
||||
bool state = msg(menuItem, s("state"));
|
||||
@ -512,17 +540,17 @@ void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hash
|
||||
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
|
||||
// Notify the backend
|
||||
const char *message = concat("TC", menuID);
|
||||
const char *message = concat("TC", menuItemID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
// Callback for context menu items
|
||||
void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
|
||||
// Get the menu item from the menu item map
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuID, strlen(menuID));
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Get the current state
|
||||
bool state = msg(menuItem, s("state"));
|
||||
@ -531,17 +559,19 @@ void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct
|
||||
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
|
||||
// Notify the backend
|
||||
const char *message = concat("XC", menuID);
|
||||
const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData);
|
||||
const char *message = concat("XC", contextMenuMessage);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
MEMFREE(contextMenuMessage);
|
||||
}
|
||||
|
||||
// radioMenuItemPressedForApplicationMenu
|
||||
void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
|
||||
// Get the menu item from the menu item map
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuID, strlen(menuID));
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Check the menu items' current state
|
||||
bool selected = msg(menuItem, s("state"));
|
||||
@ -552,7 +582,7 @@ void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
||||
}
|
||||
|
||||
// Get this item's radio group members and turn them off
|
||||
id *members = (id*)hashmap_get(&radioGroupMapForApplicationMenu, (char*)menuID, strlen(menuID));
|
||||
id *members = (id*)hashmap_get(&radioGroupMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Uncheck all members of the group
|
||||
id thisMember = members[0];
|
||||
@ -567,18 +597,18 @@ void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
||||
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||
|
||||
// Notify the backend
|
||||
const char *message = concat("MC", menuID);
|
||||
const char *message = concat("MC", menuItemID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
|
||||
// radioMenuItemPressedForTrayMenu
|
||||
void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
|
||||
// Get the menu item from the menu item map
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuID, strlen(menuID));
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Check the menu items' current state
|
||||
bool selected = msg(menuItem, s("state"));
|
||||
@ -589,7 +619,7 @@ void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||
}
|
||||
|
||||
// Get this item's radio group members and turn them off
|
||||
id *members = (id*)hashmap_get(&radioGroupMapForTrayMenu, (char*)menuID, strlen(menuID));
|
||||
id *members = (id*)hashmap_get(&radioGroupMapForTrayMenu, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Uncheck all members of the group
|
||||
id thisMember = members[0];
|
||||
@ -604,17 +634,17 @@ void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||
|
||||
// Notify the backend
|
||||
const char *message = concat("TC", menuID);
|
||||
const char *message = concat("TC", menuItemID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
// radioMenuItemPressedForContextMenus
|
||||
void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
|
||||
// Get the menu item from the menu item map
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuID, strlen(menuID));
|
||||
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Check the menu items' current state
|
||||
bool selected = msg(menuItem, s("state"));
|
||||
@ -625,7 +655,7 @@ void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
||||
}
|
||||
|
||||
// Get this item's radio group members and turn them off
|
||||
id *members = (id*)hashmap_get(&radioGroupMapForContextMenus, (char*)menuID, strlen(menuID));
|
||||
id *members = (id*)hashmap_get(&radioGroupMapForContextMenus, (char*)menuItemID, strlen(menuItemID));
|
||||
|
||||
// Uncheck all members of the group
|
||||
id thisMember = members[0];
|
||||
@ -639,10 +669,12 @@ void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
||||
// check the selected menu item
|
||||
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||
|
||||
// Notify the backend
|
||||
const char *message = concat("XC", menuID);
|
||||
messageFromWindowCallback(message);
|
||||
free((void*)message);
|
||||
// Notify the backend
|
||||
const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData);
|
||||
const char *message = concat("XC", contextMenuMessage);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
MEMFREE(contextMenuMessage);
|
||||
}
|
||||
|
||||
// closeWindow is called when the close button is pressed
|
||||
@ -651,6 +683,11 @@ void closeWindow(id self, SEL cmd, id sender) {
|
||||
app->sendMessageToBackend("WC");
|
||||
}
|
||||
|
||||
void willFinishLaunching(id self, SEL cmd, id sender) {
|
||||
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||
printf("\n\n\n\n\n\n\n\n\n\n\n\nI AM HERE!!!!!!!\n\n\n\n\n\n\n\n\n\n\n");
|
||||
}
|
||||
|
||||
bool isDarkMode(struct Application *app) {
|
||||
id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults"));
|
||||
const char *mode = cstr(msg(userDefaults, s("stringForKey:"), str("AppleInterfaceStyle")));
|
||||
@ -780,11 +817,12 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
|
||||
// Tray
|
||||
result->trayMenuAsJSON = NULL;
|
||||
result->processedTrayMenu = NULL;
|
||||
result->statusItem = NULL;
|
||||
result->processedTrayMenu = NULL;
|
||||
result->statusItem = NULL;
|
||||
|
||||
// Context Menus
|
||||
result->contextMenusAsJSON = NULL;
|
||||
// Context Menus
|
||||
result->contextMenusAsJSON = NULL;
|
||||
contextMenuData = NULL;
|
||||
|
||||
// Window Appearance
|
||||
result->vibrancyLayer = NULL;
|
||||
@ -818,8 +856,7 @@ void destroyMenu(struct Application *app) {
|
||||
|
||||
// Release the menu json if we have it
|
||||
if ( app->menuAsJSON != NULL ) {
|
||||
free((void*)app->menuAsJSON);
|
||||
app->menuAsJSON = NULL;
|
||||
MEMFREE(app->menuAsJSON);
|
||||
}
|
||||
|
||||
// Release processed menu
|
||||
@ -840,21 +877,27 @@ void destroyContextMenus(struct Application *app) {
|
||||
hashmap_destroy(&menuItemMapForContextMenus);
|
||||
|
||||
// Free radio group members
|
||||
if( hashmap_num_entries(&radioGroupMapForContextMenus) > 0 ) {
|
||||
if (0!=hashmap_iterate_pairs(&radioGroupMapForContextMenus, freeHashmapItem, NULL)) {
|
||||
Fatal(app, "failed to deallocate hashmap entries!");
|
||||
}
|
||||
}
|
||||
if( hashmap_num_entries(&radioGroupMapForContextMenus) > 0 ) {
|
||||
if (0!=hashmap_iterate_pairs(&radioGroupMapForContextMenus, freeHashmapItem, NULL)) {
|
||||
Fatal(app, "failed to deallocate hashmap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
//Free radio groups hashmap
|
||||
hashmap_destroy(&radioGroupMapForContextMenus);
|
||||
//Free radio groups hashmap
|
||||
hashmap_destroy(&radioGroupMapForContextMenus);
|
||||
|
||||
//Free context menu map
|
||||
hashmap_destroy(&contextMenuMap);
|
||||
//Free context menu map
|
||||
hashmap_destroy(&contextMenuMap);
|
||||
|
||||
// Destroy processed Context Menus
|
||||
if( app->processedContextMenus != NULL) {
|
||||
json_delete(app->processedContextMenus);
|
||||
app->processedContextMenus = NULL;
|
||||
}
|
||||
|
||||
// Release the menu json
|
||||
MEMFREE(app->contextMenusAsJSON);
|
||||
|
||||
// Destroy context menu JSON
|
||||
free((void*)app->contextMenusAsJSON);
|
||||
app->contextMenusAsJSON = NULL;
|
||||
}
|
||||
|
||||
|
||||
@ -878,12 +921,8 @@ void destroyTray(struct Application *app) {
|
||||
//Free radio groups hashmap
|
||||
hashmap_destroy(&radioGroupMapForTrayMenu);
|
||||
|
||||
// Free up the context menu map
|
||||
hashmap_destroy(&contextMenuMap);
|
||||
|
||||
// Release the menu json
|
||||
free((void*)app->trayMenuAsJSON);
|
||||
app->trayMenuAsJSON = NULL;
|
||||
MEMFREE(app->trayMenuAsJSON);
|
||||
|
||||
|
||||
// Release processed tray
|
||||
@ -893,16 +932,12 @@ void destroyTray(struct Application *app) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void DestroyApplication(struct Application *app) {
|
||||
Debug(app, "Destroying Application");
|
||||
|
||||
// Free the bindings
|
||||
if (app->bindings != NULL) {
|
||||
free((void*)app->bindings);
|
||||
app->bindings = NULL;
|
||||
MEMFREE(app->bindings);
|
||||
} else {
|
||||
Debug(app, "Almost a double free for app->bindings");
|
||||
}
|
||||
@ -924,6 +959,11 @@ void DestroyApplication(struct Application *app) {
|
||||
// Destroy the context menus
|
||||
destroyContextMenus(app);
|
||||
|
||||
// Clear context menu data if we have it
|
||||
if( contextMenuData != NULL ) {
|
||||
MEMFREE(contextMenuData);
|
||||
}
|
||||
|
||||
// Remove script handlers
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
||||
@ -995,13 +1035,13 @@ void ToggleMaximise(struct Application *app) {
|
||||
);
|
||||
}
|
||||
|
||||
void Maximise(struct Application *app) {
|
||||
void Maximise(struct Application *app) {
|
||||
if( app->maximised == 0) {
|
||||
ToggleMaximise(app);
|
||||
}
|
||||
}
|
||||
|
||||
void Unmaximise(struct Application *app) {
|
||||
void Unmaximise(struct Application *app) {
|
||||
if( app->maximised == 1) {
|
||||
ToggleMaximise(app);
|
||||
}
|
||||
@ -1035,7 +1075,7 @@ void dumpFrame(struct Application *app, const char *message, CGRect frame) {
|
||||
Debug(app, "size.height %f", frame.size.height);
|
||||
}
|
||||
|
||||
void SetSize(struct Application *app, int width, int height) {
|
||||
void SetSize(struct Application *app, int width, int height) {
|
||||
ON_MAIN_THREAD(
|
||||
id screen = getCurrentScreen(app);
|
||||
|
||||
@ -1051,7 +1091,7 @@ void SetSize(struct Application *app, int width, int height) {
|
||||
);
|
||||
}
|
||||
|
||||
void SetPosition(struct Application *app, int x, int y) {
|
||||
void SetPosition(struct Application *app, int x, int y) {
|
||||
ON_MAIN_THREAD(
|
||||
id screen = getCurrentScreen(app);
|
||||
CGRect screenFrame = GET_FRAME(screen);
|
||||
@ -1142,9 +1182,9 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
|
||||
app->sendMessageToBackend(responseMessage);
|
||||
|
||||
// Free memory
|
||||
free((void*)header);
|
||||
free((void*)callback);
|
||||
free((void*)responseMessage);
|
||||
MEMFREE(header);
|
||||
MEMFREE(callback);
|
||||
MEMFREE(responseMessage);
|
||||
});
|
||||
|
||||
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
|
||||
@ -1211,9 +1251,9 @@ void SaveDialog(struct Application *app, char *callbackID, char *title, char *fi
|
||||
app->sendMessageToBackend(responseMessage);
|
||||
|
||||
// Free memory
|
||||
free((void*)header);
|
||||
free((void*)callback);
|
||||
free((void*)responseMessage);
|
||||
MEMFREE(header);
|
||||
MEMFREE(callback);
|
||||
MEMFREE(responseMessage);
|
||||
});
|
||||
|
||||
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
|
||||
@ -1289,7 +1329,7 @@ void SetContextMenus(struct Application *app, const char *contextMenusAsJSON) {
|
||||
void SetBindings(struct Application *app, const char *bindings) {
|
||||
const char* temp = concat("window.wailsbindings = \"", bindings);
|
||||
const char* jscall = concat(temp, "\";");
|
||||
free((void*)temp);
|
||||
MEMFREE(temp);
|
||||
app->bindings = jscall;
|
||||
}
|
||||
|
||||
@ -1360,9 +1400,9 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
||||
app->sendMessageToBackend(responseMessage);
|
||||
|
||||
// Free memory
|
||||
free((void*)header);
|
||||
free((void*)callback);
|
||||
free((void*)responseMessage);
|
||||
MEMFREE(header);
|
||||
MEMFREE(callback);
|
||||
MEMFREE(responseMessage);
|
||||
);
|
||||
}
|
||||
|
||||
@ -1372,14 +1412,15 @@ void createDelegate(struct Application *app) {
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
|
||||
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// Menu Callbacks
|
||||
class_addMethod(delegateClass, s("menuCallbackForApplicationMenu:"), (IMP)menuItemPressedForApplicationMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("checkboxMenuCallbackForApplicationMenu:"), (IMP) checkboxMenuItemPressedForApplicationMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("radioMenuCallbackForApplicationMenu:"), (IMP) radioMenuItemPressedForApplicationMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("menuCallbackForTrayMenu:"), (IMP)menuItemPressedForTrayMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@");
|
||||
class_addMethod(delegateClass, s("menuCallbackForContextMenus:"), (IMP)menuItemPressedForContextMenus, "v@:@");
|
||||
class_addMethod(delegateClass, s("checkboxMenuCallbackForContextMenus:"), (IMP) checkboxMenuItemPressedForContextMenus, "v@:@");
|
||||
class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@");
|
||||
@ -1600,7 +1641,7 @@ const char* getJSONString(JsonNode *item, const char* key) {
|
||||
const char *result = "";
|
||||
if ( node != NULL && node->tag == JSON_STRING) {
|
||||
result = node->string_;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1609,7 +1650,7 @@ bool getJSONBool(JsonNode *item, const char* key, bool *result) {
|
||||
if ( node != NULL && node->tag == JSON_BOOL) {
|
||||
*result = node->bool_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1618,7 +1659,7 @@ bool getJSONInt(JsonNode *item, const char* key, int *result) {
|
||||
if ( node != NULL && node->tag == JSON_NUMBER) {
|
||||
*result = (int) node->number_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1832,7 +1873,7 @@ id parseTextMenuItem(struct Application *app, id parentMenu, const char *title,
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s(menuCallback), key);
|
||||
s(menuCallback), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
@ -1939,12 +1980,12 @@ struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
}
|
||||
|
||||
|
||||
bool disabled = false;
|
||||
getJSONBool(item, "Disabled", &disabled);
|
||||
|
||||
@ -2150,34 +2191,56 @@ void UpdateMenu(struct Application *app, const char *menuAsJSON) {
|
||||
);
|
||||
}
|
||||
|
||||
void dumpContextMenus(struct Application *app) {
|
||||
dumpHashmap("menuItemMapForContextMenus", &menuItemMapForContextMenus);
|
||||
printf("&menuItemMapForContextMenus = %p\n", &menuItemMapForContextMenus);
|
||||
|
||||
//Free radio groups hashmap
|
||||
dumpHashmap("radioGroupMapForContextMenus", &radioGroupMapForContextMenus);
|
||||
printf("&radioGroupMapForContextMenus = %p\n", &radioGroupMapForContextMenus);
|
||||
|
||||
//Free context menu map
|
||||
dumpHashmap("contextMenuMap", &contextMenuMap);
|
||||
printf("&contextMenuMap = %p\n", &contextMenuMap);
|
||||
}
|
||||
|
||||
void parseContextMenus(struct Application *app) {
|
||||
|
||||
// Allocation the hashmaps we need
|
||||
allocateContextMenuHashMaps(app);
|
||||
|
||||
// Parse the context menu json
|
||||
app->processedContextMenus = json_decode(app->contextMenusAsJSON);
|
||||
app->processedContextMenus = json_decode(app->contextMenusAsJSON);
|
||||
|
||||
if( app->processedContextMenus == NULL ) {
|
||||
// Parse error!
|
||||
Fatal(app, "Unable to parse Context Menus JSON: %s", app->contextMenusAsJSON);
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate context menus
|
||||
JsonNode *contextMenu;
|
||||
json_foreach(contextMenu, app->processedContextMenus) {
|
||||
// Create a new menu
|
||||
id menu = createMenu(str(""));
|
||||
|
||||
// parse the menu
|
||||
parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus,
|
||||
"checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:");
|
||||
|
||||
// Store the item in the context menu map
|
||||
hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu);
|
||||
if( app->processedContextMenus == NULL ) {
|
||||
// Parse error!
|
||||
Fatal(app, "Unable to parse Context Menus JSON: %s", app->contextMenusAsJSON);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode *contextMenuItems = json_find_member(app->processedContextMenus, "Items");
|
||||
if( contextMenuItems == NULL ) {
|
||||
// Parse error!
|
||||
Fatal(app, "Unable to find Items:", app->processedContextMenus);
|
||||
return;
|
||||
}
|
||||
// Iterate context menus
|
||||
JsonNode *contextMenu;
|
||||
json_foreach(contextMenu, contextMenuItems) {
|
||||
// Create a new menu
|
||||
id menu = createMenu(str(""));
|
||||
printf("Context menu NSMenu pointer = %p\n", menu);
|
||||
|
||||
// parse the menu
|
||||
parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus,
|
||||
"checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:");
|
||||
|
||||
// Store the item in the context menu map
|
||||
printf("Putting context menu %p with key '%s' in contextMenuMap %p\n", menu, contextMenu->key, &contextMenuMap);
|
||||
hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu);
|
||||
}
|
||||
|
||||
dumpContextMenus(app);
|
||||
}
|
||||
|
||||
void parseTrayData(struct Application *app) {
|
||||
@ -2186,26 +2249,26 @@ void parseTrayData(struct Application *app) {
|
||||
allocateTrayHashMaps(app);
|
||||
|
||||
// Create a new menu
|
||||
id traymenu = createMenu(str(""));
|
||||
id traymenu = createMenu(str(""));
|
||||
|
||||
id statusItem = app->statusItem;
|
||||
|
||||
// Create a new menu bar if we need to
|
||||
if ( statusItem == NULL ) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
statusItem = msg(statusBar, s("statusItemWithLength:"), -1.0);
|
||||
app->statusItem = statusItem;
|
||||
msg(statusItem, s("retain"));
|
||||
id statusBarButton = msg(statusItem, s("button"));
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
statusItem = msg(statusBar, s("statusItemWithLength:"), -1.0);
|
||||
app->statusItem = statusItem;
|
||||
msg(statusItem, s("retain"));
|
||||
id statusBarButton = msg(statusItem, s("button"));
|
||||
|
||||
// If we have a tray icon
|
||||
if ( trayIconLength > 0 ) {
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), trayIcon, trayIconLength);
|
||||
id trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
}
|
||||
}
|
||||
// If we have a tray icon
|
||||
if ( trayIconLength > 0 ) {
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), trayIcon, trayIconLength);
|
||||
id trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the processed menu json
|
||||
app->processedTrayMenu = json_decode(app->trayMenuAsJSON);
|
||||
@ -2250,7 +2313,7 @@ void parseTrayData(struct Application *app) {
|
||||
// msg(c("NSString"), s("stringWithUTF8String:"), tray->icon)));
|
||||
|
||||
|
||||
msg(statusItem, s("setMenu:"), traymenu);
|
||||
msg(statusItem, s("setMenu:"), traymenu);
|
||||
}
|
||||
|
||||
|
||||
@ -2266,6 +2329,20 @@ void UpdateTray(struct Application *app, const char *trayMenuAsJSON) {
|
||||
);
|
||||
}
|
||||
|
||||
void UpdateContextMenus(struct Application *app, const char *contextMenusAsJSON) {
|
||||
ON_MAIN_THREAD (
|
||||
|
||||
dumpContextMenus(app);
|
||||
|
||||
// Free up memory
|
||||
destroyContextMenus(app);
|
||||
|
||||
// Set the context menu JSON
|
||||
app->contextMenusAsJSON = contextMenusAsJSON;
|
||||
parseContextMenus(app);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void Run(struct Application *app, int argc, char **argv) {
|
||||
|
||||
@ -2378,11 +2455,11 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
// We want to evaluate the internal code plus runtime before the assets
|
||||
const char *temp = concat(invoke, app->bindings);
|
||||
const char *internalCode = concat(temp, (const char*)&runtime);
|
||||
free((void*)temp);
|
||||
MEMFREE(temp);
|
||||
|
||||
// Add code that sets up the initial state, EG: State Stores.
|
||||
temp = concat(internalCode, getInitialState(app));
|
||||
free((void*)internalCode);
|
||||
MEMFREE(internalCode);
|
||||
internalCode = temp;
|
||||
|
||||
// Loop over assets and build up one giant Mother Of All Evals
|
||||
@ -2397,7 +2474,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
}
|
||||
|
||||
temp = concat(internalCode, (const char *)asset);
|
||||
free((void*)internalCode);
|
||||
MEMFREE(internalCode);
|
||||
internalCode = temp;
|
||||
index++;
|
||||
};
|
||||
@ -2405,14 +2482,14 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
// Disable context menu if not in debug mode
|
||||
if( debug != 1 ) {
|
||||
temp = concat(internalCode, "wails._.DisableDefaultContextMenu();");
|
||||
free((void*)internalCode);
|
||||
MEMFREE(internalCode);
|
||||
internalCode = temp;
|
||||
}
|
||||
|
||||
// class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "@@:@");
|
||||
// Include callback after evaluation
|
||||
temp = concat(internalCode, "webkit.messageHandlers.completed.postMessage(true);");
|
||||
free((void*)internalCode);
|
||||
MEMFREE(internalCode);
|
||||
internalCode = temp;
|
||||
|
||||
// const char *viewportScriptString = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); meta.setAttribute('initial-scale', '1.0'); meta.setAttribute('maximum-scale', '1.0'); meta.setAttribute('minimum-scale', '1.0'); meta.setAttribute('user-scalable', 'no'); document.getElementsByTagName('head')[0].appendChild(meta);";
|
||||
@ -2449,11 +2526,14 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
parseContextMenus(app);
|
||||
}
|
||||
|
||||
// We set it to be invisible by default. It will become visible when everything has initialised
|
||||
msg(app->mainWindow, s("setIsVisible:"), NO);
|
||||
|
||||
// Finally call run
|
||||
Debug(app, "Run called");
|
||||
msg(app->application, s("run"));
|
||||
|
||||
free((void*)internalCode);
|
||||
MEMFREE(internalCode);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -21,6 +21,7 @@ extern void SetContextMenus(void *, const char *);
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
@ -116,6 +117,7 @@ func (a *Application) processPlatformSettings() error {
|
||||
contextMenus := options.GetContextMenus(a.config)
|
||||
if contextMenus != nil {
|
||||
contextMenusJSON, err := json.Marshal(contextMenus)
|
||||
fmt.Printf("\n\nCONTEXT MENUS:\n %+v\n\n", string(contextMenusJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ type Client interface {
|
||||
DarkModeEnabled(callbackID string)
|
||||
UpdateMenu(menu *menu.Menu)
|
||||
UpdateTray(menu *menu.Menu)
|
||||
UpdateContextMenus(contextMenus *menu.ContextMenus)
|
||||
}
|
||||
|
||||
// DispatchClient is what the frontends use to interface with the
|
||||
|
43
v2/internal/messagedispatcher/message/contextmenus.go
Normal file
43
v2/internal/messagedispatcher/message/contextmenus.go
Normal file
@ -0,0 +1,43 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
// ContextMenusOnMessage is used to emit listener registration requests
|
||||
// on the service bus
|
||||
type ContextMenusOnMessage struct {
|
||||
// MenuID is the id of the menu item we are interested in
|
||||
MenuID string
|
||||
// Callback is called when the menu is clicked
|
||||
Callback func(*menu.MenuItem, string)
|
||||
}
|
||||
|
||||
// contextMenusMessageParser does what it says on the tin!
|
||||
func contextMenusMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// Sanity check: Menu messages must be at least 2 bytes
|
||||
if len(message) < 3 {
|
||||
return nil, fmt.Errorf("context menus message was an invalid length")
|
||||
}
|
||||
|
||||
var topic string
|
||||
var data interface{}
|
||||
|
||||
// Switch the message type
|
||||
switch message[1] {
|
||||
case 'C':
|
||||
contextMenuData := message[2:]
|
||||
topic = "contextmenus:clicked"
|
||||
data = contextMenuData
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||
}
|
||||
|
||||
// Create a new parsed message struct
|
||||
parsedMessage := &parsedMessage{Topic: topic, Data: data}
|
||||
|
||||
return parsedMessage, nil
|
||||
}
|
@ -20,6 +20,7 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
||||
'S': systemMessageParser,
|
||||
'M': menuMessageParser,
|
||||
'T': trayMessageParser,
|
||||
'X': contextMenusMessageParser,
|
||||
}
|
||||
|
||||
// Parse will attempt to parse the given message
|
||||
|
@ -20,7 +20,7 @@ func trayMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// Sanity check: Menu messages must be at least 2 bytes
|
||||
if len(message) < 3 {
|
||||
return nil, fmt.Errorf("event message was an invalid length")
|
||||
return nil, fmt.Errorf("tray message was an invalid length")
|
||||
}
|
||||
|
||||
var topic string
|
||||
|
@ -17,15 +17,16 @@ import (
|
||||
// Dispatcher translates messages received from the frontend
|
||||
// and publishes them onto the service bus
|
||||
type Dispatcher struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
resultChannel <-chan *servicebus.Message
|
||||
eventChannel <-chan *servicebus.Message
|
||||
windowChannel <-chan *servicebus.Message
|
||||
dialogChannel <-chan *servicebus.Message
|
||||
systemChannel <-chan *servicebus.Message
|
||||
menuChannel <-chan *servicebus.Message
|
||||
trayChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
quitChannel <-chan *servicebus.Message
|
||||
resultChannel <-chan *servicebus.Message
|
||||
eventChannel <-chan *servicebus.Message
|
||||
windowChannel <-chan *servicebus.Message
|
||||
dialogChannel <-chan *servicebus.Message
|
||||
systemChannel <-chan *servicebus.Message
|
||||
menuChannel <-chan *servicebus.Message
|
||||
contextMenuChannel <-chan *servicebus.Message
|
||||
trayChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger logger.CustomLogger
|
||||
@ -77,23 +78,29 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contextMenuChannel, err := servicebus.Subscribe("contextmenufrontend:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trayChannel, err := servicebus.Subscribe("trayfrontend:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &Dispatcher{
|
||||
servicebus: servicebus,
|
||||
eventChannel: eventChannel,
|
||||
logger: logger.CustomLogger("Message Dispatcher"),
|
||||
clients: make(map[string]*DispatchClient),
|
||||
resultChannel: resultChannel,
|
||||
quitChannel: quitChannel,
|
||||
windowChannel: windowChannel,
|
||||
dialogChannel: dialogChannel,
|
||||
systemChannel: systemChannel,
|
||||
menuChannel: menuChannel,
|
||||
trayChannel: trayChannel,
|
||||
servicebus: servicebus,
|
||||
eventChannel: eventChannel,
|
||||
logger: logger.CustomLogger("Message Dispatcher"),
|
||||
clients: make(map[string]*DispatchClient),
|
||||
resultChannel: resultChannel,
|
||||
quitChannel: quitChannel,
|
||||
windowChannel: windowChannel,
|
||||
dialogChannel: dialogChannel,
|
||||
systemChannel: systemChannel,
|
||||
menuChannel: menuChannel,
|
||||
trayChannel: trayChannel,
|
||||
contextMenuChannel: contextMenuChannel,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@ -125,6 +132,8 @@ func (d *Dispatcher) Start() error {
|
||||
d.processSystemMessage(systemMessage)
|
||||
case menuMessage := <-d.menuChannel:
|
||||
d.processMenuMessage(menuMessage)
|
||||
case contextMenuMessage := <-d.contextMenuChannel:
|
||||
d.processContextMenuMessage(contextMenuMessage)
|
||||
case trayMessage := <-d.trayChannel:
|
||||
d.processTrayMessage(trayMessage)
|
||||
}
|
||||
@ -446,6 +455,34 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
}
|
||||
}
|
||||
func (d *Dispatcher) processContextMenuMessage(result *servicebus.Message) {
|
||||
splitTopic := strings.Split(result.Topic(), ":")
|
||||
if len(splitTopic) < 2 {
|
||||
d.logger.Error("Invalid contextmenu message : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
command := splitTopic[1]
|
||||
switch command {
|
||||
case "update":
|
||||
|
||||
updatedContextMenus, ok := result.Data().(*menu.ContextMenus)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'contextmenufrontend:update' : %#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.UpdateContextMenus(updatedContextMenus)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown contextmenufrontend command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) processTrayMessage(result *servicebus.Message) {
|
||||
splitTopic := strings.Split(result.Topic(), ":")
|
||||
|
48
v2/internal/runtime/contextmenus.go
Normal file
48
v2/internal/runtime/contextmenus.go
Normal file
@ -0,0 +1,48 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
// ContextMenus defines all ContextMenu related operations
|
||||
type ContextMenus interface {
|
||||
On(menuID string, callback func(*menu.MenuItem, string))
|
||||
Update()
|
||||
GetByID(menuID string) *menu.MenuItem
|
||||
RemoveByID(id string) bool
|
||||
}
|
||||
|
||||
type contextMenus struct {
|
||||
bus *servicebus.ServiceBus
|
||||
contextmenus *menu.ContextMenus
|
||||
}
|
||||
|
||||
// newContextMenus creates a new ContextMenu struct
|
||||
func newContextMenus(bus *servicebus.ServiceBus, contextmenus *menu.ContextMenus) ContextMenus {
|
||||
return &contextMenus{
|
||||
bus: bus,
|
||||
contextmenus: contextmenus,
|
||||
}
|
||||
}
|
||||
|
||||
// On registers a listener for a particular event
|
||||
func (t *contextMenus) On(menuID string, callback func(*menu.MenuItem, string)) {
|
||||
t.bus.Publish("contextmenus:on", &message.ContextMenusOnMessage{
|
||||
MenuID: menuID,
|
||||
Callback: callback,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *contextMenus) Update() {
|
||||
t.bus.Publish("contextmenus:update", t.contextmenus)
|
||||
}
|
||||
|
||||
func (t *contextMenus) GetByID(menuItemID string) *menu.MenuItem {
|
||||
return t.contextmenus.GetByID(menuItemID)
|
||||
}
|
||||
|
||||
func (t *contextMenus) RemoveByID(menuItemID string) bool {
|
||||
return t.contextmenus.RemoveByID(menuItemID)
|
||||
}
|
@ -57,10 +57,10 @@ export function Init() {
|
||||
e.preventDefault();
|
||||
}
|
||||
if( contextMenuId != null ) {
|
||||
let contextData = currentElement.dataset['wails-context-menu-data'];
|
||||
let message = {
|
||||
id: contextMenuId,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
data: contextData || "",
|
||||
};
|
||||
window.webkit.messageHandlers.contextMenu.postMessage(JSON.stringify(message));
|
||||
}
|
||||
|
@ -7,30 +7,32 @@ import (
|
||||
|
||||
// Runtime is a means for the user to interact with the application at runtime
|
||||
type Runtime struct {
|
||||
Browser Browser
|
||||
Events Events
|
||||
Window Window
|
||||
Dialog Dialog
|
||||
System System
|
||||
Menu Menu
|
||||
Tray Tray
|
||||
Store *StoreProvider
|
||||
Log Log
|
||||
bus *servicebus.ServiceBus
|
||||
Browser Browser
|
||||
Events Events
|
||||
Window Window
|
||||
Dialog Dialog
|
||||
System System
|
||||
Menu Menu
|
||||
ContextMenu ContextMenus
|
||||
Tray Tray
|
||||
Store *StoreProvider
|
||||
Log Log
|
||||
bus *servicebus.ServiceBus
|
||||
}
|
||||
|
||||
// New creates a new runtime
|
||||
func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Menu) *Runtime {
|
||||
func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Menu, contextMenus *menu.ContextMenus) *Runtime {
|
||||
result := &Runtime{
|
||||
Browser: newBrowser(),
|
||||
Events: newEvents(serviceBus),
|
||||
Window: newWindow(serviceBus),
|
||||
Dialog: newDialog(serviceBus),
|
||||
System: newSystem(serviceBus),
|
||||
Menu: newMenu(serviceBus, menu),
|
||||
Tray: newTray(serviceBus, trayMenu),
|
||||
Log: newLog(serviceBus),
|
||||
bus: serviceBus,
|
||||
Browser: newBrowser(),
|
||||
Events: newEvents(serviceBus),
|
||||
Window: newWindow(serviceBus),
|
||||
Dialog: newDialog(serviceBus),
|
||||
System: newSystem(serviceBus),
|
||||
Menu: newMenu(serviceBus, menu),
|
||||
Tray: newTray(serviceBus, trayMenu),
|
||||
ContextMenu: newContextMenus(serviceBus, contextMenus),
|
||||
Log: newLog(serviceBus),
|
||||
bus: serviceBus,
|
||||
}
|
||||
result.Store = newStore(result)
|
||||
return result
|
||||
|
@ -20,7 +20,7 @@ type trayRuntime struct {
|
||||
}
|
||||
|
||||
// newTray creates a new Menu struct
|
||||
func newTray(bus *servicebus.ServiceBus, menu *menu.Menu) Menu {
|
||||
func newTray(bus *servicebus.ServiceBus, menu *menu.Menu) Tray {
|
||||
return &trayRuntime{
|
||||
bus: bus,
|
||||
trayMenu: menu,
|
||||
|
200
v2/internal/subsystem/contextmenus.go
Normal file
200
v2/internal/subsystem/contextmenus.go
Normal file
@ -0,0 +1,200 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"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/menu"
|
||||
)
|
||||
|
||||
// ContextMenus is the subsystem that handles the operation of context menus. It manages all service bus messages
|
||||
// starting with "contextmenus".
|
||||
type ContextMenus struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
menuChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// Event listeners
|
||||
listeners map[string][]func(*menu.MenuItem, string)
|
||||
menuItems map[string]*menu.MenuItem
|
||||
notifyLock sync.RWMutex
|
||||
|
||||
// logger
|
||||
logger logger.CustomLogger
|
||||
|
||||
// The context menus
|
||||
contextMenus *menu.ContextMenus
|
||||
|
||||
// Service Bus
|
||||
bus *servicebus.ServiceBus
|
||||
}
|
||||
|
||||
// NewContextMenus creates a new context menu subsystem
|
||||
func NewContextMenus(contextMenus *menu.ContextMenus, bus *servicebus.ServiceBus, logger *logger.Logger) (*ContextMenus, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to menu messages
|
||||
menuChannel, err := bus.Subscribe("contextmenus:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &ContextMenus{
|
||||
quitChannel: quitChannel,
|
||||
menuChannel: menuChannel,
|
||||
logger: logger.CustomLogger("Context Menu Subsystem"),
|
||||
listeners: make(map[string][]func(*menu.MenuItem, string)),
|
||||
menuItems: make(map[string]*menu.MenuItem),
|
||||
contextMenus: contextMenus,
|
||||
bus: bus,
|
||||
}
|
||||
|
||||
// Build up list of item/id pairs
|
||||
result.processContextMenus(contextMenus)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type contextMenuData struct {
|
||||
MenuItemID string `json:"menuItemID"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// Start the subsystem
|
||||
func (c *ContextMenus) Start() error {
|
||||
|
||||
c.logger.Trace("Starting")
|
||||
|
||||
c.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for c.running {
|
||||
select {
|
||||
case <-c.quitChannel:
|
||||
c.running = false
|
||||
break
|
||||
case menuMessage := <-c.menuChannel:
|
||||
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||
menuMessageType := splitTopic[1]
|
||||
switch menuMessageType {
|
||||
case "clicked":
|
||||
if len(splitTopic) != 2 {
|
||||
c.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||
continue
|
||||
}
|
||||
c.logger.Trace("Got Context Menu clicked Message: %s %+v", menuMessage.Topic(), menuMessage.Data())
|
||||
contextMenuDataJSON := menuMessage.Data().(string)
|
||||
|
||||
var data contextMenuData
|
||||
err := json.Unmarshal([]byte(contextMenuDataJSON), &data)
|
||||
if err != nil {
|
||||
c.logger.Trace("Cannot process contextMenuDataJSON %s", string(contextMenuDataJSON))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the menu item
|
||||
menuItem := c.menuItems[data.MenuItemID]
|
||||
if menuItem == nil {
|
||||
c.logger.Trace("Cannot process menuitem id %s - unknown", data.MenuItemID)
|
||||
return
|
||||
}
|
||||
|
||||
// Is the menu item a checkbox?
|
||||
if menuItem.Type == menu.CheckboxType {
|
||||
// Toggle state
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
}
|
||||
|
||||
// Notify listeners
|
||||
c.notifyListeners(data, menuItem)
|
||||
case "on":
|
||||
listenerDetails := menuMessage.Data().(*message.ContextMenusOnMessage)
|
||||
id := listenerDetails.MenuID
|
||||
c.listeners[id] = append(c.listeners[id], listenerDetails.Callback)
|
||||
|
||||
// Make sure we catch any menu updates
|
||||
case "update":
|
||||
updatedMenu := menuMessage.Data().(*menu.ContextMenus)
|
||||
c.processContextMenus(updatedMenu)
|
||||
|
||||
// Notify frontend of menu change
|
||||
c.bus.Publish("contextmenufrontend:update", updatedMenu)
|
||||
|
||||
default:
|
||||
c.logger.Error("unknown context menu message: %+v", menuMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
c.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContextMenus) processContextMenus(contextMenus *menu.ContextMenus) {
|
||||
// Initialise the variables
|
||||
c.menuItems = make(map[string]*menu.MenuItem)
|
||||
c.contextMenus = contextMenus
|
||||
|
||||
for _, contextMenu := range contextMenus.Items {
|
||||
for _, item := range contextMenu.Items {
|
||||
c.processMenuItem(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ContextMenus) processMenuItem(item *menu.MenuItem) {
|
||||
|
||||
if item.SubMenu != nil {
|
||||
for _, submenuitem := range item.SubMenu {
|
||||
c.processMenuItem(submenuitem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if item.ID != "" {
|
||||
if c.menuItems[item.ID] != nil {
|
||||
c.logger.Error("Context Menu id '%s' is used by multiple menu items: %s %s", c.menuItems[item.ID].Label, item.Label)
|
||||
return
|
||||
}
|
||||
c.menuItems[item.ID] = item
|
||||
}
|
||||
}
|
||||
|
||||
// Notifies listeners that the given menu was clicked
|
||||
func (c *ContextMenus) notifyListeners(contextData contextMenuData, menuItem *menu.MenuItem) {
|
||||
|
||||
// Get list of menu listeners
|
||||
listeners := c.listeners[contextData.MenuItemID]
|
||||
if listeners == nil {
|
||||
c.logger.Trace("No listeners for MenuItem with ID '%s'", contextData.MenuItemID)
|
||||
return
|
||||
}
|
||||
|
||||
// Lock the listeners
|
||||
c.notifyLock.Lock()
|
||||
|
||||
// Callback in goroutine
|
||||
for _, listener := range listeners {
|
||||
go listener(menuItem, contextData.Data)
|
||||
}
|
||||
|
||||
// Unlock
|
||||
c.notifyLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ContextMenus) shutdown() {
|
||||
c.logger.Trace("Shutdown")
|
||||
}
|
@ -24,7 +24,7 @@ type Runtime struct {
|
||||
}
|
||||
|
||||
// NewRuntime creates a new runtime subsystem
|
||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu, trayMenu *menu.Menu) (*Runtime, error) {
|
||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu, trayMenu *menu.Menu, contextMenus *menu.ContextMenus) (*Runtime, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
@ -42,7 +42,7 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Me
|
||||
quitChannel: quitChannel,
|
||||
runtimeChannel: runtimeChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus, menu, trayMenu),
|
||||
runtime: runtime.New(bus, menu, trayMenu, contextMenus),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
38
v2/pkg/menu/contextmenu.go
Normal file
38
v2/pkg/menu/contextmenu.go
Normal file
@ -0,0 +1,38 @@
|
||||
package menu
|
||||
|
||||
type ContextMenus struct {
|
||||
Items map[string]*Menu
|
||||
}
|
||||
|
||||
func NewContextMenus() *ContextMenus {
|
||||
return &ContextMenus{
|
||||
Items: make(map[string]*Menu),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ContextMenus) AddMenu(ID string, menu *Menu) {
|
||||
c.Items[ID] = menu
|
||||
}
|
||||
|
||||
func (c *ContextMenus) GetByID(menuID string) *MenuItem {
|
||||
|
||||
// Loop over menu items
|
||||
for _, item := range c.Items {
|
||||
result := item.GetByID(menuID)
|
||||
if result != nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContextMenus) RemoveByID(id string) bool {
|
||||
// Loop over menu items
|
||||
for _, item := range c.Items {
|
||||
result := item.RemoveByID(id)
|
||||
if result == true {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -10,5 +10,5 @@ type Options struct {
|
||||
WindowBackgroundIsTranslucent bool
|
||||
Menu *menu.Menu
|
||||
Tray *menu.Menu
|
||||
ContextMenus map[string]*menu.Menu
|
||||
ContextMenus *menu.ContextMenus
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ type App struct {
|
||||
StartHidden bool
|
||||
DevTools bool
|
||||
RGBA int
|
||||
ContextMenus map[string]*menu.Menu
|
||||
ContextMenus *menu.ContextMenus
|
||||
Tray *menu.Menu
|
||||
Menu *menu.Menu
|
||||
Mac *mac.Options
|
||||
@ -97,11 +97,11 @@ func GetApplicationMenu(appoptions *App) *menu.Menu {
|
||||
return result
|
||||
}
|
||||
|
||||
func GetContextMenus(appoptions *App) map[string]*menu.Menu {
|
||||
var result map[string]*menu.Menu
|
||||
func GetContextMenus(appoptions *App) *menu.ContextMenus {
|
||||
var result *menu.ContextMenus
|
||||
|
||||
result = appoptions.ContextMenus
|
||||
var contextMenuOverrides map[string]*menu.Menu
|
||||
var contextMenuOverrides *menu.ContextMenus
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
if appoptions.Mac != nil {
|
||||
@ -118,8 +118,10 @@ func GetContextMenus(appoptions *App) map[string]*menu.Menu {
|
||||
}
|
||||
|
||||
// Overwrite defaults with OS Specific context menus
|
||||
for id, contextMenu := range contextMenuOverrides {
|
||||
result[id] = contextMenu
|
||||
if contextMenuOverrides != nil {
|
||||
for id, contextMenu := range contextMenuOverrides.Items {
|
||||
result.AddMenu(id, contextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
40
v2/test/kitchensink/contextmenus.go
Normal file
40
v2/test/kitchensink/contextmenus.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
// ContextMenu struct
|
||||
type ContextMenu struct {
|
||||
runtime *wails.Runtime
|
||||
counter int
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// WailsInit is called at application startup
|
||||
func (c *ContextMenu) WailsInit(runtime *wails.Runtime) error {
|
||||
// Perform your setup here
|
||||
c.runtime = runtime
|
||||
|
||||
// Setup Menu Listeners
|
||||
c.runtime.ContextMenu.On("Test Context Menu", func(mi *menu.MenuItem, contextData string) {
|
||||
fmt.Printf("\n\nContext Data = '%s'\n\n", contextData)
|
||||
c.lock.Lock()
|
||||
c.counter++
|
||||
mi.Label = fmt.Sprintf("Clicked %d times", c.counter)
|
||||
c.lock.Unlock()
|
||||
c.runtime.ContextMenu.Update()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createContextMenus() *menu.ContextMenus {
|
||||
result := menu.NewContextMenus()
|
||||
result.AddMenu("test", menu.NewMenuFromItems(menu.Text("Clicked 0 times", "Test Context Menu")))
|
||||
return result
|
||||
}
|
@ -43,7 +43,7 @@
|
||||
<!-- Sticky alerts (toasts), empty container -->
|
||||
<div class="sticky-alerts"></div>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar noselect" data-wails-context-menu-id="test">
|
||||
<div class="sidebar noselect" data-wails-context-menu-id="test" data-wails-context-menu-data="hello!">
|
||||
<div data-wails-no-drag class="sidebar-menu">
|
||||
<!-- Sidebar brand -->
|
||||
<div on:click="{ homepageClicked }" class="sidebar-brand">
|
||||
|
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
"log"
|
||||
@ -20,9 +19,8 @@ func main() {
|
||||
MinHeight: 600,
|
||||
//Tray: menu.NewMenuFromItems(menu.AppMenu()),
|
||||
//Menu: menu.NewMenuFromItems(menu.AppMenu()),
|
||||
ContextMenus: map[string]*menu.Menu{
|
||||
"test": menu.NewMenuFromItems(menu.Text("Test Menu", "Test Context Menu")),
|
||||
},
|
||||
StartHidden: true,
|
||||
ContextMenus: createContextMenus(),
|
||||
Mac: &mac.Options{
|
||||
WebviewIsTransparent: true,
|
||||
WindowBackgroundIsTranslucent: true,
|
||||
@ -46,6 +44,7 @@ func main() {
|
||||
app.Bind(&Window{})
|
||||
app.Bind(&Menu{})
|
||||
app.Bind(&Tray{})
|
||||
app.Bind(&ContextMenu{})
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user