From 7e42052da077a9017dcadbb2a95534e8ad7dc0ee Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 5 Dec 2020 14:14:46 +1100 Subject: [PATCH] InsertBefore/InsertAfter supported --- v2/internal/logger/custom_logger.go | 42 ++++---- v2/internal/logger/default_logger.go | 45 ++++----- v2/internal/servicebus/servicebus.go | 13 ++- v2/pkg/menu/menuitem.go | 139 ++++++++++++++++++++++++++- v2/test/kitchensink/menu.go | 108 ++++++++++++++++++++- 5 files changed, 287 insertions(+), 60 deletions(-) diff --git a/v2/internal/logger/custom_logger.go b/v2/internal/logger/custom_logger.go index 852b301cb..5e24aa093 100644 --- a/v2/internal/logger/custom_logger.go +++ b/v2/internal/logger/custom_logger.go @@ -7,25 +7,25 @@ import ( // CustomLogger defines what a user can do with a logger type CustomLogger interface { // Writeln writes directly to the output with no log level plus line ending - Writeln(message string) error + Writeln(message string) // Write writes directly to the output with no log level - Write(message string) error + Write(message string) // Trace level logging. Works like Sprintf. - Trace(format string, args ...interface{}) error + Trace(format string, args ...interface{}) // Debug level logging. Works like Sprintf. - Debug(format string, args ...interface{}) error + Debug(format string, args ...interface{}) // Info level logging. Works like Sprintf. - Info(format string, args ...interface{}) error + Info(format string, args ...interface{}) // Warning level logging. Works like Sprintf. - Warning(format string, args ...interface{}) error + Warning(format string, args ...interface{}) // Error level logging. Works like Sprintf. - Error(format string, args ...interface{}) error + Error(format string, args ...interface{}) // Fatal level logging. Works like Sprintf. Fatal(format string, args ...interface{}) @@ -49,43 +49,43 @@ func newcustomLogger(logger *Logger, name string) *customLogger { // Writeln writes directly to the output with no log level // Appends a carriage return to the message -func (l *customLogger) Writeln(message string) error { - return l.logger.Writeln(message) +func (l *customLogger) Writeln(message string) { + l.logger.Writeln(message) } // Write writes directly to the output with no log level -func (l *customLogger) Write(message string) error { - return l.logger.Write(message) +func (l *customLogger) Write(message string) { + l.logger.Write(message) } // Trace level logging. Works like Sprintf. -func (l *customLogger) Trace(format string, args ...interface{}) error { +func (l *customLogger) Trace(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) - return l.logger.Trace(format, args...) + l.logger.Trace(format, args...) } // Debug level logging. Works like Sprintf. -func (l *customLogger) Debug(format string, args ...interface{}) error { +func (l *customLogger) Debug(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) - return l.logger.Debug(format, args...) + l.logger.Debug(format, args...) } // Info level logging. Works like Sprintf. -func (l *customLogger) Info(format string, args ...interface{}) error { +func (l *customLogger) Info(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) - return l.logger.Info(format, args...) + l.logger.Info(format, args...) } // Warning level logging. Works like Sprintf. -func (l *customLogger) Warning(format string, args ...interface{}) error { +func (l *customLogger) Warning(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) - return l.logger.Warning(format, args...) + l.logger.Warning(format, args...) } // Error level logging. Works like Sprintf. -func (l *customLogger) Error(format string, args ...interface{}) error { +func (l *customLogger) Error(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) - return l.logger.Error(format, args...) + l.logger.Error(format, args...) } diff --git a/v2/internal/logger/default_logger.go b/v2/internal/logger/default_logger.go index 40d80140f..d5e25f9c2 100644 --- a/v2/internal/logger/default_logger.go +++ b/v2/internal/logger/default_logger.go @@ -46,67 +46,60 @@ func (l *Logger) SetLogLevel(level LogLevel) { // Writeln writes directly to the output with no log level // Appends a carriage return to the message -func (l *Logger) Writeln(message string) error { - return l.output.Print(message) +func (l *Logger) Writeln(message string) { + l.output.Print(message) } // Write writes directly to the output with no log level -func (l *Logger) Write(message string) error { - return l.output.Print(message) +func (l *Logger) Write(message string) { + l.output.Print(message) } // Print writes directly to the output with no log level // Appends a carriage return to the message -func (l *Logger) Print(message string) error { - return l.Write(message) +func (l *Logger) Print(message string) { + l.Write(message) } // Trace level logging. Works like Sprintf. -func (l *Logger) Trace(format string, args ...interface{}) error { +func (l *Logger) Trace(format string, args ...interface{}) { if l.logLevel <= logger.TRACE { - return l.output.Trace(fmt.Sprintf(format, args...)) + l.output.Trace(fmt.Sprintf(format, args...)) } - return nil } // Debug level logging. Works like Sprintf. -func (l *Logger) Debug(format string, args ...interface{}) error { +func (l *Logger) Debug(format string, args ...interface{}) { if l.logLevel <= logger.DEBUG { - return l.output.Debug(fmt.Sprintf(format, args...)) + l.output.Debug(fmt.Sprintf(format, args...)) } - return nil } // Info level logging. Works like Sprintf. -func (l *Logger) Info(format string, args ...interface{}) error { +func (l *Logger) Info(format string, args ...interface{}) { if l.logLevel <= logger.INFO { - return l.output.Info(fmt.Sprintf(format, args...)) + l.output.Info(fmt.Sprintf(format, args...)) } - return nil + } // Warning level logging. Works like Sprintf. -func (l *Logger) Warning(format string, args ...interface{}) error { +func (l *Logger) Warning(format string, args ...interface{}) { if l.logLevel <= logger.WARNING { - return l.output.Warning(fmt.Sprintf(format, args...)) + l.output.Warning(fmt.Sprintf(format, args...)) } - return nil } // Error level logging. Works like Sprintf. -func (l *Logger) Error(format string, args ...interface{}) error { +func (l *Logger) Error(format string, args ...interface{}) { if l.logLevel <= logger.ERROR { - return l.output.Error(fmt.Sprintf(format, args...)) + l.output.Error(fmt.Sprintf(format, args...)) } - return nil + } // Fatal level logging. Works like Sprintf. func (l *Logger) Fatal(format string, args ...interface{}) { - err := l.output.Fatal(fmt.Sprintf(format, args...)) - // Not much we can do but print it out before exiting - if err != nil { - println(err.Error()) - } + l.output.Fatal(fmt.Sprintf(format, args...)) os.Exit(1) } diff --git a/v2/internal/servicebus/servicebus.go b/v2/internal/servicebus/servicebus.go index 55c37480e..cb04b32ba 100644 --- a/v2/internal/servicebus/servicebus.go +++ b/v2/internal/servicebus/servicebus.go @@ -168,25 +168,24 @@ func (s *ServiceBus) Subscribe(topic string) (<-chan *Message, error) { } // Publish sends the given message on the service bus -func (s *ServiceBus) Publish(topic string, data interface{}) error { +func (s *ServiceBus) Publish(topic string, data interface{}) { // Prevent publish when closed if s.closed { - return fmt.Errorf("cannot call publish on closed servicebus") + s.logger.Fatal("cannot call publish on closed servicebus") + return } message := NewMessage(topic, data) s.messageQueue <- message - return nil } // PublishForTarget sends the given message on the service bus for the given target -func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) error { +func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) { // Prevent publish when closed if s.closed { - return fmt.Errorf("cannot call publish on closed servicebus") + s.logger.Fatal("cannot call publish on closed servicebus") + return } - message := NewMessageForTarget(topic, data, target) s.messageQueue <- message - return nil } diff --git a/v2/pkg/menu/menuitem.go b/v2/pkg/menu/menuitem.go index 3d0426839..c6e00d6c2 100644 --- a/v2/pkg/menu/menuitem.go +++ b/v2/pkg/menu/menuitem.go @@ -36,9 +36,10 @@ func (m *MenuItem) Parent() *MenuItem { // submenu, then this method will not add the item and // simply return false. func (m *MenuItem) Append(item *MenuItem) bool { - if m.Type != SubmenuType { + if !m.isSubMenu() { return false } + item.parent = m m.SubMenu = append(m.SubMenu, item) return true } @@ -48,9 +49,10 @@ func (m *MenuItem) Append(item *MenuItem) bool { // submenu, then this method will not add the item and // simply return false. func (m *MenuItem) Prepend(item *MenuItem) bool { - if m.Type != SubmenuType { + if !m.isSubMenu() { return false } + item.parent = m m.SubMenu = append([]*MenuItem{item}, m.SubMenu...) return true } @@ -80,7 +82,7 @@ func (m *MenuItem) removeByID(id string) bool { m.SubMenu = append(m.SubMenu[:index], m.SubMenu[index+1:]...) return true } - if item.Type == SubmenuType { + if item.isSubMenu() { result := item.removeByID(id) if result == true { return result @@ -90,6 +92,120 @@ func (m *MenuItem) removeByID(id string) bool { return false } +// InsertAfter attempts to add the given item after this item in the parent +// menu. If there is no parent menu (we are a top level menu) then false is +// returned +func (m *MenuItem) InsertAfter(item *MenuItem) bool { + + // We need to find my parent + if m.parent == nil { + return false + } + + // Get my parent to insert the item + return m.parent.insertNewItemAfterGivenItem(m, item) +} + +// InsertBefore attempts to add the given item before this item in the parent +// menu. If there is no parent menu (we are a top level menu) then false is +// returned +func (m *MenuItem) InsertBefore(item *MenuItem) bool { + + // We need to find my parent + if m.parent == nil { + return false + } + + // Get my parent to insert the item + return m.parent.insertNewItemBeforeGivenItem(m, item) +} + +// insertNewItemAfterGivenItem will insert the given item after the given target +// in this item's submenu. If we are not a submenu, +// then something bad has happened :/ +func (m *MenuItem) insertNewItemAfterGivenItem(target *MenuItem, + newItem *MenuItem) bool { + + if !m.isSubMenu() { + return false + } + + // Find the index of the target + targetIndex := m.getItemIndex(target) + if targetIndex == -1 { + return false + } + + // Insert element into slice + return m.insertItemAtIndex(targetIndex+1, newItem) +} + +// insertNewItemBeforeGivenItem will insert the given item before the given +// target in this item's submenu. If we are not a submenu, then something bad +// has happened :/ +func (m *MenuItem) insertNewItemBeforeGivenItem(target *MenuItem, + newItem *MenuItem) bool { + + if !m.isSubMenu() { + return false + } + + // Find the index of the target + targetIndex := m.getItemIndex(target) + if targetIndex == -1 { + return false + } + + // Insert element into slice + return m.insertItemAtIndex(targetIndex, newItem) +} + +func (m *MenuItem) isSubMenu() bool { + return m.Type == SubmenuType +} + +// getItemIndex returns the index of the given target relative to this menu +func (m *MenuItem) getItemIndex(target *MenuItem) int { + + // This should only be called on submenus + if !m.isSubMenu() { + return -1 + } + + // hunt down that bad boy + for index, item := range m.SubMenu { + if item == target { + return index + } + } + + return -1 +} + +// insertItemAtIndex attempts to insert the given item into the submenu at +// the given index +// Credit: https://stackoverflow.com/a/61822301 +func (m *MenuItem) insertItemAtIndex(index int, target *MenuItem) bool { + + // If index is OOB, return false + if index > len(m.SubMenu) { + return false + } + + // Save parent reference + target.parent = m + + // If index is last item, then just regular append + if index == len(m.SubMenu) { + m.SubMenu = append(m.SubMenu, target) + return true + } + + m.SubMenu = append(m.SubMenu[:index+1], m.SubMenu[index:]...) + m.SubMenu[index] = target + return true +} + // Text is a helper to create basic Text menu items func Text(label string, id string) *MenuItem { return TextWithAccelerator(label, id, nil) @@ -159,3 +275,20 @@ func SubMenu(label string, items []*MenuItem) *MenuItem { return result } + +// SubMenuWithID is a helper to create Submenus with an ID +func SubMenuWithID(label string, id string, items []*MenuItem) *MenuItem { + result := &MenuItem{ + Label: label, + SubMenu: items, + ID: id, + Type: SubmenuType, + } + + // Fix up parent pointers + for _, item := range items { + item.parent = result + } + + return result +} diff --git a/v2/test/kitchensink/menu.go b/v2/test/kitchensink/menu.go index 7ade4682e..faa51ac19 100644 --- a/v2/test/kitchensink/menu.go +++ b/v2/test/kitchensink/menu.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math/rand" "strconv" "sync" @@ -13,8 +14,10 @@ import ( type Menu struct { runtime *wails.Runtime - dynamicMenuCounter int - lock sync.Mutex + dynamicMenuCounter int + lock sync.Mutex + dynamicMenuItems map[string]*menu.MenuItem + anotherDynamicMenuCounter int } // WailsInit is called at application startup @@ -34,6 +37,9 @@ func (m *Menu) WailsInit(runtime *wails.Runtime) error { fmt.Printf("We can use UTF-8 IDs: %s\n", mi.Label) }) + // Create dynamic menu items 2 submenu + m.createDynamicMenuTwo() + // Setup dynamic menus m.runtime.Menu.On("Add Menu Item", m.addMenu) return nil @@ -111,6 +117,102 @@ func (m *Menu) removeMenu(_ *menu.MenuItem) { m.runtime.Menu.Update() } +func (m *Menu) createDynamicMenuTwo() { + + // Create our submenu + dm2 := menu.SubMenu("Dynamic Menus 2", []*menu.MenuItem{ + menu.TextWithAccelerator("Insert Before Random Menu Item", + "Insert Before Random", menu.CmdOrCtrlAccel("]")), + menu.TextWithAccelerator("Insert After Random Menu Item", + "Insert After Random", menu.CmdOrCtrlAccel("[")), + menu.Separator(), + }) + + m.runtime.Menu.On("Insert Before Random", m.insertBeforeRandom) + m.runtime.Menu.On("Insert After Random", m.insertAfterRandom) + + // Initialise out map + m.dynamicMenuItems = make(map[string]*menu.MenuItem) + + // Create some random menu items + m.anotherDynamicMenuCounter = 5 + for index := 0; index < m.anotherDynamicMenuCounter; index++ { + text := "Other Dynamic Menu Item " + strconv.Itoa(index+1) + item := menu.Text(text, text) + m.dynamicMenuItems[text] = item + dm2.Append(item) + } + + // Insert this menu after Dynamic Menu Item 1 + dm1 := m.runtime.Menu.GetByID("Dynamic Menus 1") + dm1.InsertAfter(dm2) + m.runtime.Menu.Update() +} + +func (m *Menu) insertBeforeRandom(_ *menu.MenuItem) { + + // Lock because this method will be called in a goroutine + m.lock.Lock() + defer m.lock.Unlock() + + // Pick a random menu + var randomItemID string + var count int + var random = rand.Intn(len(m.dynamicMenuItems)) + for randomItemID = range m.dynamicMenuItems { + if count == random { + break + } + count++ + } + m.anotherDynamicMenuCounter++ + text := "Other Dynamic Menu Item " + strconv.Itoa( + m.anotherDynamicMenuCounter+1) + newItem := menu.Text(text, text) + m.dynamicMenuItems[text] = newItem + + item := m.runtime.Menu.GetByID(randomItemID) + + m.runtime.Log.Info(fmt.Sprintf( + "Inserting menu item '%s' before menu item '%s'", newItem.Label, + item.Label)) + + item.InsertBefore(newItem) + m.runtime.Menu.Update() +} + +func (m *Menu) insertAfterRandom(_ *menu.MenuItem) { + + // Lock because this method will be called in a goroutine + m.lock.Lock() + defer m.lock.Unlock() + + // Pick a random menu + var randomItemID string + var count int + var random = rand.Intn(len(m.dynamicMenuItems)) + for randomItemID = range m.dynamicMenuItems { + if count == random { + break + } + count++ + } + m.anotherDynamicMenuCounter++ + text := "Other Dynamic Menu Item " + strconv.Itoa( + m.anotherDynamicMenuCounter+1) + newItem := menu.Text(text, text) + + item := m.runtime.Menu.GetByID(randomItemID) + m.dynamicMenuItems[text] = newItem + + m.runtime.Log.Info(fmt.Sprintf( + "Inserting menu item '%s' after menu item '%s'", newItem.Label, + item.Label)) + + item.InsertAfter(newItem) + m.runtime.Menu.Update() +} + func createApplicationMenu() *menu.Menu { // Create menu @@ -184,7 +286,7 @@ func createApplicationMenu() *menu.Menu { menu.TextWithAccelerator("Plus", "Plus", menu.Accel("+")), }), }), - menu.SubMenu("Dynamic Menus", []*menu.MenuItem{ + menu.SubMenuWithID("Dynamic Menus 1", "Dynamic Menus 1", []*menu.MenuItem{ menu.TextWithAccelerator("Add Menu Item", "Add Menu Item", menu.CmdOrCtrlAccel("+")), menu.Separator(), }),