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-14 22:06:51 -05:00
"unsafe"
2014-03-14 17:44:59 -05:00
"image"
2014-03-18 11:44:21 -05:00
"fmt"
2014-03-14 17:44:59 -05:00
)
// #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 22:06:51 -05:00
// /* HACK - see https://code.google.com/p/go/issues/detail?id=7548 */
// struct _cairo {};
2014-03-14 17:44:59 -05:00
import "C"
func gtkAreaNew ( ) * gtkWidget {
drawingarea := C . gtk_drawing_area_new ( )
2014-03-14 22:06:51 -05:00
C . gtk_widget_set_size_request ( drawingarea , 320 , 240 )
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-03-14 17:44:59 -05:00
return fromgtkwidget ( scrollarea )
}
2014-03-14 22:15:24 -05:00
func gtkAreaGetControl ( scrollarea * gtkWidget ) * gtkWidget {
viewport := C . gtk_bin_get_child ( ( * C . GtkBin ) ( unsafe . Pointer ( scrollarea ) ) )
control := C . gtk_bin_get_child ( ( * C . GtkBin ) ( unsafe . Pointer ( viewport ) ) )
return fromgtkwidget ( control )
}
//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
s := ( * sysData ) ( unsafe . Pointer ( data ) )
// thanks to desrt in irc.gimp.net/#gtk+
C . cairo_clip_extents ( cr , & x , & y , & w , & h )
cliprect := image . Rect ( int ( x ) , int ( y ) , int ( w ) , int ( h ) )
2014-03-16 20:40:33 -05:00
i := s . handler . Paint ( cliprect )
2014-03-14 17:44:59 -05:00
// pixel order is [R G B A] (see Example 1 on https://developer.gnome.org/gdk-pixbuf/2.26/gdk-pixbuf-The-GdkPixbuf-Structure.html) so we don't have to convert anything
// gdk-pixbuf is not alpha-premultiplied (thanks to desrt in irc.gimp.net/#gtk+)
pixbuf := C . gdk_pixbuf_new_from_data (
( * C . guchar ) ( unsafe . Pointer ( & i . Pix [ 0 ] ) ) ,
C . GDK_COLORSPACE_RGB ,
C . TRUE , // has alpha channel
8 , // bits per sample
C . int ( i . Rect . Dx ( ) ) ,
C . int ( i . Rect . Dy ( ) ) ,
C . int ( i . Stride ) ,
nil , nil ) // do not free data
C . gdk_cairo_set_source_pixbuf ( cr ,
pixbuf ,
C . gdouble ( cliprect . Min . X ) ,
C . gdouble ( cliprect . Min . Y ) )
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-03-14 17:44:59 -05:00
C . g_object_unref ( ( C . gpointer ) ( unsafe . Pointer ( pixbuf ) ) ) // free pixbuf
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
}
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
}
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
func finishMouseEvent ( data C . gpointer , me MouseEvent , mb uint , x C . gdouble , y C . gdouble , state C . guint , gdkwindow * C . GdkWindow ) {
s := ( * sysData ) ( unsafe . Pointer ( data ) )
state = translateModifiers ( state , gdkwindow )
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 )
}
// 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-03-16 20:40:33 -05:00
s . handler . Mouse ( me )
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 {
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-16 00:39:30 -05:00
finishMouseEvent ( 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-16 00:39:30 -05:00
finishMouseEvent ( 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-16 00:39:30 -05:00
finishMouseEvent ( 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
func doKeyEvent ( event * C . GdkEvent , data C . gpointer , up bool ) bool {
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
} else if predef , ok := predefkeys [ keyval ] ; ok {
ke . ASCII = predef
} else if _ , ok := modonlykeys [ keyval ] ; ! ok { // use ok form here to save memory/avoid racy map write
cp := C . gdk_keyval_to_unicode ( keyval )
// GDK keycodes in GDK 3.4 the ASCII plane map to their ASCII values
// (proof: https://git.gnome.org/browse/gtk+/tree/gdk/gdkkeysyms.h?h=gtk-3-4)
// this also handles the numeric keypad keys (proof: https://git.gnome.org/browse/gtk+/tree/gdk/gdkkeyuni.c?h=gtk-3-4#n846)
// the cp < 0x20 will also handle the case where the key is totally unknown to us (gdk_keyval_to_unicode() returns 0) and the space key
if cp < 0x20 || cp >= 0x7F {
// TODO really stop here? or should we handle modifiers?
return false // pretend unhandled
}
ke . ASCII = byte ( cp )
}
state := translateModifiers ( e . state , e . window )
ke . Modifiers = makeModifiers ( state )
ke . Up = up
return s . handler . Key ( ke )
}
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
* /
ret := doKeyEvent ( event , data , false )
_ = 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-23 16:12:30 -05:00
ret := doKeyEvent ( event , data , true )
_ = 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 ,
// numeric keypad equivalents:
C . GDK_KEY_KP_Insert : Insert ,
C . GDK_KEY_KP_Delete : Delete ,
C . GDK_KEY_KP_Home : Home ,
C . GDK_KEY_KP_End : End ,
C . GDK_KEY_KP_Page_Up : PageUp ,
C . GDK_KEY_KP_Page_Down : PageDown ,
C . GDK_KEY_KP_Up : Up ,
C . GDK_KEY_KP_Down : Down ,
C . GDK_KEY_KP_Left : Left ,
C . GDK_KEY_KP_Right : Right ,
}
// sanity check
func init ( ) {
included := make ( [ ] bool , _nextkeys )
for _ , v := range extkeys {
included [ v ] = true
}
for i := 1 ; i < int ( _nextkeys ) ; i ++ {
if ! included [ i ] {
panic ( fmt . Errorf ( "error: not all ExtKeys defined on Unix (missing %d)" , i ) )
}
}
}
var predefkeys = map [ C . guint ] byte {
C . GDK_KEY_Return : '\n' ,
// TODO C.GDK_KEY_Linefeed too? What key is this?
C . GDK_KEY_Tab : '\t' ,
C . GDK_KEY_BackSpace : '\b' ,
// tests indicate that this is sent on Shift+Tab
C . GDK_KEY_ISO_Left_Tab : '\t' ,
// numeric keypad equivalents:
C . GDK_KEY_KP_Enter : '\n' ,
// all other numeric keypad equivalents are handled by gdk_keymap_to_unicode() as mentioned above
// no space; handled by the code above
}
var modonlykeys = map [ C . guint ] bool {
C . GDK_KEY_Shift_L : true ,
C . GDK_KEY_Shift_R : true ,
C . GDK_KEY_Control_L : true ,
C . GDK_KEY_Control_R : true ,
C . GDK_KEY_Meta_L : true ,
C . GDK_KEY_Meta_R : true ,
// TODO GDK_KEY_Alt_L/R too?
C . GDK_KEY_Super_L : true ,
C . GDK_KEY_Super_R : true ,
}