Major overhaul of the way GTK+ events are handled. Closures that perform events are no longer generated; instead all events are given to fixed, compile-time, cgo-exported callback functions that take the sysData as user data. This saves memory (we no longer need to generate closures and we no longer have to save them elsewhere to keep them from being garbage collected) and makes the top of sysdata_unix.go slightly cleaner, but it moves the code for handling UI events into callbacks_unix.go. This is needed for Area, in which we need to feed the sysData to an event connected to a subwidget instead of the main widget returned.

This commit is contained in:
Pietro Gagliardi 2014-03-14 20:03:02 -04:00
parent af7a218b47
commit a7f7ea1b8d
4 changed files with 72 additions and 79 deletions

View File

@ -16,43 +16,81 @@ while we're at it the callback for our idle function will be handled here too
// #cgo pkg-config: gtk+-3.0
// #include <gtk/gtk.h>
// extern gboolean our_callback(gpointer);
// extern gboolean our_window_callback(GtkWidget *, GdkEvent *, gpointer);
// extern void our_clicked_callback(GtkButton *, gpointer);
// #include <stdlib.h>
// extern gboolean our_window_delete_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_window_configure_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern void our_button_clicked_callback(GtkButton *, gpointer);
// extern gboolean our_idle_callback(gpointer);
// /* because cgo is flaky with macros; static inline because we have //exports */
// static inline void gSignalConnect(GtkWidget *widget, char *signal, GCallback callback, void *data) { g_signal_connect(widget, signal, callback, data); }
import "C"
//export our_callback
func our_callback(what C.gpointer) C.gboolean {
f := *(*func() bool)(unsafe.Pointer(what))
return togbool(f())
//export our_window_delete_event_callback
func our_window_delete_event_callback(widget *C.GtkWidget, event *C.GdkEvent, what C.gpointer) C.gboolean {
// called when the user tries to close the window
s := (*sysData)(unsafe.Pointer(what))
s.signal()
return C.TRUE // do not close the window
}
//export our_window_callback
func our_window_callback(widget *C.GtkWidget, event *C.GdkEvent, what C.gpointer) C.gboolean {
return our_callback(what)
var window_delete_event_callback = C.GCallback(C.our_window_delete_event_callback)
//export our_window_configure_event_callback
func our_window_configure_event_callback(widget *C.GtkWidget, event *C.GdkEvent, what C.gpointer) C.gboolean {
// called when the window is resized
s := (*sysData)(unsafe.Pointer(what))
if s.container != nil && s.resize != nil { // wait for init
width, height := gtk_window_get_size(s.widget)
// top-left is (0,0) so no need for winheight
err := s.resize(0, 0, width, height, 0)
if err != nil {
panic("child resize failed: " + err.Error())
}
}
// returning false indicates that we continue processing events related to configure-event; if we choose not to, then after some controls have been added, the layout fails completely and everything stays in the starting position/size
// TODO make sure this is the case
return C.FALSE
}
//export our_clicked_callback
func our_clicked_callback(button *C.GtkButton, what C.gpointer) {
our_callback(what)
var window_configure_event_callback = C.GCallback(C.our_window_configure_event_callback)
//export our_button_clicked_callback
func our_button_clicked_callback(button *C.GtkButton, what C.gpointer) {
// called when the user clicks a button
s := (*sysData)(unsafe.Pointer(what))
s.signal()
}
var button_clicked_callback = C.GCallback(C.our_button_clicked_callback)
// this is the type of the signals fields in classData; here to avoid needing to import C
type callbackMap map[string]C.GCallback
// this is what actually connects a signal
func g_signal_connect(obj *gtkWidget, sig string, callback C.GCallback, sysData *sysData) {
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
C.gSignalConnect(togtkwidget(obj), csig, callback, unsafe.Pointer(sysData))
}
// there are two issues we solve here:
// 1) we need to make sure the uitask request gets garbage collected when we're done so as to not waste memory, but only when we're done so as to not have craziness happen
// 2) we need to make sure one idle function runs and finishes running before we start the next; otherwise we could wind up with weird things like the ret channel being closed early
// so our_idle_callback() calls the uitask function in what and sends a message back to the dispatcher over done that it finished running; the dispatcher is still holding onto the uitask function so it won't be collected
type gtkIdleOp struct {
what func()
done chan struct{}
}
//export our_idle_callback
func our_idle_callback(what C.gpointer) C.gboolean {
// there are two issues we solve here:
// 1) we need to make sure the uitask request gets garbage collected when we're done so as to not waste memory, but only when we're done so as to not have craziness happen
// 2) we need to make sure one idle function runs and finishes running before we start the next; otherwise we could wind up with weird things like the ret channel being closed early
// so this function calls the uitask function and sends a message back to the dispatcher that it finished running; the dispatcher is still holding onto the uitask function so it won't be collected
idleop := (*gtkIdleOp)(unsafe.Pointer(what))
idleop.what()
idleop.done <- struct{}{}
return C.FALSE // remove this idle function; we're finished
}
var callbacks = map[string]C.GCallback{
"idle": C.GCallback(C.our_idle_callback),
"delete-event": C.GCallback(C.our_window_callback),
"configure-event": C.GCallback(C.our_window_callback),
"clicked": C.GCallback(C.our_clicked_callback),
func gdk_threads_add_idle(idleop *gtkIdleOp) {
C.gdk_threads_add_idle(C.GCallback(C.our_idle_callback),
C.gpointer(unsafe.Pointer(idleop)))
}

View File

@ -12,8 +12,6 @@ import (
// #cgo pkg-config: gtk+-3.0
// #include <stdlib.h>
// #include <gtk/gtk.h>
// /* because cgo is flaky with macros */
// void gSignalConnect(GtkWidget *widget, char *signal, GCallback callback, void *data) { g_signal_connect(widget, signal, callback, data); }
import "C"
type (
@ -25,16 +23,6 @@ func gtk_init() bool {
return fromgbool(C.gtk_init_check((*C.int)(nil), (***C.char)(nil)))
}
// see our_idle_callback in callbacks_unix.go for details
type gtkIdleOp struct {
what func()
done chan struct{}
}
func gdk_threads_add_idle(idleop *gtkIdleOp) {
C.gdk_threads_add_idle(callbacks["idle"], C.gpointer(unsafe.Pointer(idleop)))
}
func gtk_main() {
C.gtk_main()
}
@ -48,17 +36,6 @@ func gtk_window_new() *gtkWidget {
return fromgtkwidget(C.gtk_window_new(0))
}
// the garbage collector has been found to eat my callback functions; this will stop it
var callbackstore = make([]*func() bool, 0, 50)
func g_signal_connect(obj *gtkWidget, sig string, callback func() bool) {
callbackstore = append(callbackstore, &callback)
ccallback := callbacks[sig]
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
C.gSignalConnect(togtkwidget(obj), csig, ccallback, unsafe.Pointer(&callback))
}
// TODO ensure this works if called on an individual control
func gtk_widget_show(widget *gtkWidget) {
C.gtk_widget_show_all(togtkwidget(widget))

View File

@ -49,6 +49,8 @@ As the GTK+ main loop system does not quite run in a sane way (it allows recursi
GTK+ layout managers are not used since the UI library's layout managers are coded in a portable way. (`GtkFixed` is used instead.) This isn't ideal, but it works for now.
All event handlers take the `sysData` as their user data parameter; this means all the event-handling code is stored in static functions in callbacks_unix.go. (Early versions of the package generated signal handlers for each control on the fly, but this needed to be changed to accommodoate Area, which not only needs the `sysData` but also needs to connect to a subwidget of a subwidget (specifically the subwidget of the `GtkViewport` of a `GtkScrolledWindow`); the current setup also avoids creating closures for each and every Window and Button created, and also means we can stop having to shove those callbacks in an ever-growing slice to prevent them from being garbage collected.)
The only major snag with the GTK+ implementation is the implementation of `Listbox`; see `listbox_unix.go` for details.
## Mac OS X

View File

@ -29,7 +29,7 @@ type classData struct {
delete func(widget *gtkWidget, index int)
len func(widget *gtkWidget) int
// ...
signals map[string]func(*sysData) func() bool
signals callbackMap
}
var classTypes = [nctypes]*classData{
@ -37,41 +37,17 @@ var classTypes = [nctypes]*classData{
make: gtk_window_new,
setText: gtk_window_set_title,
text: gtk_window_get_title,
signals: map[string]func(*sysData) func() bool{
"delete-event": func(s *sysData) func() bool {
return func() bool {
s.signal()
return true // do not close the window
}
},
"configure-event": func(s *sysData) func() bool {
return func() bool {
if s.container != nil && s.resize != nil { // wait for init
width, height := gtk_window_get_size(s.widget)
// top-left is (0,0) so no need for winheight
err := s.resize(0, 0, width, height, 0)
if err != nil {
panic("child resize failed: " + err.Error())
}
}
// returning false indicates that we continue processing events related to configure-event; if we choose not to, then after some controls have been added, the layout fails completely and everything stays in the starting position/size
// TODO make sure this is the case
return false
}
},
signals: callbackMap{
"delete-event": window_delete_event_callback,
"configure-event": window_configure_event_callback,
},
},
c_button: &classData{
make: gtk_button_new,
setText: gtk_button_set_label,
text: gtk_button_get_label,
signals: map[string]func(*sysData) func() bool{
"clicked": func(s *sysData) func() bool {
return func() bool {
s.signal()
return true // do not close the window
}
},
signals: callbackMap{
"clicked": button_clicked_callback,
},
},
c_checkbox: &classData{
@ -135,8 +111,8 @@ func (s *sysData) make(initText string, window *sysData) error {
fixed := gtk_fixed_new()
gtk_container_add(s.widget, fixed)
// TODO return the container before assigning the signals?
for signal, generator := range ct.signals {
g_signal_connect(s.widget, signal, generator(s))
for signame, sigfunc := range ct.signals {
g_signal_connect(s.widget, signame, sigfunc, s)
}
ret <- fixed
}
@ -145,8 +121,8 @@ func (s *sysData) make(initText string, window *sysData) error {
s.container = window.container
uitask <- func() {
gtk_container_add(s.container, s.widget)
for signal, generator := range ct.signals {
g_signal_connect(s.widget, signal, generator(s))
for signame, sigfunc := range ct.signals {
g_signal_connect(s.widget, signame, sigfunc, s)
}
ret <- nil
}