886 lines
20 KiB
Go
886 lines
20 KiB
Go
|
// Copyright 2012 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 (
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"sync"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/lxn/win"
|
||
|
)
|
||
|
|
||
|
type CloseReason byte
|
||
|
|
||
|
const (
|
||
|
CloseReasonUnknown CloseReason = iota
|
||
|
CloseReasonUser
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
syncFuncs struct {
|
||
|
m sync.Mutex
|
||
|
funcs []func()
|
||
|
}
|
||
|
|
||
|
syncMsgId uint32
|
||
|
taskbarButtonCreatedMsgId uint32
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
AppendToWalkInit(func() {
|
||
|
syncMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("WalkSync"))
|
||
|
taskbarButtonCreatedMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("TaskbarButtonCreated"))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type Form interface {
|
||
|
Container
|
||
|
AsFormBase() *FormBase
|
||
|
Run() int
|
||
|
Starting() *Event
|
||
|
Closing() *CloseEvent
|
||
|
Activating() *Event
|
||
|
Deactivating() *Event
|
||
|
Activate() error
|
||
|
Show()
|
||
|
Hide()
|
||
|
Title() string
|
||
|
SetTitle(title string) error
|
||
|
TitleChanged() *Event
|
||
|
Icon() Image
|
||
|
SetIcon(icon Image) error
|
||
|
IconChanged() *Event
|
||
|
Owner() Form
|
||
|
SetOwner(owner Form) error
|
||
|
ProgressIndicator() *ProgressIndicator
|
||
|
|
||
|
// RightToLeftLayout returns whether coordinates on the x axis of the
|
||
|
// Form increase from right to left.
|
||
|
RightToLeftLayout() bool
|
||
|
|
||
|
// SetRightToLeftLayout sets whether coordinates on the x axis of the
|
||
|
// Form increase from right to left.
|
||
|
SetRightToLeftLayout(rtl bool) error
|
||
|
}
|
||
|
|
||
|
type FormBase struct {
|
||
|
WindowBase
|
||
|
clientComposite *Composite
|
||
|
owner Form
|
||
|
stopwatch *stopwatch
|
||
|
inProgressEventCount int
|
||
|
performLayout chan ContainerLayoutItem
|
||
|
layoutResults chan []LayoutResult
|
||
|
inSizeLoop chan bool
|
||
|
updateStopwatch chan *stopwatch
|
||
|
quitLayoutPerformer chan struct{}
|
||
|
closingPublisher CloseEventPublisher
|
||
|
activatingPublisher EventPublisher
|
||
|
deactivatingPublisher EventPublisher
|
||
|
startingPublisher EventPublisher
|
||
|
titleChangedPublisher EventPublisher
|
||
|
iconChangedPublisher EventPublisher
|
||
|
progressIndicator *ProgressIndicator
|
||
|
icon Image
|
||
|
prevFocusHWnd win.HWND
|
||
|
proposedSize Size // in native pixels
|
||
|
closeReason CloseReason
|
||
|
inSizingLoop bool
|
||
|
startingLayoutViaSizingLoop bool
|
||
|
isInRestoreState bool
|
||
|
started bool
|
||
|
layoutScheduled bool
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) init(form Form) error {
|
||
|
var err error
|
||
|
if fb.clientComposite, err = NewComposite(form); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
fb.clientComposite.SetName("clientComposite")
|
||
|
fb.clientComposite.background = nil
|
||
|
|
||
|
fb.clientComposite.children.observer = form.AsFormBase()
|
||
|
|
||
|
fb.MustRegisterProperty("Icon", NewProperty(
|
||
|
func() interface{} {
|
||
|
return fb.Icon()
|
||
|
},
|
||
|
func(v interface{}) error {
|
||
|
icon, err := IconFrom(v, fb.DPI())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var img Image
|
||
|
if icon != nil {
|
||
|
img = icon
|
||
|
}
|
||
|
|
||
|
fb.SetIcon(img)
|
||
|
|
||
|
return nil
|
||
|
},
|
||
|
fb.iconChangedPublisher.Event()))
|
||
|
|
||
|
fb.MustRegisterProperty("Title", NewProperty(
|
||
|
func() interface{} {
|
||
|
return fb.Title()
|
||
|
},
|
||
|
func(v interface{}) error {
|
||
|
return fb.SetTitle(assertStringOr(v, ""))
|
||
|
},
|
||
|
fb.titleChangedPublisher.Event()))
|
||
|
|
||
|
version := win.GetVersion()
|
||
|
if (version&0xFF) > 6 || ((version&0xFF) == 6 && (version&0xFF00>>8) > 0) {
|
||
|
win.ChangeWindowMessageFilterEx(fb.hWnd, taskbarButtonCreatedMsgId, win.MSGFLT_ALLOW, nil)
|
||
|
}
|
||
|
|
||
|
fb.performLayout, fb.layoutResults, fb.inSizeLoop, fb.updateStopwatch, fb.quitLayoutPerformer = startLayoutPerformer(fb)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Dispose() {
|
||
|
if fb.hWnd != 0 {
|
||
|
fb.quitLayoutPerformer <- struct{}{}
|
||
|
}
|
||
|
|
||
|
fb.WindowBase.Dispose()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) AsContainerBase() *ContainerBase {
|
||
|
if fb.clientComposite == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return fb.clientComposite.AsContainerBase()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) AsFormBase() *FormBase {
|
||
|
return fb
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Children() *WidgetList {
|
||
|
if fb.clientComposite == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return fb.clientComposite.Children()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Layout() Layout {
|
||
|
if fb.clientComposite == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return fb.clientComposite.Layout()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetLayout(value Layout) error {
|
||
|
if fb.clientComposite == nil {
|
||
|
return newError("clientComposite not initialized")
|
||
|
}
|
||
|
|
||
|
return fb.clientComposite.SetLayout(value)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetBoundsPixels(bounds Rectangle) error {
|
||
|
if layout := fb.Layout(); layout != nil {
|
||
|
layoutItem := CreateLayoutItemsForContainer(fb)
|
||
|
minSize := fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(bounds.Size()))
|
||
|
minSize = fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(minSize))
|
||
|
|
||
|
if bounds.Width < minSize.Width {
|
||
|
bounds.Width = minSize.Width
|
||
|
}
|
||
|
if bounds.Height < minSize.Height {
|
||
|
bounds.Height = minSize.Height
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := fb.WindowBase.SetBoundsPixels(bounds); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
fb.proposedSize = bounds.Size()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) fixedSize() bool {
|
||
|
return !fb.hasStyleBits(win.WS_THICKFRAME)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) DataBinder() *DataBinder {
|
||
|
return fb.clientComposite.DataBinder()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetDataBinder(db *DataBinder) {
|
||
|
fb.clientComposite.SetDataBinder(db)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetSuspended(suspended bool) {
|
||
|
if suspended == fb.suspended {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fb.suspended = suspended
|
||
|
|
||
|
if fb.clientComposite != nil {
|
||
|
fb.clientComposite.SetSuspended(suspended)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) MouseDown() *MouseEvent {
|
||
|
return fb.clientComposite.MouseDown()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) MouseMove() *MouseEvent {
|
||
|
return fb.clientComposite.MouseMove()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) MouseUp() *MouseEvent {
|
||
|
return fb.clientComposite.MouseUp()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) onInsertingWidget(index int, widget Widget) error {
|
||
|
return fb.clientComposite.onInsertingWidget(index, widget)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) onInsertedWidget(index int, widget Widget) error {
|
||
|
return fb.clientComposite.onInsertedWidget(index, widget)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) onRemovingWidget(index int, widget Widget) error {
|
||
|
return fb.clientComposite.onRemovingWidget(index, widget)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) onRemovedWidget(index int, widget Widget) error {
|
||
|
return fb.clientComposite.onRemovedWidget(index, widget)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) onClearingWidgets() error {
|
||
|
return fb.clientComposite.onClearingWidgets()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) onClearedWidgets() error {
|
||
|
return fb.clientComposite.onClearedWidgets()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) ContextMenu() *Menu {
|
||
|
return fb.clientComposite.ContextMenu()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetContextMenu(contextMenu *Menu) {
|
||
|
fb.clientComposite.SetContextMenu(contextMenu)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) ContextMenuLocation() Point {
|
||
|
return fb.clientComposite.ContextMenuLocation()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) applyEnabled(enabled bool) {
|
||
|
fb.WindowBase.applyEnabled(enabled)
|
||
|
|
||
|
fb.clientComposite.applyEnabled(enabled)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) applyFont(font *Font) {
|
||
|
fb.WindowBase.applyFont(font)
|
||
|
|
||
|
fb.clientComposite.applyFont(font)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) ApplySysColors() {
|
||
|
fb.WindowBase.ApplySysColors()
|
||
|
fb.clientComposite.ApplySysColors()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Background() Brush {
|
||
|
return fb.clientComposite.Background()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetBackground(background Brush) {
|
||
|
fb.clientComposite.SetBackground(background)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Title() string {
|
||
|
return fb.text()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetTitle(value string) error {
|
||
|
return fb.setText(value)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) TitleChanged() *Event {
|
||
|
return fb.titleChangedPublisher.Event()
|
||
|
}
|
||
|
|
||
|
// RightToLeftLayout returns whether coordinates on the x axis of the
|
||
|
// FormBase increase from right to left.
|
||
|
func (fb *FormBase) RightToLeftLayout() bool {
|
||
|
return fb.hasExtendedStyleBits(win.WS_EX_LAYOUTRTL)
|
||
|
}
|
||
|
|
||
|
// SetRightToLeftLayout sets whether coordinates on the x axis of the
|
||
|
// FormBase increase from right to left.
|
||
|
func (fb *FormBase) SetRightToLeftLayout(rtl bool) error {
|
||
|
return fb.ensureExtendedStyleBits(win.WS_EX_LAYOUTRTL, rtl)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Run() int {
|
||
|
if fb.owner != nil {
|
||
|
win.EnableWindow(fb.owner.Handle(), false)
|
||
|
|
||
|
invalidateDescendentBorders := func() {
|
||
|
walkDescendants(fb.owner, func(wnd Window) bool {
|
||
|
if widget, ok := wnd.(Widget); ok {
|
||
|
widget.AsWidgetBase().invalidateBorderInParent()
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
})
|
||
|
}
|
||
|
|
||
|
invalidateDescendentBorders()
|
||
|
defer invalidateDescendentBorders()
|
||
|
}
|
||
|
|
||
|
fb.started = true
|
||
|
fb.startingPublisher.Publish()
|
||
|
|
||
|
fb.SetBoundsPixels(fb.BoundsPixels())
|
||
|
|
||
|
if fb.proposedSize == (Size{}) {
|
||
|
fb.proposedSize = maxSize(SizeFrom96DPI(fb.minSize96dpi, fb.DPI()), fb.SizePixels())
|
||
|
if !fb.Suspended() {
|
||
|
fb.startLayout()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fb.SetSuspended(false)
|
||
|
|
||
|
return fb.mainLoop()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) handleKeyDown(msg *win.MSG) bool {
|
||
|
ret := false
|
||
|
|
||
|
key, mods := Key(msg.WParam), ModifiersDown()
|
||
|
|
||
|
// Tabbing
|
||
|
if key == KeyTab && (mods&ModControl) != 0 {
|
||
|
doTabbing := func(tw *TabWidget) {
|
||
|
index := tw.CurrentIndex()
|
||
|
if (mods & ModShift) != 0 {
|
||
|
index--
|
||
|
if index < 0 {
|
||
|
index = tw.Pages().Len() - 1
|
||
|
}
|
||
|
} else {
|
||
|
index++
|
||
|
if index >= tw.Pages().Len() {
|
||
|
index = 0
|
||
|
}
|
||
|
}
|
||
|
tw.SetCurrentIndex(index)
|
||
|
}
|
||
|
|
||
|
hwnd := win.GetFocus()
|
||
|
|
||
|
LOOP:
|
||
|
for hwnd != 0 {
|
||
|
window := windowFromHandle(hwnd)
|
||
|
|
||
|
switch widget := window.(type) {
|
||
|
case nil:
|
||
|
|
||
|
case *TabWidget:
|
||
|
doTabbing(widget)
|
||
|
return true
|
||
|
|
||
|
case Widget:
|
||
|
|
||
|
default:
|
||
|
break LOOP
|
||
|
}
|
||
|
|
||
|
hwnd = win.GetParent(hwnd)
|
||
|
}
|
||
|
|
||
|
walkDescendants(fb.window, func(w Window) bool {
|
||
|
if tw, ok := w.(*TabWidget); ok {
|
||
|
doTabbing(tw)
|
||
|
ret = true
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
})
|
||
|
if ret {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Shortcut actions
|
||
|
hwnd := msg.HWnd
|
||
|
for hwnd != 0 {
|
||
|
if window := windowFromHandle(hwnd); window != nil {
|
||
|
wb := window.AsWindowBase()
|
||
|
|
||
|
if wb.shortcutActions != nil {
|
||
|
for _, action := range wb.shortcutActions.actions {
|
||
|
if action.shortcut.Key == key && action.shortcut.Modifiers == mods && action.Visible() && action.Enabled() {
|
||
|
action.raiseTriggered()
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hwnd = win.GetParent(hwnd)
|
||
|
}
|
||
|
|
||
|
// WebView
|
||
|
walkDescendants(fb.window, func(w Window) bool {
|
||
|
if webView, ok := w.(*WebView); ok {
|
||
|
webViewHWnd := webView.Handle()
|
||
|
if webViewHWnd == msg.HWnd || win.IsChild(webViewHWnd, msg.HWnd) {
|
||
|
_ret := webView.translateAccelerator(msg)
|
||
|
if _ret {
|
||
|
ret = _ret
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
})
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Starting() *Event {
|
||
|
return fb.startingPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Activating() *Event {
|
||
|
return fb.activatingPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Deactivating() *Event {
|
||
|
return fb.deactivatingPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Activate() error {
|
||
|
if hwndPrevActive := win.SetActiveWindow(fb.hWnd); hwndPrevActive == 0 {
|
||
|
return lastError("SetActiveWindow")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Owner() Form {
|
||
|
return fb.owner
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetOwner(value Form) error {
|
||
|
fb.owner = value
|
||
|
|
||
|
var ownerHWnd win.HWND
|
||
|
if value != nil {
|
||
|
ownerHWnd = value.Handle()
|
||
|
}
|
||
|
|
||
|
win.SetLastError(0)
|
||
|
if 0 == win.SetWindowLong(
|
||
|
fb.hWnd,
|
||
|
win.GWL_HWNDPARENT,
|
||
|
int32(ownerHWnd)) && win.GetLastError() != 0 {
|
||
|
|
||
|
return lastError("SetWindowLong")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Icon() Image {
|
||
|
return fb.icon
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetIcon(icon Image) error {
|
||
|
var hIconSmall, hIconBig uintptr
|
||
|
|
||
|
if icon != nil {
|
||
|
dpi := fb.DPI()
|
||
|
size96dpi := icon.Size()
|
||
|
|
||
|
smallHeight := int(win.GetSystemMetricsForDpi(win.SM_CYSMICON, uint32(dpi)))
|
||
|
smallDPI := int(math.Round(float64(smallHeight) / float64(size96dpi.Height) * 96.0))
|
||
|
smallIcon, err := iconCache.Icon(icon, smallDPI)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
hIconSmall = uintptr(smallIcon.handleForDPI(smallDPI))
|
||
|
|
||
|
bigHeight := int(win.GetSystemMetricsForDpi(win.SM_CYICON, uint32(dpi)))
|
||
|
bigDPI := int(math.Round(float64(bigHeight) / float64(size96dpi.Height) * 96.0))
|
||
|
bigIcon, err := iconCache.Icon(icon, bigDPI)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
hIconBig = uintptr(bigIcon.handleForDPI(bigDPI))
|
||
|
}
|
||
|
|
||
|
fb.SendMessage(win.WM_SETICON, 0, hIconSmall)
|
||
|
fb.SendMessage(win.WM_SETICON, 1, hIconBig)
|
||
|
|
||
|
fb.icon = icon
|
||
|
|
||
|
fb.iconChangedPublisher.Publish()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) IconChanged() *Event {
|
||
|
return fb.iconChangedPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Hide() {
|
||
|
fb.window.SetVisible(false)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Show() {
|
||
|
fb.proposedSize = maxSize(SizeFrom96DPI(fb.minSize96dpi, fb.DPI()), fb.SizePixels())
|
||
|
|
||
|
if p, ok := fb.window.(Persistable); ok && p.Persistent() && App().Settings() != nil {
|
||
|
p.RestoreState()
|
||
|
}
|
||
|
|
||
|
fb.window.SetVisible(true)
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) close() error {
|
||
|
if p, ok := fb.window.(Persistable); ok && p.Persistent() && App().Settings() != nil {
|
||
|
p.SaveState()
|
||
|
}
|
||
|
|
||
|
fb.window.Dispose()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Close() error {
|
||
|
fb.SendMessage(win.WM_CLOSE, 0, 0)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Persistent() bool {
|
||
|
return fb.clientComposite.persistent
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SetPersistent(value bool) {
|
||
|
fb.clientComposite.persistent = value
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) SaveState() error {
|
||
|
if err := fb.clientComposite.SaveState(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var wp win.WINDOWPLACEMENT
|
||
|
|
||
|
wp.Length = uint32(unsafe.Sizeof(wp))
|
||
|
|
||
|
if !win.GetWindowPlacement(fb.hWnd, &wp) {
|
||
|
return lastError("GetWindowPlacement")
|
||
|
}
|
||
|
|
||
|
state := fmt.Sprint(
|
||
|
wp.Flags, wp.ShowCmd,
|
||
|
wp.PtMinPosition.X, wp.PtMinPosition.Y,
|
||
|
wp.PtMaxPosition.X, wp.PtMaxPosition.Y,
|
||
|
wp.RcNormalPosition.Left, wp.RcNormalPosition.Top,
|
||
|
wp.RcNormalPosition.Right, wp.RcNormalPosition.Bottom)
|
||
|
|
||
|
if err := fb.WriteState(state); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) RestoreState() error {
|
||
|
if fb.isInRestoreState {
|
||
|
return nil
|
||
|
}
|
||
|
fb.isInRestoreState = true
|
||
|
defer func() {
|
||
|
fb.isInRestoreState = false
|
||
|
}()
|
||
|
|
||
|
state, err := fb.ReadState()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if state == "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var wp win.WINDOWPLACEMENT
|
||
|
|
||
|
if _, err := fmt.Sscan(state,
|
||
|
&wp.Flags, &wp.ShowCmd,
|
||
|
&wp.PtMinPosition.X, &wp.PtMinPosition.Y,
|
||
|
&wp.PtMaxPosition.X, &wp.PtMaxPosition.Y,
|
||
|
&wp.RcNormalPosition.Left, &wp.RcNormalPosition.Top,
|
||
|
&wp.RcNormalPosition.Right, &wp.RcNormalPosition.Bottom); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
wp.Length = uint32(unsafe.Sizeof(wp))
|
||
|
|
||
|
if layout := fb.Layout(); layout != nil && fb.fixedSize() {
|
||
|
layoutItem := CreateLayoutItemsForContainer(fb)
|
||
|
minSize := fb.sizeFromClientSizePixels(layoutItem.MinSize())
|
||
|
|
||
|
wp.RcNormalPosition.Right = wp.RcNormalPosition.Left + int32(minSize.Width) - 1
|
||
|
wp.RcNormalPosition.Bottom = wp.RcNormalPosition.Top + int32(minSize.Height) - 1
|
||
|
}
|
||
|
|
||
|
if !win.SetWindowPlacement(fb.hWnd, &wp) {
|
||
|
return lastError("SetWindowPlacement")
|
||
|
}
|
||
|
|
||
|
return fb.clientComposite.RestoreState()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) Closing() *CloseEvent {
|
||
|
return fb.closingPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) ProgressIndicator() *ProgressIndicator {
|
||
|
return fb.progressIndicator
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) setStopwatch(sw *stopwatch) {
|
||
|
fb.stopwatch = sw
|
||
|
|
||
|
fb.updateStopwatch <- sw
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) startLayout() bool {
|
||
|
if fb.performLayout == nil || fb.inSizingLoop && !fb.startingLayoutViaSizingLoop {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
cs := fb.clientSizeFromSizePixels(fb.proposedSize)
|
||
|
min := CreateLayoutItemsForContainer(fb.clientComposite).MinSizeForSize(fb.proposedSize)
|
||
|
|
||
|
if cs.Width < min.Width || cs.Height < min.Height {
|
||
|
cs = maxSize(cs, min)
|
||
|
size := fb.sizeFromClientSizePixels(cs)
|
||
|
fb.SetSizePixels(size)
|
||
|
fb.Invalidate()
|
||
|
}
|
||
|
|
||
|
fb.clientComposite.SetBoundsPixels(Rectangle{Width: cs.Width, Height: cs.Height})
|
||
|
|
||
|
cli := CreateLayoutItemsForContainer(fb)
|
||
|
cli.Geometry().ClientSize = cs
|
||
|
|
||
|
fb.performLayout <- cli
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (fb *FormBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||
|
switch msg {
|
||
|
case win.WM_ACTIVATE:
|
||
|
switch win.LOWORD(uint32(wParam)) {
|
||
|
case win.WA_ACTIVE, win.WA_CLICKACTIVE:
|
||
|
if fb.prevFocusHWnd != 0 {
|
||
|
win.SetFocus(fb.prevFocusHWnd)
|
||
|
}
|
||
|
|
||
|
fb.group.SetActiveForm(fb.window.(Form))
|
||
|
|
||
|
fb.activatingPublisher.Publish()
|
||
|
|
||
|
case win.WA_INACTIVE:
|
||
|
fb.prevFocusHWnd = win.GetFocus()
|
||
|
|
||
|
fb.group.SetActiveForm(nil)
|
||
|
|
||
|
fb.deactivatingPublisher.Publish()
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
|
||
|
case win.WM_CLOSE:
|
||
|
fb.closeReason = CloseReasonUnknown
|
||
|
var canceled bool
|
||
|
fb.closingPublisher.Publish(&canceled, fb.closeReason)
|
||
|
if !canceled {
|
||
|
if fb.owner != nil {
|
||
|
win.EnableWindow(fb.owner.Handle(), true)
|
||
|
if !win.SetWindowPos(fb.owner.Handle(), win.HWND_NOTOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_SHOWWINDOW) {
|
||
|
lastError("SetWindowPos")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fb.close()
|
||
|
}
|
||
|
return 0
|
||
|
|
||
|
case win.WM_COMMAND:
|
||
|
return fb.clientComposite.WndProc(hwnd, msg, wParam, lParam)
|
||
|
|
||
|
case win.WM_GETMINMAXINFO:
|
||
|
if fb.Suspended() || fb.proposedSize == (Size{}) {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
mmi := (*win.MINMAXINFO)(unsafe.Pointer(lParam))
|
||
|
|
||
|
var min Size
|
||
|
if layout := fb.clientComposite.layout; layout != nil {
|
||
|
size := fb.clientSizeFromSizePixels(fb.proposedSize)
|
||
|
layoutItem := CreateLayoutItemsForContainer(fb)
|
||
|
min = fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(size))
|
||
|
|
||
|
if fb.proposedSize.Width < min.Width {
|
||
|
min = fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(min))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
minSize := SizeFrom96DPI(fb.minSize96dpi, fb.DPI())
|
||
|
|
||
|
mmi.PtMinTrackSize = Point{
|
||
|
maxi(min.Width, minSize.Width),
|
||
|
maxi(min.Height, minSize.Height),
|
||
|
}.toPOINT()
|
||
|
return 0
|
||
|
|
||
|
case win.WM_NOTIFY:
|
||
|
return fb.clientComposite.WndProc(hwnd, msg, wParam, lParam)
|
||
|
|
||
|
case win.WM_SETTEXT:
|
||
|
fb.titleChangedPublisher.Publish()
|
||
|
|
||
|
case win.WM_ENTERSIZEMOVE:
|
||
|
fb.inSizingLoop = true
|
||
|
fb.inSizeLoop <- true
|
||
|
|
||
|
case win.WM_EXITSIZEMOVE:
|
||
|
fb.inSizingLoop = false
|
||
|
fb.inSizeLoop <- false
|
||
|
|
||
|
case win.WM_WINDOWPOSCHANGED:
|
||
|
wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))
|
||
|
|
||
|
if wp.Flags&win.SWP_SHOWWINDOW != 0 {
|
||
|
fb.startLayout()
|
||
|
}
|
||
|
|
||
|
if wp.Flags&win.SWP_NOSIZE != 0 || fb.Layout() == nil || fb.Suspended() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
fb.proposedSize = Size{int(wp.Cx), int(wp.Cy)}
|
||
|
|
||
|
const performingLayoutSubject = "*FormBase.WndProc - WM_WINDOWPOSCHANGED - full layout from sizing loop"
|
||
|
|
||
|
if fb.inSizingLoop {
|
||
|
fb.startingLayoutViaSizingLoop = true
|
||
|
|
||
|
if fb.stopwatch != nil {
|
||
|
fb.stopwatch.Start(performingLayoutSubject)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if fb.startLayout() {
|
||
|
if fb.inSizingLoop {
|
||
|
fb.startingLayoutViaSizingLoop = false
|
||
|
|
||
|
applyLayoutResults(<-fb.layoutResults, fb.stopwatch)
|
||
|
|
||
|
if fb.stopwatch != nil {
|
||
|
fb.stopwatch.Stop(performingLayoutSubject)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case win.WM_SYSCOLORCHANGE:
|
||
|
fb.ApplySysColors()
|
||
|
|
||
|
case win.WM_DPICHANGED:
|
||
|
wasSuspended := fb.Suspended()
|
||
|
fb.SetSuspended(true)
|
||
|
defer fb.SetSuspended(wasSuspended)
|
||
|
|
||
|
dpi := int(win.HIWORD(uint32(wParam)))
|
||
|
|
||
|
seenInApplyFontToDescendantsDuringDPIChange = make(map[*WindowBase]bool)
|
||
|
seenInApplyDPIToDescendantsDuringDPIChange = make(map[*WindowBase]bool)
|
||
|
defer func() {
|
||
|
seenInApplyFontToDescendantsDuringDPIChange = nil
|
||
|
seenInApplyDPIToDescendantsDuringDPIChange = nil
|
||
|
}()
|
||
|
|
||
|
fb.clientComposite.ApplyDPI(dpi)
|
||
|
fb.ApplyDPI(dpi)
|
||
|
if fb.progressIndicator != nil {
|
||
|
fb.progressIndicator.SetOverlayIcon(fb.progressIndicator.overlayIcon, fb.progressIndicator.overlayIconDescription)
|
||
|
}
|
||
|
applyDPIToDescendants(fb.window, dpi)
|
||
|
|
||
|
fb.SetSuspended(wasSuspended)
|
||
|
|
||
|
rc := (*win.RECT)(unsafe.Pointer(lParam))
|
||
|
bounds := rectangleFromRECT(*rc)
|
||
|
fb.proposedSize = bounds.Size()
|
||
|
fb.window.SetBoundsPixels(bounds)
|
||
|
|
||
|
fb.SetIcon(fb.icon)
|
||
|
|
||
|
time.AfterFunc(time.Second, func() {
|
||
|
if fb.hWnd == 0 {
|
||
|
return
|
||
|
}
|
||
|
fb.Synchronize(func() {
|
||
|
for ni := range notifyIcons {
|
||
|
// We do this on all NotifyIcons, not just ones attached to this form or descendents, because
|
||
|
// the notify icon might be on a different screen, and since it can't get notifications itself
|
||
|
// we hope that one of the forms did for it. We also have to delay it by a second, because the
|
||
|
// tray usually gets resized sometime after us. This is a nasty hack!
|
||
|
ni.applyDPI()
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
|
||
|
case win.WM_SYSCOMMAND:
|
||
|
if wParam == win.SC_CLOSE {
|
||
|
fb.closeReason = CloseReasonUser
|
||
|
}
|
||
|
|
||
|
case taskbarButtonCreatedMsgId:
|
||
|
version := win.GetVersion()
|
||
|
major := version & 0xFF
|
||
|
minor := version & 0xFF00 >> 8
|
||
|
// Check that the OS is Win 7 or later (Win 7 is v6.1).
|
||
|
if fb.progressIndicator == nil && (major > 6 || (major == 6 && minor > 0)) {
|
||
|
fb.progressIndicator, _ = newTaskbarList3(fb.hWnd)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fb.WindowBase.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|