2014-10-28 14:46:13 -05:00
// 28 october 2014
package ui
import (
2014-11-03 16:44:46 -06:00
"strconv"
2014-10-28 14:46:13 -05:00
"unsafe"
)
// #include "winapi_windows.h"
import "C"
// TODO do we have to manually monitor user changes to the edit control?
2014-10-30 09:49:22 -05:00
// TODO WS_EX_CLIENTEDGE on the updown?
2014-10-28 14:46:13 -05:00
type spinbox struct {
hwndEdit C . HWND
hwndUpDown C . HWND
2014-11-01 07:35:21 -05:00
changed * event
2014-10-30 09:42:59 -05:00
// updown state
updownVisible bool
2014-10-30 11:43:06 -05:00
// keep these here to avoid having to get them out
value int
min int
max int
2014-10-28 14:46:13 -05:00
}
2014-10-30 11:43:06 -05:00
func newSpinbox ( min int , max int ) Spinbox {
2014-10-28 14:46:13 -05:00
s := new ( spinbox )
s . hwndEdit = C . newControl ( editclass ,
C . textfieldStyle | C . ES_NUMBER ,
C . textfieldExtStyle )
2014-11-01 07:35:21 -05:00
s . changed = newEvent ( )
2014-10-30 09:42:59 -05:00
s . updownVisible = true // initially shown
2014-10-30 11:43:06 -05:00
s . min = min
s . max = max
s . value = s . min
2014-10-30 09:42:59 -05:00
s . remakeUpDown ( )
2014-11-04 10:55:07 -06:00
C . controlSetControlFont ( s . hwndEdit )
2014-11-02 19:49:39 -06:00
C . setSpinboxEditSubclass ( s . hwndEdit , unsafe . Pointer ( s ) )
2014-10-28 14:46:13 -05:00
return s
}
2014-11-02 15:00:35 -06:00
func ( s * spinbox ) cap ( ) {
if s . value < s . min {
s . value = s . min
}
if s . value > s . max {
s . value = s . max
}
}
2014-10-30 11:43:06 -05:00
func ( s * spinbox ) Value ( ) int {
2014-11-01 07:35:21 -05:00
return s . value
2014-10-30 11:43:06 -05:00
}
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
2014-11-02 15:00:35 -06:00
s . cap ( )
C . SendMessageW ( s . hwndUpDown , C . UDM_SETPOS32 , 0 , C . LPARAM ( s . value ) )
2014-10-30 11:43:06 -05:00
}
2014-11-01 07:35:21 -05:00
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 )
2014-11-02 15:00:35 -06:00
// 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 ( )
2014-11-01 07:35:21 -05:00
s . changed . fire ( )
}
2014-11-02 19:49:39 -06:00
//export spinboxEditChanged
func spinboxEditChanged ( data unsafe . Pointer ) {
2014-11-03 22:16:48 -06:00
// we're basically on our own here
2014-11-02 19:49:39 -06:00
s := ( * spinbox ) ( unsafe . Pointer ( data ) )
2014-11-03 22:16:48 -06:00
// this basically does what OS X does: values too low get clamped to the minimum, values too high get clamped to the maximum, and deleting everything clamps to the minimum
2014-11-03 16:44:46 -06:00
value , err := strconv . Atoi ( getWindowText ( s . hwndEdit ) )
if err != nil {
2014-11-03 22:16:48 -06:00
// best we can do fo rnow in this case :S
// a partial atoi() like in C would be more optimal
// it handles the deleting everything case just fine
value = s . min
2014-11-03 16:44:46 -06:00
}
2014-11-03 22:16:48 -06:00
s . value = value
2014-11-03 16:44:46 -06:00
s . cap ( )
2014-11-03 22:16:48 -06:00
C . SendMessageW ( s . hwndUpDown , C . UDM_SETPOS32 , 0 , C . LPARAM ( s . value ) )
// TODO position the insertion caret at the end (or wherever is appropriate)
2014-11-02 19:49:39 -06:00
s . changed . fire ( )
}
2014-10-28 14:46:13 -05:00
func ( s * spinbox ) setParent ( p * controlParent ) {
C . controlSetParent ( s . hwndEdit , p . hwnd )
C . controlSetParent ( s . hwndUpDown , p . hwnd )
}
2014-10-30 09:42:59 -05:00
// 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 ( ) {
2014-11-01 07:35:21 -05:00
// destroying the previous one, setting the parent properly, and subclassing are handled here
s . hwndUpDown = C . newUpDown ( s . hwndUpDown , unsafe . Pointer ( s ) )
2014-10-30 09:42:59 -05:00
// 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 )
2014-10-30 11:43:06 -05:00
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 ) )
2014-10-30 09:42:59 -05:00
if s . updownVisible {
C . ShowWindow ( s . hwndUpDown , C . SW_SHOW )
}
}
2014-11-04 10:55:07 -06:00
// use the same height as normal text fields
// TODO constrain the width somehow
2014-10-28 14:46:13 -05:00
func ( s * spinbox ) preferredSize ( d * sizing ) ( width , height int ) {
2014-11-04 10:55:07 -06:00
return fromdlgunitsX ( textfieldWidth , d ) , fromdlgunitsY ( textfieldHeight , d )
2014-10-28 14:46:13 -05:00
}
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 ) )
2014-10-30 09:42:59 -05:00
s . remakeUpDown ( )
2014-10-28 14:46:13 -05:00
}
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 )
2014-10-30 09:42:59 -05:00
s . updownVisible = true
2014-10-28 14:46:13 -05:00
}
// 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 )
2014-10-30 09:42:59 -05:00
s . updownVisible = false
2014-10-28 14:46:13 -05:00
}