2014-02-08 22:51:11 -06:00
// 8 february 2014
2014-03-12 20:55:45 -05:00
2014-02-19 10:41:10 -06:00
package ui
2014-02-08 22:51:11 -06:00
import (
2014-02-11 16:38:38 -06:00
"fmt"
2014-02-08 22:51:11 -06:00
"syscall"
"unsafe"
2014-02-12 19:51:07 -06:00
)
2014-06-03 09:52:53 -05:00
var (
stdWndClass = toUTF16 ( "gouiwnd" )
2014-02-11 16:38:38 -06:00
)
var (
2014-05-30 13:13:47 -05:00
_defWindowProc = user32 . NewProc ( "DefWindowProcW" )
2014-02-11 16:38:38 -06:00
)
2014-05-30 13:13:47 -05:00
func defWindowProc ( hwnd _HWND , uMsg uint32 , wParam _WPARAM , lParam _LPARAM ) _LRESULT {
r1 , _ , _ := _defWindowProc . Call (
uintptr ( hwnd ) ,
uintptr ( uMsg ) ,
uintptr ( wParam ) ,
uintptr ( lParam ) )
return _LRESULT ( r1 )
}
2014-05-30 16:59:29 -05:00
// don't worry about error returns from GetWindowLongPtr()/SetWindowLongPtr()
// see comments of http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx
func getWindowLongPtr ( hwnd _HWND , what uintptr ) uintptr {
r1 , _ , _ := _getWindowLongPtr . Call (
uintptr ( hwnd ) ,
what )
return r1
}
func setWindowLongPtr ( hwnd _HWND , what uintptr , value uintptr ) {
_setWindowLongPtr . Call (
uintptr ( hwnd ) ,
what ,
value )
}
// we can store a pointer in extra space provided by Windows
// we'll store sysData there
// see http://blogs.msdn.com/b/oldnewthing/archive/2005/03/03/384285.aspx
2014-05-30 17:03:57 -05:00
2014-05-30 16:59:29 -05:00
func getSysData ( hwnd _HWND ) * sysData {
return ( * sysData ) ( unsafe . Pointer ( getWindowLongPtr ( hwnd , negConst ( _GWLP_USERDATA ) ) ) )
}
func storeSysData ( hwnd _HWND , uMsg uint32 , wParam _WPARAM , lParam _LPARAM ) _LRESULT {
// we can get the lpParam from CreateWindowEx() in WM_NCCREATE and WM_CREATE
// we can freely skip any messages that come prior
// see http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx (note the date on the latter one!)
if uMsg == _WM_NCCREATE {
// the lpParam to CreateWindowEx() is the first uintptr of the CREATESTRUCT
// so rather than create that whole structure, we'll just grab the uintptr at the address pointed to by lParam
cs := ( * uintptr ) ( unsafe . Pointer ( lParam ) )
saddr := * cs
setWindowLongPtr ( hwnd , negConst ( _GWLP_USERDATA ) , saddr )
2014-05-30 22:28:41 -05:00
// also set s.hwnd here so it can be used by other window messages right away
s := ( * sysData ) ( unsafe . Pointer ( saddr ) )
s . hwnd = hwnd
2014-05-30 16:59:29 -05:00
}
2014-06-04 00:17:59 -05:00
// then regardless of what happens, defer to DefWindowProc() (if you trace the execution of the above links, this is what they do)
2014-05-30 16:59:29 -05:00
return defWindowProc ( hwnd , uMsg , wParam , lParam )
}
2014-06-05 22:27:17 -05:00
var (
_getFocus = user32 . NewProc ( "GetFocus" )
2014-06-10 10:12:32 -05:00
_isChild = user32 . NewProc ( "IsChild" )
2014-06-05 22:27:17 -05:00
// _setFocus in area_windows.go
)
// this is needed to ensure focus is preserved when switching away from and back to our program
// from http://blogs.msdn.com/b/oldnewthing/archive/2014/05/21/10527168.aspx
func ( s * sysData ) handleFocus ( wParam _WPARAM ) {
// parameter splitting from Microsoft's windowsx.h
2014-06-10 10:12:32 -05:00
state := uint32 ( wParam . LOWORD ( ) ) // originally UINT
2014-06-05 22:27:17 -05:00
minimized := wParam . HIWORD ( ) != 0
2014-06-10 10:12:32 -05:00
if minimized { // don't do anything on minimize
2014-06-05 22:27:17 -05:00
return
}
2014-06-10 10:12:32 -05:00
if state == _WA_INACTIVE { // focusing out
2014-06-05 22:27:17 -05:00
old , _ , _ := _getFocus . Call ( )
2014-06-10 10:12:32 -05:00
if _HWND ( old ) != _HWND ( _NULL ) { // if there is one
2014-06-05 22:27:17 -05:00
r1 , _ , _ := _isChild . Call (
uintptr ( s . hwnd ) ,
old )
if r1 != 0 {
s . lastfocus = _HWND ( old )
}
}
2014-06-10 10:12:32 -05:00
} else { // focusing in
if s . lastfocus != _HWND ( _NULL ) { // if we have one
2014-06-06 00:49:23 -05:00
// don't bother checking SetFocus()'s error; see http://stackoverflow.com/questions/24073695/winapi-can-setfocus-return-null-without-an-error-because-thats-what-im-see/24074912#24074912
_setFocus . Call ( uintptr ( s . lastfocus ) )
2014-06-05 22:27:17 -05:00
}
}
}
2014-05-30 20:49:55 -05:00
func stdWndProc ( hwnd _HWND , uMsg uint32 , wParam _WPARAM , lParam _LPARAM ) _LRESULT {
s := getSysData ( hwnd )
2014-06-10 10:12:32 -05:00
if s == nil { // not yet saved
2014-05-30 20:49:55 -05:00
return storeSysData ( hwnd , uMsg , wParam , lParam )
}
switch uMsg {
2014-07-03 10:04:07 -05:00
case msgPost :
data := ( * interface { } ) ( unsafe . Pointer ( lParam ) )
s . post ( * data )
return 0
2014-05-30 20:49:55 -05:00
case _WM_COMMAND :
id := _HMENU ( wParam . LOWORD ( ) )
s . childrenLock . Lock ( )
ss := s . children [ id ]
s . childrenLock . Unlock ( )
switch ss . ctype {
case c_button :
if wParam . HIWORD ( ) == _BN_CLICKED {
2014-06-30 21:48:12 -05:00
ss . event ( )
2014-02-12 10:32:17 -06:00
}
2014-06-05 17:28:40 -05:00
case c_checkbox :
// we opt into doing this ourselves because http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx
if wParam . HIWORD ( ) == _BN_CLICKED {
state , _ , _ := _sendMessage . Call (
uintptr ( ss . hwnd ) ,
uintptr ( _BM_GETCHECK ) ,
uintptr ( 0 ) ,
uintptr ( 0 ) )
if state == _BST_CHECKED {
state = _BST_UNCHECKED
} else if state == _BST_UNCHECKED {
state = _BST_CHECKED
}
_sendMessage . Call (
uintptr ( ss . hwnd ) ,
uintptr ( _BM_SETCHECK ) ,
2014-06-10 10:12:32 -05:00
state , // already uintptr
2014-06-05 17:28:40 -05:00
uintptr ( 0 ) )
}
2014-05-30 20:49:55 -05:00
}
return 0
2014-06-06 00:49:23 -05:00
case _WM_ACTIVATE :
s . handleFocus ( wParam )
return 0
2014-05-30 20:49:55 -05:00
case _WM_GETMINMAXINFO :
mm := lParam . MINMAXINFO ( )
// ... minimum size
_ = mm
return 0
case _WM_SIZE :
2014-06-25 22:21:57 -05:00
if s . allocate != nil {
2014-05-30 20:49:55 -05:00
var r _RECT
r1 , _ , err := _getClientRect . Call (
uintptr ( hwnd ) ,
uintptr ( unsafe . Pointer ( & r ) ) )
if r1 == 0 {
panic ( "GetClientRect failed: " + err . Error ( ) )
2014-02-13 04:28:26 -06:00
}
2014-06-25 22:22:47 -05:00
// top-left corner of a client rect is always (0,0) so no need for left/top
2014-06-25 22:21:57 -05:00
s . resizeWindow ( int ( r . right ) , int ( r . bottom ) )
2014-05-30 20:49:55 -05:00
// TODO use the Defer movement functions here?
// TODO redraw window and all children here?
2014-02-12 10:32:17 -06:00
}
2014-05-30 20:49:55 -05:00
return 0
case _WM_CLOSE :
2014-06-30 22:48:08 -05:00
if s . close ( ) {
// TODO destroy
2014-06-30 21:48:12 -05:00
s . hide ( )
}
2014-05-30 20:49:55 -05:00
return 0
default :
return defWindowProc ( hwnd , uMsg , wParam , lParam )
2014-02-11 16:38:38 -06:00
}
2014-05-30 20:49:55 -05:00
panic ( fmt . Sprintf ( "stdWndProc message %d did not return: internal bug in ui library" , uMsg ) )
2014-02-08 22:51:11 -06:00
}
2014-02-11 16:38:38 -06:00
type _WNDCLASS struct {
2014-06-10 10:12:32 -05:00
style uint32
lpfnWndProc uintptr
cbClsExtra int32 // originally int
cbWndExtra int32 // originally int
hInstance _HANDLE
hIcon _HANDLE
hCursor _HANDLE
hbrBackground _HBRUSH
lpszMenuName * uint16
lpszClassName uintptr
2014-02-08 22:51:11 -06:00
}
2014-02-12 19:51:07 -06:00
var (
icon , cursor _HANDLE
)
var (
_registerClass = user32 . NewProc ( "RegisterClassW" )
)
2014-05-30 20:49:55 -05:00
func registerStdWndClass ( ) ( err error ) {
2014-02-12 19:51:07 -06:00
wc := & _WNDCLASS {
2014-06-10 10:12:32 -05:00
lpszClassName : utf16ToArg ( stdWndClass ) ,
lpfnWndProc : syscall . NewCallback ( stdWndProc ) ,
hInstance : hInstance ,
hIcon : icon ,
hCursor : cursor ,
hbrBackground : _HBRUSH ( _COLOR_BTNFACE + 1 ) ,
2014-02-12 19:51:07 -06:00
}
2014-05-30 20:49:55 -05:00
r1 , _ , err := _registerClass . Call ( uintptr ( unsafe . Pointer ( wc ) ) )
2014-06-10 10:12:32 -05:00
if r1 == 0 { // failure
2014-05-30 20:49:55 -05:00
return err
2014-02-12 19:51:07 -06:00
}
2014-05-30 20:49:55 -05:00
return nil
2014-02-12 19:51:07 -06:00
}
2014-05-25 14:09:01 -05:00
// no need to use/recreate MAKEINTRESOURCE() here as the Windows constant generator already took care of that because Microsoft's headers do already
2014-02-12 19:51:07 -06:00
func initWndClassInfo ( ) ( err error ) {
2014-02-11 18:18:03 -06:00
r1 , _ , err := user32 . NewProc ( "LoadIconW" ) . Call (
2014-02-11 16:38:38 -06:00
uintptr ( _NULL ) ,
2014-05-25 14:09:01 -05:00
uintptr ( _IDI_APPLICATION ) )
2014-06-10 10:12:32 -05:00
if r1 == 0 { // failure
2014-02-11 16:38:38 -06:00
return fmt . Errorf ( "error getting window icon: %v" , err )
2014-02-08 22:51:11 -06:00
}
2014-02-12 19:51:07 -06:00
icon = _HANDLE ( r1 )
2014-02-11 18:18:03 -06:00
r1 , _ , err = user32 . NewProc ( "LoadCursorW" ) . Call (
2014-02-11 16:38:38 -06:00
uintptr ( _NULL ) ,
2014-05-25 14:09:01 -05:00
uintptr ( _IDC_ARROW ) )
2014-06-10 10:12:32 -05:00
if r1 == 0 { // failure
2014-02-11 16:38:38 -06:00
return fmt . Errorf ( "error getting window cursor: %v" , err )
2014-02-08 22:51:11 -06:00
}
2014-02-12 19:51:07 -06:00
cursor = _HANDLE ( r1 )
2014-02-08 22:51:11 -06:00
2014-02-11 16:38:38 -06:00
return nil
2014-02-08 22:51:11 -06:00
}