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
// shared code for finishing up and sending a mouse event
2014-03-16 00:39:30 -05:00
func finishMouseEvent ( data C . gpointer , me MouseEvent , mb uint , x C . gdouble , y C . gdouble , state C . guint , gdkwindow * C . GdkWindow ) {
2014-03-15 21:29:47 -05:00
s := ( * sysData ) ( unsafe . Pointer ( data ) )
// 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 (
C . gdk_keymap_get_for_display ( C . gdk_window_get_display ( gdkwindow ) ) ,
2014-03-15 21:29:47 -05:00
( * C . GdkModifierType ) ( unsafe . Pointer ( & state ) ) )
if ( state & C . GDK_CONTROL_MASK ) != 0 {
me . Modifiers |= Ctrl
}
if ( state & C . GDK_META_MASK ) != 0 {
me . Modifiers |= Alt
}
if ( state & C . GDK_SHIFT_MASK ) != 0 {
me . Modifiers |= Shift
}
// 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
//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 {
e := ( * C . GdkEventKey ) ( unsafe . Pointer ( event ) )
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-18 11:44:21 -05:00
return C . FALSE // TODO really false?
}
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 {
e := ( * C . GdkEventKey ) ( unsafe . Pointer ( event ) )
fmt . Printf ( "RELEASE %#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-18 11:44:21 -05:00
return C . FALSE // TODO really false?
}
var area_key_release_event_callback = C . GCallback ( C . our_area_key_release_event_callback )
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 ) ) )
}