526 lines
11 KiB
Go
526 lines
11 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 (
|
||
|
"syscall"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/lxn/win"
|
||
|
)
|
||
|
|
||
|
type Container interface {
|
||
|
Window
|
||
|
AsContainerBase() *ContainerBase
|
||
|
Children() *WidgetList
|
||
|
Layout() Layout
|
||
|
SetLayout(value Layout) error
|
||
|
DataBinder() *DataBinder
|
||
|
SetDataBinder(dbm *DataBinder)
|
||
|
}
|
||
|
|
||
|
type ContainerBase struct {
|
||
|
WidgetBase
|
||
|
layout Layout
|
||
|
children *WidgetList
|
||
|
dataBinder *DataBinder
|
||
|
nextChildID int32
|
||
|
persistent bool
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) AsWidgetBase() *WidgetBase {
|
||
|
return &cb.WidgetBase
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) AsContainerBase() *ContainerBase {
|
||
|
return cb
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) NextChildID() int32 {
|
||
|
cb.nextChildID++
|
||
|
return cb.nextChildID
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) applyEnabled(enabled bool) {
|
||
|
cb.WidgetBase.applyEnabled(enabled)
|
||
|
|
||
|
applyEnabledToDescendants(cb.window.(Widget), enabled)
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) applyFont(font *Font) {
|
||
|
cb.WidgetBase.applyFont(font)
|
||
|
|
||
|
applyFontToDescendants(cb.window.(Widget), font)
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) ApplySysColors() {
|
||
|
cb.WidgetBase.ApplySysColors()
|
||
|
|
||
|
applySysColorsToDescendants(cb.window.(Widget))
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) ApplyDPI(dpi int) {
|
||
|
cb.WidgetBase.ApplyDPI(dpi)
|
||
|
|
||
|
applyDPIToDescendants(cb.window.(Widget), dpi)
|
||
|
|
||
|
if cb.layout != nil {
|
||
|
if ums, ok := cb.layout.(interface {
|
||
|
updateMargins()
|
||
|
updateSpacing()
|
||
|
}); ok {
|
||
|
ums.updateMargins()
|
||
|
ums.updateSpacing()
|
||
|
}
|
||
|
|
||
|
cb.RequestLayout()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) Children() *WidgetList {
|
||
|
return cb.children
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) Layout() Layout {
|
||
|
return cb.layout
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) SetLayout(value Layout) error {
|
||
|
if cb.layout != value {
|
||
|
if cb.layout != nil {
|
||
|
cb.layout.SetContainer(nil)
|
||
|
}
|
||
|
|
||
|
cb.layout = value
|
||
|
|
||
|
if value != nil && value.Container() != Container(cb) {
|
||
|
value.SetContainer(cb)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
|
||
|
return cb.layout.CreateLayoutItem(ctx)
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) DataBinder() *DataBinder {
|
||
|
return cb.dataBinder
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) SetDataBinder(db *DataBinder) {
|
||
|
if db == cb.dataBinder {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if cb.dataBinder != nil {
|
||
|
cb.dataBinder.SetBoundWidgets(nil)
|
||
|
}
|
||
|
|
||
|
cb.dataBinder = db
|
||
|
|
||
|
if db != nil {
|
||
|
var boundWidgets []Widget
|
||
|
|
||
|
walkDescendants(cb.window, func(w Window) bool {
|
||
|
if w.Handle() == cb.hWnd {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if c, ok := w.(Container); ok && c.DataBinder() != nil {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
for _, prop := range w.AsWindowBase().name2Property {
|
||
|
if _, ok := prop.Source().(string); ok {
|
||
|
boundWidgets = append(boundWidgets, w.(Widget))
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
db.SetBoundWidgets(boundWidgets)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) forEachPersistableChild(f func(p Persistable) error) error {
|
||
|
if cb.children == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
for _, wb := range cb.children.items {
|
||
|
if persistable, ok := wb.window.(Persistable); ok && persistable.Persistent() {
|
||
|
if err := f(persistable); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) Persistent() bool {
|
||
|
return cb.persistent
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) SetPersistent(value bool) {
|
||
|
cb.persistent = value
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) SaveState() error {
|
||
|
return cb.forEachPersistableChild(func(p Persistable) error {
|
||
|
return p.SaveState()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) RestoreState() error {
|
||
|
return cb.forEachPersistableChild(func(p Persistable) error {
|
||
|
return p.RestoreState()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) doPaint() error {
|
||
|
var ps win.PAINTSTRUCT
|
||
|
|
||
|
hdc := win.BeginPaint(cb.hWnd, &ps)
|
||
|
defer win.EndPaint(cb.hWnd, &ps)
|
||
|
|
||
|
canvas, err := newCanvasFromHDC(hdc)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer canvas.Dispose()
|
||
|
|
||
|
for _, wb := range cb.children.items {
|
||
|
widget := wb.window.(Widget)
|
||
|
|
||
|
for _, effect := range widget.GraphicsEffects().items {
|
||
|
switch effect {
|
||
|
case InteractionEffect:
|
||
|
type ReadOnlyer interface {
|
||
|
ReadOnly() bool
|
||
|
}
|
||
|
if ro, ok := widget.(ReadOnlyer); ok {
|
||
|
if ro.ReadOnly() {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if hwnd := widget.Handle(); !win.IsWindowEnabled(hwnd) || !win.IsWindowVisible(hwnd) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
case FocusEffect:
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
b := widget.BoundsPixels().toRECT()
|
||
|
win.ExcludeClipRect(hdc, b.Left, b.Top, b.Right, b.Bottom)
|
||
|
|
||
|
if err := effect.Draw(widget, canvas); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if FocusEffect != nil {
|
||
|
hwndFocused := win.GetFocus()
|
||
|
var widget Widget
|
||
|
if wnd := windowFromHandle(hwndFocused); wnd != nil {
|
||
|
widget, _ = wnd.(Widget)
|
||
|
}
|
||
|
for hwndFocused != 0 && (widget == nil || widget.Parent() == nil) {
|
||
|
hwndFocused = win.GetParent(hwndFocused)
|
||
|
if wnd := windowFromHandle(hwndFocused); wnd != nil {
|
||
|
widget, _ = wnd.(Widget)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if widget != nil && widget.Parent() != nil && widget.Parent().Handle() == cb.hWnd {
|
||
|
for _, effect := range widget.GraphicsEffects().items {
|
||
|
if effect == FocusEffect {
|
||
|
b := widget.BoundsPixels().toRECT()
|
||
|
win.ExcludeClipRect(hdc, b.Left, b.Top, b.Right, b.Bottom)
|
||
|
|
||
|
if err := FocusEffect.Draw(widget, canvas); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||
|
switch msg {
|
||
|
case win.WM_CTLCOLOREDIT, win.WM_CTLCOLORSTATIC:
|
||
|
if hBrush := cb.handleWMCTLCOLOR(wParam, lParam); hBrush != 0 {
|
||
|
return hBrush
|
||
|
}
|
||
|
|
||
|
case win.WM_PAINT:
|
||
|
if FocusEffect == nil && InteractionEffect == nil && ValidationErrorEffect == nil {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
// If it fails, what can we do about it? Panic? That's extreme. So just ignore it.
|
||
|
_ = cb.doPaint()
|
||
|
|
||
|
return 0
|
||
|
|
||
|
case win.WM_COMMAND:
|
||
|
if lParam == 0 {
|
||
|
switch win.HIWORD(uint32(wParam)) {
|
||
|
case 0:
|
||
|
cmdId := win.LOWORD(uint32(wParam))
|
||
|
switch cmdId {
|
||
|
case win.IDOK, win.IDCANCEL:
|
||
|
form := ancestor(cb)
|
||
|
if form == nil {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
dlg, ok := form.(dialogish)
|
||
|
if !ok {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
var button *PushButton
|
||
|
if cmdId == win.IDOK {
|
||
|
button = dlg.DefaultButton()
|
||
|
} else {
|
||
|
button = dlg.CancelButton()
|
||
|
}
|
||
|
|
||
|
if button != nil && button.Visible() && button.Enabled() {
|
||
|
button.raiseClicked()
|
||
|
}
|
||
|
|
||
|
break
|
||
|
}
|
||
|
|
||
|
// Menu
|
||
|
actionId := uint16(win.LOWORD(uint32(wParam)))
|
||
|
if action, ok := actionsById[actionId]; ok {
|
||
|
action.raiseTriggered()
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
case 1:
|
||
|
// Accelerator
|
||
|
}
|
||
|
} else {
|
||
|
// The window that sent the notification shall handle it itself.
|
||
|
hwndSrc := win.GetDlgItem(cb.hWnd, int32(win.LOWORD(uint32(wParam))))
|
||
|
|
||
|
var toolBarOnly bool
|
||
|
if hwndSrc == 0 {
|
||
|
toolBarOnly = true
|
||
|
hwndSrc = win.HWND(lParam)
|
||
|
}
|
||
|
|
||
|
if window := windowFromHandle(hwndSrc); window != nil {
|
||
|
if _, ok := window.(*ToolBar); toolBarOnly && !ok {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
window.WndProc(hwnd, msg, wParam, lParam)
|
||
|
return 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case win.WM_MEASUREITEM:
|
||
|
mis := (*win.MEASUREITEMSTRUCT)(unsafe.Pointer(lParam))
|
||
|
if window := windowFromHandle(win.GetDlgItem(hwnd, int32(mis.CtlID))); window != nil {
|
||
|
// The window that sent the notification shall handle it itself.
|
||
|
return window.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
case win.WM_DRAWITEM:
|
||
|
dis := (*win.DRAWITEMSTRUCT)(unsafe.Pointer(lParam))
|
||
|
if window := windowFromHandle(dis.HwndItem); window != nil {
|
||
|
// The window that sent the notification shall handle it itself.
|
||
|
return window.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
case win.WM_NOTIFY:
|
||
|
nmh := (*win.NMHDR)(unsafe.Pointer(lParam))
|
||
|
if window := windowFromHandle(nmh.HwndFrom); window != nil {
|
||
|
// The window that sent the notification shall handle it itself.
|
||
|
return window.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
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_WINDOWPOSCHANGED:
|
||
|
wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))
|
||
|
|
||
|
if wp.Flags&win.SWP_NOSIZE != 0 || cb.Layout() == nil {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if cb.background == nullBrushSingleton {
|
||
|
cb.Invalidate()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return cb.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) onInsertingWidget(index int, widget Widget) (err error) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) onInsertedWidget(index int, widget Widget) (err error) {
|
||
|
if parent := widget.Parent(); parent == nil || parent.Handle() != cb.hWnd {
|
||
|
if err = widget.SetParent(cb.window.(Container)); err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cb.RequestLayout()
|
||
|
|
||
|
widget.(applyFonter).applyFont(cb.Font())
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) onRemovingWidget(index int, widget Widget) (err error) {
|
||
|
if widget.Parent() == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if widget.Parent().Handle() == cb.hWnd {
|
||
|
err = widget.SetParent(nil)
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) onRemovedWidget(index int, widget Widget) (err error) {
|
||
|
cb.RequestLayout()
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) onClearingWidgets() (err error) {
|
||
|
for i := cb.children.Len() - 1; i >= 0; i-- {
|
||
|
widget := cb.children.At(i)
|
||
|
|
||
|
if parent := widget.Parent(); parent != nil && parent.Handle() == cb.hWnd {
|
||
|
if err = widget.SetParent(nil); err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) onClearedWidgets() (err error) {
|
||
|
cb.RequestLayout()
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (cb *ContainerBase) focusFirstCandidateDescendant() {
|
||
|
window := firstFocusableDescendant(cb)
|
||
|
if window == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := window.SetFocus(); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if textSel, ok := window.(textSelectable); ok {
|
||
|
time.AfterFunc(time.Millisecond, func() {
|
||
|
window.Synchronize(func() {
|
||
|
if window.Focused() {
|
||
|
textSel.SetTextSelection(0, -1)
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func firstFocusableDescendantCallback(hwnd win.HWND, lParam uintptr) uintptr {
|
||
|
if !win.IsWindowVisible(hwnd) || !win.IsWindowEnabled(hwnd) {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
if win.GetWindowLong(hwnd, win.GWL_STYLE)&win.WS_TABSTOP > 0 {
|
||
|
if rb, ok := windowFromHandle(hwnd).(radioButtonish); ok {
|
||
|
if !rb.radioButton().Checked() {
|
||
|
return 1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hwndPtr := (*win.HWND)(unsafe.Pointer(lParam))
|
||
|
*hwndPtr = hwnd
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
var firstFocusableDescendantCallbackPtr uintptr
|
||
|
|
||
|
func init() {
|
||
|
AppendToWalkInit(func() {
|
||
|
firstFocusableDescendantCallbackPtr = syscall.NewCallback(firstFocusableDescendantCallback)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func firstFocusableDescendant(container Container) Window {
|
||
|
var hwnd win.HWND
|
||
|
|
||
|
win.EnumChildWindows(container.Handle(), firstFocusableDescendantCallbackPtr, uintptr(unsafe.Pointer(&hwnd)))
|
||
|
|
||
|
window := windowFromHandle(hwnd)
|
||
|
|
||
|
for hwnd != 0 && window == nil {
|
||
|
hwnd = win.GetParent(hwnd)
|
||
|
window = windowFromHandle(hwnd)
|
||
|
}
|
||
|
|
||
|
return window
|
||
|
}
|
||
|
|
||
|
type textSelectable interface {
|
||
|
SetTextSelection(start, end int)
|
||
|
}
|
||
|
|
||
|
func DescendantByName(container Container, name string) Widget {
|
||
|
var widget Widget
|
||
|
|
||
|
walkDescendants(container.AsContainerBase(), func(w Window) bool {
|
||
|
if w.Name() == name {
|
||
|
widget = w.(Widget)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
if widget == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return widget
|
||
|
}
|