package application import ( "sync" "sync/atomic" ) type menuItemType int const ( text menuItemType = iota separator checkbox radio submenu ) var menuItemID uintptr var menuItemMap = make(map[uint]*MenuItem) var menuItemMapLock sync.Mutex func addToMenuItemMap(menuItem *MenuItem) { menuItemMapLock.Lock() menuItemMap[menuItem.id] = menuItem menuItemMapLock.Unlock() } func getMenuItemByID(id uint) *MenuItem { menuItemMapLock.Lock() defer menuItemMapLock.Unlock() return menuItemMap[id] } func removeMenuItemByID(id uint) { menuItemMapLock.Lock() defer menuItemMapLock.Unlock() delete(menuItemMap, id) } type menuItemImpl interface { setTooltip(s string) setLabel(s string) setDisabled(disabled bool) setChecked(checked bool) setAccelerator(accelerator *accelerator) setHidden(hidden bool) setBitmap(bitmap []byte) destroy() } type MenuItem struct { id uint label string tooltip string disabled bool checked bool hidden bool bitmap []byte submenu *Menu callback func(*Context) itemType menuItemType accelerator *accelerator role Role contextMenuData *ContextMenuData impl menuItemImpl radioGroupMembers []*MenuItem } func NewMenuItem(label string) *MenuItem { result := &MenuItem{ id: uint(atomic.AddUintptr(&menuItemID, 1)), label: label, itemType: text, } addToMenuItemMap(result) return result } func NewMenuItemSeparator() *MenuItem { result := &MenuItem{ id: uint(atomic.AddUintptr(&menuItemID, 1)), itemType: separator, } return result } func NewMenuItemCheckbox(label string, checked bool) *MenuItem { result := &MenuItem{ id: uint(atomic.AddUintptr(&menuItemID, 1)), label: label, checked: checked, itemType: checkbox, } addToMenuItemMap(result) return result } func NewMenuItemRadio(label string, checked bool) *MenuItem { result := &MenuItem{ id: uint(atomic.AddUintptr(&menuItemID, 1)), label: label, checked: checked, itemType: radio, } addToMenuItemMap(result) return result } func NewSubMenuItem(label string) *MenuItem { result := &MenuItem{ id: uint(atomic.AddUintptr(&menuItemID, 1)), label: label, itemType: submenu, submenu: &Menu{ label: label, }, } addToMenuItemMap(result) return result } func NewRole(role Role) *MenuItem { var result *MenuItem switch role { case AppMenu: result = NewAppMenu() case EditMenu: result = NewEditMenu() case FileMenu: result = NewFileMenu() case ViewMenu: result = NewViewMenu() case ServicesMenu: return NewServicesMenu() case SpeechMenu: result = NewSpeechMenu() case WindowMenu: result = NewWindowMenu() case HelpMenu: result = NewHelpMenu() case Hide: result = NewHideMenuItem() case Front: result = NewFrontMenuItem() case HideOthers: result = NewHideOthersMenuItem() case UnHide: result = NewUnhideMenuItem() case Undo: result = NewUndoMenuItem() case Redo: result = NewRedoMenuItem() case Cut: result = NewCutMenuItem() case Copy: result = NewCopyMenuItem() case Paste: result = NewPasteMenuItem() case PasteAndMatchStyle: result = NewPasteAndMatchStyleMenuItem() case SelectAll: result = NewSelectAllMenuItem() case Delete: result = NewDeleteMenuItem() case Quit: result = NewQuitMenuItem() case CloseWindow: result = NewCloseMenuItem() case About: result = NewAboutMenuItem() case Reload: result = NewReloadMenuItem() case ForceReload: result = NewForceReloadMenuItem() case ToggleFullscreen: result = NewToggleFullscreenMenuItem() case OpenDevTools: result = NewOpenDevToolsMenuItem() case ResetZoom: result = NewZoomResetMenuItem() case ZoomIn: result = NewZoomInMenuItem() case ZoomOut: result = NewZoomOutMenuItem() case Minimise: result = NewMinimiseMenuItem() case Zoom: result = NewZoomMenuItem() case FullScreen: result = NewFullScreenMenuItem() case Print: result = NewPrintMenuItem() case PageLayout: result = NewPageLayoutMenuItem() case NoRole: case ShowAll: result = NewShowAllMenuItem() case BringAllToFront: result = NewBringAllToFrontMenuItem() case NewFile: result = NewNewFileMenuItem() case Open: result = NewOpenMenuItem() case Save: result = NewSaveMenuItem() case SaveAs: result = NewSaveAsMenuItem() case StartSpeaking: result = NewStartSpeakingMenuItem() case StopSpeaking: result = NewStopSpeakingMenuItem() case Revert: result = NewRevertMenuItem() case Find: result = NewFindMenuItem() case FindAndReplace: result = NewFindAndReplaceMenuItem() case FindNext: result = NewFindNextMenuItem() case FindPrevious: result = NewFindPreviousMenuItem() case Help: result = NewHelpMenuItem() default: globalApplication.error("no support for role: %v", role) } if result != nil { result.role = role } return result } func NewServicesMenu() *MenuItem { serviceMenu := NewSubMenuItem("Services") serviceMenu.role = ServicesMenu return serviceMenu } func (m *MenuItem) handleClick() { var ctx = newContext(). withClickedMenuItem(m). withContextMenuData(m.contextMenuData) if m.itemType == checkbox { m.checked = !m.checked ctx.withChecked(m.checked) if m.impl != nil { m.impl.setChecked(m.checked) } } if m.itemType == radio { for _, member := range m.radioGroupMembers { member.checked = false if member.impl != nil { member.impl.setChecked(false) } } m.checked = true ctx.withChecked(true) if m.impl != nil { m.impl.setChecked(true) } } if m.callback != nil { go func() { defer handlePanic() m.callback(ctx) }() } } func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem { accelerator, err := parseAccelerator(shortcut) if err != nil { globalApplication.error("invalid accelerator: %w", err) return m } m.accelerator = accelerator if m.impl != nil { m.impl.setAccelerator(accelerator) } return m } func (m *MenuItem) GetAccelerator() string { if m.accelerator == nil { return "" } return m.accelerator.String() } func (m *MenuItem) RemoveAccelerator() { m.accelerator = nil } func (m *MenuItem) SetTooltip(s string) *MenuItem { m.tooltip = s if m.impl != nil { m.impl.setTooltip(s) } return m } func (m *MenuItem) SetRole(role Role) *MenuItem { m.role = role return m } func (m *MenuItem) SetLabel(s string) *MenuItem { m.label = s if m.impl != nil { m.impl.setLabel(s) } return m } func (m *MenuItem) SetEnabled(enabled bool) *MenuItem { m.disabled = !enabled if m.impl != nil { m.impl.setDisabled(m.disabled) } return m } func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem { m.bitmap = bitmap if m.impl != nil { m.impl.setBitmap(bitmap) } return m } func (m *MenuItem) SetChecked(checked bool) *MenuItem { m.checked = checked if m.impl != nil { m.impl.setChecked(m.checked) } return m } func (m *MenuItem) SetHidden(hidden bool) *MenuItem { m.hidden = hidden if m.impl != nil { m.impl.setHidden(m.hidden) } return m } // GetSubmenu returns the submenu of the MenuItem. // If the MenuItem is not a submenu, it returns nil. func (m *MenuItem) GetSubmenu() *Menu { return m.submenu } func (m *MenuItem) Checked() bool { return m.checked } func (m *MenuItem) IsSeparator() bool { return m.itemType == separator } func (m *MenuItem) IsSubmenu() bool { return m.itemType == submenu } func (m *MenuItem) IsCheckbox() bool { return m.itemType == checkbox } func (m *MenuItem) IsRadio() bool { return m.itemType == radio } func (m *MenuItem) Hidden() bool { return m.hidden } func (m *MenuItem) OnClick(f func(*Context)) *MenuItem { m.callback = f return m } func (m *MenuItem) Label() string { return m.label } func (m *MenuItem) Tooltip() string { return m.tooltip } func (m *MenuItem) Enabled() bool { return !m.disabled } func (m *MenuItem) setContextData(data *ContextMenuData) { m.contextMenuData = data if m.submenu != nil { m.submenu.setContextData(data) } } // Clone returns a deep copy of the MenuItem func (m *MenuItem) Clone() *MenuItem { result := &MenuItem{ id: m.id, label: m.label, tooltip: m.tooltip, disabled: m.disabled, checked: m.checked, hidden: m.hidden, bitmap: m.bitmap, callback: m.callback, itemType: m.itemType, role: m.role, } if m.submenu != nil { result.submenu = m.submenu.Clone() } if m.accelerator != nil { result.accelerator = m.accelerator.clone() } if m.contextMenuData != nil { result.contextMenuData = m.contextMenuData.clone() } return result } func (m *MenuItem) Destroy() { removeMenuItemByID(m.id) // Clean up resources if m.impl != nil { m.impl.destroy() } if m.submenu != nil { m.submenu.Destroy() m.submenu = nil } if m.contextMenuData != nil { m.contextMenuData = nil } if m.accelerator != nil { m.accelerator = nil } m.callback = nil m.radioGroupMembers = nil }