Merge pull request #131 from oxtoacart/nested-menus

Support for nested sub-menus on OS X
This commit is contained in:
joesis
2020-03-16 05:36:48 -07:00
committed by GitHub
8 changed files with 133 additions and 52 deletions
+27 -9
View File
@@ -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...")
+1
View File
@@ -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
View File
@@ -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(&currentID, 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(&currentID, 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(&currentID, 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()
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+5
View File
@@ -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,
+1
View File
@@ -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.