From 77bf566ebbcb62acd4d08d905d9542d6ff9b6b80 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 30 Aug 2014 23:02:02 -0400 Subject: ...in with the new. --- table_unix.go | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 table_unix.go (limited to 'table_unix.go') diff --git a/table_unix.go b/table_unix.go new file mode 100644 index 0000000..3566c87 --- /dev/null +++ b/table_unix.go @@ -0,0 +1,249 @@ +// +build !windows,!darwin + +// 29 july 2014 + +package ui + +import ( + "fmt" + "reflect" + "unsafe" +) + +// #include "gtk_unix.h" +// extern void goTableModel_toggled(GtkCellRendererToggle *, gchar *, gpointer); +// extern void tableSelectionChanged(GtkTreeSelection *, gpointer); +import "C" + +type table struct { + *tablebase + + _widget *C.GtkWidget + treeview *C.GtkTreeView + scroller *scroller + + model *C.goTableModel + modelgtk *C.GtkTreeModel + selection *C.GtkTreeSelection + + pixbufs []*C.GdkPixbuf + + selected *event + + // stuff required by GtkTreeModel + nColumns C.gint + old C.gint + types []C.GType + crtocol map[*C.GtkCellRendererToggle]int +} + +var ( + attribText = togstr("text") + attribPixbuf = togstr("pixbuf") + attribActive = togstr("active") +) + +func finishNewTable(b *tablebase, ty reflect.Type) Table { + widget := C.gtk_tree_view_new() + t := &table{ + scroller: newScroller(widget, true, true, false), // natively scrollable; has a border; no overlay + tablebase: b, + _widget: widget, + treeview: (*C.GtkTreeView)(unsafe.Pointer(widget)), + crtocol: make(map[*C.GtkCellRendererToggle]int), + selected: newEvent(), + } + model := C.newTableModel(unsafe.Pointer(t)) + t.model = model + t.modelgtk = (*C.GtkTreeModel)(unsafe.Pointer(model)) + t.selection = C.gtk_tree_view_get_selection(t.treeview) + g_signal_connect( + C.gpointer(unsafe.Pointer(t.selection)), + "changed", + C.GCallback(C.tableSelectionChanged), + C.gpointer(unsafe.Pointer(t))) + C.gtk_tree_view_set_model(t.treeview, t.modelgtk) + for i := 0; i < ty.NumField(); i++ { + cname := togstr(ty.Field(i).Name) + switch { + case ty.Field(i).Type == reflect.TypeOf(ImageIndex(0)): + // can't use GDK_TYPE_PIXBUF here because it's a macro that expands to a function and cgo hates that + t.types = append(t.types, C.gdk_pixbuf_get_type()) + C.tableAppendColumn(t.treeview, C.gint(i), cname, + C.gtk_cell_renderer_pixbuf_new(), attribPixbuf) + case ty.Field(i).Type.Kind() == reflect.Bool: + t.types = append(t.types, C.G_TYPE_BOOLEAN) + cr := C.gtk_cell_renderer_toggle_new() + crt := (*C.GtkCellRendererToggle)(unsafe.Pointer(cr)) + t.crtocol[crt] = i + g_signal_connect(C.gpointer(unsafe.Pointer(cr)), + "toggled", + C.GCallback(C.goTableModel_toggled), + C.gpointer(unsafe.Pointer(t))) + C.tableAppendColumn(t.treeview, C.gint(i), cname, + cr, attribActive) + default: + t.types = append(t.types, C.G_TYPE_STRING) + C.tableAppendColumn(t.treeview, C.gint(i), cname, + C.gtk_cell_renderer_text_new(), attribText) + } + freegstr(cname) // free now (not deferred) to conserve memory + } + // and for some GtkTreeModel boilerplate + t.nColumns = C.gint(ty.NumField()) + return t +} + +func (t *table) Lock() { + t.tablebase.Lock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + t.old = C.gint(d.Len()) +} + +func (t *table) Unlock() { + t.unlock() + // there's a possibility that user actions can happen at this point, before the view is updated + // alas, this is something we have to deal with, because Unlock() can be called from any thread + go func() { + Do(func() { + t.RLock() + defer t.RUnlock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + new := C.gint(d.Len()) + C.tableUpdate(t.model, t.old, new) + }) + }() +} + +func (t *table) LoadImageList(i ImageList) { + i.apply(&t.pixbufs) +} + +func (t *table) Selected() int { + var iter C.GtkTreeIter + + t.RLock() + defer t.RUnlock() + if C.gtk_tree_selection_get_selected(t.selection, nil, &iter) == C.FALSE { + return -1 + } + path := C.gtk_tree_model_get_path(t.modelgtk, &iter) + if path == nil { + panic(fmt.Errorf("invalid iter in Table.Selected()")) + } + defer C.gtk_tree_path_free(path) + return int(*C.gtk_tree_path_get_indices(path)) +} + +func (t *table) Select(index int) { + t.RLock() + defer t.RUnlock() + C.gtk_tree_selection_unselect_all(t.selection) + if index == -1 { + return + } + path := C.gtk_tree_path_new() + defer C.gtk_tree_path_free(path) + C.gtk_tree_path_append_index(path, C.gint(index)) + C.gtk_tree_selection_select_path(t.selection, path) +} + +func (t *table) OnSelected(f func()) { + t.selected.set(f) +} + +//export goTableModel_get_n_columns +func goTableModel_get_n_columns(model *C.GtkTreeModel) C.gint { + tm := (*C.goTableModel)(unsafe.Pointer(model)) + t := (*table)(tm.gotable) + return t.nColumns +} + +//export goTableModel_get_column_type +func goTableModel_get_column_type(model *C.GtkTreeModel, column C.gint) C.GType { + tm := (*C.goTableModel)(unsafe.Pointer(model)) + t := (*table)(tm.gotable) + return t.types[column] +} + +//export goTableModel_do_get_value +func goTableModel_do_get_value(data unsafe.Pointer, row C.gint, col C.gint, value *C.GValue) { + t := (*table)(data) + t.RLock() + defer t.RUnlock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + datum := d.Index(int(row)).Field(int(col)) + switch { + case datum.Type() == reflect.TypeOf(ImageIndex(0)): + d := datum.Interface().(ImageIndex) + C.g_value_init(value, C.gdk_pixbuf_get_type()) + C.g_value_set_object(value, C.gpointer(unsafe.Pointer(t.pixbufs[d]))) + case datum.Kind() == reflect.Bool: + d := datum.Interface().(bool) + C.g_value_init(value, C.G_TYPE_BOOLEAN) + C.g_value_set_boolean(value, togbool(d)) + default: + s := fmt.Sprintf("%v", datum) + str := togstr(s) + defer freegstr(str) + C.g_value_init(value, C.G_TYPE_STRING) + C.g_value_set_string(value, str) + } +} + +//export goTableModel_getRowCount +func goTableModel_getRowCount(data unsafe.Pointer) C.gint { + t := (*table)(data) + t.RLock() + defer t.RUnlock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + return C.gint(d.Len()) +} + +//export goTableModel_toggled +func goTableModel_toggled(cr *C.GtkCellRendererToggle, pathstr *C.gchar, data C.gpointer) { + t := (*table)(unsafe.Pointer(data)) + t.Lock() + defer t.Unlock() + path := C.gtk_tree_path_new_from_string(pathstr) + if len := C.gtk_tree_path_get_depth(path); len != 1 { + panic(fmt.Errorf("invalid path of depth %d given to goTableModel_toggled()", len)) + } + // dereference return value to get our sole member + row := *C.gtk_tree_path_get_indices(path) + col := t.crtocol[cr] + d := reflect.Indirect(reflect.ValueOf(t.data)) + datum := d.Index(int(row)).Field(int(col)) + datum.SetBool(!datum.Bool()) +} + +//export tableSelectionChanged +func tableSelectionChanged(sel *C.GtkTreeSelection, data C.gpointer) { + t := (*table)(unsafe.Pointer(data)) + t.selected.fire() +} + +func (t *table) widget() *C.GtkWidget { + return t._widget +} + +func (t *table) setParent(p *controlParent) { + t.scroller.setParent(p) +} + +func (t *table) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(t, x, y, width, height, d) +} + +func (t *table) preferredSize(d *sizing) (width, height int) { + return basepreferredSize(t, d) +} + +func (t *table) commitResize(c *allocation, d *sizing) { + t.scroller.commitResize(c, d) +} + +func (t *table) getAuxResizeInfo(d *sizing) { + // a Label to the left of a Table should be vertically aligned to the top + d.shouldVAlignTop = true +} -- cgit v1.2.3