erm/vendor/github.com/lxn/walk/container.go
2021-07-30 23:29:20 +01:00

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
}