diff --git a/gtkcalls_unix.go b/gtkcalls_unix.go new file mode 100644 index 0000000..d09fd61 --- /dev/null +++ b/gtkcalls_unix.go @@ -0,0 +1,116 @@ +// +build !windows,!darwin,!plan9 +// TODO is there a way to simplify the above? :/ + +// 16 february 2014 +package main + +import ( + "fmt" + "unsafe" + "reflect" +) + +// #cgo pkg-config: gtk+-3.0 +// #include +// #include +// /* because cgo is flaky with macros */ +// static inline void gSignalConnect(GtkWidget *widget, char *signal, GCallback callback, void *data) { g_signal_connect(widget, signal, callback, data); } +// /* so we can call uistep */ +// extern gboolean our_thread_callback(gpointer); +import "C" + +type ( + gtkWidget C.GtkWidget +) + +func fromgbool(b C.gboolean) bool { + return b != C.FALSE +} + +func togbool(b bool) C.gboolean { + if b { + return C.TRUE + } + return C.FALSE +} + +//export our_thread_callback +func our_thread_callback(C.gpointer) C.gboolean { + uistep() + return C.TRUE +} + +func gtk_init() bool { + // TODO allow GTK+ standard command-line argument processing + b := fromgbool(C.gtk_init_check((*C.int)(nil), (***C.char)(nil))) + if !b { + return false + } + // thanks to tristan in irc.gimp.net/#gtk + C.gdk_threads_add_idle(C.GSourceFunc(C.our_thread_callback), C.gpointer(unsafe.Pointer(nil))) + return true +} + +func gtk_main() { + C.gtk_main() +} + +func gtk_window_new() *gtkWidget { + // 0 == GTK_WINDOW_TOPLEVEL (the only other type, _POPUP, should not be used) + return (*gtkWidget)(unsafe.Pointer(C.gtk_window_new(0))) +} + +// because *gtkWidget and *C.GtkWidget are not compatible +func gtkwidget(g *gtkWidget) (*C.GtkWidget) { + return (*C.GtkWidget)(unsafe.Pointer(g)) +} + +// TODO do we need the argument? +// TODO fine-tune the function type +func g_signal_connect(obj *gtkWidget, sig string, callback interface{}) { + v := reflect.ValueOf(callback) + if v.Kind() != reflect.Func { + panic(fmt.Sprintf("UI library internal error: callback %v given to g_signal_connect not a function", v)) + } + ccallback := C.GCallback(unsafe.Pointer(v.Pointer())) + csig := C.CString(sig) + defer C.free(unsafe.Pointer(csig)) + C.gSignalConnect(gtkwidget(obj), csig, ccallback, unsafe.Pointer(nil)) +} + +// TODO ensure this works if called on an individual control +func gtk_widget_show(widget *gtkWidget) { + C.gtk_widget_show_all(gtkwidget(widget)) +} + +func gtk_widget_hide(widget *gtkWidget) { + C.gtk_widget_hide(gtkwidget(widget)) +} + +func gtk_window_set_title(window *gtkWidget, title string) { + ctitle := C.CString(title) + defer C.free(unsafe.Pointer(ctitle)) + C.gtk_window_set_title((*C.GtkWindow)(unsafe.Pointer(window)), + (*C.gchar)(unsafe.Pointer(ctitle))) +} + +func gtk_window_resize(window *gtkWidget, width int, height int) { + C.gtk_window_resize((*C.GtkWindow)(unsafe.Pointer(window)), C.gint(width), C.gint(height)) +} + +func gtk_fixed_new() *gtkWidget { + return (*gtkWidget)(unsafe.Pointer(C.gtk_fixed_new())) +} + +func gtk_container_add(container *gtkWidget, widget *gtkWidget) { + C.gtk_container_add((*C.GtkContainer)(unsafe.Pointer(container)), gtkwidget(widget)) +} + +func gtk_fixed_put(container *gtkWidget, widget *gtkWidget, x int, y int) { + C.gtk_fixed_put((*C.GtkFixed)(unsafe.Pointer(container)), gtkwidget(widget), + C.gint(x), C.gint(y)) +} + +func gtk_widget_set_size_request(widget *gtkWidget, width int, height int) { + C.gtk_widget_set_size_request(gtkwidget(widget), C.gint(width), C.gint(height)) +} diff --git a/sysdata_unix.go b/sysdata_unix.go new file mode 100644 index 0000000..363e3c8 --- /dev/null +++ b/sysdata_unix.go @@ -0,0 +1,168 @@ +// +build !windows,!darwin,!plan9 + +// 16 february 2014 +//package ui +package main + +import ( + "fmt" +) + +type sysData struct { + cSysData + + widget *gtkWidget + container *gtkWidget // for moving +} + +type classData struct { + make func() *gtkWidget + setText func(widget *gtkWidget, text string) +} + +var classTypes = [nctypes]*classData{ + c_window: &classData{ + make: gtk_window_new, + setText: gtk_window_set_title, + }, + c_button: &classData{ +// make: gtk_button_new, + }, + c_checkbox: &classData{ + }, + c_combobox: &classData{ + }, + c_lineedit: &classData{ + }, + c_label: &classData{ + }, + c_listbox: &classData{ + }, +} + +func (s *sysData) make(initText string, window *sysData) error { + ct := classTypes[s.ctype] + if ct.make == nil { // not yet implemented + println(s.ctype, "not implemented") + return nil + } + ret := make(chan *gtkWidget) + defer close(ret) + uitask <- func() { + ret <- ct.make() + } + s.widget = <-ret +println(s.widget) + if window == nil { + uitask <- func() { + fixed := gtk_fixed_new() + gtk_container_add(s.widget, fixed) + ret <- fixed + } + s.container = <-ret + } else { + s.container = window.container + } + err := s.setText(initText) + if err != nil { + return fmt.Errorf("error setting initial text of new window/control: %v", err) + } + return nil +} + +func (s *sysData) show() error { + ret := make(chan struct{}) + defer close(ret) + uitask <- func() { + gtk_widget_show(s.widget) + ret <- struct{}{} + } + <-ret + return nil +} + +func (s *sysData) hide() error { + ret := make(chan struct{}) + defer close(ret) + uitask <- func() { + gtk_widget_hide(s.widget) + ret <- struct{}{} + } + <-ret + return nil +} + +func (s *sysData) setText(text string) error { +if classTypes[s.ctype] == nil || classTypes[s.ctype].setText == nil { return nil } + ret := make(chan struct{}) + defer close(ret) + uitask <- func() { + classTypes[s.ctype].setText(s.widget, text) + ret <- struct{}{} + } + <-ret + return nil +} + +func (s *sysData) setRect(x int, y int, width int, height int) error { + ret := make(chan struct{}) + defer close(ret) + uitask <- func() { + gtk_fixed_put(s.container, s.widget, x, y) + gtk_widget_set_size_request(s.widget, width, height) + ret <- struct{}{} + } + <-ret + return nil +} + +func (s *sysData) isChecked() bool { + // TODO + return false +} + +func (s *sysData) text() string { + // TODO + return "" +} + +func (s *sysData) append(what string) error { + // TODO + return nil +} + +func (s *sysData) insertBefore(what string, before int) error { + // TODO + return nil +} + +func (s *sysData) selectedIndex() int { + // TODO + return -1 +} + +func (s *sysData) selectedIndices() []int { + // TODO + return nil +} + +func (s *sysData) selectedTexts() []string { + // TODO + return nil +} + +func (s *sysData) setWindowSize(width int, height int) error { + ret := make(chan struct{}) + defer close(ret) + uitask <- func() { + gtk_window_resize(s.widget, width, height) + ret <- struct{}{} + } + <-ret + return nil +} + +func (s *sysData) delete(index int) error { + // TODO + return nil +} diff --git a/todo.md b/todo.md index ff5aa77..951eafb 100644 --- a/todo.md +++ b/todo.md @@ -19,6 +19,10 @@ so I don't forget: - Listbox.SelectAll - have Combobox.InsertBefore, Listbox.InsertBefore, Combobox.Delete, and Listbox.Delete return an error on invalid index before creation +important things: +- the C.gdk_threads_add_idle logic is messy and will break when I implement actual connections... +- there's no GTK+ error handling whatsoever; we need to figure out how it works + super ultra important things: - the windows build appears to be unstable: - 64-bit doesn't work, period: it crashes in malloc in wine with heap corruption warnings aplenty during DLL loading; in windows 7 CreateWindowExW complains about an unregistered window class, yet the RegisterClassW appears to have succeeded and examining the stack in WinDbg indicates the correct class name is being sent (see below) diff --git a/uitask_unix.go b/uitask_unix.go new file mode 100644 index 0000000..0c0152f --- /dev/null +++ b/uitask_unix.go @@ -0,0 +1,37 @@ +// +build !windows,!darwin,!plan9 + +// 16 february 2014 +//package ui +package main + +import ( + "fmt" + "runtime" +) + +var uitask chan func() + +func ui(initDone chan error) { + runtime.LockOSThread() + + uitask = make(chan func()) + if gtk_init() != true { + initDone <- fmt.Errorf("gtk_init failed (reason unknown; TODO)") + return + } + initDone <- nil + + gtk_main() +} + +func uistep() { + select { + case f := <-uitask: + f() + default: // do not block + } +} + +// temporary +func MsgBox(string, string, ...interface{}) {} +func MsgBoxError(title string, text string, args ...interface{}) {panic(title+"\n"+fmt.Sprintf(text,args...))}