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"
)
// #cgo pkg-config: gtk+-3.0
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-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-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-03-23 19:54:11 -05:00
C . gtk_widget_set_size_request ( drawingarea , 100 , 100 ) // default initial size (TODO do we need it?); use C. to avoid casting drawingarea
2014-03-15 21:29:47 -05:00
// we need to explicitly subscribe to mouse events with GtkDrawingArea
C . gtk_widget_add_events ( drawingarea ,
C . GDK_BUTTON_PRESS_MASK | C . GDK_BUTTON_RELEASE_MASK | C . GDK_POINTER_MOTION_MASK | C . GDK_BUTTON_MOTION_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-03-14 17:44:59 -05:00
var x , y , w , h 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+
C . cairo_clip_extents ( cr , & x , & y , & w , & h )
2014-03-29 18:02:09 -05:00
// we do not need to clear the cliprect; GtkDrawingArea did it for us beforehand
2014-03-14 17:44:59 -05:00
cliprect := image . Rect ( int ( x ) , int ( y ) , int ( w ) , int ( h ) )
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)
C . cairo_rectangle ( cr , x , y , w , h ) // breaking the nrom here since we have the double data already
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-03-23 16:24:13 -05:00
func makeModifiers ( state C . guint , m Modifiers ) 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
}
if ( state & C . GDK_META_MASK ) != 0 {
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-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-03-23 16:12:30 -05:00
s := ( * sysData ) ( unsafe . Pointer ( data ) )
state = translateModifiers ( state , gdkwindow )
2014-03-23 16:24:13 -05:00
me . Modifiers = makeModifiers ( state , 0 )
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 )
}
// TODO keep?
if mb != 4 && ( state & C . GDK_BUTTON4_MASK ) != 0 {
me . Held = append ( me . Held , 4 )
}
if mb != 5 && ( state & C . GDK_BUTTON5_MASK ) != 0 {
me . Held = append ( me . Held , 5 )
}
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-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 {
// GDK button ID == our button ID
Down : uint ( e . button ) ,
}
switch e . _type {
case C . GDK_BUTTON_PRESS :
me . Count = 1
case C . GDK_2BUTTON_PRESS :
me . Count = 2
default : // ignore triple-clicks and beyond; we don't handle those
return C . FALSE // TODO really false?
}
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 {
// GDK button ID == our button ID
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-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
if extkey , ok := extkeys [ keyval ] ; ok {
ke . ExtKey = extkey
2014-03-23 16:24:13 -05:00
} else if mod , ok := modonlykeys [ keyval ] ; ok {
// modifier keys don't seem to be set on their initial keypress; set them here
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
}
state := translateModifiers ( e . state , e . window )
2014-03-23 16:24:13 -05:00
ke . Modifiers = makeModifiers ( state , ke . Modifiers )
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
/ *
2014-03-21 16:05:53 -05:00
func pk ( keyval C . guint , window * C . GdkWindow ) {
var kk * C . GdkKeymapKey
var nk C . gint
km := C . gdk_keymap_get_for_display ( C . gdk_window_get_display ( window ) )
b := C . gdk_keymap_get_entries_for_keyval ( km , keyval , & kk , & nk )
if b == C . FALSE {
fmt . Println ( "(no key equivalent)" )
return
}
ok := kk
for i := C . gint ( 0 ) ; i < nk ; i ++ {
2014-03-21 21:33:07 -05:00
fmt . Printf ( "equiv %d/%d: %#v\n" , i + 1 , nk , kk )
2014-03-21 16:05:53 -05:00
xkk := uintptr ( unsafe . Pointer ( kk ) )
xkk += unsafe . Sizeof ( kk )
kk = ( * C . GdkKeymapKey ) ( unsafe . Pointer ( xkk ) )
}
C . g_free ( C . gpointer ( unsafe . Pointer ( ok ) ) )
}
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_Shift_L : Shift ,
C . GDK_KEY_Shift_R : Shift ,
C . GDK_KEY_Control_L : Ctrl ,
C . GDK_KEY_Control_R : Ctrl ,
C . GDK_KEY_Meta_L : Alt ,
C . GDK_KEY_Meta_R : Alt ,
2014-03-23 16:26:11 -05:00
// my system generats these two for the Alt keys instead of Meta
C . GDK_KEY_Alt_L : Alt ,
C . GDK_KEY_Alt_R : Alt ,
2014-03-23 16:24:13 -05:00
// C.GDK_KEY_Super_L: Super,
// C.GDK_KEY_Super_R: Super,
2014-03-23 16:12:30 -05:00
}