2014-03-14 22:06:51 -05:00
// +build !windows,!darwin,!plan9
2014-03-14 17:44:59 -05:00
// 14 march 2014
package ui
import (
2014-03-26 10:01:37 -05:00
"fmt"
2014-03-14 22:06:51 -05:00
"unsafe"
2014-03-14 17:44:59 -05:00
"image"
)
2014-03-16 09:34:12 -05:00
// #include "gtk_unix.h"
2014-03-14 22:15:24 -05:00
// extern gboolean our_area_draw_callback(GtkWidget *, cairo_t *, gpointer);
2014-03-15 21:29:47 -05:00
// extern gboolean our_area_button_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_button_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_motion_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
2014-05-31 23:16:57 -05:00
// extern gboolean our_area_enterleave_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
2014-03-18 11:44:21 -05:00
// extern gboolean our_area_key_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
2014-05-23 17:30:08 -05:00
// /* because cgo doesn't like ... */
// static inline void gtkGetDoubleClickSettings(GtkSettings *settings, gint *maxTime, gint *maxDistance)
// {
// g_object_get(settings,
// "gtk-double-click-time", maxTime,
// "gtk-double-click-distance", maxDistance,
// NULL);
// }
2014-03-14 17:44:59 -05:00
import "C"
2014-04-01 15:30:38 -05:00
func gtkAreaNew ( ) * C . GtkWidget {
2014-03-14 17:44:59 -05:00
drawingarea := C . gtk_drawing_area_new ( )
2014-04-27 19:54:39 -05:00
// the Area's size will be set later
2014-03-15 21:29:47 -05:00
// we need to explicitly subscribe to mouse events with GtkDrawingArea
C . gtk_widget_add_events ( drawingarea ,
2014-05-31 23:16:57 -05:00
C . GDK_BUTTON_PRESS_MASK | C . GDK_BUTTON_RELEASE_MASK | C . GDK_POINTER_MOTION_MASK | C . GDK_BUTTON_MOTION_MASK | C . GDK_ENTER_NOTIFY_MASK | C . GDK_LEAVE_NOTIFY_MASK )
2014-03-18 11:44:21 -05:00
// and we need to allow focusing on a GtkDrawingArea to enable keyboard events
C . gtk_widget_set_can_focus ( drawingarea , C . TRUE )
2014-03-14 17:44:59 -05:00
scrollarea := C . gtk_scrolled_window_new ( ( * C . GtkAdjustment ) ( nil ) , ( * C . GtkAdjustment ) ( nil ) )
// need a viewport because GtkDrawingArea isn't natively scrollable
2014-03-14 22:06:51 -05:00
C . gtk_scrolled_window_add_with_viewport ( ( * C . GtkScrolledWindow ) ( unsafe . Pointer ( scrollarea ) ) , drawingarea )
2014-04-01 15:30:38 -05:00
return scrollarea
2014-03-14 17:44:59 -05:00
}
2014-04-01 15:30:38 -05:00
func gtkAreaGetControl ( scrollarea * C . GtkWidget ) * C . GtkWidget {
2014-03-14 22:15:24 -05:00
viewport := C . gtk_bin_get_child ( ( * C . GtkBin ) ( unsafe . Pointer ( scrollarea ) ) )
control := C . gtk_bin_get_child ( ( * C . GtkBin ) ( unsafe . Pointer ( viewport ) ) )
2014-04-01 15:30:38 -05:00
return control
2014-03-14 22:15:24 -05:00
}
//export our_area_draw_callback
func our_area_draw_callback ( widget * C . GtkWidget , cr * C . cairo_t , data C . gpointer ) C . gboolean {
2014-05-17 16:01:52 -05:00
var x0 , y0 , x1 , y1 C . double
2014-03-23 20:31:29 -05:00
var maxwid , maxht C . gint
2014-03-14 17:44:59 -05:00
s := ( * sysData ) ( unsafe . Pointer ( data ) )
// thanks to desrt in irc.gimp.net/#gtk+
2014-05-17 16:01:52 -05:00
// TODO these are in "user coordinates"; is that what we want?
C . cairo_clip_extents ( cr , & x0 , & y0 , & x1 , & y1 )
2014-03-29 18:02:09 -05:00
// we do not need to clear the cliprect; GtkDrawingArea did it for us beforehand
2014-05-17 16:01:52 -05:00
cliprect := image . Rect ( int ( x0 ) , int ( y0 ) , int ( x1 ) , int ( y1 ) )
2014-03-23 20:31:29 -05:00
// the cliprect can actually fall outside the size of the Area; clip it by intersecting the two rectangles
C . gtk_widget_get_size_request ( widget , & maxwid , & maxht )
cliprect = image . Rect ( 0 , 0 , int ( maxwid ) , int ( maxht ) ) . Intersect ( cliprect )
if cliprect . Empty ( ) { // no intersection; nothing to paint
return C . FALSE // signals handled without stopping the event chain (thanks to desrt again)
}
2014-03-16 20:40:33 -05:00
i := s . handler . Paint ( cliprect )
2014-04-12 12:48:59 -05:00
surface := C . cairo_image_surface_create (
C . CAIRO_FORMAT_ARGB32 , // alpha-premultiplied; native byte order
2014-03-14 17:44:59 -05:00
C . int ( i . Rect . Dx ( ) ) ,
2014-04-12 12:48:59 -05:00
C . int ( i . Rect . Dy ( ) ) )
if status := C . cairo_surface_status ( surface ) ; status != C . CAIRO_STATUS_SUCCESS {
panic ( fmt . Errorf ( "cairo_create_image_surface() failed: %s\n" ,
C . GoString ( C . cairo_status_to_string ( status ) ) ) )
}
// the flush and mark_dirty calls are required; see the cairo docs and https://git.gnome.org/browse/gtk+/tree/gdk/gdkcairo.c#n232 (thanks desrt in irc.gimp.net/#gtk+)
C . cairo_surface_flush ( surface )
toARGB ( i , uintptr ( unsafe . Pointer ( C . cairo_image_surface_get_data ( surface ) ) ) ,
int ( C . cairo_image_surface_get_stride ( surface ) ) )
C . cairo_surface_mark_dirty ( surface )
C . cairo_set_source_surface ( cr ,
surface ,
0 , 0 ) // origin of the surface
2014-03-14 22:36:47 -05:00
// that just set the brush that cairo uses: we have to actually draw now
// (via https://developer.gnome.org/gtkmm-tutorial/stable/sec-draw-images.html.en)
2014-05-17 16:01:52 -05:00
// TODO see above about user coordinates; if we do change to device coordinates the following line will need to change or be added to
C . cairo_rectangle ( cr , x0 , y0 , x1 , y1 ) // breaking the nrom here since we have the coordinates as a C double already
2014-03-14 22:36:47 -05:00
C . cairo_fill ( cr )
2014-04-12 12:48:59 -05:00
C . cairo_surface_destroy ( surface ) // free surface
2014-03-14 22:06:51 -05:00
return C . FALSE // signals handled without stopping the event chain (thanks to desrt again)
2014-03-14 17:44:59 -05:00
}
2014-03-14 22:15:24 -05:00
var area_draw_callback = C . GCallback ( C . our_area_draw_callback )
2014-03-15 21:29:47 -05:00
2014-03-23 16:12:30 -05:00
func translateModifiers ( state C . guint , window * C . GdkWindow ) C . guint {
2014-03-15 21:29:47 -05:00
// GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+)
2014-03-16 00:39:30 -05:00
C . gdk_keymap_add_virtual_modifiers (
2014-03-23 16:12:30 -05:00
C . gdk_keymap_get_for_display ( C . gdk_window_get_display ( window ) ) ,
2014-03-15 21:29:47 -05:00
( * C . GdkModifierType ) ( unsafe . Pointer ( & state ) ) )
2014-03-23 16:12:30 -05:00
return state
}
2014-05-29 10:49:59 -05:00
func makeModifiers ( state C . guint ) ( m Modifiers ) {
2014-03-15 21:29:47 -05:00
if ( state & C . GDK_CONTROL_MASK ) != 0 {
2014-03-23 16:12:30 -05:00
m |= Ctrl
2014-03-15 21:29:47 -05:00
}
2014-05-16 17:39:08 -05:00
if ( state & C . GDK_META_MASK ) != 0 { // TODO get equivalent for Alt
2014-03-23 16:12:30 -05:00
m |= Alt
2014-03-15 21:29:47 -05:00
}
if ( state & C . GDK_SHIFT_MASK ) != 0 {
2014-03-23 16:12:30 -05:00
m |= Shift
2014-03-15 21:29:47 -05:00
}
2014-05-16 17:39:08 -05:00
if ( state & C . GDK_SUPER_MASK ) != 0 {
m |= Super
}
2014-03-23 16:12:30 -05:00
return m
}
// shared code for finishing up and sending a mouse event
2014-03-27 19:31:23 -05:00
func finishMouseEvent ( widget * C . GtkWidget , data C . gpointer , me MouseEvent , mb uint , x C . gdouble , y C . gdouble , state C . guint , gdkwindow * C . GdkWindow ) {
2014-04-10 17:52:43 -05:00
var areawidth , areaheight C . gint
2014-05-07 16:51:04 -05:00
// on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake (and see the TODOs on return values below)
if mb >= 4 && mb <= 7 {
return
}
2014-03-23 16:12:30 -05:00
s := ( * sysData ) ( unsafe . Pointer ( data ) )
state = translateModifiers ( state , gdkwindow )
2014-05-29 10:49:59 -05:00
me . Modifiers = makeModifiers ( state )
2014-03-15 21:29:47 -05:00
// the mb != # checks exclude the Up/Down button from Held
if mb != 1 && ( state & C . GDK_BUTTON1_MASK ) != 0 {
me . Held = append ( me . Held , 1 )
}
if mb != 2 && ( state & C . GDK_BUTTON2_MASK ) != 0 {
me . Held = append ( me . Held , 2 )
}
if mb != 3 && ( state & C . GDK_BUTTON3_MASK ) != 0 {
me . Held = append ( me . Held , 3 )
}
2014-05-07 16:51:04 -05:00
// don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those are for the scrolling buttons mentioned above; there doesn't seem to be a way to detect higher buttons... (TODO)
2014-03-15 21:29:47 -05:00
me . Pos = image . Pt ( int ( x ) , int ( y ) )
2014-04-10 17:52:43 -05:00
C . gtk_widget_get_size_request ( widget , & areawidth , & areaheight )
if ! me . Pos . In ( image . Rect ( 0 , 0 , int ( areawidth ) , int ( areaheight ) ) ) { // outside the actual Area; no event
return
}
2014-05-07 16:51:04 -05:00
// and finally, if the button ID >= 8, continue counting from 4, as above and as in the MouseEvent spec
if me . Down >= 8 {
me . Down -= 4
}
if me . Up >= 8 {
me . Up -= 4
}
2014-03-27 19:31:23 -05:00
repaint := s . handler . Mouse ( me )
if repaint {
C . gtk_widget_queue_draw ( widget )
}
2014-03-15 21:29:47 -05:00
}
//export our_area_button_press_event_callback
func our_area_button_press_event_callback ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer ) C . gboolean {
2014-04-14 22:11:28 -05:00
// clicking doesn't automatically transfer keyboard focus; we must do so manually (thanks tristan in irc.gimp.net/#gtk+)
C . gtk_widget_grab_focus ( widget )
2014-03-15 21:29:47 -05:00
e := ( * C . GdkEventButton ) ( unsafe . Pointer ( event ) )
me := MouseEvent {
2014-05-07 16:51:04 -05:00
// GDK button ID == our button ID with some exceptions taken care of by finishMouseEvent()
2014-03-15 21:29:47 -05:00
Down : uint ( e . button ) ,
}
2014-05-23 17:30:08 -05:00
var maxTime C . gint
var maxDistance C . gint
if e . _type != C . GDK_BUTTON_PRESS {
// ignore GDK's generated double-clicks and beyond; we handled those ourselves below
2014-03-15 21:29:47 -05:00
return C . FALSE // TODO really false?
}
2014-05-23 17:30:08 -05:00
s := ( * sysData ) ( unsafe . Pointer ( data ) )
// e.time is unsigned and in milliseconds
// maxTime is also milliseconds; despite being gint, it is only allowed to be positive
// maxDistance is also only allowed to be positive
settings := C . gtk_widget_get_settings ( widget )
C . gtkGetDoubleClickSettings ( settings , & maxTime , & maxDistance )
me . Count = s . clickCounter . click ( me . Down , int ( e . x ) , int ( e . y ) ,
uintptr ( e . time ) , uintptr ( maxTime ) ,
int ( maxDistance ) , int ( maxDistance ) )
2014-03-27 19:31:23 -05:00
finishMouseEvent ( widget , data , me , me . Down , e . x , e . y , e . state , e . window )
2014-03-15 21:29:47 -05:00
return C . FALSE // TODO really false?
}
var area_button_press_event_callback = C . GCallback ( C . our_area_button_press_event_callback )
//export our_area_button_release_event_callback
func our_area_button_release_event_callback ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer ) C . gboolean {
e := ( * C . GdkEventButton ) ( unsafe . Pointer ( event ) )
me := MouseEvent {
2014-05-07 16:51:04 -05:00
// GDK button ID == our button ID with some exceptions taken care of by finishMouseEvent()
2014-03-15 21:29:47 -05:00
Up : uint ( e . button ) ,
}
2014-03-27 19:31:23 -05:00
finishMouseEvent ( widget , data , me , me . Up , e . x , e . y , e . state , e . window )
2014-03-15 21:29:47 -05:00
return C . FALSE // TODO really false?
}
var area_button_release_event_callback = C . GCallback ( C . our_area_button_release_event_callback )
//export our_area_motion_notify_event_callback
func our_area_motion_notify_event_callback ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer ) C . gboolean {
e := ( * C . GdkEventMotion ) ( unsafe . Pointer ( event ) )
me := MouseEvent { }
2014-03-27 19:31:23 -05:00
finishMouseEvent ( widget , data , me , 0 , e . x , e . y , e . state , e . window )
2014-03-15 21:29:47 -05:00
return C . FALSE // TODO really false?
}
var area_motion_notify_event_callback = C . GCallback ( C . our_area_motion_notify_event_callback )
2014-03-18 11:44:21 -05:00
2014-05-31 23:16:57 -05:00
// we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows
2014-05-31 23:22:15 -05:00
// according to tristan in irc.gimp.net/#gtk+, doing this on enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out))
2014-05-31 23:16:57 -05:00
// differentiating between enter-notify-event and leave-notify-event is unimportant
2014-05-23 22:38:14 -05:00
2014-05-31 23:16:57 -05:00
//export our_area_enterleave_notify_event_callback
func our_area_enterleave_notify_event_callback ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer ) C . gboolean {
2014-05-23 22:38:14 -05:00
s := ( * sysData ) ( unsafe . Pointer ( data ) )
s . clickCounter . reset ( )
return C . FALSE // TODO really false?
}
2014-05-31 23:16:57 -05:00
var area_enterleave_notify_event_callback = C . GCallback ( C . our_area_enterleave_notify_event_callback )
2014-05-23 22:38:14 -05:00
2014-03-23 16:12:30 -05:00
// shared code for doing a key event
2014-03-27 19:31:23 -05:00
func doKeyEvent ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer , up bool ) bool {
2014-03-23 16:12:30 -05:00
var ke KeyEvent
e := ( * C . GdkEventKey ) ( unsafe . Pointer ( event ) )
s := ( * sysData ) ( unsafe . Pointer ( data ) )
keyval := e . keyval
2014-05-29 10:49:59 -05:00
// get modifiers now in case a modifier was pressed
state := translateModifiers ( e . state , e . window )
ke . Modifiers = makeModifiers ( state )
2014-03-23 16:12:30 -05:00
if extkey , ok := extkeys [ keyval ] ; ok {
ke . ExtKey = extkey
2014-03-23 16:24:13 -05:00
} else if mod , ok := modonlykeys [ keyval ] ; ok {
2014-05-29 10:49:59 -05:00
ke . Modifier = mod
// don't include the modifier in ke.Modifiers
ke . Modifiers &^= mod
2014-03-29 11:57:54 -05:00
} else if xke , ok := fromScancode ( uintptr ( e . hardware_keycode ) - 8 ) ; ok {
2014-03-29 11:33:36 -05:00
// see events_notdarwin.go for details of the above map lookup
2014-03-29 11:57:54 -05:00
// one of these will be nonzero
ke . Key = xke . Key
ke . ExtKey = xke . ExtKey
2014-03-29 11:33:36 -05:00
} else { // no match
// TODO really stop here? [or should we handle modifiers?]
return false // pretend unhandled
2014-03-23 16:12:30 -05:00
}
ke . Up = up
2014-03-27 19:31:23 -05:00
handled , repaint := s . handler . Key ( ke )
if repaint {
C . gtk_widget_queue_draw ( widget )
}
return handled
2014-03-23 16:12:30 -05:00
}
2014-03-18 11:44:21 -05:00
//export our_area_key_press_event_callback
func our_area_key_press_event_callback ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer ) C . gboolean {
2014-03-23 16:12:30 -05:00
/ *
2014-03-18 11:44:21 -05:00
fmt . Printf ( "PRESS %#v\n" , e )
2014-03-21 21:33:07 -05:00
fmt . Printf ( "this (%d/GDK_KEY_%s):\n" , e . keyval ,
C . GoString ( ( * C . char ) ( unsafe . Pointer (
C . gdk_keyval_name ( e . keyval ) ) ) ) )
2014-03-21 16:05:53 -05:00
pk ( e . keyval , e . window )
2014-03-21 21:33:07 -05:00
fmt . Printf ( "%d/GDK_KEY_A:\n" , C . GDK_KEY_A )
pk ( C . GDK_KEY_A , e . window )
fmt . Printf ( "%d/GDK_KEY_a:\n" , C . GDK_KEY_a )
pk ( C . GDK_KEY_a , e . window )
2014-03-23 16:12:30 -05:00
* /
2014-03-27 19:31:23 -05:00
ret := doKeyEvent ( widget , event , data , false )
2014-03-23 16:12:30 -05:00
_ = ret
return C . FALSE // TODO really false? should probably return !ret (since true indicates stop processing)
2014-03-18 11:44:21 -05:00
}
var area_key_press_event_callback = C . GCallback ( C . our_area_key_press_event_callback )
//export our_area_key_release_event_callback
func our_area_key_release_event_callback ( widget * C . GtkWidget , event * C . GdkEvent , data C . gpointer ) C . gboolean {
2014-03-27 19:31:23 -05:00
ret := doKeyEvent ( widget , event , data , true )
2014-03-23 16:12:30 -05:00
_ = ret
return C . FALSE // TODO really false? should probably return !ret (since true indicates stop processing)
2014-03-18 11:44:21 -05:00
}
var area_key_release_event_callback = C . GCallback ( C . our_area_key_release_event_callback )
2014-03-21 16:05:53 -05:00
2014-03-23 16:12:30 -05:00
var extkeys = map [ C . guint ] ExtKey {
C . GDK_KEY_Escape : Escape ,
C . GDK_KEY_Insert : Insert ,
C . GDK_KEY_Delete : Delete ,
C . GDK_KEY_Home : Home ,
C . GDK_KEY_End : End ,
C . GDK_KEY_Page_Up : PageUp ,
C . GDK_KEY_Page_Down : PageDown ,
C . GDK_KEY_Up : Up ,
C . GDK_KEY_Down : Down ,
C . GDK_KEY_Left : Left ,
C . GDK_KEY_Right : Right ,
C . GDK_KEY_F1 : F1 ,
C . GDK_KEY_F2 : F2 ,
C . GDK_KEY_F3 : F3 ,
C . GDK_KEY_F4 : F4 ,
C . GDK_KEY_F5 : F5 ,
C . GDK_KEY_F6 : F6 ,
C . GDK_KEY_F7 : F7 ,
C . GDK_KEY_F8 : F8 ,
C . GDK_KEY_F9 : F9 ,
C . GDK_KEY_F10 : F10 ,
C . GDK_KEY_F11 : F11 ,
C . GDK_KEY_F12 : F12 ,
2014-03-29 12:09:27 -05:00
// numpad numeric keys and . are handled in events_notdarwin.go
2014-03-29 12:01:42 -05:00
C . GDK_KEY_KP_Enter : NEnter ,
2014-03-29 12:09:27 -05:00
C . GDK_KEY_KP_Add : NAdd ,
C . GDK_KEY_KP_Subtract : NSubtract ,
C . GDK_KEY_KP_Multiply : NMultiply ,
C . GDK_KEY_KP_Divide : NDivide ,
2014-03-23 16:12:30 -05:00
}
// sanity check
func init ( ) {
included := make ( [ ] bool , _nextkeys )
for _ , v := range extkeys {
included [ v ] = true
}
for i := 1 ; i < int ( _nextkeys ) ; i ++ {
2014-03-29 12:09:27 -05:00
if i >= int ( N0 ) && i <= int ( N9 ) { // skip numpad numbers and .
continue
}
if i == int ( NDot ) {
2014-03-29 12:01:42 -05:00
continue
}
2014-03-23 16:12:30 -05:00
if ! included [ i ] {
panic ( fmt . Errorf ( "error: not all ExtKeys defined on Unix (missing %d)" , i ) )
}
}
}
2014-03-23 16:24:13 -05:00
var modonlykeys = map [ C . guint ] Modifiers {
C . GDK_KEY_Control_L : Ctrl ,
C . GDK_KEY_Control_R : Ctrl ,
2014-03-23 16:26:11 -05:00
C . GDK_KEY_Alt_L : Alt ,
C . GDK_KEY_Alt_R : Alt ,
2014-05-16 17:39:08 -05:00
C . GDK_KEY_Meta_L : Alt ,
C . GDK_KEY_Meta_R : Alt ,
C . GDK_KEY_Shift_L : Shift ,
C . GDK_KEY_Shift_R : Shift ,
C . GDK_KEY_Super_L : Super ,
C . GDK_KEY_Super_R : Super ,
2014-03-23 16:12:30 -05:00
}