// 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 }