441 lines
9.3 KiB
Go
441 lines
9.3 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"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/lxn/win"
|
||
|
)
|
||
|
|
||
|
const groupBoxWindowClass = `\o/ Walk_GroupBox_Class \o/`
|
||
|
|
||
|
func init() {
|
||
|
AppendToWalkInit(func() {
|
||
|
MustRegisterWindowClass(groupBoxWindowClass)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type GroupBox struct {
|
||
|
WidgetBase
|
||
|
hWndGroupBox win.HWND
|
||
|
checkBox *CheckBox
|
||
|
composite *Composite
|
||
|
headerHeight int // in native pixels
|
||
|
titleChangedPublisher EventPublisher
|
||
|
}
|
||
|
|
||
|
func NewGroupBox(parent Container) (*GroupBox, error) {
|
||
|
gb := new(GroupBox)
|
||
|
|
||
|
if err := InitWidget(
|
||
|
gb,
|
||
|
parent,
|
||
|
groupBoxWindowClass,
|
||
|
win.WS_VISIBLE,
|
||
|
win.WS_EX_CONTROLPARENT); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
succeeded := false
|
||
|
defer func() {
|
||
|
if !succeeded {
|
||
|
gb.Dispose()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
gb.hWndGroupBox = win.CreateWindowEx(
|
||
|
0, syscall.StringToUTF16Ptr("BUTTON"), nil,
|
||
|
win.WS_CHILD|win.WS_VISIBLE|win.BS_GROUPBOX,
|
||
|
0, 0, 80, 24, gb.hWnd, 0, 0, nil)
|
||
|
if gb.hWndGroupBox == 0 {
|
||
|
return nil, lastError("CreateWindowEx(BUTTON)")
|
||
|
}
|
||
|
win.SetWindowLong(gb.hWndGroupBox, win.GWL_ID, 1)
|
||
|
|
||
|
gb.applyFont(gb.Font())
|
||
|
gb.updateHeaderHeight()
|
||
|
|
||
|
var err error
|
||
|
|
||
|
gb.checkBox, err = NewCheckBox(gb)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
win.SetWindowLong(gb.checkBox.hWnd, win.GWL_ID, 2)
|
||
|
|
||
|
gb.SetCheckable(false)
|
||
|
gb.checkBox.SetChecked(true)
|
||
|
|
||
|
gb.checkBox.CheckedChanged().Attach(func() {
|
||
|
gb.applyEnabledFromCheckBox(gb.checkBox.Checked())
|
||
|
})
|
||
|
|
||
|
setWindowVisible(gb.checkBox.hWnd, false)
|
||
|
|
||
|
gb.composite, err = NewComposite(gb)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
win.SetWindowLong(gb.composite.hWnd, win.GWL_ID, 3)
|
||
|
gb.composite.name = "composite"
|
||
|
|
||
|
win.SetWindowPos(gb.checkBox.hWnd, win.HWND_TOP, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE)
|
||
|
|
||
|
gb.SetBackground(NullBrush())
|
||
|
|
||
|
gb.MustRegisterProperty("Title", NewProperty(
|
||
|
func() interface{} {
|
||
|
return gb.Title()
|
||
|
},
|
||
|
func(v interface{}) error {
|
||
|
return gb.SetTitle(assertStringOr(v, ""))
|
||
|
},
|
||
|
gb.titleChangedPublisher.Event()))
|
||
|
|
||
|
gb.MustRegisterProperty("Checked", NewBoolProperty(
|
||
|
func() bool {
|
||
|
return gb.Checked()
|
||
|
},
|
||
|
func(v bool) error {
|
||
|
gb.SetChecked(v)
|
||
|
return nil
|
||
|
},
|
||
|
gb.CheckedChanged()))
|
||
|
|
||
|
succeeded = true
|
||
|
|
||
|
return gb, nil
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) AsContainerBase() *ContainerBase {
|
||
|
if gb.composite == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return gb.composite.AsContainerBase()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) ClientBoundsPixels() Rectangle {
|
||
|
cb := windowClientBounds(gb.hWndGroupBox)
|
||
|
|
||
|
if gb.Layout() == nil {
|
||
|
return cb
|
||
|
}
|
||
|
|
||
|
if gb.Checkable() {
|
||
|
s := createLayoutItemForWidget(gb.checkBox).(MinSizer).MinSize()
|
||
|
|
||
|
cb.Y += s.Height
|
||
|
cb.Height -= s.Height
|
||
|
}
|
||
|
|
||
|
padding := gb.IntFrom96DPI(1)
|
||
|
return Rectangle{cb.X + padding, cb.Y + gb.headerHeight, cb.Width - 2*padding, cb.Height - gb.headerHeight - 2*padding}
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) updateHeaderHeight() {
|
||
|
gb.headerHeight = gb.calculateTextSizeImpl("gM").Height
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) Persistent() bool {
|
||
|
return gb.composite.Persistent()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetPersistent(value bool) {
|
||
|
gb.composite.SetPersistent(value)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SaveState() error {
|
||
|
return gb.composite.SaveState()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) RestoreState() error {
|
||
|
return gb.composite.RestoreState()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) applyEnabled(enabled bool) {
|
||
|
gb.WidgetBase.applyEnabled(enabled)
|
||
|
|
||
|
if gb.hWndGroupBox != 0 {
|
||
|
setWindowEnabled(gb.hWndGroupBox, enabled)
|
||
|
}
|
||
|
|
||
|
if gb.checkBox != nil {
|
||
|
gb.checkBox.applyEnabled(enabled)
|
||
|
}
|
||
|
|
||
|
if gb.composite != nil {
|
||
|
gb.composite.applyEnabled(enabled)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) applyEnabledFromCheckBox(enabled bool) {
|
||
|
if gb.hWndGroupBox != 0 {
|
||
|
setWindowEnabled(gb.hWndGroupBox, enabled)
|
||
|
}
|
||
|
|
||
|
if gb.composite != nil {
|
||
|
gb.composite.applyEnabled(enabled)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) applyFont(font *Font) {
|
||
|
gb.WidgetBase.applyFont(font)
|
||
|
|
||
|
if gb.checkBox != nil {
|
||
|
gb.checkBox.applyFont(font)
|
||
|
}
|
||
|
|
||
|
if gb.hWndGroupBox != 0 {
|
||
|
SetWindowFont(gb.hWndGroupBox, font)
|
||
|
}
|
||
|
|
||
|
if gb.composite != nil {
|
||
|
gb.composite.applyFont(font)
|
||
|
}
|
||
|
|
||
|
gb.updateHeaderHeight()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetSuspended(suspend bool) {
|
||
|
gb.composite.SetSuspended(suspend)
|
||
|
gb.WidgetBase.SetSuspended(suspend)
|
||
|
gb.Invalidate()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) DataBinder() *DataBinder {
|
||
|
return gb.composite.dataBinder
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetDataBinder(dataBinder *DataBinder) {
|
||
|
gb.composite.SetDataBinder(dataBinder)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) Title() string {
|
||
|
if gb.Checkable() {
|
||
|
return gb.checkBox.Text()
|
||
|
}
|
||
|
|
||
|
return windowText(gb.hWndGroupBox)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetTitle(title string) error {
|
||
|
if gb.Checkable() {
|
||
|
if err := setWindowText(gb.hWndGroupBox, ""); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return gb.checkBox.SetText(title)
|
||
|
}
|
||
|
|
||
|
return setWindowText(gb.hWndGroupBox, title)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) Checkable() bool {
|
||
|
return gb.checkBox.visible
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetCheckable(checkable bool) {
|
||
|
title := gb.Title()
|
||
|
|
||
|
gb.checkBox.SetVisible(checkable)
|
||
|
|
||
|
gb.SetTitle(title)
|
||
|
|
||
|
gb.RequestLayout()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) Checked() bool {
|
||
|
return gb.checkBox.Checked()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetChecked(checked bool) {
|
||
|
gb.checkBox.SetChecked(checked)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) CheckedChanged() *Event {
|
||
|
return gb.checkBox.CheckedChanged()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) ApplyDPI(dpi int) {
|
||
|
gb.WidgetBase.ApplyDPI(dpi)
|
||
|
if gb.checkBox != nil {
|
||
|
gb.checkBox.ApplyDPI(dpi)
|
||
|
}
|
||
|
if gb.composite != nil {
|
||
|
gb.composite.ApplyDPI(dpi)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) Children() *WidgetList {
|
||
|
if gb.composite == nil {
|
||
|
// Without this we would get into trouble in NewComposite.
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return gb.composite.Children()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) Layout() Layout {
|
||
|
if gb.composite == nil {
|
||
|
// Without this we would get into trouble through the call to
|
||
|
// SetCheckable in NewGroupBox.
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return gb.composite.Layout()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) SetLayout(value Layout) error {
|
||
|
return gb.composite.SetLayout(value)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) MouseDown() *MouseEvent {
|
||
|
return gb.composite.MouseDown()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) MouseMove() *MouseEvent {
|
||
|
return gb.composite.MouseMove()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) MouseUp() *MouseEvent {
|
||
|
return gb.composite.MouseUp()
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||
|
if gb.composite != nil {
|
||
|
switch msg {
|
||
|
case win.WM_CTLCOLORSTATIC:
|
||
|
if hBrush := gb.handleWMCTLCOLOR(wParam, lParam); hBrush != 0 {
|
||
|
return hBrush
|
||
|
}
|
||
|
|
||
|
case win.WM_COMMAND:
|
||
|
hwndSrc := win.GetDlgItem(gb.hWnd, int32(win.LOWORD(uint32(wParam))))
|
||
|
|
||
|
if window := windowFromHandle(hwndSrc); window != nil {
|
||
|
window.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
case win.WM_NOTIFY:
|
||
|
gb.composite.WndProc(hwnd, msg, wParam, lParam)
|
||
|
|
||
|
case win.WM_SETTEXT:
|
||
|
gb.titleChangedPublisher.Publish()
|
||
|
|
||
|
case win.WM_PAINT:
|
||
|
win.UpdateWindow(gb.checkBox.hWnd)
|
||
|
|
||
|
case win.WM_WINDOWPOSCHANGED:
|
||
|
wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))
|
||
|
|
||
|
if wp.Flags&win.SWP_NOSIZE != 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
offset := gb.headerHeight / 4
|
||
|
wbcb := gb.WidgetBase.ClientBoundsPixels()
|
||
|
if !win.MoveWindow(
|
||
|
gb.hWndGroupBox,
|
||
|
int32(wbcb.X),
|
||
|
int32(wbcb.Y-offset),
|
||
|
int32(wbcb.Width),
|
||
|
int32(wbcb.Height),
|
||
|
true) {
|
||
|
|
||
|
lastError("MoveWindow")
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if gb.Checkable() {
|
||
|
s := createLayoutItemForWidget(gb.checkBox).(MinSizer).MinSize()
|
||
|
var x int
|
||
|
if l := gb.Layout(); l != nil {
|
||
|
x = gb.IntFrom96DPI(l.Margins().HNear)
|
||
|
} else {
|
||
|
x = gb.headerHeight * 2 / 3
|
||
|
}
|
||
|
gb.checkBox.SetBoundsPixels(Rectangle{x, gb.headerHeight, s.Width, s.Height})
|
||
|
}
|
||
|
|
||
|
gbcb := gb.ClientBoundsPixels()
|
||
|
gbcb.Y -= offset
|
||
|
gb.composite.SetBoundsPixels(gbcb)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return gb.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
func (gb *GroupBox) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
|
||
|
compositePos := Point{gb.IntFrom96DPI(1), gb.headerHeight}
|
||
|
if gb.Checkable() {
|
||
|
idealSize := gb.checkBox.idealSize()
|
||
|
|
||
|
compositePos.Y += idealSize.Height
|
||
|
}
|
||
|
|
||
|
li := &groupBoxLayoutItem{
|
||
|
compositePos: compositePos,
|
||
|
title: gb.Title(),
|
||
|
}
|
||
|
|
||
|
gbli := CreateLayoutItemsForContainerWithContext(gb.composite, ctx)
|
||
|
gbli.AsLayoutItemBase().parent = li
|
||
|
|
||
|
li.children = append(li.children, gbli)
|
||
|
|
||
|
return li
|
||
|
}
|
||
|
|
||
|
type groupBoxLayoutItem struct {
|
||
|
ContainerLayoutItemBase
|
||
|
compositePos Point // in native pixels
|
||
|
title string
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) LayoutFlags() LayoutFlags {
|
||
|
return li.children[0].LayoutFlags()
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) MinSize() Size {
|
||
|
min := li.children[0].(MinSizer).MinSize()
|
||
|
min.Width += li.compositePos.X * 2
|
||
|
min.Height += li.compositePos.Y + IntFrom96DPI(5, li.ctx.dpi)
|
||
|
|
||
|
return min
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) MinSizeForSize(size Size) Size {
|
||
|
return li.MinSize()
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) HasHeightForWidth() bool {
|
||
|
return li.children[0].(HeightForWidther).HasHeightForWidth()
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) HeightForWidth(width int) int {
|
||
|
return li.children[0].(HeightForWidther).HeightForWidth(width-li.compositePos.X*2) + li.compositePos.Y + IntFrom96DPI(5, li.ctx.dpi)
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) IdealSize() Size {
|
||
|
size := li.children[0].(IdealSizer).IdealSize()
|
||
|
size.Height += li.compositePos.Y
|
||
|
return size
|
||
|
}
|
||
|
|
||
|
func (li *groupBoxLayoutItem) PerformLayout() []LayoutResultItem {
|
||
|
return []LayoutResultItem{
|
||
|
{
|
||
|
Item: li.children[0],
|
||
|
Bounds: Rectangle{X: li.compositePos.X, Y: li.compositePos.Y, Width: li.geometry.Size.Width - li.compositePos.X*2, Height: li.geometry.Size.Height - li.compositePos.Y - IntFrom96DPI(5, li.ctx.dpi)},
|
||
|
},
|
||
|
}
|
||
|
}
|