erm/vendor/github.com/lxn/walk/menu.go
2021-07-30 23:29:20 +01:00

310 lines
6.0 KiB
Go

// Copyright 2010 The Walk Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package walk
import (
"fmt"
"syscall"
"unsafe"
"github.com/lxn/win"
)
type Menu struct {
hMenu win.HMENU
window Window
actions *ActionList
getDPI func() int
}
func newMenuBar(window Window) (*Menu, error) {
hMenu := win.CreateMenu()
if hMenu == 0 {
return nil, lastError("CreateMenu")
}
m := &Menu{
hMenu: hMenu,
window: window,
}
m.actions = newActionList(m)
return m, nil
}
func NewMenu() (*Menu, error) {
hMenu := win.CreatePopupMenu()
if hMenu == 0 {
return nil, lastError("CreatePopupMenu")
}
var mi win.MENUINFO
mi.CbSize = uint32(unsafe.Sizeof(mi))
if !win.GetMenuInfo(hMenu, &mi) {
return nil, lastError("GetMenuInfo")
}
mi.FMask |= win.MIM_STYLE
mi.DwStyle = win.MNS_CHECKORBMP
if !win.SetMenuInfo(hMenu, &mi) {
return nil, lastError("SetMenuInfo")
}
m := &Menu{
hMenu: hMenu,
}
m.actions = newActionList(m)
return m, nil
}
func (m *Menu) Dispose() {
m.actions.Clear()
if m.hMenu != 0 {
win.DestroyMenu(m.hMenu)
m.hMenu = 0
}
}
func (m *Menu) IsDisposed() bool {
return m.hMenu == 0
}
func (m *Menu) Actions() *ActionList {
return m.actions
}
func (m *Menu) updateItemsWithImageForWindow(window Window) {
if m.window == nil {
m.window = window
defer func() {
m.window = nil
}()
}
for _, action := range m.actions.actions {
if action.image != nil {
m.onActionChanged(action)
}
if action.menu != nil {
action.menu.updateItemsWithImageForWindow(window)
}
}
}
func (m *Menu) initMenuItemInfoFromAction(mii *win.MENUITEMINFO, action *Action) {
mii.CbSize = uint32(unsafe.Sizeof(*mii))
mii.FMask = win.MIIM_FTYPE | win.MIIM_ID | win.MIIM_STATE | win.MIIM_STRING
if action.image != nil {
mii.FMask |= win.MIIM_BITMAP
dpi := 96
if m.getDPI != nil {
dpi = m.getDPI()
} else if m.window != nil {
dpi = m.window.DPI()
} else {
dpi = screenDPI()
}
if bmp, err := iconCache.Bitmap(action.image, dpi); err == nil {
mii.HbmpItem = bmp.hBmp
}
}
if action.IsSeparator() {
mii.FType |= win.MFT_SEPARATOR
} else {
mii.FType |= win.MFT_STRING
var text string
if s := action.shortcut; s.Key != 0 {
text = fmt.Sprintf("%s\t%s", action.text, s.String())
} else {
text = action.text
}
mii.DwTypeData = syscall.StringToUTF16Ptr(text)
mii.Cch = uint32(len([]rune(action.text)))
}
mii.WID = uint32(action.id)
if action.Enabled() {
mii.FState &^= win.MFS_DISABLED
} else {
mii.FState |= win.MFS_DISABLED
}
if action.Checkable() {
mii.FMask |= win.MIIM_CHECKMARKS
}
if action.Checked() {
mii.FState |= win.MFS_CHECKED
}
if action.Exclusive() {
mii.FType |= win.MFT_RADIOCHECK
}
menu := action.menu
if menu != nil {
mii.FMask |= win.MIIM_SUBMENU
mii.HSubMenu = menu.hMenu
}
}
func (m *Menu) handleDefaultState(action *Action) {
if action.Default() {
// Unset other default actions before we set this one. Otherwise insertion fails.
win.SetMenuDefaultItem(m.hMenu, ^uint32(0), false)
for _, otherAction := range m.actions.actions {
if otherAction != action {
otherAction.SetDefault(false)
}
}
}
}
func (m *Menu) onActionChanged(action *Action) error {
m.handleDefaultState(action)
if !action.Visible() {
return nil
}
var mii win.MENUITEMINFO
m.initMenuItemInfoFromAction(&mii, action)
if !win.SetMenuItemInfo(m.hMenu, uint32(m.actions.indexInObserver(action)), true, &mii) {
return newError("SetMenuItemInfo failed")
}
if action.Default() {
win.SetMenuDefaultItem(m.hMenu, uint32(m.actions.indexInObserver(action)), true)
}
if action.Exclusive() && action.Checked() {
var first, last int
index := m.actions.Index(action)
for i := index; i >= 0; i-- {
first = i
if !m.actions.At(i).Exclusive() {
break
}
}
for i := index; i < m.actions.Len(); i++ {
last = i
if !m.actions.At(i).Exclusive() {
break
}
}
if !win.CheckMenuRadioItem(m.hMenu, uint32(first), uint32(last), uint32(index), win.MF_BYPOSITION) {
return newError("CheckMenuRadioItem failed")
}
}
return nil
}
func (m *Menu) onActionVisibleChanged(action *Action) error {
if !action.IsSeparator() {
defer m.actions.updateSeparatorVisibility()
}
if action.Visible() {
return m.insertAction(action, true)
}
return m.removeAction(action, true)
}
func (m *Menu) insertAction(action *Action, visibleChanged bool) (err error) {
m.handleDefaultState(action)
if !visibleChanged {
action.addChangedHandler(m)
defer func() {
if err != nil {
action.removeChangedHandler(m)
}
}()
}
if !action.Visible() {
return
}
index := m.actions.indexInObserver(action)
var mii win.MENUITEMINFO
m.initMenuItemInfoFromAction(&mii, action)
if !win.InsertMenuItem(m.hMenu, uint32(index), true, &mii) {
return newError("InsertMenuItem failed")
}
if action.Default() {
win.SetMenuDefaultItem(m.hMenu, uint32(m.actions.indexInObserver(action)), true)
}
menu := action.menu
if menu != nil {
menu.window = m.window
}
m.ensureMenuBarRedrawn()
return
}
func (m *Menu) removeAction(action *Action, visibleChanged bool) error {
index := m.actions.indexInObserver(action)
if !win.RemoveMenu(m.hMenu, uint32(index), win.MF_BYPOSITION) {
return lastError("RemoveMenu")
}
if !visibleChanged {
action.removeChangedHandler(m)
}
m.ensureMenuBarRedrawn()
return nil
}
func (m *Menu) ensureMenuBarRedrawn() {
if m.window != nil {
if mw, ok := m.window.(*MainWindow); ok && mw != nil {
win.DrawMenuBar(mw.Handle())
}
}
}
func (m *Menu) onInsertedAction(action *Action) error {
return m.insertAction(action, false)
}
func (m *Menu) onRemovingAction(action *Action) error {
return m.removeAction(action, false)
}
func (m *Menu) onClearingActions() error {
for i := m.actions.Len() - 1; i >= 0; i-- {
if action := m.actions.At(i); action.Visible() {
if err := m.onRemovingAction(action); err != nil {
return err
}
}
}
return nil
}