mirror of
https://github.com/netbirdio/systray.git
synced 2026-05-22 17:08:41 -07:00
Merge pull request #131 from oxtoacart/nested-menus
Support for nested sub-menus on OS X
This commit is contained in:
+27
-9
@@ -44,6 +44,12 @@ func onReady() {
|
||||
mEnabled.SetTemplateIcon(icon.Data, icon.Data)
|
||||
|
||||
systray.AddMenuItem("Ignored", "Ignored")
|
||||
|
||||
subMenuTop := systray.AddMenuItem("SubMenu", "SubMenu Test (top)")
|
||||
subMenuMiddle := subMenuTop.AddSubMenuItem("SubMenu - Level 2", "SubMenu Test (middle)")
|
||||
subMenuBottom := subMenuMiddle.AddSubMenuItem("SubMenu - Level 3", "SubMenu Test (bottom)")
|
||||
subMenuBottom2 := subMenuMiddle.AddSubMenuItem("Panic!", "SubMenu Test (bottom)")
|
||||
|
||||
mUrl := systray.AddMenuItem("Open UI", "my home")
|
||||
mQuit := systray.AddMenuItem("退出", "Quit the whole app")
|
||||
|
||||
@@ -53,6 +59,22 @@ func onReady() {
|
||||
systray.AddSeparator()
|
||||
mToggle := systray.AddMenuItem("Toggle", "Toggle the Quit button")
|
||||
shown := true
|
||||
toggle := func() {
|
||||
if shown {
|
||||
subMenuBottom.Check()
|
||||
subMenuBottom2.Hide()
|
||||
mQuitOrig.Hide()
|
||||
mEnabled.Hide()
|
||||
shown = false
|
||||
} else {
|
||||
subMenuBottom.Uncheck()
|
||||
subMenuBottom2.Show()
|
||||
mQuitOrig.Show()
|
||||
mEnabled.Show()
|
||||
shown = true
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mChange.ClickedCh:
|
||||
@@ -70,16 +92,12 @@ func onReady() {
|
||||
mEnabled.Disable()
|
||||
case <-mUrl.ClickedCh:
|
||||
systray.ShowAppWindow("https://www.github.com/getlantern/lantern")
|
||||
case <-subMenuBottom2.ClickedCh:
|
||||
panic("panic button pressed")
|
||||
case <-subMenuBottom.ClickedCh:
|
||||
toggle()
|
||||
case <-mToggle.ClickedCh:
|
||||
if shown {
|
||||
mQuitOrig.Hide()
|
||||
mEnabled.Hide()
|
||||
shown = false
|
||||
} else {
|
||||
mQuitOrig.Show()
|
||||
mEnabled.Show()
|
||||
shown = true
|
||||
}
|
||||
toggle()
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
fmt.Println("Quit2 now...")
|
||||
|
||||
@@ -28,6 +28,7 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgF
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
|
||||
+38
-10
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
Package systray is a cross platfrom Go library to place an icon and menu in the
|
||||
notification area.
|
||||
Supports Windows, Mac OSX and Linux currently.
|
||||
Package systray is a cross-platform Go library to place an icon and menu in the notification area.
|
||||
|
||||
Methods can be called from any goroutine except Run(), which should be called
|
||||
at the very beginning of main() to lock at main thread.
|
||||
*/
|
||||
package systray
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -36,6 +36,28 @@ type MenuItem struct {
|
||||
disabled bool
|
||||
// checked menu item has a tick before the title
|
||||
checked bool
|
||||
// parent item, for sub menus
|
||||
parent *MenuItem
|
||||
}
|
||||
|
||||
func (item *MenuItem) String() string {
|
||||
if item.parent == nil {
|
||||
return fmt.Sprintf("MenuItem[%d, %q]", item.id, item.title)
|
||||
}
|
||||
return fmt.Sprintf("MenuItem[%d, parent %d, %q]", item.id, item.parent.id, item.title)
|
||||
}
|
||||
|
||||
// newMenuItem returns a populated MenuItem object
|
||||
func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
|
||||
return &MenuItem{
|
||||
ClickedCh: make(chan struct{}),
|
||||
id: atomic.AddInt32(¤tID, 1),
|
||||
title: title,
|
||||
tooltip: tooltip,
|
||||
disabled: false,
|
||||
checked: false,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -92,14 +114,11 @@ func Quit() {
|
||||
}
|
||||
}
|
||||
|
||||
// AddMenuItem adds menu item with designated title and tooltip, returning a channel
|
||||
// that notifies whenever that menu item is clicked.
|
||||
// AddMenuItem adds a menu item with the designated title and tooltip.
|
||||
//
|
||||
// It can be safely invoked from different goroutines.
|
||||
func AddMenuItem(title string, tooltip string) *MenuItem {
|
||||
id := atomic.AddInt32(¤tID, 1)
|
||||
item := &MenuItem{nil, id, title, tooltip, false, false}
|
||||
item.ClickedCh = make(chan struct{})
|
||||
item := newMenuItem(title, tooltip, nil)
|
||||
item.update()
|
||||
return item
|
||||
}
|
||||
@@ -109,6 +128,15 @@ func AddSeparator() {
|
||||
addSeparator(atomic.AddInt32(¤tID, 1))
|
||||
}
|
||||
|
||||
// AddSubMenuItem adds a nested sub-menu item with the designated title and tooltip.
|
||||
//
|
||||
// It can be safely invoked from different goroutines.
|
||||
func (item *MenuItem) AddSubMenuItem(title string, tooltip string) *MenuItem {
|
||||
child := newMenuItem(title, tooltip, item)
|
||||
child.update()
|
||||
return child
|
||||
}
|
||||
|
||||
// SetTitle set the text to display on a menu item
|
||||
func (item *MenuItem) SetTitle(title string) {
|
||||
item.title = title
|
||||
@@ -121,7 +149,7 @@ func (item *MenuItem) SetTooltip(tooltip string) {
|
||||
item.update()
|
||||
}
|
||||
|
||||
// Disabled checkes if the menu item is disabled
|
||||
// Disabled checks if the menu item is disabled
|
||||
func (item *MenuItem) Disabled() bool {
|
||||
return item.disabled
|
||||
}
|
||||
@@ -165,7 +193,7 @@ func (item *MenuItem) Uncheck() {
|
||||
item.update()
|
||||
}
|
||||
|
||||
// update propogates changes on a menu item to systray
|
||||
// update propagates changes on a menu item to systray
|
||||
func (item *MenuItem) update() {
|
||||
menuItemsLock.Lock()
|
||||
defer menuItemsLock.Unlock()
|
||||
|
||||
@@ -11,8 +11,8 @@ void setTitle(char* title);
|
||||
void setTooltip(char* tooltip);
|
||||
void configureAppWindow(char* title, int width, int height);
|
||||
void showAppWindow(char* url);
|
||||
void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked);
|
||||
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked);
|
||||
void add_separator(int menuId);
|
||||
void hide_menu_item(int menuId);
|
||||
void show_menu_item(int menuId);
|
||||
void quit();
|
||||
void quit();
|
||||
+57
-30
@@ -17,12 +17,14 @@
|
||||
{
|
||||
@public
|
||||
NSNumber* menuId;
|
||||
NSNumber* parentMenuId;
|
||||
NSString* title;
|
||||
NSString* tooltip;
|
||||
short disabled;
|
||||
short checked;
|
||||
}
|
||||
-(id) initWithId: (int)theMenuId
|
||||
withParentMenuId: (int)theParentMenuId
|
||||
withTitle: (const char*)theTitle
|
||||
withTooltip: (const char*)theTooltip
|
||||
withDisabled: (short)theDisabled
|
||||
@@ -30,12 +32,14 @@
|
||||
@end
|
||||
@implementation MenuItem
|
||||
-(id) initWithId: (int)theMenuId
|
||||
withParentMenuId: (int)theParentMenuId
|
||||
withTitle: (const char*)theTitle
|
||||
withTooltip: (const char*)theTooltip
|
||||
withDisabled: (short)theDisabled
|
||||
withChecked: (short)theChecked
|
||||
{
|
||||
menuId = [NSNumber numberWithInt:theMenuId];
|
||||
parentMenuId = [NSNumber numberWithInt:theParentMenuId];
|
||||
title = [[NSString alloc] initWithCString:theTitle
|
||||
encoding:NSUTF8StringEncoding];
|
||||
tooltip = [[NSString alloc] initWithCString:theTooltip
|
||||
@@ -113,20 +117,30 @@
|
||||
systray_menu_item_selected(menuId.intValue);
|
||||
}
|
||||
|
||||
- (void) add_or_update_menu_item:(MenuItem*) item
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: item->menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
menuItem = [menu addItemWithTitle:item->title action:@selector(menuHandler:) keyEquivalent:@""];
|
||||
[menuItem setTarget:self];
|
||||
[menuItem setRepresentedObject: item->menuId];
|
||||
|
||||
- (void)add_or_update_menu_item:(MenuItem *)item {
|
||||
NSMenu *theMenu = self->menu;
|
||||
NSMenuItem *parentItem;
|
||||
if ([item->parentMenuId integerValue] > 0) {
|
||||
parentItem = find_menu_item(menu, item->parentMenuId);
|
||||
if (parentItem.hasSubmenu) {
|
||||
theMenu = parentItem.submenu;
|
||||
} else {
|
||||
theMenu = [[NSMenu alloc] init];
|
||||
[parentItem setSubmenu:theMenu];
|
||||
}
|
||||
}
|
||||
else {
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
[menuItem setTitle:item->title];
|
||||
|
||||
NSMenuItem *menuItem;
|
||||
menuItem = find_menu_item(theMenu, item->menuId);
|
||||
if (menuItem == NULL) {
|
||||
menuItem = [theMenu addItemWithTitle:item->title
|
||||
action:@selector(menuHandler:)
|
||||
keyEquivalent:@""];
|
||||
[menuItem setRepresentedObject:item->menuId];
|
||||
}
|
||||
[menuItem setTitle:item->title];
|
||||
[menuItem setTag:[item->menuId integerValue]];
|
||||
[menuItem setTarget:self];
|
||||
[menuItem setToolTip:item->tooltip];
|
||||
if (item->disabled == 1) {
|
||||
menuItem.enabled = FALSE;
|
||||
@@ -140,6 +154,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
|
||||
NSMenuItem *foundItem = [ourMenu itemWithTag:[menuId integerValue]];
|
||||
if (foundItem != NULL) {
|
||||
return foundItem;
|
||||
}
|
||||
NSArray *menu_items = ourMenu.itemArray;
|
||||
int i;
|
||||
for (i = 0; i < [menu_items count]; i++) {
|
||||
NSMenuItem *i_item = [menu_items objectAtIndex:i];
|
||||
if (i_item.hasSubmenu) {
|
||||
foundItem = find_menu_item(i_item.submenu, menuId);
|
||||
if (foundItem != NULL) {
|
||||
return foundItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
};
|
||||
|
||||
- (void) add_separator:(NSNumber*) menuId
|
||||
{
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
@@ -147,37 +181,30 @@
|
||||
|
||||
- (void) hide_menu_item:(NSNumber*) menuId
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
return;
|
||||
NSMenuItem* menuItem = find_menu_item(menu, menuId);
|
||||
if (menuItem != NULL) {
|
||||
[menuItem setHidden:TRUE];
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
[menuItem setHidden:TRUE];
|
||||
}
|
||||
|
||||
- (void)setMenuItemIcon:(NSArray*)imageAndMenuId {
|
||||
- (void) setMenuItemIcon:(NSArray*)imageAndMenuId {
|
||||
NSImage* image = [imageAndMenuId objectAtIndex:0];
|
||||
NSNumber* menuId = [imageAndMenuId objectAtIndex:1];
|
||||
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
menuItem = find_menu_item(menu, menuId);
|
||||
if (menuItem == NULL) {
|
||||
return;
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
menuItem.image = image;
|
||||
}
|
||||
|
||||
- (void) show_menu_item:(NSNumber*) menuId
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
return;
|
||||
NSMenuItem* menuItem = find_menu_item(menu, menuId);
|
||||
if (menuItem != NULL) {
|
||||
[menuItem setHidden:FALSE];
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
[menuItem setHidden:FALSE];
|
||||
}
|
||||
|
||||
- (void) quit
|
||||
@@ -235,8 +262,8 @@ void setTooltip(char* ctooltip) {
|
||||
runInMainThread(@selector(setTooltip:), (id)tooltip);
|
||||
}
|
||||
|
||||
void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked) {
|
||||
MenuItem* item = [[MenuItem alloc] initWithId: menuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
|
||||
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked) {
|
||||
MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
|
||||
free(title);
|
||||
free(tooltip);
|
||||
runInMainThread(@selector(add_or_update_menu_item:), (id)item);
|
||||
|
||||
+2
-1
@@ -189,7 +189,8 @@ void setTooltip(char* ctooltip) {
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) {
|
||||
}
|
||||
|
||||
void add_or_update_menu_item(int menu_id, char* title, char* tooltip, short disabled, short checked) {
|
||||
void add_or_update_menu_item(int menu_id, int parent_menu_id, char* title, char* tooltip, short disabled, short checked) {
|
||||
// TODO: add support for sub-menus
|
||||
MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
|
||||
mii->menu_id = menu_id;
|
||||
mii->title = title;
|
||||
|
||||
@@ -58,8 +58,13 @@ func addOrUpdateMenuItem(item *MenuItem) {
|
||||
if item.checked {
|
||||
checked = 1
|
||||
}
|
||||
var parentID int32 = 0
|
||||
if item.parent != nil {
|
||||
parentID = item.parent.id
|
||||
}
|
||||
C.add_or_update_menu_item(
|
||||
C.int(item.id),
|
||||
C.int(parentID),
|
||||
C.CString(item.title),
|
||||
C.CString(item.tooltip),
|
||||
disabled,
|
||||
|
||||
@@ -180,6 +180,7 @@ func addOrUpdateMenuItem(item *MenuItem) {
|
||||
if err != nil {
|
||||
fail("Unable to set menu item enabled", err)
|
||||
}
|
||||
// TODO: add support for sub-menus
|
||||
}
|
||||
|
||||
// SetIcon sets the icon of a menu item. Only works on macOS and Windows.
|
||||
|
||||
Reference in New Issue
Block a user