2460 lines
65 KiB
Go
2460 lines
65 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 (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"runtime"
|
|
"strings"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/lxn/win"
|
|
)
|
|
|
|
// App-specific message ids for internal use in Walk.
|
|
// TODO: Document reserved range somewhere (when we have an idea how many we need).
|
|
const (
|
|
notifyIconMessageId = win.WM_APP + iota
|
|
)
|
|
|
|
// Window is an interface that provides operations common to all windows.
|
|
type Window interface {
|
|
// AddDisposable adds a Disposable resource that should be disposed of
|
|
// together with this Window.
|
|
AddDisposable(d Disposable)
|
|
|
|
// AsWindowBase returns a *WindowBase, a pointer to an instance of the
|
|
// struct that implements most operations common to all windows.
|
|
AsWindowBase() *WindowBase
|
|
|
|
// Accessibility returns the accessibility object used to set Dynamic Annotation properties of the
|
|
// window.
|
|
Accessibility() *Accessibility
|
|
|
|
// Background returns the background Brush of the Window.
|
|
//
|
|
// By default this is nil.
|
|
Background() Brush
|
|
|
|
// Bounds returns the outer bounding box rectangle of the Window, including
|
|
// decorations.
|
|
//
|
|
// For a Form, like *MainWindow or *Dialog, the rectangle is in screen
|
|
// coordinates, for a child Window the coordinates are relative to its
|
|
// parent.
|
|
Bounds() Rectangle
|
|
|
|
// BoundsPixels returns the outer bounding box rectangle of the Window, including
|
|
// decorations.
|
|
//
|
|
// For a Form, like *MainWindow or *Dialog, the rectangle is in screen
|
|
// coordinates, for a child Window the coordinates are relative to its
|
|
// parent.
|
|
BoundsPixels() Rectangle
|
|
|
|
// BoundsChanged returns an *Event that you can attach to for handling bounds
|
|
// changed events for the Window.
|
|
BoundsChanged() *Event
|
|
|
|
// BringToTop moves the Window to the top of the keyboard focus order.
|
|
BringToTop() error
|
|
|
|
// ClientBounds returns the inner bounding box rectangle of the Window,
|
|
// excluding decorations.
|
|
ClientBounds() Rectangle
|
|
|
|
// ClientBoundsPixels returns the inner bounding box rectangle of the Window,
|
|
// excluding decorations.
|
|
ClientBoundsPixels() Rectangle
|
|
|
|
// ContextMenu returns the context menu of the Window.
|
|
//
|
|
// By default this is nil.
|
|
ContextMenu() *Menu
|
|
|
|
// ContextMenuLocation returns the context menu suggested location in screen coordinates in
|
|
// native pixels. This method is called when context menu is invoked using keyboard and mouse
|
|
// coordinates are not available.
|
|
ContextMenuLocation() Point
|
|
|
|
// CreateCanvas creates and returns a *Canvas that can be used to draw
|
|
// inside the ClientBoundsPixels of the Window.
|
|
//
|
|
// Remember to call the Dispose method on the canvas to release resources,
|
|
// when you no longer need it.
|
|
CreateCanvas() (*Canvas, error)
|
|
|
|
// Cursor returns the Cursor of the Window.
|
|
//
|
|
// By default this is nil.
|
|
Cursor() Cursor
|
|
|
|
// Dispose releases the operating system resources, associated with the
|
|
// Window.
|
|
//
|
|
// If a user closes a *MainWindow or *Dialog, it is automatically released.
|
|
// Also, if a Container is disposed of, all its descendants will be released
|
|
// as well.
|
|
Dispose()
|
|
|
|
// Disposing returns an Event that is published when the Window is disposed
|
|
// of.
|
|
Disposing() *Event
|
|
|
|
// DoubleBuffering returns whether double buffering of the
|
|
// drawing is enabled, which may help reduce flicker.
|
|
DoubleBuffering() bool
|
|
|
|
// DPI returns the current DPI value of the Window.
|
|
DPI() int
|
|
|
|
// Enabled returns if the Window is enabled for user interaction.
|
|
Enabled() bool
|
|
|
|
// Focused returns whether the Window has the keyboard input focus.
|
|
Focused() bool
|
|
|
|
// FocusedChanged returns an Event that you can attach to for handling focus
|
|
// changed events for the Window.
|
|
FocusedChanged() *Event
|
|
|
|
// Font returns the *Font of the Window.
|
|
//
|
|
// By default this is a MS Shell Dlg 2, 8 point font.
|
|
Font() *Font
|
|
|
|
// Form returns the Form of the Window.
|
|
Form() Form
|
|
|
|
// Handle returns the window handle of the Window.
|
|
Handle() win.HWND
|
|
|
|
// Height returns the outer height of the Window, including decorations.
|
|
Height() int
|
|
|
|
// HeightPixels returns the outer height of the Window, including decorations.
|
|
HeightPixels() int
|
|
|
|
// Invalidate schedules a full repaint of the Window.
|
|
Invalidate() error
|
|
|
|
// IsDisposed returns if the Window has been disposed of.
|
|
IsDisposed() bool
|
|
|
|
// KeyDown returns a *KeyEvent that you can attach to for handling key down
|
|
// events for the Window.
|
|
KeyDown() *KeyEvent
|
|
|
|
// KeyPress returns a *KeyEvent that you can attach to for handling key
|
|
// press events for the Window.
|
|
KeyPress() *KeyEvent
|
|
|
|
// KeyUp returns a *KeyEvent that you can attach to for handling key up
|
|
// events for the Window.
|
|
KeyUp() *KeyEvent
|
|
|
|
// MaxSize returns the maximum allowed outer size for the Window, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the Window
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
MaxSize() Size
|
|
|
|
// MaxSizePixels returns the maximum allowed outer size for the Window, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the Window
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
MaxSizePixels() Size
|
|
|
|
// MinSize returns the minimum allowed outer size for the Window, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the Window
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
MinSize() Size
|
|
|
|
// MinSizePixels returns the minimum allowed outer size for the Window, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the Window
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
MinSizePixels() Size
|
|
|
|
// MouseDown returns a *MouseEvent that you can attach to for handling
|
|
// mouse down events for the Window.
|
|
MouseDown() *MouseEvent
|
|
|
|
// MouseMove returns a *MouseEvent that you can attach to for handling
|
|
// mouse move events for the Window.
|
|
MouseMove() *MouseEvent
|
|
|
|
// MouseUp returns a *MouseEvent that you can attach to for handling
|
|
// mouse up events for the Window.
|
|
MouseUp() *MouseEvent
|
|
|
|
// Name returns the name of the Window.
|
|
Name() string
|
|
|
|
// RequestLayout either schedules or immediately starts performing layout.
|
|
RequestLayout()
|
|
|
|
// RightToLeftReading returns whether the reading order of the Window
|
|
// is from right to left.
|
|
RightToLeftReading() bool
|
|
|
|
// Screenshot returns an image of the window.
|
|
Screenshot() (*image.RGBA, error)
|
|
|
|
// SendMessage sends a message to the window and returns the result.
|
|
SendMessage(msg uint32, wParam, lParam uintptr) uintptr
|
|
|
|
// SetBackground sets the background Brush of the Window.
|
|
SetBackground(value Brush)
|
|
|
|
// SetBounds sets the outer bounding box rectangle of the Window, including
|
|
// decorations.
|
|
//
|
|
// For a Form, like *MainWindow or *Dialog, the rectangle is in screen
|
|
// coordinates, for a child Window the coordinates are relative to its
|
|
// parent.
|
|
SetBounds(value Rectangle) error
|
|
|
|
// SetBoundsPixels sets the outer bounding box rectangle of the Window, including
|
|
// decorations.
|
|
//
|
|
// For a Form, like *MainWindow or *Dialog, the rectangle is in screen
|
|
// coordinates, for a child Window the coordinates are relative to its
|
|
// parent.
|
|
SetBoundsPixels(value Rectangle) error
|
|
|
|
// SetClientSize sets the size of the inner bounding box of the Window,
|
|
// excluding decorations.
|
|
SetClientSize(value Size) error
|
|
|
|
// SetClientSizePixels sets the size of the inner bounding box of the Window,
|
|
// excluding decorations.
|
|
SetClientSizePixels(value Size) error
|
|
|
|
// SetContextMenu sets the context menu of the Window.
|
|
SetContextMenu(value *Menu)
|
|
|
|
// SetCursor sets the Cursor of the Window.
|
|
SetCursor(value Cursor)
|
|
|
|
// SetDoubleBuffering enables or disables double buffering of the
|
|
// drawing, which may help reduce flicker.
|
|
SetDoubleBuffering(value bool) error
|
|
|
|
// SetEnabled sets if the Window is enabled for user interaction.
|
|
SetEnabled(value bool)
|
|
|
|
// SetFocus sets the keyboard input focus to the Window.
|
|
SetFocus() error
|
|
|
|
// SetFont sets the *Font of the Window.
|
|
SetFont(value *Font)
|
|
|
|
// SetHeight sets the outer height of the Window, including decorations.
|
|
SetHeight(value int) error
|
|
|
|
// SetHeightPixels sets the outer height of the Window, including decorations.
|
|
SetHeightPixels(value int) error
|
|
|
|
// SetMinMaxSize sets the minimum and maximum outer size of the Window,
|
|
// including decorations.
|
|
//
|
|
// Use walk.Size{} to make the respective limit be ignored.
|
|
SetMinMaxSize(min, max Size) error
|
|
|
|
// SetMinMaxSizePixels sets the minimum and maximum outer size of the Window,
|
|
// including decorations.
|
|
//
|
|
// Use walk.Size{} to make the respective limit be ignored.
|
|
SetMinMaxSizePixels(min, max Size) error
|
|
|
|
// SetName sets the name of the Window.
|
|
//
|
|
// This is important if you want to make use of the built-in UI persistence.
|
|
// Some windows support automatic state persistence. See Settings for
|
|
// details.
|
|
SetName(name string)
|
|
|
|
// SetRightToLeftReading sets whether the reading order of the Window
|
|
// is from right to left.
|
|
SetRightToLeftReading(rtl bool) error
|
|
|
|
// SetSize sets the outer size of the Window, including decorations.
|
|
SetSize(value Size) error
|
|
|
|
// SetSizePixels sets the outer size of the Window, including decorations.
|
|
SetSizePixels(value Size) error
|
|
|
|
// SetSuspended sets if the Window is suspended for layout and repainting
|
|
// purposes.
|
|
//
|
|
// You should call SetSuspended(true), before doing a batch of modifications
|
|
// that would cause multiple layout or drawing updates. Remember to call
|
|
// SetSuspended(false) afterwards, which will update the Window accordingly.
|
|
SetSuspended(suspend bool)
|
|
|
|
// SetVisible sets if the Window is visible.
|
|
SetVisible(value bool)
|
|
|
|
// SetWidth sets the outer width of the Window, including decorations.
|
|
SetWidth(value int) error
|
|
|
|
// SetWidthPixels sets the outer width of the Window, including decorations.
|
|
SetWidthPixels(value int) error
|
|
|
|
// SetX sets the x coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
SetX(value int) error
|
|
|
|
// SetXPixels sets the x coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
SetXPixels(value int) error
|
|
|
|
// SetY sets the y coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
SetY(value int) error
|
|
|
|
// SetYPixels sets the y coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
SetYPixels(value int) error
|
|
|
|
// Size returns the outer size of the Window, including decorations.
|
|
Size() Size
|
|
|
|
// SizePixels returns the outer size of the Window, including decorations.
|
|
SizePixels() Size
|
|
|
|
// SizeChanged returns an *Event that you can attach to for handling size
|
|
// changed events for the Window.
|
|
SizeChanged() *Event
|
|
|
|
// Suspended returns if the Window is suspended for layout and repainting
|
|
// purposes.
|
|
Suspended() bool
|
|
|
|
// Synchronize enqueues func f to be called some time later by the main
|
|
// goroutine from inside a message loop.
|
|
Synchronize(f func())
|
|
|
|
// Visible returns if the Window is visible.
|
|
Visible() bool
|
|
|
|
// VisibleChanged returns an Event that you can attach to for handling
|
|
// visible changed events for the Window.
|
|
VisibleChanged() *Event
|
|
|
|
// Width returns the outer width of the Window, including decorations.
|
|
Width() int
|
|
|
|
// WidthPixels returns the outer width of the Window, including decorations.
|
|
WidthPixels() int
|
|
|
|
// WndProc is the window procedure of the window.
|
|
//
|
|
// When implementing your own WndProc to add or modify behavior, call the
|
|
// WndProc of the embedded window for messages you don't handle yourself.
|
|
WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr
|
|
|
|
// X returns the x coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
X() int
|
|
|
|
// XPixels returns the x coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
XPixels() int
|
|
|
|
// Y returns the y coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
Y() int
|
|
|
|
// YPixels returns the y coordinate of the Window, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
YPixels() int
|
|
}
|
|
|
|
type calcTextSizeInfo struct {
|
|
width int // in native pixels
|
|
font fontInfo
|
|
text string
|
|
dpi int
|
|
}
|
|
|
|
// WindowBase implements many operations common to all Windows.
|
|
type WindowBase struct {
|
|
nopActionListObserver
|
|
group *WindowGroup
|
|
window Window
|
|
form Form
|
|
hWnd win.HWND
|
|
origWndProcPtr uintptr
|
|
name string
|
|
font *Font
|
|
hFont win.HFONT
|
|
contextMenu *Menu
|
|
shortcutActions *ActionList
|
|
disposables []Disposable
|
|
disposingPublisher EventPublisher
|
|
dropFilesPublisher DropFilesEventPublisher
|
|
keyDownPublisher KeyEventPublisher
|
|
keyPressPublisher KeyEventPublisher
|
|
keyUpPublisher KeyEventPublisher
|
|
mouseDownPublisher MouseEventPublisher
|
|
mouseUpPublisher MouseEventPublisher
|
|
mouseMovePublisher MouseEventPublisher
|
|
mouseWheelPublisher MouseEventPublisher
|
|
boundsChangedPublisher EventPublisher
|
|
sizeChangedPublisher EventPublisher
|
|
maxSize96dpi Size
|
|
minSize96dpi Size
|
|
background Brush
|
|
cursor Cursor
|
|
name2Property map[string]Property
|
|
enabledProperty Property
|
|
enabledChangedPublisher EventPublisher
|
|
visibleProperty Property
|
|
visibleChangedPublisher EventPublisher
|
|
focusedProperty Property
|
|
focusedChangedPublisher EventPublisher
|
|
calcTextSizeInfo2TextSize map[calcTextSizeInfo]Size // in native pixels
|
|
suspended bool
|
|
visible bool
|
|
enabled bool
|
|
acc *Accessibility
|
|
}
|
|
|
|
var (
|
|
registeredWindowClasses = make(map[string]bool)
|
|
defaultWndProcPtr uintptr
|
|
hwnd2WindowBase = make(map[win.HWND]*WindowBase)
|
|
)
|
|
|
|
func init() {
|
|
AppendToWalkInit(func() {
|
|
forEachDescendantCallbackPtr = syscall.NewCallback(forEachDescendant)
|
|
dialogBaseUnitsUTF16StringPtr = syscall.StringToUTF16Ptr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
|
})
|
|
}
|
|
|
|
// MustRegisterWindowClass registers the specified window class.
|
|
//
|
|
// MustRegisterWindowClass must be called once for every window type that is not
|
|
// based on any system provided control, before calling InitChildWidget or
|
|
// InitWidget. Calling MustRegisterWindowClass twice with the same className
|
|
// results in a panic.
|
|
func MustRegisterWindowClass(className string) {
|
|
MustRegisterWindowClassWithWndProcPtr(className, defaultWndProcPtr)
|
|
}
|
|
|
|
func MustRegisterWindowClassWithStyle(className string, style uint32) {
|
|
MustRegisterWindowClassWithWndProcPtrAndStyle(className, defaultWndProcPtr, style)
|
|
}
|
|
|
|
func MustRegisterWindowClassWithWndProcPtr(className string, wndProcPtr uintptr) {
|
|
MustRegisterWindowClassWithWndProcPtrAndStyle(className, wndProcPtr, 0)
|
|
}
|
|
|
|
func MustRegisterWindowClassWithWndProcPtrAndStyle(className string, wndProcPtr uintptr, style uint32) {
|
|
if registeredWindowClasses[className] {
|
|
panic("window class already registered")
|
|
}
|
|
|
|
hInst := win.GetModuleHandle(nil)
|
|
if hInst == 0 {
|
|
panic("GetModuleHandle")
|
|
}
|
|
|
|
hIcon := win.LoadIcon(hInst, win.MAKEINTRESOURCE(7)) // rsrc uses 7 for app icon
|
|
if hIcon == 0 {
|
|
hIcon = win.LoadIcon(0, win.MAKEINTRESOURCE(win.IDI_APPLICATION))
|
|
}
|
|
if hIcon == 0 {
|
|
panic("LoadIcon")
|
|
}
|
|
|
|
hCursor := win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_ARROW))
|
|
if hCursor == 0 {
|
|
panic("LoadCursor")
|
|
}
|
|
|
|
var wc win.WNDCLASSEX
|
|
wc.CbSize = uint32(unsafe.Sizeof(wc))
|
|
wc.LpfnWndProc = wndProcPtr
|
|
wc.HInstance = hInst
|
|
wc.HIcon = hIcon
|
|
wc.HCursor = hCursor
|
|
wc.HbrBackground = win.COLOR_BTNFACE + 1
|
|
wc.LpszClassName = syscall.StringToUTF16Ptr(className)
|
|
wc.Style = style
|
|
|
|
if atom := win.RegisterClassEx(&wc); atom == 0 {
|
|
panic("RegisterClassEx")
|
|
}
|
|
|
|
registeredWindowClasses[className] = true
|
|
}
|
|
|
|
var initedWalk uint32
|
|
var walkInit []func()
|
|
|
|
func AppendToWalkInit(fn func()) {
|
|
walkInit = append(walkInit, fn)
|
|
}
|
|
|
|
// InitWindow initializes a window.
|
|
//
|
|
// Widgets should be initialized using InitWidget instead.
|
|
func InitWindow(window, parent Window, className string, style, exStyle uint32) error {
|
|
// We can't use sync.Once, because tooltip.go's init also calls InitWindow, so we deadlock.
|
|
if atomic.CompareAndSwapUint32(&initedWalk, 0, 1) {
|
|
runtime.LockOSThread()
|
|
|
|
var initCtrls win.INITCOMMONCONTROLSEX
|
|
initCtrls.DwSize = uint32(unsafe.Sizeof(initCtrls))
|
|
initCtrls.DwICC = win.ICC_LINK_CLASS | win.ICC_LISTVIEW_CLASSES | win.ICC_PROGRESS_CLASS | win.ICC_TAB_CLASSES | win.ICC_TREEVIEW_CLASSES
|
|
win.InitCommonControlsEx(&initCtrls)
|
|
|
|
defaultWndProcPtr = syscall.NewCallback(defaultWndProc)
|
|
for _, fn := range walkInit {
|
|
fn()
|
|
}
|
|
}
|
|
|
|
wb := window.AsWindowBase()
|
|
wb.window = window
|
|
wb.enabled = true
|
|
wb.visible = style&win.WS_VISIBLE != 0
|
|
wb.calcTextSizeInfo2TextSize = make(map[calcTextSizeInfo]Size)
|
|
wb.name2Property = make(map[string]Property)
|
|
|
|
var hwndParent win.HWND
|
|
var hMenu win.HMENU
|
|
if parent != nil {
|
|
hwndParent = parent.Handle()
|
|
|
|
if widget, ok := window.(Widget); ok {
|
|
if container, ok := parent.(Container); ok {
|
|
if cb := container.AsContainerBase(); cb != nil {
|
|
hMenu = win.HMENU(cb.NextChildID())
|
|
}
|
|
widget.AsWidgetBase().parent = container
|
|
}
|
|
}
|
|
}
|
|
|
|
var windowName *uint16
|
|
if len(wb.name) != 0 {
|
|
windowName = syscall.StringToUTF16Ptr(wb.name)
|
|
}
|
|
|
|
if hwnd := window.Handle(); hwnd == 0 {
|
|
wb.hWnd = win.CreateWindowEx(
|
|
exStyle,
|
|
syscall.StringToUTF16Ptr(className),
|
|
windowName,
|
|
style|win.WS_CLIPSIBLINGS,
|
|
win.CW_USEDEFAULT,
|
|
win.CW_USEDEFAULT,
|
|
win.CW_USEDEFAULT,
|
|
win.CW_USEDEFAULT,
|
|
hwndParent,
|
|
hMenu,
|
|
0,
|
|
nil)
|
|
if wb.hWnd == 0 {
|
|
return lastError("CreateWindowEx")
|
|
}
|
|
} else {
|
|
wb.hWnd = hwnd
|
|
}
|
|
|
|
// Handles returned by CreateWindowEx can only be used by the calling
|
|
// thread. As a result, InitWindow *must* be called from a goroutine that
|
|
// has been locked to an OS thread via runtime.LockOSThread().
|
|
//
|
|
// This means we can ask the OS for the ID of the current thread and we
|
|
// don't have to worry about the scheduler moving us onto another thread
|
|
// later.
|
|
tid := win.GetCurrentThreadId()
|
|
|
|
// Use the thread ID to look up our window group, which stores data that
|
|
// is common to all windows on a common thread. A group will be created
|
|
// if one doesn't already exist for the thread ID.
|
|
//
|
|
// CreateGroup automatically increments the reference counter for the
|
|
// group. The counter will be decremented later in WindowBase.Dispose.
|
|
wb.group = wgm.CreateGroup(tid)
|
|
|
|
succeeded := false
|
|
defer func() {
|
|
if !succeeded {
|
|
wb.Dispose()
|
|
}
|
|
}()
|
|
|
|
hwnd2WindowBase[wb.hWnd] = wb
|
|
|
|
if !registeredWindowClasses[className] {
|
|
// We subclass all windows of system classes.
|
|
wb.origWndProcPtr = win.SetWindowLongPtr(wb.hWnd, win.GWLP_WNDPROC, defaultWndProcPtr)
|
|
if wb.origWndProcPtr == 0 {
|
|
return lastError("SetWindowLongPtr")
|
|
}
|
|
}
|
|
|
|
SetWindowFont(wb.hWnd, defaultFont)
|
|
|
|
if form, ok := window.(Form); ok {
|
|
if fb := form.AsFormBase(); fb != nil {
|
|
if err := fb.init(form); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if widget, ok := window.(Widget); ok {
|
|
if wb := widget.AsWidgetBase(); wb != nil {
|
|
if err := wb.init(widget); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
wb.enabledProperty = NewBoolProperty(
|
|
func() bool {
|
|
return wb.window.Enabled()
|
|
},
|
|
func(b bool) error {
|
|
wb.window.SetEnabled(b)
|
|
return nil
|
|
},
|
|
wb.enabledChangedPublisher.Event())
|
|
|
|
wb.visibleProperty = NewBoolProperty(
|
|
func() bool {
|
|
return window.AsWindowBase().visible
|
|
},
|
|
func(b bool) error {
|
|
wb.window.SetVisible(b)
|
|
return nil
|
|
},
|
|
wb.visibleChangedPublisher.Event())
|
|
|
|
wb.focusedProperty = NewReadOnlyBoolProperty(
|
|
func() bool {
|
|
return wb.window.Focused()
|
|
},
|
|
wb.focusedChangedPublisher.Event())
|
|
|
|
wb.MustRegisterProperty("Enabled", wb.enabledProperty)
|
|
wb.MustRegisterProperty("Visible", wb.visibleProperty)
|
|
wb.MustRegisterProperty("Focused", wb.focusedProperty)
|
|
|
|
succeeded = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitWrapperWindow initializes a window that wraps (embeds) another window.
|
|
//
|
|
// Calling this method is necessary, if you want to be able to override the
|
|
// WndProc method of the embedded window. The embedded window should only be
|
|
// used as inseparable part of the wrapper window to avoid undefined behavior.
|
|
func InitWrapperWindow(window Window) error {
|
|
wb := window.AsWindowBase()
|
|
|
|
wb.window = window
|
|
|
|
if container, ok := window.(Container); ok {
|
|
children := container.Children()
|
|
|
|
if wlo, ok := window.(widgetListObserver); ok {
|
|
children.observer = wlo
|
|
}
|
|
|
|
for _, child := range children.items {
|
|
child.parent = container
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (wb *WindowBase) MustRegisterProperty(name string, property Property) {
|
|
if property == nil {
|
|
panic("property must not be nil")
|
|
}
|
|
if wb.name2Property[name] != nil {
|
|
panic("property already registered")
|
|
}
|
|
|
|
wb.name2Property[name] = property
|
|
}
|
|
|
|
func (wb *WindowBase) Property(name string) Property {
|
|
return wb.name2Property[name]
|
|
}
|
|
|
|
func (wb *WindowBase) hasStyleBits(bits uint32) bool {
|
|
return hasWindowLongBits(wb.hWnd, win.GWL_STYLE, bits)
|
|
}
|
|
|
|
func (wb *WindowBase) hasExtendedStyleBits(bits uint32) bool {
|
|
return hasWindowLongBits(wb.hWnd, win.GWL_EXSTYLE, bits)
|
|
}
|
|
|
|
func hasWindowLongBits(hwnd win.HWND, index int32, bits uint32) bool {
|
|
value := uint32(win.GetWindowLong(hwnd, index))
|
|
|
|
return value&bits == bits
|
|
}
|
|
|
|
func (wb *WindowBase) setAndClearStyleBits(set, clear uint32) error {
|
|
return setAndClearWindowLongBits(wb.hWnd, win.GWL_STYLE, set, clear)
|
|
}
|
|
|
|
func (wb *WindowBase) setAndClearExtendedStyleBits(set, clear uint32) error {
|
|
return setAndClearWindowLongBits(wb.hWnd, win.GWL_EXSTYLE, set, clear)
|
|
}
|
|
|
|
func setAndClearWindowLongBits(hwnd win.HWND, index int32, set, clear uint32) error {
|
|
value := uint32(win.GetWindowLong(hwnd, index))
|
|
if value == 0 {
|
|
return lastError("GetWindowLong")
|
|
}
|
|
|
|
if newValue := value&^clear | set; newValue != value {
|
|
win.SetLastError(0)
|
|
if win.SetWindowLong(hwnd, index, int32(newValue)) == 0 {
|
|
return lastError("SetWindowLong")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (wb *WindowBase) ensureStyleBits(bits uint32, set bool) error {
|
|
return ensureWindowLongBits(wb.hWnd, win.GWL_STYLE, bits, set)
|
|
}
|
|
|
|
func (wb *WindowBase) ensureExtendedStyleBits(bits uint32, set bool) error {
|
|
return ensureWindowLongBits(wb.hWnd, win.GWL_EXSTYLE, bits, set)
|
|
}
|
|
|
|
func ensureWindowLongBits(hwnd win.HWND, index int32, bits uint32, set bool) error {
|
|
var setBits uint32
|
|
var clearBits uint32
|
|
if set {
|
|
setBits = bits
|
|
} else {
|
|
clearBits = bits
|
|
}
|
|
return setAndClearWindowLongBits(hwnd, index, setBits, clearBits)
|
|
}
|
|
|
|
// Accessibility returns the accessibility object used to set Dynamic Annotation properties of the
|
|
// window.
|
|
func (wb *WindowBase) Accessibility() *Accessibility {
|
|
if wb.acc == nil {
|
|
wb.acc = &Accessibility{wb: wb}
|
|
}
|
|
return wb.acc
|
|
}
|
|
|
|
// Handle returns the window handle of the Window.
|
|
func (wb *WindowBase) Handle() win.HWND {
|
|
return wb.hWnd
|
|
}
|
|
|
|
// SendMessage sends a message to the window and returns the result.
|
|
func (wb *WindowBase) SendMessage(msg uint32, wParam, lParam uintptr) uintptr {
|
|
return win.SendMessage(wb.hWnd, msg, wParam, lParam)
|
|
}
|
|
|
|
// Name returns the name of the *WindowBase.
|
|
func (wb *WindowBase) Name() string {
|
|
return wb.name
|
|
}
|
|
|
|
// SetName sets the name of the *WindowBase.
|
|
func (wb *WindowBase) SetName(name string) {
|
|
wb.name = name
|
|
}
|
|
|
|
func (wb *WindowBase) writePath(buf *bytes.Buffer) {
|
|
hWndParent := win.GetAncestor(wb.hWnd, win.GA_PARENT)
|
|
if pwi := windowFromHandle(hWndParent); pwi != nil {
|
|
if sv, ok := pwi.(*ScrollView); ok {
|
|
pwi = sv.Parent()
|
|
}
|
|
pwi.AsWindowBase().writePath(buf)
|
|
buf.WriteByte('/')
|
|
}
|
|
|
|
buf.WriteString(wb.name)
|
|
}
|
|
|
|
func (wb *WindowBase) path() string {
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
wb.writePath(buf)
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// WindowBase simply returns the receiver.
|
|
func (wb *WindowBase) AsWindowBase() *WindowBase {
|
|
return wb
|
|
}
|
|
|
|
// AddDisposable adds a Disposable resource that should be disposed of
|
|
// together with this Window.
|
|
func (wb *WindowBase) AddDisposable(d Disposable) {
|
|
wb.disposables = append(wb.disposables, d)
|
|
}
|
|
|
|
// Dispose releases the operating system resources, associated with the
|
|
// *WindowBase.
|
|
//
|
|
// If a user closes a *MainWindow or *Dialog, it is automatically released.
|
|
// Also, if a Container is disposed of, all its descendants will be released
|
|
// as well.
|
|
func (wb *WindowBase) Dispose() {
|
|
for _, d := range wb.disposables {
|
|
d.Dispose()
|
|
}
|
|
|
|
if wb.background != nil {
|
|
wb.background.detachWindow(wb)
|
|
}
|
|
|
|
hWnd := wb.hWnd
|
|
if hWnd != 0 {
|
|
wb.disposingPublisher.Publish()
|
|
|
|
wb.hWnd = 0
|
|
if _, ok := hwnd2WindowBase[hWnd]; ok {
|
|
win.DestroyWindow(hWnd)
|
|
}
|
|
}
|
|
|
|
if cm := wb.contextMenu; cm != nil {
|
|
cm.actions.Clear()
|
|
cm.Dispose()
|
|
}
|
|
|
|
if wb.shortcutActions != nil {
|
|
wb.shortcutActions.Clear()
|
|
}
|
|
|
|
for _, p := range wb.name2Property {
|
|
p.SetSource(nil)
|
|
}
|
|
|
|
if hWnd != 0 {
|
|
wb.group.accClearHwndProps(wb.hWnd)
|
|
wb.group.Done()
|
|
}
|
|
}
|
|
|
|
// Disposing returns an Event that is published when the Window is disposed
|
|
// of.
|
|
func (wb *WindowBase) Disposing() *Event {
|
|
return wb.disposingPublisher.Event()
|
|
}
|
|
|
|
// IsDisposed returns if the *WindowBase has been disposed of.
|
|
func (wb *WindowBase) IsDisposed() bool {
|
|
return wb.hWnd == 0
|
|
}
|
|
|
|
// ContextMenu returns the context menu of the *WindowBase.
|
|
//
|
|
// By default this is nil.
|
|
func (wb *WindowBase) ContextMenu() *Menu {
|
|
return wb.contextMenu
|
|
}
|
|
|
|
// SetContextMenu sets the context menu of the *WindowBase.
|
|
func (wb *WindowBase) SetContextMenu(value *Menu) {
|
|
wb.contextMenu = value
|
|
}
|
|
|
|
// ContextMenuLocation returns the the *WindowBase center in screen coordinates in native pixels.
|
|
func (wb *WindowBase) ContextMenuLocation() Point {
|
|
var rc win.RECT
|
|
if !win.GetWindowRect(wb.hWnd, &rc) {
|
|
return Point{}
|
|
}
|
|
return Point{int(rc.Left+rc.Right) / 2, int(rc.Top+rc.Bottom) / 2}
|
|
}
|
|
|
|
// ShortcutActions returns the list of actions that will be triggered if their
|
|
// shortcut is pressed when this window or one of its descendants has the
|
|
// keyboard focus.
|
|
func (wb *WindowBase) ShortcutActions() *ActionList {
|
|
if wb.shortcutActions == nil {
|
|
wb.shortcutActions = newActionList(wb)
|
|
}
|
|
|
|
return wb.shortcutActions
|
|
}
|
|
|
|
// Background returns the background Brush of the *WindowBase.
|
|
//
|
|
// By default this is nil.
|
|
func (wb *WindowBase) Background() Brush {
|
|
return wb.background
|
|
}
|
|
|
|
// SetBackground sets the background Brush of the *WindowBase.
|
|
func (wb *WindowBase) SetBackground(background Brush) {
|
|
if wb.background != nil {
|
|
wb.background.detachWindow(wb)
|
|
}
|
|
|
|
wb.background = background
|
|
|
|
if background != nil {
|
|
background.attachWindow(wb)
|
|
}
|
|
|
|
wb.Invalidate()
|
|
|
|
// Sliders need some extra encouragement...
|
|
walkDescendants(wb, func(w Window) bool {
|
|
if s, ok := w.(*Slider); ok {
|
|
s.SetRange(s.MinValue(), s.MaxValue()+1)
|
|
s.SetRange(s.MinValue(), s.MaxValue()-1)
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
// Cursor returns the Cursor of the *WindowBase.
|
|
//
|
|
// By default this is nil.
|
|
func (wb *WindowBase) Cursor() Cursor {
|
|
return wb.cursor
|
|
}
|
|
|
|
// SetCursor sets the Cursor of the *WindowBase.
|
|
func (wb *WindowBase) SetCursor(value Cursor) {
|
|
wb.cursor = value
|
|
}
|
|
|
|
// DoubleBuffering returns whether double buffering of the
|
|
// drawing is enabled, which may help reduce flicker.
|
|
func (wb *WindowBase) DoubleBuffering() bool {
|
|
return wb.hasExtendedStyleBits(win.WS_EX_COMPOSITED)
|
|
}
|
|
|
|
// SetDoubleBuffering enables or disables double buffering of the
|
|
// drawing, which may help reduce flicker.
|
|
func (wb *WindowBase) SetDoubleBuffering(enabled bool) error {
|
|
return wb.ensureExtendedStyleBits(win.WS_EX_COMPOSITED, enabled)
|
|
}
|
|
|
|
type ApplySysColorser interface {
|
|
ApplySysColors()
|
|
}
|
|
|
|
func (wb *WindowBase) ApplySysColors() {
|
|
wb.Invalidate()
|
|
}
|
|
|
|
// DPI returns the current DPI value of the WindowBase.
|
|
func (wb *WindowBase) DPI() int {
|
|
return int(win.GetDpiForWindow(wb.hWnd))
|
|
}
|
|
|
|
type ApplyDPIer interface {
|
|
ApplyDPI(dpi int)
|
|
}
|
|
|
|
func (wb *WindowBase) ApplyDPI(dpi int) {
|
|
if af, ok := wb.window.(applyFonter); ok {
|
|
af.applyFont(wb.window.Font())
|
|
}
|
|
}
|
|
|
|
// IntFrom96DPI converts from 1/96" units to native pixels.
|
|
func (wb *WindowBase) IntFrom96DPI(value int) int {
|
|
return IntFrom96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// IntTo96DPI converts from native pixels to 1/96" units.
|
|
func (wb *WindowBase) IntTo96DPI(value int) int {
|
|
return IntTo96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// MarginsFrom96DPI converts from 1/96" units to native pixels.
|
|
func (wb *WindowBase) MarginsFrom96DPI(value Margins) Margins {
|
|
return MarginsFrom96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// MarginsTo96DPI converts from native pixels to 1/96" units.
|
|
func (wb *WindowBase) MarginsTo96DPI(value Margins) Margins {
|
|
return MarginsTo96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// PointFrom96DPI converts from 1/96" units to native pixels.
|
|
func (wb *WindowBase) PointFrom96DPI(value Point) Point {
|
|
return PointFrom96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// PointTo96DPI converts from native pixels to 1/96" units.
|
|
func (wb *WindowBase) PointTo96DPI(value Point) Point {
|
|
return PointTo96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// RectangleFrom96DPI converts from 1/96" units to native pixels.
|
|
func (wb *WindowBase) RectangleFrom96DPI(value Rectangle) Rectangle {
|
|
return RectangleFrom96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// RectangleTo96DPI converts from native pixels to 1/96" units.
|
|
func (wb *WindowBase) RectangleTo96DPI(value Rectangle) Rectangle {
|
|
return RectangleTo96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// SizeFrom96DPI converts from 1/96" units to native pixels.
|
|
func (wb *WindowBase) SizeFrom96DPI(value Size) Size {
|
|
return SizeFrom96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// SizeTo96DPI converts from native pixels to 1/96" units.
|
|
func (wb *WindowBase) SizeTo96DPI(value Size) Size {
|
|
return SizeTo96DPI(value, wb.DPI())
|
|
}
|
|
|
|
// Enabled returns if the *WindowBase is enabled for user interaction.
|
|
func (wb *WindowBase) Enabled() bool {
|
|
return wb.enabled
|
|
}
|
|
|
|
// SetEnabled sets if the *WindowBase is enabled for user interaction.
|
|
func (wb *WindowBase) SetEnabled(enabled bool) {
|
|
wb.enabled = enabled
|
|
|
|
wb.window.(applyEnableder).applyEnabled(wb.window.Enabled())
|
|
|
|
if widget, ok := wb.window.(Widget); ok {
|
|
widget.AsWidgetBase().invalidateBorderInParent()
|
|
}
|
|
|
|
wb.enabledChangedPublisher.Publish()
|
|
}
|
|
|
|
type applyEnableder interface {
|
|
applyEnabled(enabled bool)
|
|
}
|
|
|
|
func (wb *WindowBase) applyEnabled(enabled bool) {
|
|
setWindowEnabled(wb.hWnd, enabled)
|
|
}
|
|
|
|
func setWindowEnabled(hwnd win.HWND, enabled bool) {
|
|
win.EnableWindow(hwnd, enabled)
|
|
|
|
win.UpdateWindow(hwnd)
|
|
}
|
|
|
|
// Font returns the *Font of the *WindowBase.
|
|
//
|
|
// By default this is a MS Shell Dlg 2, 8 point font.
|
|
func (wb *WindowBase) Font() *Font {
|
|
if wb.font != nil {
|
|
return wb.font
|
|
}
|
|
|
|
return defaultFont
|
|
}
|
|
|
|
// SetFont sets the *Font of the *WindowBase.
|
|
func (wb *WindowBase) SetFont(font *Font) {
|
|
if font != wb.font {
|
|
wb.font = font
|
|
|
|
wb.window.(applyFonter).applyFont(font)
|
|
}
|
|
}
|
|
|
|
type applyFonter interface {
|
|
applyFont(font *Font)
|
|
}
|
|
|
|
type ApplyFonter interface {
|
|
ApplyFont(font *Font)
|
|
}
|
|
|
|
func (wb *WindowBase) applyFont(font *Font) {
|
|
if hFont := font.handleForDPI(wb.DPI()); hFont != wb.hFont {
|
|
wb.hFont = hFont
|
|
|
|
setWindowFont(wb.hWnd, hFont)
|
|
}
|
|
|
|
if af, ok := wb.window.(ApplyFonter); ok {
|
|
af.ApplyFont(font)
|
|
}
|
|
}
|
|
|
|
func SetWindowFont(hwnd win.HWND, font *Font) {
|
|
dpi := int(win.GetDpiForWindow(hwnd))
|
|
setWindowFont(hwnd, font.handleForDPI(dpi))
|
|
}
|
|
|
|
func setWindowFont(hwnd win.HWND, hFont win.HFONT) {
|
|
win.SendMessage(hwnd, win.WM_SETFONT, uintptr(hFont), 1)
|
|
|
|
if window := windowFromHandle(hwnd); window != nil {
|
|
if widget, ok := window.(Widget); ok {
|
|
widget.AsWidgetBase().RequestLayout()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Suspended returns if the *WindowBase is suspended for layout and repainting
|
|
// purposes.
|
|
func (wb *WindowBase) Suspended() bool {
|
|
return wb.suspended
|
|
}
|
|
|
|
// SetSuspended sets if the *WindowBase is suspended for layout and repainting
|
|
// purposes.
|
|
//
|
|
// You should call SetSuspended(true), before doing a batch of modifications
|
|
// that would cause multiple layout or drawing updates. Remember to call
|
|
// SetSuspended(false) afterwards, which will update the *WindowBase
|
|
// accordingly.
|
|
func (wb *WindowBase) SetSuspended(suspend bool) {
|
|
if suspend == wb.suspended {
|
|
return
|
|
}
|
|
|
|
var wParam int
|
|
if suspend {
|
|
wParam = 0
|
|
} else {
|
|
wParam = 1
|
|
}
|
|
|
|
if wb.visible {
|
|
wb.SendMessage(win.WM_SETREDRAW, uintptr(wParam), 0)
|
|
}
|
|
|
|
wb.suspended = suspend
|
|
|
|
if !suspend {
|
|
wb.Invalidate()
|
|
wb.RequestLayout()
|
|
}
|
|
}
|
|
|
|
// Invalidate schedules a full repaint of the *WindowBase.
|
|
func (wb *WindowBase) Invalidate() error {
|
|
if !win.InvalidateRect(wb.hWnd, nil, true) {
|
|
return newError("InvalidateRect failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (wb *WindowBase) text() string {
|
|
return windowText(wb.hWnd)
|
|
}
|
|
|
|
func (wb *WindowBase) setText(text string) error {
|
|
if err := setWindowText(wb.hWnd, text); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func windowText(hwnd win.HWND) string {
|
|
textLength := win.SendMessage(hwnd, win.WM_GETTEXTLENGTH, 0, 0)
|
|
buf := make([]uint16, textLength+1)
|
|
win.SendMessage(hwnd, win.WM_GETTEXT, uintptr(textLength+1), uintptr(unsafe.Pointer(&buf[0])))
|
|
return syscall.UTF16ToString(buf)
|
|
}
|
|
|
|
func setWindowText(hwnd win.HWND, text string) error {
|
|
if win.TRUE != win.SendMessage(hwnd, win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) {
|
|
return newError("WM_SETTEXT failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (wb *WindowBase) RestoreState() (err error) {
|
|
wb.ForEachDescendant(func(widget Widget) bool {
|
|
if persistable, ok := widget.(Persistable); ok && persistable.Persistent() {
|
|
if err = persistable.RestoreState(); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if _, ok := widget.(Container); ok {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
func (wb *WindowBase) SaveState() (err error) {
|
|
wb.ForEachDescendant(func(widget Widget) bool {
|
|
if persistable, ok := widget.(Persistable); ok && persistable.Persistent() {
|
|
if err = persistable.SaveState(); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if _, ok := widget.(Container); ok {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// Form returns the Form of the Window.
|
|
func (wb *WindowBase) Form() Form {
|
|
if wb.form == nil {
|
|
if form, ok := wb.window.(Form); ok {
|
|
wb.form = form
|
|
} else {
|
|
wb.form = ancestor(wb.window.(Widget))
|
|
}
|
|
}
|
|
|
|
return wb.form
|
|
}
|
|
|
|
func forEachDescendant(hwnd win.HWND, lParam uintptr) uintptr {
|
|
if window := windowFromHandle(hwnd); window != nil && forEachDescendantCallback(window.(Widget)) {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
var (
|
|
forEachDescendantCallbackPtr uintptr
|
|
forEachDescendantCallback func(widget Widget) bool
|
|
)
|
|
|
|
func (wb *WindowBase) ForEachDescendant(f func(widget Widget) bool) {
|
|
forEachDescendantCallback = f
|
|
defer func() {
|
|
forEachDescendantCallback = nil
|
|
}()
|
|
|
|
win.EnumChildWindows(wb.hWnd, forEachDescendantCallbackPtr, 0)
|
|
}
|
|
|
|
// Visible returns if the *WindowBase is visible.
|
|
func (wb *WindowBase) Visible() bool {
|
|
return win.IsWindowVisible(wb.hWnd)
|
|
}
|
|
|
|
// SetVisible sets if the *WindowBase is visible.
|
|
func (wb *WindowBase) SetVisible(visible bool) {
|
|
old := wb.Visible()
|
|
|
|
setWindowVisible(wb.hWnd, visible)
|
|
|
|
wb.visible = visible
|
|
|
|
walkDescendants(wb.window, func(w Window) bool {
|
|
w.AsWindowBase().visibleChangedPublisher.Publish()
|
|
|
|
return true
|
|
})
|
|
|
|
if visible == old {
|
|
return
|
|
}
|
|
|
|
if widget, ok := wb.window.(Widget); ok {
|
|
wb := widget.AsWidgetBase()
|
|
wb.invalidateBorderInParent()
|
|
wb.RequestLayout()
|
|
}
|
|
|
|
wb.visibleChangedPublisher.Publish()
|
|
}
|
|
|
|
// VisibleChanged returns an Event that you can attach to for handling
|
|
// visible changed events for the Window.
|
|
func (wb *WindowBase) VisibleChanged() *Event {
|
|
return wb.visibleChangedPublisher.Event()
|
|
}
|
|
|
|
func setWindowVisible(hwnd win.HWND, visible bool) {
|
|
var cmd int32
|
|
if visible {
|
|
cmd = win.SW_SHOWNA
|
|
} else {
|
|
cmd = win.SW_HIDE
|
|
}
|
|
win.ShowWindow(hwnd, cmd)
|
|
}
|
|
|
|
// BringToTop moves the *WindowBase to the top of the keyboard focus order.
|
|
func (wb *WindowBase) BringToTop() error {
|
|
if !win.SetWindowPos(wb.hWnd, win.HWND_TOP, 0, 0, 0, 0, win.SWP_NOACTIVATE|win.SWP_NOMOVE|win.SWP_NOSIZE) {
|
|
return lastError("SetWindowPos")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Bounds returns the outer bounding box rectangle of the *WindowBase, including
|
|
// decorations.
|
|
//
|
|
// The coordinates are relative to the screen.
|
|
func (wb *WindowBase) Bounds() Rectangle {
|
|
return wb.RectangleTo96DPI(wb.BoundsPixels())
|
|
}
|
|
|
|
// SetBounds sets the outer bounding box rectangle of the *WindowBase,
|
|
// including decorations.
|
|
//
|
|
// For a Form, like *MainWindow or *Dialog, the rectangle is in screen
|
|
// coordinates, for a child Window the coordinates are relative to its parent.
|
|
func (wb *WindowBase) SetBounds(bounds Rectangle) error {
|
|
return wb.SetBoundsPixels(wb.RectangleFrom96DPI(bounds))
|
|
}
|
|
|
|
// BoundsPixels returns the outer bounding box rectangle of the *WindowBase, including
|
|
// decorations.
|
|
//
|
|
// The coordinates are relative to the screen.
|
|
func (wb *WindowBase) BoundsPixels() Rectangle {
|
|
var r win.RECT
|
|
|
|
if !win.GetWindowRect(wb.hWnd, &r) {
|
|
lastError("GetWindowRect")
|
|
return Rectangle{}
|
|
}
|
|
|
|
return rectangleFromRECT(r)
|
|
}
|
|
|
|
// SetBoundsPixels sets the outer bounding box rectangle of the *WindowBase,
|
|
// including decorations.
|
|
//
|
|
// For a Form, like *MainWindow or *Dialog, the rectangle is in screen
|
|
// coordinates, for a child Window the coordinates are relative to its parent.
|
|
func (wb *WindowBase) SetBoundsPixels(bounds Rectangle) error {
|
|
if !win.MoveWindow(
|
|
wb.hWnd,
|
|
int32(bounds.X),
|
|
int32(bounds.Y),
|
|
int32(bounds.Width),
|
|
int32(bounds.Height),
|
|
true) {
|
|
|
|
return lastError("MoveWindow")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MinSize returns the minimum allowed outer size for the *WindowBase, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the *WindowBase
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
func (wb *WindowBase) MinSize() Size {
|
|
return wb.minSize96dpi
|
|
}
|
|
|
|
// MinSizePixels returns the minimum allowed outer size for the *WindowBase, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the *WindowBase
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
func (wb *WindowBase) MinSizePixels() Size {
|
|
return wb.SizeFrom96DPI(wb.minSize96dpi)
|
|
}
|
|
|
|
// MaxSize returns the maximum allowed outer size for the *WindowBase, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the *WindowBase
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
func (wb *WindowBase) MaxSize() Size {
|
|
return wb.maxSize96dpi
|
|
}
|
|
|
|
// MaxSizePixels returns the maximum allowed outer size for the *WindowBase, including
|
|
// decorations.
|
|
//
|
|
// For child windows, this is only relevant when the parent of the *WindowBase
|
|
// has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this.
|
|
func (wb *WindowBase) MaxSizePixels() Size {
|
|
return wb.SizeFrom96DPI(wb.maxSize96dpi)
|
|
}
|
|
|
|
// SetMinMaxSize sets the minimum and maximum outer size of the *WindowBase,
|
|
// including decorations.
|
|
//
|
|
// Use walk.Size{} to make the respective limit be ignored.
|
|
func (wb *WindowBase) SetMinMaxSize(min, max Size) error {
|
|
if min.Width < 0 || min.Height < 0 {
|
|
return newError("min must be positive")
|
|
}
|
|
if max.Width > 0 && max.Width < min.Width ||
|
|
max.Height > 0 && max.Height < min.Height {
|
|
return newError("max must be greater as or equal to min")
|
|
}
|
|
wb.minSize96dpi = min
|
|
wb.maxSize96dpi = max
|
|
return nil
|
|
}
|
|
|
|
// SetMinMaxSizePixels sets the minimum and maximum outer size of the *WindowBase,
|
|
// including decorations.
|
|
//
|
|
// Use walk.Size{} to make the respective limit be ignored.
|
|
func (wb *WindowBase) SetMinMaxSizePixels(min, max Size) error {
|
|
dpi := wb.DPI()
|
|
return wb.SetMinMaxSize(SizeTo96DPI(min, dpi), SizeTo96DPI(max, dpi))
|
|
}
|
|
|
|
type fontInfoAndDPI struct {
|
|
fontInfo
|
|
dpi int
|
|
}
|
|
|
|
var (
|
|
dialogBaseUnitsUTF16StringPtr *uint16
|
|
fontInfoAndDPI2DialogBaseUnits = make(map[fontInfoAndDPI]Size)
|
|
)
|
|
|
|
// dialogBaseUnits returns dialog unit base size in native pixels.
|
|
func (wb *WindowBase) dialogBaseUnits() Size {
|
|
// The window may use a font different from that in WindowBase,
|
|
// like e.g. NumberEdit does, so we try to use the right one.
|
|
font := wb.window.Font()
|
|
fi := fontInfoAndDPI{
|
|
fontInfo: fontInfo{
|
|
family: font.Family(),
|
|
pointSize: font.PointSize(),
|
|
style: font.Style(),
|
|
},
|
|
dpi: wb.DPI()}
|
|
if s, ok := fontInfoAndDPI2DialogBaseUnits[fi]; ok {
|
|
return s
|
|
}
|
|
|
|
hdc := win.GetDC(wb.hWnd)
|
|
defer win.ReleaseDC(wb.hWnd, hdc)
|
|
|
|
hFont := font.handleForDPI(wb.DPI())
|
|
hFontOld := win.SelectObject(hdc, win.HGDIOBJ(hFont))
|
|
defer win.SelectObject(hdc, win.HGDIOBJ(hFontOld))
|
|
|
|
var tm win.TEXTMETRIC
|
|
if !win.GetTextMetrics(hdc, &tm) {
|
|
newError("GetTextMetrics failed")
|
|
}
|
|
|
|
var size win.SIZE
|
|
if !win.GetTextExtentPoint32(
|
|
hdc,
|
|
dialogBaseUnitsUTF16StringPtr,
|
|
52,
|
|
&size) {
|
|
newError("GetTextExtentPoint32 failed")
|
|
}
|
|
|
|
s := Size{int((size.CX/26 + 1) / 2), int(tm.TmHeight)}
|
|
|
|
fontInfoAndDPI2DialogBaseUnits[fi] = s
|
|
|
|
return s
|
|
}
|
|
|
|
// dialogBaseUnitsToPixels returns size in dialog based units in native pixels.
|
|
func (wb *WindowBase) dialogBaseUnitsToPixels(dlus Size) (pixels Size) {
|
|
base := wb.dialogBaseUnits()
|
|
|
|
return Size{
|
|
int(win.MulDiv(int32(dlus.Width), int32(base.Width), 4)),
|
|
int(win.MulDiv(int32(dlus.Height), int32(base.Height), 8)),
|
|
}
|
|
}
|
|
|
|
// calculateTextSizeImpl returns text size in native pixels.
|
|
func (wb *WindowBase) calculateTextSizeImpl(text string) Size {
|
|
return wb.calculateTextSizeImplForWidth(text, 0)
|
|
}
|
|
|
|
// calculateTextSizeImplForWidth calculates text size for specified width in native pixels.
|
|
func (wb *WindowBase) calculateTextSizeImplForWidth(text string, width int) Size {
|
|
font := wb.window.Font()
|
|
|
|
dpi := wb.DPI()
|
|
|
|
w := width
|
|
if w == 0 {
|
|
w = wb.WidthPixels()
|
|
}
|
|
|
|
key := calcTextSizeInfo{
|
|
width: w,
|
|
font: fontInfo{
|
|
family: font.family,
|
|
pointSize: font.pointSize,
|
|
style: font.style,
|
|
},
|
|
text: text,
|
|
dpi: dpi,
|
|
}
|
|
|
|
if size, ok := wb.calcTextSizeInfo2TextSize[key]; ok {
|
|
return size
|
|
}
|
|
|
|
size := calculateTextSize(text, font, dpi, width, wb.hWnd)
|
|
|
|
wb.calcTextSizeInfo2TextSize[key] = size
|
|
|
|
return size
|
|
}
|
|
|
|
// calculateTextSize calculates text size in native pixels.
|
|
func (wb *WindowBase) calculateTextSize() Size {
|
|
return wb.calculateTextSizeForWidth(0)
|
|
}
|
|
|
|
// calculateTextSizeForWidth calculates text size for specified width in native pixels.
|
|
func (wb *WindowBase) calculateTextSizeForWidth(width int) Size {
|
|
return wb.calculateTextSizeImplForWidth(wb.text(), width)
|
|
}
|
|
|
|
// calculateTextSize calculates text size at specified DPI and for width in native pixels.
|
|
func calculateTextSize(text string, font *Font, dpi int, width int, hwnd win.HWND) Size {
|
|
hdc := win.GetDC(hwnd)
|
|
if hdc == 0 {
|
|
newError("GetDC failed")
|
|
return Size{}
|
|
}
|
|
defer win.ReleaseDC(hwnd, hdc)
|
|
|
|
var size Size
|
|
if width > 0 {
|
|
canvas, err := newCanvasFromHDC(hdc)
|
|
if err != nil {
|
|
return size
|
|
}
|
|
defer canvas.Dispose()
|
|
|
|
bounds, err := canvas.measureTextForDPI(text, font, Rectangle{Width: width, Height: 9999999}, 0, dpi)
|
|
if err != nil {
|
|
return size
|
|
}
|
|
|
|
size = bounds.Size()
|
|
} else {
|
|
hFontOld := win.SelectObject(hdc, win.HGDIOBJ(font.handleForDPI(dpi)))
|
|
defer win.SelectObject(hdc, hFontOld)
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
for _, line := range lines {
|
|
var s win.SIZE
|
|
str := syscall.StringToUTF16(strings.TrimRight(line, "\r "))
|
|
|
|
if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) {
|
|
newError("GetTextExtentPoint32 failed")
|
|
return Size{}
|
|
}
|
|
|
|
size.Width = maxi(size.Width, int(s.CX))
|
|
size.Height += int(s.CY)
|
|
}
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
// Size returns the outer size of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) Size() Size {
|
|
return wb.SizeTo96DPI(wb.SizePixels())
|
|
}
|
|
|
|
// SizePixels returns the outer size of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SizePixels() Size {
|
|
return wb.window.BoundsPixels().Size()
|
|
}
|
|
|
|
// SetSize sets the outer size of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SetSize(size Size) error {
|
|
return wb.SetSizePixels(wb.SizeFrom96DPI(size))
|
|
}
|
|
|
|
// SetSizePixels sets the outer size of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SetSizePixels(size Size) error {
|
|
bounds := wb.window.BoundsPixels()
|
|
|
|
return wb.SetBoundsPixels(bounds.SetSize(size))
|
|
}
|
|
|
|
// X returns the x coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) X() int {
|
|
return wb.IntTo96DPI(wb.XPixels())
|
|
}
|
|
|
|
// XPixels returns the x coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) XPixels() int {
|
|
return wb.window.BoundsPixels().X
|
|
}
|
|
|
|
// SetX sets the x coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) SetX(value int) error {
|
|
return wb.SetXPixels(wb.IntFrom96DPI(value))
|
|
}
|
|
|
|
// SetXPixels sets the x coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) SetXPixels(value int) error {
|
|
bounds := wb.window.BoundsPixels()
|
|
bounds.X = value
|
|
|
|
return wb.SetBoundsPixels(bounds)
|
|
}
|
|
|
|
// Y returns the y coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) Y() int {
|
|
return wb.IntTo96DPI(wb.YPixels())
|
|
}
|
|
|
|
// YPixels returns the y coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) YPixels() int {
|
|
return wb.window.BoundsPixels().Y
|
|
}
|
|
|
|
// SetY sets the y coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) SetY(value int) error {
|
|
return wb.SetYPixels(wb.IntFrom96DPI(value))
|
|
}
|
|
|
|
// SetYPixels sets the y coordinate of the *WindowBase, relative to the screen for
|
|
// RootWidgets like *MainWindow or *Dialog and relative to the parent for
|
|
// child Windows.
|
|
func (wb *WindowBase) SetYPixels(value int) error {
|
|
bounds := wb.window.BoundsPixels()
|
|
bounds.Y = value
|
|
|
|
return wb.SetBoundsPixels(bounds)
|
|
}
|
|
|
|
// Width returns the outer width of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) Width() int {
|
|
return wb.IntTo96DPI(wb.WidthPixels())
|
|
}
|
|
|
|
// WidthPixels returns the outer width of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) WidthPixels() int {
|
|
return wb.window.BoundsPixels().Width
|
|
}
|
|
|
|
// SetWidth sets the outer width of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SetWidth(value int) error {
|
|
return wb.SetWidthPixels(wb.IntFrom96DPI(value))
|
|
}
|
|
|
|
// SetWidthPixels sets the outer width of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SetWidthPixels(value int) error {
|
|
bounds := wb.window.BoundsPixels()
|
|
bounds.Width = value
|
|
|
|
return wb.SetBoundsPixels(bounds)
|
|
}
|
|
|
|
// Height returns the outer height of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) Height() int {
|
|
return wb.IntTo96DPI(wb.HeightPixels())
|
|
}
|
|
|
|
// HeightPixels returns the outer height of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) HeightPixels() int {
|
|
return wb.window.BoundsPixels().Height
|
|
}
|
|
|
|
// SetHeight sets the outer height of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SetHeight(value int) error {
|
|
return wb.SetHeightPixels(wb.IntFrom96DPI(value))
|
|
}
|
|
|
|
// SetHeightPixels sets the outer height of the *WindowBase, including decorations.
|
|
func (wb *WindowBase) SetHeightPixels(value int) error {
|
|
bounds := wb.window.BoundsPixels()
|
|
bounds.Height = value
|
|
|
|
return wb.SetBoundsPixels(bounds)
|
|
}
|
|
|
|
func windowTrimToClientBounds(hwnd win.HWND, pt *win.POINT) {
|
|
var r win.RECT
|
|
|
|
if !win.GetClientRect(hwnd, &r) {
|
|
lastError("GetClientRect")
|
|
return
|
|
}
|
|
|
|
if pt.X < r.Left {
|
|
pt.X = r.Left
|
|
}
|
|
if pt.X > r.Right {
|
|
pt.X = r.Right
|
|
}
|
|
if pt.Y < r.Top {
|
|
pt.Y = r.Top
|
|
}
|
|
if pt.Y > r.Bottom {
|
|
pt.Y = r.Bottom
|
|
}
|
|
}
|
|
|
|
// windowClientBounds returns window client bounds in native pixels.
|
|
func windowClientBounds(hwnd win.HWND) Rectangle {
|
|
var r win.RECT
|
|
|
|
if !win.GetClientRect(hwnd, &r) {
|
|
lastError("GetClientRect")
|
|
return Rectangle{}
|
|
}
|
|
|
|
return rectangleFromRECT(r)
|
|
}
|
|
|
|
// ClientBounds returns the inner bounding box rectangle of the *WindowBase,
|
|
// excluding decorations.
|
|
func (wb *WindowBase) ClientBounds() Rectangle {
|
|
return wb.RectangleTo96DPI(wb.ClientBoundsPixels())
|
|
}
|
|
|
|
// ClientBoundsPixels returns the inner bounding box rectangle of the *WindowBase,
|
|
// excluding decorations.
|
|
func (wb *WindowBase) ClientBoundsPixels() Rectangle {
|
|
return windowClientBounds(wb.hWnd)
|
|
}
|
|
|
|
// sizeFromClientSizePixels calculates size from client size in native pixels.
|
|
func (wb *WindowBase) sizeFromClientSizePixels(clientSize Size) Size {
|
|
window := wb.window
|
|
s := window.SizePixels()
|
|
cs := window.ClientBoundsPixels().Size()
|
|
ncs := Size{s.Width - cs.Width, s.Height - cs.Height}
|
|
|
|
return Size{clientSize.Width + ncs.Width, clientSize.Height + ncs.Height}
|
|
}
|
|
|
|
// clientSizeFromSizePixels calculates client size from size in native pixels.
|
|
func (wb *WindowBase) clientSizeFromSizePixels(size Size) Size {
|
|
window := wb.window
|
|
s := window.SizePixels()
|
|
cs := window.ClientBoundsPixels().Size()
|
|
ncs := Size{s.Width - cs.Width, s.Height - cs.Height}
|
|
|
|
return Size{size.Width - ncs.Width, size.Height - ncs.Height}
|
|
}
|
|
|
|
// SetClientSize sets the size of the inner bounding box of the *WindowBase,
|
|
// excluding decorations.
|
|
func (wb *WindowBase) SetClientSize(value Size) error {
|
|
return wb.SetClientSizePixels(wb.SizeFrom96DPI(value))
|
|
}
|
|
|
|
// SetClientSizePixels sets the size of the inner bounding box of the *WindowBase,
|
|
// excluding decorations.
|
|
func (wb *WindowBase) SetClientSizePixels(value Size) error {
|
|
return wb.SetSizePixels(wb.sizeFromClientSizePixels(value))
|
|
}
|
|
|
|
// RequestLayout either schedules or immediately starts performing layout.
|
|
func (wb *WindowBase) RequestLayout() {
|
|
var form Form
|
|
|
|
hwnd := wb.hWnd
|
|
window := wb.window
|
|
|
|
for hwnd != 0 {
|
|
if window != nil {
|
|
var ok, visible bool
|
|
if form, ok = window.(Form); ok {
|
|
visible = form.Visible()
|
|
} else {
|
|
visible = window.AsWindowBase().visible
|
|
}
|
|
|
|
if !visible && window != wb.window || window.Suspended() {
|
|
return
|
|
}
|
|
|
|
if container, ok := window.(Container); ok && container.Layout() == nil {
|
|
return
|
|
}
|
|
|
|
if widget, ok := window.(Widget); ok {
|
|
if window = widget.Parent(); window != nil {
|
|
hwnd = window.Handle()
|
|
continue
|
|
}
|
|
}
|
|
} else if !win.IsWindowVisible(hwnd) {
|
|
return
|
|
}
|
|
|
|
hwnd = win.GetParent(hwnd)
|
|
window = windowFromHandle(hwnd)
|
|
}
|
|
|
|
if form == nil {
|
|
return
|
|
}
|
|
|
|
if fb := form.AsFormBase(); fb.group.ActiveForm() != form || fb.inProgressEventCount == 0 {
|
|
fb.startLayout()
|
|
} else {
|
|
fb.layoutScheduled = true
|
|
}
|
|
}
|
|
|
|
// RightToLeftReading returns whether the reading order of the Window
|
|
// is from right to left.
|
|
func (wb *WindowBase) RightToLeftReading() bool {
|
|
return wb.hasExtendedStyleBits(win.WS_EX_RTLREADING)
|
|
}
|
|
|
|
// SetRightToLeftReading sets whether the reading order of the Window
|
|
// is from right to left.
|
|
func (wb *WindowBase) SetRightToLeftReading(rtl bool) error {
|
|
return wb.ensureExtendedStyleBits(win.WS_EX_RTLREADING, rtl)
|
|
}
|
|
|
|
// Screenshot returns an image of the window.
|
|
func (wb *WindowBase) Screenshot() (*image.RGBA, error) {
|
|
bmp, err := NewBitmapFromWindow(wb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer bmp.Dispose()
|
|
|
|
return bmp.ToImage()
|
|
}
|
|
|
|
// FocusedWindow returns the Window that has the keyboard input focus.
|
|
func FocusedWindow() Window {
|
|
return windowFromHandle(win.GetFocus())
|
|
}
|
|
|
|
// Focused returns whether the Window has the keyboard input focus.
|
|
func (wb *WindowBase) Focused() bool {
|
|
return wb.hWnd == win.GetFocus()
|
|
}
|
|
|
|
// SetFocus sets the keyboard input focus to the *WindowBase.
|
|
func (wb *WindowBase) SetFocus() error {
|
|
if win.SetFocus(wb.hWnd) == 0 {
|
|
return lastError("SetFocus")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FocusedChanged returns an Event that you can attach to for handling focus
|
|
// change events for the WindowBase.
|
|
func (wb *WindowBase) FocusedChanged() *Event {
|
|
return wb.focusedChangedPublisher.Event()
|
|
}
|
|
|
|
// CreateCanvas creates and returns a *Canvas that can be used to draw
|
|
// inside the ClientBoundsPixels of the *WindowBase.
|
|
//
|
|
// Remember to call the Dispose method on the canvas to release resources,
|
|
// when you no longer need it.
|
|
func (wb *WindowBase) CreateCanvas() (*Canvas, error) {
|
|
return newCanvasFromWindow(wb.window)
|
|
}
|
|
|
|
func (wb *WindowBase) setTheme(appName string) error {
|
|
if hr := win.SetWindowTheme(wb.hWnd, syscall.StringToUTF16Ptr(appName), nil); win.FAILED(hr) {
|
|
return errorFromHRESULT("SetWindowTheme", hr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// KeyDown returns a *KeyEvent that you can attach to for handling key down
|
|
// events for the *WindowBase.
|
|
func (wb *WindowBase) KeyDown() *KeyEvent {
|
|
return wb.keyDownPublisher.Event()
|
|
}
|
|
|
|
// KeyPress returns a *KeyEvent that you can attach to for handling key press
|
|
// events for the *WindowBase.
|
|
func (wb *WindowBase) KeyPress() *KeyEvent {
|
|
return wb.keyPressPublisher.Event()
|
|
}
|
|
|
|
// KeyUp returns a *KeyEvent that you can attach to for handling key up
|
|
// events for the *WindowBase.
|
|
func (wb *WindowBase) KeyUp() *KeyEvent {
|
|
return wb.keyUpPublisher.Event()
|
|
}
|
|
|
|
// DropFiles returns a *DropFilesEvent that you can attach to for handling
|
|
// drop file events for the *WindowBase.
|
|
func (wb *WindowBase) DropFiles() *DropFilesEvent {
|
|
return wb.dropFilesPublisher.Event(wb.hWnd)
|
|
}
|
|
|
|
// MouseDown returns a *MouseEvent that you can attach to for handling
|
|
// mouse down events for the *WindowBase.
|
|
func (wb *WindowBase) MouseDown() *MouseEvent {
|
|
return wb.mouseDownPublisher.Event()
|
|
}
|
|
|
|
// MouseMove returns a *MouseEvent that you can attach to for handling
|
|
// mouse move events for the *WindowBase.
|
|
func (wb *WindowBase) MouseMove() *MouseEvent {
|
|
return wb.mouseMovePublisher.Event()
|
|
}
|
|
|
|
// MouseUp returns a *MouseEvent that you can attach to for handling
|
|
// mouse up events for the *WindowBase.
|
|
func (wb *WindowBase) MouseUp() *MouseEvent {
|
|
return wb.mouseUpPublisher.Event()
|
|
}
|
|
|
|
func (wb *WindowBase) MouseWheel() *MouseEvent {
|
|
return wb.mouseWheelPublisher.Event()
|
|
}
|
|
|
|
func (wb *WindowBase) publishMouseEvent(publisher *MouseEventPublisher, msg uint32, wParam, lParam uintptr) {
|
|
x := int(win.GET_X_LPARAM(lParam))
|
|
y := int(win.GET_Y_LPARAM(lParam))
|
|
|
|
var button MouseButton
|
|
switch msg {
|
|
case win.WM_LBUTTONUP:
|
|
button = LeftButton
|
|
|
|
case win.WM_RBUTTONUP:
|
|
button = RightButton
|
|
|
|
case win.WM_MBUTTONUP:
|
|
button = MiddleButton
|
|
|
|
default:
|
|
button = MouseButton(wParam&win.MK_LBUTTON | wParam&win.MK_RBUTTON | wParam&win.MK_MBUTTON)
|
|
}
|
|
|
|
publisher.Publish(x, y, button)
|
|
}
|
|
|
|
func (wb *WindowBase) publishMouseWheelEvent(publisher *MouseEventPublisher, wParam, lParam uintptr) {
|
|
x := int(win.GET_X_LPARAM(lParam))
|
|
y := int(win.GET_Y_LPARAM(lParam))
|
|
button := MouseButton(uint32(wParam))
|
|
|
|
publisher.Publish(x, y, button)
|
|
}
|
|
|
|
// SizeChanged returns an *Event that you can attach to for handling size
|
|
// changed events for the *WindowBase.
|
|
func (wb *WindowBase) SizeChanged() *Event {
|
|
return wb.sizeChangedPublisher.Event()
|
|
}
|
|
|
|
// BoundsChanged returns an *Event that you can attach to for handling bounds
|
|
// changed events for the *WindowBase.
|
|
func (wb *WindowBase) BoundsChanged() *Event {
|
|
return wb.boundsChangedPublisher.Event()
|
|
}
|
|
|
|
// Synchronize enqueues func f to be called some time later by the main
|
|
// goroutine from inside a message loop.
|
|
func (wb *WindowBase) Synchronize(f func()) {
|
|
wb.group.Synchronize(f)
|
|
|
|
win.PostMessage(wb.hWnd, syncMsgId, 0, 0)
|
|
}
|
|
|
|
// synchronizeLayout causes the given layout computations to be applied
|
|
// later by the message loop running on the group's thread.
|
|
//
|
|
// Any previously queued layout computations that have not yet been applied
|
|
// will be replaced.
|
|
func (wb *WindowBase) synchronizeLayout(result *formLayoutResult) {
|
|
wb.group.synchronizeLayout(result)
|
|
|
|
win.PostMessage(wb.hWnd, syncMsgId, 0, 0)
|
|
}
|
|
|
|
func (wb *WindowBase) ReadState() (string, error) {
|
|
settings := App().Settings()
|
|
if settings == nil {
|
|
return "", newError("App().Settings() must not be nil")
|
|
}
|
|
|
|
state, _ := settings.Get(wb.path())
|
|
return state, nil
|
|
}
|
|
|
|
func (wb *WindowBase) WriteState(state string) error {
|
|
settings := App().Settings()
|
|
if settings == nil {
|
|
return newError("App().Settings() must not be nil")
|
|
}
|
|
|
|
p := wb.path()
|
|
if strings.HasPrefix(p, "/") ||
|
|
strings.HasSuffix(p, "/") ||
|
|
strings.Contains(p, "//") {
|
|
|
|
return nil
|
|
}
|
|
|
|
return settings.PutExpiring(p, state)
|
|
}
|
|
|
|
func windowFromHandle(hwnd win.HWND) Window {
|
|
if wb := hwnd2WindowBase[hwnd]; wb != nil {
|
|
return wb.window
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func defaultWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) (result uintptr) {
|
|
defer func() {
|
|
// FIXME: Rework the panicking publisher so that we don't have to
|
|
// access a private member here.
|
|
if len(App().panickingPublisher.event.handlers) > 0 {
|
|
var err error
|
|
if x := recover(); x != nil {
|
|
if e, ok := x.(error); ok {
|
|
err = wrapErrorNoPanic(e)
|
|
} else {
|
|
err = newErrorNoPanic(fmt.Sprint(x))
|
|
}
|
|
}
|
|
if err != nil {
|
|
App().panickingPublisher.Publish(err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
if msg == notifyIconMessageId {
|
|
return notifyIconWndProc(hwnd, msg, wParam, lParam)
|
|
}
|
|
|
|
wi := windowFromHandle(hwnd)
|
|
if wi == nil {
|
|
return win.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
}
|
|
|
|
result = wi.WndProc(hwnd, msg, wParam, lParam)
|
|
|
|
return
|
|
}
|
|
|
|
type menuer interface {
|
|
Menu() *Menu
|
|
}
|
|
|
|
func menuContainsAction(menu *Menu, action *Action) bool {
|
|
if menu.Actions().Contains(action) {
|
|
return true
|
|
}
|
|
|
|
for _, a := range menu.actions.actions {
|
|
if a.menu != nil && menuContainsAction(a.menu, action) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (wb *WindowBase) handleKeyDown(wParam, lParam uintptr) {
|
|
key := Key(wParam)
|
|
|
|
if uint32(lParam)>>30 == 0 {
|
|
wb.keyDownPublisher.Publish(key)
|
|
|
|
// Using TranslateAccelerators refused to work, so we handle them
|
|
// ourselves, at least for now.
|
|
shortcut := Shortcut{ModifiersDown(), key}
|
|
if action, ok := shortcut2Action[shortcut]; ok {
|
|
if action.Visible() && action.Enabled() {
|
|
window := wb.window
|
|
|
|
if w, ok := window.(Widget); ok {
|
|
window = ancestor(w)
|
|
}
|
|
|
|
if m, ok := window.(menuer); ok && menuContainsAction(m.Menu(), action) {
|
|
action.raiseTriggered()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch key {
|
|
case KeyAlt, KeyControl, KeyShift:
|
|
// nop
|
|
|
|
default:
|
|
wb.keyPressPublisher.Publish(key)
|
|
}
|
|
}
|
|
|
|
func (wb *WindowBase) handleKeyUp(wParam, lParam uintptr) {
|
|
wb.keyUpPublisher.Publish(Key(wParam))
|
|
}
|
|
|
|
func (wb *WindowBase) backgroundEffective() (Brush, Window) {
|
|
wnd := wb.window
|
|
bg := wnd.Background()
|
|
|
|
if widget, ok := wb.window.(Widget); ok {
|
|
for bg == nullBrushSingleton && widget != nil {
|
|
if hwndParent := win.GetParent(widget.Handle()); hwndParent != 0 {
|
|
if parent := windowFromHandle(hwndParent); parent != nil {
|
|
wnd = parent
|
|
bg = parent.Background()
|
|
|
|
widget, _ = parent.(Widget)
|
|
} else {
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if bg != nil {
|
|
if pwb, ok := bg.(perWindowBrush); ok {
|
|
bg = pwb.delegateForWindow(wnd.AsWindowBase())
|
|
}
|
|
}
|
|
|
|
return bg, wnd
|
|
}
|
|
|
|
func (wb *WindowBase) prepareDCForBackground(hdc win.HDC, hwnd win.HWND, brushWnd Window) {
|
|
win.SetBkMode(hdc, win.TRANSPARENT)
|
|
|
|
var bgRC win.RECT
|
|
win.GetWindowRect(brushWnd.Handle(), &bgRC)
|
|
|
|
var rc win.RECT
|
|
win.GetWindowRect(hwnd, &rc)
|
|
|
|
win.SetBrushOrgEx(hdc, bgRC.Left-rc.Left, bgRC.Top-rc.Top, nil)
|
|
}
|
|
|
|
func (wb *WindowBase) handleWMCTLCOLOR(wParam, lParam uintptr) uintptr {
|
|
hwnd := win.HWND(lParam)
|
|
hdc := win.HDC(wParam)
|
|
|
|
type TextColorer interface {
|
|
TextColor() Color
|
|
}
|
|
|
|
wnd := windowFromHandle(hwnd)
|
|
if wnd == nil {
|
|
switch windowFromHandle(win.GetParent(hwnd)).(type) {
|
|
case *ComboBox:
|
|
// nop
|
|
return 0
|
|
}
|
|
|
|
wnd = wb
|
|
} else if tc, ok := wnd.(TextColorer); ok {
|
|
color := tc.TextColor()
|
|
if color == 0 {
|
|
color = Color(win.GetSysColor(win.COLOR_WINDOWTEXT))
|
|
}
|
|
win.SetTextColor(hdc, win.COLORREF(color))
|
|
}
|
|
|
|
if bg, wnd := wnd.AsWindowBase().backgroundEffective(); bg != nil {
|
|
wb.prepareDCForBackground(hdc, hwnd, wnd)
|
|
|
|
type Colorer interface {
|
|
Color() Color
|
|
}
|
|
|
|
if c, ok := bg.(Colorer); ok {
|
|
win.SetBkColor(hdc, win.COLORREF(c.Color()))
|
|
}
|
|
|
|
return uintptr(bg.handle())
|
|
}
|
|
|
|
switch wnd.(type) {
|
|
case *LineEdit, *numberLineEdit, *TextEdit:
|
|
type ReadOnlyer interface {
|
|
ReadOnly() bool
|
|
}
|
|
|
|
var sysColor int
|
|
if ro, ok := wnd.(ReadOnlyer); ok && ro.ReadOnly() {
|
|
sysColor = win.COLOR_BTNFACE
|
|
} else {
|
|
sysColor = win.COLOR_WINDOW
|
|
}
|
|
|
|
win.SetBkColor(hdc, win.COLORREF(win.GetSysColor(sysColor)))
|
|
|
|
return uintptr(win.GetSysColorBrush(sysColor))
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// WndProc is the window procedure of the window.
|
|
//
|
|
// When implementing your own WndProc to add or modify behavior, call the
|
|
// WndProc of the embedded window for messages you don't handle yourself.
|
|
func (wb *WindowBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
|
window := windowFromHandle(hwnd)
|
|
|
|
switch msg {
|
|
case win.WM_ERASEBKGND:
|
|
if _, ok := window.(Widget); !ok {
|
|
return 0
|
|
}
|
|
|
|
bg, wnd := wb.backgroundEffective()
|
|
if bg == nil {
|
|
break
|
|
}
|
|
|
|
hdc := win.HDC(wParam)
|
|
|
|
canvas, err := newCanvasFromHDC(hdc)
|
|
if err != nil {
|
|
break
|
|
}
|
|
defer canvas.Dispose()
|
|
|
|
wb.prepareDCForBackground(hdc, hwnd, wnd)
|
|
|
|
if err := canvas.FillRectanglePixels(bg, wb.ClientBoundsPixels()); err != nil {
|
|
break
|
|
}
|
|
|
|
return 1
|
|
|
|
case win.WM_HSCROLL, win.WM_VSCROLL:
|
|
if window := windowFromHandle(win.HWND(lParam)); window != nil {
|
|
// The window that sent the notification shall handle it itself.
|
|
return window.WndProc(hwnd, msg, wParam, lParam)
|
|
}
|
|
|
|
case win.WM_LBUTTONDOWN, win.WM_MBUTTONDOWN, win.WM_RBUTTONDOWN:
|
|
if msg == win.WM_LBUTTONDOWN && wb.origWndProcPtr == 0 {
|
|
// Only call SetCapture if this is no subclassed control.
|
|
// (Otherwise e.g. WM_COMMAND(BN_CLICKED) would no longer
|
|
// be generated for PushButton.)
|
|
win.SetCapture(wb.hWnd)
|
|
}
|
|
wb.publishMouseEvent(&wb.mouseDownPublisher, msg, wParam, lParam)
|
|
|
|
case win.WM_LBUTTONUP, win.WM_MBUTTONUP, win.WM_RBUTTONUP:
|
|
if msg == win.WM_LBUTTONUP && wb.origWndProcPtr == 0 {
|
|
// See WM_LBUTTONDOWN for why we require origWndProcPtr == 0 here.
|
|
if !win.ReleaseCapture() {
|
|
lastError("ReleaseCapture")
|
|
}
|
|
}
|
|
wb.publishMouseEvent(&wb.mouseUpPublisher, msg, wParam, lParam)
|
|
|
|
case win.WM_MOUSEMOVE:
|
|
wb.publishMouseEvent(&wb.mouseMovePublisher, msg, wParam, lParam)
|
|
|
|
case win.WM_MOUSEWHEEL:
|
|
wb.publishMouseWheelEvent(&wb.mouseWheelPublisher, wParam, lParam)
|
|
|
|
case win.WM_SETFOCUS, win.WM_KILLFOCUS:
|
|
switch wnd := wb.window.(type) {
|
|
// case *splitterHandle:
|
|
// nop
|
|
|
|
case Widget:
|
|
parent := wnd.Parent()
|
|
if parent == nil {
|
|
hwndParent := win.GetParent(wnd.Handle())
|
|
for parent == nil && hwndParent != 0 {
|
|
hwndParent = win.GetParent(hwndParent)
|
|
if wnd := windowFromHandle(hwndParent); wnd != nil {
|
|
parent, _ = wnd.(Container)
|
|
}
|
|
}
|
|
}
|
|
|
|
if wb.Form() == wb.group.ActiveForm() {
|
|
wnd.AsWidgetBase().invalidateBorderInParent()
|
|
}
|
|
}
|
|
|
|
wb.focusedChangedPublisher.Publish()
|
|
|
|
case win.WM_SETCURSOR:
|
|
if wb.cursor != nil {
|
|
win.SetCursor(wb.cursor.handle())
|
|
return 0
|
|
}
|
|
|
|
case win.WM_CONTEXTMENU:
|
|
sourceWindow := windowFromHandle(win.HWND(wParam))
|
|
if sourceWindow == nil {
|
|
break
|
|
}
|
|
|
|
contextMenu := sourceWindow.ContextMenu()
|
|
|
|
var handle win.HWND
|
|
if widget, ok := sourceWindow.(Widget); ok {
|
|
if form := ancestor(widget); form != nil {
|
|
handle = form.Handle()
|
|
}
|
|
}
|
|
|
|
if handle == 0 {
|
|
handle = sourceWindow.Handle()
|
|
}
|
|
|
|
if contextMenu != nil {
|
|
x := win.GET_X_LPARAM(lParam)
|
|
y := win.GET_Y_LPARAM(lParam)
|
|
if x == -1 && y == -1 {
|
|
pt := sourceWindow.ContextMenuLocation()
|
|
x = int32(pt.X)
|
|
y = int32(pt.Y)
|
|
}
|
|
|
|
contextMenu.updateItemsWithImageForWindow(wb.window)
|
|
|
|
win.TrackPopupMenuEx(
|
|
contextMenu.hMenu,
|
|
win.TPM_NOANIMATION,
|
|
x,
|
|
y,
|
|
handle,
|
|
nil)
|
|
return 0
|
|
}
|
|
|
|
case win.WM_KEYDOWN:
|
|
wb.handleKeyDown(wParam, lParam)
|
|
|
|
case win.WM_KEYUP:
|
|
wb.handleKeyUp(wParam, lParam)
|
|
|
|
case win.WM_DROPFILES:
|
|
wb.dropFilesPublisher.Publish(win.HDROP(wParam))
|
|
|
|
case win.WM_WINDOWPOSCHANGED:
|
|
wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))
|
|
|
|
if wp.Flags&win.SWP_NOMOVE != 0 && wp.Flags&win.SWP_NOSIZE != 0 {
|
|
break
|
|
}
|
|
|
|
if wp.Flags&win.SWP_NOSIZE == 0 {
|
|
if widget, ok := wb.window.(Widget); ok {
|
|
wb := widget.AsWidgetBase()
|
|
wb.geometry.Size = wb.window.SizePixels()
|
|
wb.geometry.ClientSize = Size{int(wp.Cx), int(wp.Cy)}
|
|
|
|
wb.invalidateBorderInParent()
|
|
}
|
|
|
|
wb.sizeChangedPublisher.Publish()
|
|
}
|
|
|
|
wb.boundsChangedPublisher.Publish()
|
|
|
|
if nws, ok := wb.window.(interface{ NeedsWmSize() bool }); !ok || !nws.NeedsWmSize() {
|
|
return 0
|
|
}
|
|
|
|
case win.WM_THEMECHANGED:
|
|
wb.window.(ApplySysColorser).ApplySysColors()
|
|
|
|
case win.WM_DESTROY:
|
|
if wb.origWndProcPtr != 0 {
|
|
// As we subclass all windows of system classes, we prevented the
|
|
// clean-up code in the WM_NCDESTROY handlers of some windows from
|
|
// being called. To fix this, we restore the original window
|
|
// procedure here.
|
|
win.SetWindowLongPtr(wb.hWnd, win.GWLP_WNDPROC, wb.origWndProcPtr)
|
|
}
|
|
|
|
delete(hwnd2WindowBase, hwnd)
|
|
|
|
wb.window.Dispose()
|
|
wb.hWnd = 0
|
|
}
|
|
|
|
if window != nil {
|
|
if wndProc := window.AsWindowBase().origWndProcPtr; wndProc != 0 {
|
|
return win.CallWindowProc(wndProc, hwnd, msg, wParam, lParam)
|
|
}
|
|
}
|
|
|
|
return win.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
}
|