summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorAlexander Neumann <alexander.neumann@picos-software.com>2019-05-06 14:49:37 +0200
committerAlexander Neumann <alexander.neumann@picos-software.com>2019-05-06 14:49:37 +0200
commitbcb938ee1fcb27801ceda364da2d70ea3f4f66c0 (patch)
tree608f47909ccfa643aa6e4fc513f908479da42568
parentExport some HIDPI related helpers (diff)
parentMerge pull request #503 from zx2c4-forks/jd/notifyicon-icons (diff)
downloadwireguard-windows-bcb938ee1fcb27801ceda364da2d70ea3f4f66c0.tar.xz
wireguard-windows-bcb938ee1fcb27801ceda364da2d70ea3f4f66c0.zip
Merge branch 'master' of github.com:lxn/walk
-rw-r--r--AUTHORS1
-rw-r--r--action.go58
-rw-r--r--actionlist.go14
-rw-r--r--fontresource.go83
-rw-r--r--menu.go24
-rw-r--r--notifyicon.go70
-rw-r--r--toolbar.go8
-rw-r--r--tooltip.go6
8 files changed, 240 insertions, 24 deletions
diff --git a/AUTHORS b/AUTHORS
index ce5e8663..b44a7a88 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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>
diff --git a/action.go b/action.go
index 554cdc3f..30404a7d 100644
--- a/action.go
+++ b/action.go
@@ -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
+ }
+}
diff --git a/menu.go b/menu.go
index 58493c00..1345cc22 100644
--- a/menu.go
+++ b/menu.go
@@ -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()
+}
diff --git a/toolbar.go b/toolbar.go
index 76e60900..2ccf2c8e 100644
--- a/toolbar.go
+++ b/toolbar.go
@@ -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
}
diff --git a/tooltip.go b/tooltip.go
index 5aa996d0..dde551ea 100644
--- a/tooltip.go
+++ b/tooltip.go
@@ -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