summaryrefslogtreecommitdiff
path: root/spinbox_windows.go
blob: 4a16364616a1d90e2cdd9949a6ac3777eba8c100 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 28 october 2014

package ui

import (
	"strconv"
	"unsafe"
)

// #include "winapi_windows.h"
import "C"

// TODO do we have to manually monitor user changes to the edit control?
// TODO WS_EX_CLIENTEDGE on the updown?

type spinbox struct {
	hwndEdit			C.HWND
	hwndUpDown		C.HWND
	changed			*event
	// updown state
	updownVisible		bool
	// keep these here to avoid having to get them out
	value			int
	min				int
	max				int
}

func newSpinbox(min int, max int) Spinbox {
	s := new(spinbox)
	s.hwndEdit = C.newControl(editclass,
		C.textfieldStyle | C.ES_NUMBER,
		C.textfieldExtStyle)
	s.changed = newEvent()
	s.updownVisible = true		// initially shown
	s.min = min
	s.max = max
	s.value = s.min
	s.remakeUpDown()
	C.setSpinboxEditSubclass(s.hwndEdit, unsafe.Pointer(s))
	return s
}

func (s *spinbox) cap() {
	if s.value < s.min {
		s.value = s.min
	}
	if s.value > s.max {
		s.value = s.max
	}
}

func (s *spinbox) Value() int {
	return s.value
}

func (s *spinbox) SetValue(value int) {
	// UDM_SETPOS32 is documented to do what we want, but since we're keeping a copy of value we need to do it anyway
	s.value = value
	s.cap()
	C.SendMessageW(s.hwndUpDown, C.UDM_SETPOS32, 0, C.LPARAM(s.value))
}

func (s *spinbox) OnChanged(e func()) {
	s.changed.set(e)
}

//export spinboxUpDownClicked
func spinboxUpDownClicked(data unsafe.Pointer, nud *C.NMUPDOWN) {
	// this is where we do custom increments
	s := (*spinbox)(data)
	s.value = int(nud.iPos + nud.iDelta)
	// this can go above or below the bounds (the spinbox only rejects invalid values after the UDN_DELTAPOS notification is processed)
	// because we have a copy of the value, we need to fix that here
	s.cap()
	s.changed.fire()
}

//export spinboxEditChanged
func spinboxEditChanged(data unsafe.Pointer) {
	s := (*spinbox)(unsafe.Pointer(data))
	// TODO
	value, err := strconv.Atoi(getWindowText(s.hwndEdit))
	if err != nil {
		// TODO see what OS X does
	}
	s.value = int(value)
	s.cap()
	// TODO see what OS X does if the cap happened
	s.changed.fire()
}

func (s *spinbox) setParent(p *controlParent) {
	C.controlSetParent(s.hwndEdit, p.hwnd)
	C.controlSetParent(s.hwndUpDown, p.hwnd)
}

// an up-down control will only properly position itself the first time
// stupidly, there are no messages to force a size calculation, nor can I seem to reset the buddy window to force a new position
// alas, we have to make a new up/down control each time :(
// TODO we'll need to store a copy of the current position and range for this
func (s *spinbox) remakeUpDown() {
	// destroying the previous one, setting the parent properly, and subclassing are handled here
	s.hwndUpDown = C.newUpDown(s.hwndUpDown, unsafe.Pointer(s))
	// for this to work, hwndUpDown needs to have rect [0 0 0 0]
	C.moveWindow(s.hwndUpDown, 0, 0, 0, 0)
	C.SendMessageW(s.hwndUpDown, C.UDM_SETBUDDY, C.WPARAM(uintptr(unsafe.Pointer(s.hwndEdit))), 0)
	C.SendMessageW(s.hwndUpDown, C.UDM_SETRANGE32, C.WPARAM(s.min), C.LPARAM(s.max))
	C.SendMessageW(s.hwndUpDown, C.UDM_SETPOS32, 0, C.LPARAM(s.value))
	if s.updownVisible {
		C.ShowWindow(s.hwndUpDown, C.SW_SHOW)
	}
}

func (s *spinbox) preferredSize(d *sizing) (width, height int) {
	// TODO
	return 20, 20
}

func (s *spinbox) resize(x int, y int, width int, height int, d *sizing) {
	C.moveWindow(s.hwndEdit, C.int(x), C.int(y), C.int(width), C.int(height))
	s.remakeUpDown()
}

func (s *spinbox) nTabStops() int {
	// TODO does the up-down control count?
	return 1
}

// TODO be sure to modify this when we add Show()/Hide()
func (s *spinbox) containerShow() {
	C.ShowWindow(s.hwndEdit, C.SW_SHOW)
	C.ShowWindow(s.hwndUpDown, C.SW_SHOW)
	s.updownVisible = true
}

// TODO be sure to modify this when we add Show()/Hide()
func (s *spinbox) containerHide() {
	C.ShowWindow(s.hwndEdit, C.SW_HIDE)
	C.ShowWindow(s.hwndUpDown, C.SW_HIDE)
	s.updownVisible = false
}