diff options
author | Alexander Neumann <alexander.neumann@picos-software.com> | 2019-05-06 14:49:37 +0200 |
---|---|---|
committer | Alexander Neumann <alexander.neumann@picos-software.com> | 2019-05-06 14:49:37 +0200 |
commit | bcb938ee1fcb27801ceda364da2d70ea3f4f66c0 (patch) | |
tree | 608f47909ccfa643aa6e4fc513f908479da42568 | |
parent | Export some HIDPI related helpers (diff) | |
parent | Merge pull request #503 from zx2c4-forks/jd/notifyicon-icons (diff) | |
download | wireguard-windows-bcb938ee1fcb27801ceda364da2d70ea3f4f66c0.tar.xz wireguard-windows-bcb938ee1fcb27801ceda364da2d70ea3f4f66c0.zip |
Merge branch 'master' of github.com:lxn/walk
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | action.go | 58 | ||||
-rw-r--r-- | actionlist.go | 14 | ||||
-rw-r--r-- | fontresource.go | 83 | ||||
-rw-r--r-- | menu.go | 24 | ||||
-rw-r--r-- | notifyicon.go | 70 | ||||
-rw-r--r-- | toolbar.go | 8 | ||||
-rw-r--r-- | tooltip.go | 6 |
8 files changed, 240 insertions, 24 deletions
@@ -29,3 +29,4 @@ Semyon Tokarev <zlobzn@gmail.com> Shawn Sun <datago@yeah.net> Tim Dufrane <tim.dufrane@gmail.com> Vincent Vanackere <vincent.vanackere@gmail.com> +Dmitry Bagdanov <dimbojob@gmail.com> @@ -29,6 +29,8 @@ type Action struct { image *Bitmap checkedCondition Condition checkedConditionChangedHandle int + defaultCondition Condition + defaultConditionChangedHandle int enabledCondition Condition enabledConditionChangedHandle int visibleCondition Condition @@ -39,6 +41,7 @@ type Action struct { visible bool checkable bool checked bool + defawlt bool exclusive bool id uint16 } @@ -166,6 +169,61 @@ func (a *Action) SetCheckedCondition(c Condition) { a.raiseChanged() } +func (a *Action) Default() bool { + return a.defawlt +} + +func (a *Action) SetDefault(value bool) (err error) { + if a.defaultCondition != nil { + if bp, ok := a.defaultCondition.(*boolProperty); ok { + if err := bp.Set(value); err != nil { + return err + } + } else { + return newError("DefaultCondition != nil") + } + } + + if value != a.defawlt { + old := a.defawlt + + a.defawlt = value + + if err = a.raiseChanged(); err != nil { + a.defawlt = old + a.raiseChanged() + } + } + + return +} + +func (a *Action) DefaultCondition() Condition { + return a.defaultCondition +} + +func (a *Action) SetDefaultCondition(c Condition) { + if a.defaultCondition != nil { + a.defaultCondition.Changed().Detach(a.defaultConditionChangedHandle) + } + + a.defaultCondition = c + + if c != nil { + a.defawlt = c.Satisfied() + + a.defaultConditionChangedHandle = c.Changed().Attach(func() { + if a.defawlt != c.Satisfied() { + a.defawlt = !a.defawlt + + a.raiseChanged() + } + }) + } + + a.raiseChanged() +} + func (a *Action) Enabled() bool { return a.enabled } diff --git a/actionlist.go b/actionlist.go index c15880eb..508faf31 100644 --- a/actionlist.go +++ b/actionlist.go @@ -174,3 +174,17 @@ func (l *ActionList) updateSeparatorVisibility() error { return nil } + +func (l *ActionList) AttachShortcuts(w Window) { + shortcuts := make(map[Shortcut]*Action, len(l.actions)) + for _, action := range l.actions { + if action.Shortcut().Key != 0 { + shortcuts[action.Shortcut()] = action + } + } + w.KeyDown().Attach(func(key Key) { + if action, ok := shortcuts[Shortcut{ModifiersDown(), key}]; ok { + action.raiseTriggered() + } + }) +} diff --git a/fontresource.go b/fontresource.go new file mode 100644 index 00000000..1496232a --- /dev/null +++ b/fontresource.go @@ -0,0 +1,83 @@ +// 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 (
+ "github.com/lxn/win"
+ "syscall"
+)
+
+// FontMemResource represents a font resource loaded into memory from
+// the application's resources.
+type FontMemResource struct {
+ hFontResource win.HANDLE
+}
+
+func newFontMemResource(resourceName *uint16) (*FontMemResource, error) {
+ hModule := win.HMODULE(win.GetModuleHandle(nil))
+ if hModule == win.HMODULE(0) {
+ return nil, lastError("GetModuleHandle")
+ }
+
+ hres := win.FindResource(hModule, resourceName, win.MAKEINTRESOURCE(8) /*RT_FONT*/)
+ if hres == win.HRSRC(0) {
+ return nil, lastError("FindResource")
+ }
+
+ size := win.SizeofResource(hModule, hres)
+ if size == 0 {
+ return nil, lastError("SizeofResource")
+ }
+
+ hResLoad := win.LoadResource(hModule, hres)
+ if hResLoad == win.HGLOBAL(0) {
+ return nil, lastError("LoadResource")
+ }
+
+ ptr := win.LockResource(hResLoad)
+ if ptr == 0 {
+ return nil, lastError("LockResource")
+ }
+
+ numFonts := uint32(0)
+ hFontResource := win.AddFontMemResourceEx(ptr, size, nil, &numFonts)
+
+ if hFontResource == win.HANDLE(0) || numFonts == 0 {
+ return nil, lastError("AddFontMemResource")
+ }
+
+ return &FontMemResource { hFontResource: hFontResource }, nil
+}
+
+// NewFontMemResourceByName function loads a font resource from the executable's resources
+// using the resource name.
+// The font must be embedded into resources using corresponding operator in the
+// application's RC script.
+func NewFontMemResourceByName(name string) (*FontMemResource, error) {
+ lpstr, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ return nil, err
+ }
+
+ return newFontMemResource(lpstr)
+}
+
+// NewFontMemResourceById function loads a font resource from the executable's resources
+// using the resource ID.
+// The font must be embedded into resources using corresponding operator in the
+// application's RC script.
+func NewFontMemResourceById(id int) (*FontMemResource, error) {
+ return newFontMemResource(win.MAKEINTRESOURCE(uintptr(id)))
+}
+
+// Dispose removes the font resource from memory
+func (fmr *FontMemResource) Dispose() {
+ if fmr.hFontResource != 0 {
+ win.RemoveFontMemResourceEx(fmr.hFontResource)
+ fmr.hFontResource = 0
+ }
+}
@@ -118,7 +118,21 @@ func (m *Menu) initMenuItemInfoFromAction(mii *win.MENUITEMINFO, action *Action) } } +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 } @@ -131,6 +145,10 @@ func (m *Menu) onActionChanged(action *Action) error { 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 @@ -171,6 +189,8 @@ func (m *Menu) onActionVisibleChanged(action *Action) error { } func (m *Menu) insertAction(action *Action, visibleChanged bool) (err error) { + m.handleDefaultState(action) + if !visibleChanged { action.addChangedHandler(m) defer func() { @@ -194,6 +214,10 @@ func (m *Menu) insertAction(action *Action, visibleChanged bool) (err error) { return newError("InsertMenuItem failed") } + if action.Default() { + win.SetMenuDefaultItem(m.hMenu, uint32(m.actions.indexInObserver(action)), true) + } + menu := action.menu if menu != nil { menu.hWnd = m.hWnd diff --git a/notifyicon.go b/notifyicon.go index 2acaa971..0997050f 100644 --- a/notifyicon.go +++ b/notifyicon.go @@ -61,6 +61,8 @@ func notifyIconWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) (resul } return 0 + case win.NIN_BALLOONUSERCLICK: + ni.messageClickedPublisher.Publish() } return win.DefWindowProc(hwnd, msg, wParam, lParam) @@ -68,14 +70,15 @@ func notifyIconWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) (resul // NotifyIcon represents an icon in the taskbar notification area. type NotifyIcon struct { - id uint32 - hWnd win.HWND - contextMenu *Menu - icon *Icon - toolTip string - visible bool - mouseDownPublisher MouseEventPublisher - mouseUpPublisher MouseEventPublisher + id uint32 + hWnd win.HWND + contextMenu *Menu + icon *Icon + toolTip string + visible bool + mouseDownPublisher MouseEventPublisher + mouseUpPublisher MouseEventPublisher + messageClickedPublisher EventPublisher } // NewNotifyIcon creates and returns a new NotifyIcon. @@ -90,7 +93,7 @@ func NewNotifyIcon(mw *MainWindow) (*NotifyIcon, error) { DwStateMask: win.NIS_HIDDEN, UCallbackMessage: notifyIconMessageId, } - nid.CbSize = uint32(unsafe.Sizeof(nid)) + nid.CbSize = uint32(unsafe.Sizeof(nid) - unsafe.Sizeof(win.HICON(0))) if !win.Shell_NotifyIcon(win.NIM_ADD, &nid) { return nil, newError("Shell_NotifyIcon") @@ -126,7 +129,7 @@ func (ni *NotifyIcon) notifyIconData() *win.NOTIFYICONDATA { UID: ni.id, HWnd: ni.hWnd, } - nid.CbSize = uint32(unsafe.Sizeof(*nid)) + nid.CbSize = uint32(unsafe.Sizeof(*nid) - unsafe.Sizeof(win.HICON(0))) return nid } @@ -154,16 +157,36 @@ func (ni *NotifyIcon) Dispose() error { return nil } -func (ni *NotifyIcon) showMessage(title, info string, iconType uint32) error { +func (ni *NotifyIcon) showMessage(title, info string, iconType uint32, icon *Icon) error { nid := ni.notifyIconData() nid.UFlags = win.NIF_INFO nid.DwInfoFlags = iconType - copy(nid.SzInfoTitle[:], syscall.StringToUTF16(title)) - copy(nid.SzInfo[:], syscall.StringToUTF16(info)) - + var oldIcon *Icon + if iconType == win.NIIF_USER && icon != nil { + if win.GetVersion()&0xff >= 6 { + nid.HBalloonIcon = icon.hIcon + if ni.icon != nil { + nid.HIcon = ni.icon.hIcon + } + } else { + oldIcon = ni.icon + nid.HIcon = icon.hIcon + } + nid.UFlags |= win.NIF_ICON + } + if title16, err := syscall.UTF16FromString(title); err == nil { + copy(nid.SzInfoTitle[:], title16) + } + if info16, err := syscall.UTF16FromString(info); err == nil { + copy(nid.SzInfo[:], info16) + } if !win.Shell_NotifyIcon(win.NIM_MODIFY, nid) { return newError("Shell_NotifyIcon") } + if oldIcon != nil { + ni.icon = nil + ni.SetIcon(oldIcon) + } return nil } @@ -172,35 +195,36 @@ func (ni *NotifyIcon) showMessage(title, info string, iconType uint32) error { // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowMessage(title, info string) error { - return ni.showMessage(title, info, win.NIIF_NONE) + return ni.showMessage(title, info, win.NIIF_NONE, nil) } // ShowInfo displays an info message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowInfo(title, info string) error { - return ni.showMessage(title, info, win.NIIF_INFO) + return ni.showMessage(title, info, win.NIIF_INFO, nil) } // ShowWarning displays a warning message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowWarning(title, info string) error { - return ni.showMessage(title, info, win.NIIF_WARNING) + return ni.showMessage(title, info, win.NIIF_WARNING, nil) } // ShowError displays an error message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowError(title, info string) error { - return ni.showMessage(title, info, win.NIIF_ERROR) + return ni.showMessage(title, info, win.NIIF_ERROR, nil) } // ShowCustom displays a custom icon message balloon above the NotifyIcon. +// If icon is nil, the main notification icon is used instead of a custom one. // // The NotifyIcon must be visible before calling this method. -func (ni *NotifyIcon) ShowCustom(title, info string) error { - return ni.showMessage(title, info, win.NIIF_USER) +func (ni *NotifyIcon) ShowCustom(title, info string, icon *Icon) error { + return ni.showMessage(title, info, win.NIIF_USER, icon) } // ContextMenu returns the context menu of the NotifyIcon. @@ -307,3 +331,9 @@ func (ni *NotifyIcon) MouseDown() *MouseEvent { func (ni *NotifyIcon) MouseUp() *MouseEvent { return ni.mouseUpPublisher.Event() } + +// MessageClicked occurs when the user clicks a message shown with ShowMessage or +// one of its iconed variants. +func (ni *NotifyIcon) MessageClicked() *Event { + return ni.messageClickedPublisher.Event() +} @@ -329,7 +329,7 @@ func (tb *ToolBar) initButtonForAction(action *Action, state, style *byte, image *style |= win.BTNS_GROUP } - if tb.buttonStyle != ToolBarButtonImageOnly { + if tb.buttonStyle != ToolBarButtonImageOnly && len(action.text) > 0 { *style |= win.BTNS_SHOWTEXT } @@ -358,7 +358,11 @@ func (tb *ToolBar) initButtonForAction(action *Action, state, style *byte, image actionText = action.Text() } - *text = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(actionText))) + if len(actionText) != 0 { + *text = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(actionText))) + } else if len(action.toolTip) != 0 { + *text = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(action.toolTip))) + } return } @@ -15,8 +15,10 @@ import ( "github.com/lxn/win" ) -// see https://msdn.microsoft.com/en-us/library/windows/desktop/bb760416(v=vs.85).aspx -const maxToolTipTextLen = 80 // including NUL terminator +// https://msdn.microsoft.com/en-us/library/windows/desktop/bb760416(v=vs.85).aspx says 80, +// but in reality, that hasn't been enforced for many many Windows versions. So we give it +// 1024 instead. +const maxToolTipTextLen = 1024 // including NUL terminator func init() { var err error |