summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--newctrl/area_unix.go26
-rw-r--r--newctrl/button_unix.go57
-rw-r--r--newctrl/checkbox_unix.go68
-rw-r--r--newctrl/container_unix.go57
-rw-r--r--newctrl/label_unix.go64
-rw-r--r--newctrl/table_unix.go222
-rw-r--r--newctrl/textfield_unix.go81
7 files changed, 551 insertions, 24 deletions
diff --git a/newctrl/area_unix.go b/newctrl/area_unix.go
index 01e604c..fd17bc0 100644
--- a/newctrl/area_unix.go
+++ b/newctrl/area_unix.go
@@ -34,9 +34,8 @@ import "C"
type area struct {
*areabase
- _widget *C.GtkWidget
+ *scroller
drawingarea *C.GtkDrawingArea
- scroller *scroller
clickCounter *clickCounter
@@ -59,7 +58,6 @@ func newArea(ab *areabase) Area {
textfieldw := C.gtk_entry_new()
a := &area{
areabase: ab,
- _widget: widget,
drawingarea: (*C.GtkDrawingArea)(unsafe.Pointer(widget)),
scroller: newScroller(widget, false, false, true), // not natively scrollable; no border; have an overlay for OpenTextFieldAt()
clickCounter: new(clickCounter),
@@ -67,6 +65,7 @@ func newArea(ab *areabase) Area {
textfield: (*C.GtkEntry)(unsafe.Pointer(textfieldw)),
textfielddone: newEvent(),
}
+ a.fpreferredSize = a.preferredSize
for _, c := range areaCallbacks {
g_signal_connect(
C.gpointer(unsafe.Pointer(a.drawingarea)),
@@ -492,28 +491,7 @@ var modonlykeys = map[C.guint]Modifiers{
C.GDK_KEY_Super_R: Super,
}
-func (a *area) widget() *C.GtkWidget {
- return a._widget
-}
-
-func (a *area) setParent(p *controlParent) {
- a.scroller.setParent(p)
-}
-
-func (a *area) allocate(x int, y int, width int, height int, d *sizing) []*allocation {
- return baseallocate(a, x, y, width, height, d)
-}
-
func (a *area) preferredSize(d *sizing) (width, height int) {
// the preferred size of an Area is its size
return a.width, a.height
}
-
-func (a *area) commitResize(c *allocation, d *sizing) {
- a.scroller.commitResize(c, d)
-}
-
-func (a *area) getAuxResizeInfo(d *sizing) {
- // a Label to the left of an Area should be vertically aligned to the top
- d.shouldVAlignTop = true
-}
diff --git a/newctrl/button_unix.go b/newctrl/button_unix.go
new file mode 100644
index 0000000..a1da527
--- /dev/null
+++ b/newctrl/button_unix.go
@@ -0,0 +1,57 @@
+// +build !windows,!darwin
+
+// 7 july 2014
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "gtk_unix.h"
+// extern void buttonClicked(GtkButton *, gpointer);
+import "C"
+
+type button struct {
+ *controlSingleWidget
+ button *C.GtkButton
+ clicked *event
+}
+
+// shared code for setting up buttons, check boxes, etc.
+func newButton(text string) *button {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ widget := C.gtk_button_new_with_label(ctext)
+ b := &button{
+ controlSingleWidget: newControlSingleWidget(widget),
+ button: (*C.GtkButton)(unsafe.Pointer(widget)),
+ clicked: newEvent(),
+ }
+ g_signal_connect(
+ C.gpointer(unsafe.Pointer(b.button)),
+ "clicked",
+ C.GCallback(C.buttonClicked),
+ C.gpointer(unsafe.Pointer(b)))
+ return b
+}
+
+func (b *button) OnClicked(e func()) {
+ b.clicked.set(e)
+}
+
+func (b *button) Text() string {
+ return fromgstr(C.gtk_button_get_label(b.button))
+}
+
+func (b *button) SetText(text string) {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ C.gtk_button_set_label(b.button, ctext)
+}
+
+//export buttonClicked
+func buttonClicked(bwid *C.GtkButton, data C.gpointer) {
+ b := (*button)(unsafe.Pointer(data))
+ b.clicked.fire()
+}
diff --git a/newctrl/checkbox_unix.go b/newctrl/checkbox_unix.go
new file mode 100644
index 0000000..fc27bee
--- /dev/null
+++ b/newctrl/checkbox_unix.go
@@ -0,0 +1,68 @@
+// +build !windows,!darwin
+
+// 7 july 2014
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "gtk_unix.h"
+// extern void checkboxToggled(GtkToggleButton *, gpointer);
+import "C"
+
+type checkbox struct {
+ *controlSingleWidget
+ button *C.GtkButton
+ toggle *C.GtkToggleButton
+ checkbox *C.GtkCheckButton
+ toggled *event
+}
+
+func newCheckbox(text string) *checkbox {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ widget := C.gtk_check_button_new_with_label(ctext)
+ c := &checkbox{
+ controlSingleWidget: newControlSingleWidget(widget),
+ button: (*C.GtkButton)(unsafe.Pointer(widget)),
+ toggle: (*C.GtkToggleButton)(unsafe.Pointer(widget)),
+ checkbox: (*C.GtkCheckButton)(unsafe.Pointer(widget)),
+ toggled: newEvent(),
+ }
+ g_signal_connect(
+ C.gpointer(unsafe.Pointer(c.checkbox)),
+ "toggled",
+ C.GCallback(C.checkboxToggled),
+ C.gpointer(unsafe.Pointer(c)))
+ return c
+}
+
+func (c *checkbox) OnToggled(e func()) {
+ c.toggled.set(e)
+}
+
+func (c *checkbox) Text() string {
+ return fromgstr(C.gtk_button_get_label(c.button))
+}
+
+func (c *checkbox) SetText(text string) {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ C.gtk_button_set_label(c.button, ctext)
+}
+
+func (c *checkbox) Checked() bool {
+ return fromgbool(C.gtk_toggle_button_get_active(c.toggle))
+}
+
+func (c *checkbox) SetChecked(checked bool) {
+ C.gtk_toggle_button_set_active(c.toggle, togbool(checked))
+}
+
+//export checkboxToggled
+func checkboxToggled(bwid *C.GtkToggleButton, data C.gpointer) {
+ c := (*checkbox)(unsafe.Pointer(data))
+ c.toggled.fire()
+}
diff --git a/newctrl/container_unix.go b/newctrl/container_unix.go
new file mode 100644
index 0000000..3f4d949
--- /dev/null
+++ b/newctrl/container_unix.go
@@ -0,0 +1,57 @@
+// +build !windows,!darwin
+
+// 23 february 2014
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "gtk_unix.h"
+import "C"
+
+type container struct {
+ *controlSingleWidget
+ container *C.GtkContainer
+}
+
+type sizing struct {
+ sizingbase
+
+ // for size calculations
+ // gtk+ needs nothing
+
+ // for the actual resizing
+ // gtk+ needs nothing
+}
+
+func newContainer() *container {
+ c := new(container)
+ c.controlSingleWidget = newControlSingleWidget(C.newContainer(unsafe.Pointer(c)))
+ c.container = (*C.GtkContainer)(unsafe.Pointer(c.widget))
+ return c
+}
+
+func (c *container) parent() *controlParent {
+ return &controlParent{c.container}
+}
+
+const (
+ gtkXMargin = 12
+ gtkYMargin = 12
+ gtkXPadding = 12
+ gtkYPadding = 6
+)
+
+func (w *window) beginResize() (d *sizing) {
+ d = new(sizing)
+ if spaced {
+ d.xmargin = gtkXMargin
+ d.ymargintop = gtkYMargin
+ d.ymarginbottom = d.ymargintop
+ d.xpadding = gtkXPadding
+ d.ypadding = gtkYPadding
+ }
+ return d
+}
diff --git a/newctrl/label_unix.go b/newctrl/label_unix.go
new file mode 100644
index 0000000..819c0e4
--- /dev/null
+++ b/newctrl/label_unix.go
@@ -0,0 +1,64 @@
+// +build !windows,!darwin
+
+// 7 july 2014
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "gtk_unix.h"
+import "C"
+
+type label struct {
+ *controlSingleWidget
+ misc *C.GtkMisc
+ label *C.GtkLabel
+}
+
+func newLabel(text string) Label {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ widget := C.gtk_label_new(ctext)
+ l := &label{
+ controlSingleWidget: newControlSingleWidget(widget),
+ misc: (*C.GtkMisc)(unsafe.Pointer(widget)),
+ label: (*C.GtkLabel)(unsafe.Pointer(widget)),
+ }
+ return l
+}
+
+/*TODO
+func newStandaloneLabel(text string) Label {
+ l := finishNewLabel(text, true)
+ // standalone labels are always at the top left
+ C.gtk_misc_set_alignment(l.misc, 0, 0)
+ return l
+}
+*/
+
+func (l *label) Text() string {
+ return fromgstr(C.gtk_label_get_text(l.label))
+}
+
+func (l *label) SetText(text string) {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ C.gtk_label_set_text(l.label, ctext)
+}
+
+/*TODO
+func (l *label) commitResize(c *allocation, d *sizing) {
+ if !l.standalone && c.neighbor != nil {
+ c.neighbor.getAuxResizeInfo(d)
+ if d.shouldVAlignTop {
+ // don't bother aligning it to the first line of text in the control; this is harder than it's worth (thanks gregier in irc.gimp.net/#gtk+)
+ C.gtk_misc_set_alignment(l.misc, 0, 0)
+ } else {
+ C.gtk_misc_set_alignment(l.misc, 0, 0.5)
+ }
+ }
+ basecommitResize(l, c, d)
+}
+*/
diff --git a/newctrl/table_unix.go b/newctrl/table_unix.go
new file mode 100644
index 0000000..931a28e
--- /dev/null
+++ b/newctrl/table_unix.go
@@ -0,0 +1,222 @@
+// +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
+
+ *scroller
+ treeview *C.GtkTreeView
+
+ 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,
+ 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()
+}
diff --git a/newctrl/textfield_unix.go b/newctrl/textfield_unix.go
new file mode 100644
index 0000000..286ad3f
--- /dev/null
+++ b/newctrl/textfield_unix.go
@@ -0,0 +1,81 @@
+// +build !windows,!darwin
+
+// 7 july 2014
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "gtk_unix.h"
+// extern void textfieldChanged(GtkEditable *, gpointer);
+// /* because cgo doesn't like GTK_STOCK_DIALOG_ERROR */
+// static inline void setErrorIcon(GtkEntry *entry)
+// {
+// gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_ERROR);
+// }
+import "C"
+
+type textfield struct {
+ *controlSingleWidget
+ entry *C.GtkEntry
+ changed *event
+}
+
+func startNewTextField() *textfield {
+ widget := C.gtk_entry_new()
+ t := &textfield{
+ controlSingleWidget: newControlSingleWidget(widget),
+ entry: (*C.GtkEntry)(unsafe.Pointer(widget)),
+ changed: newEvent(),
+ }
+ g_signal_connect(
+ C.gpointer(unsafe.Pointer(t._widget)),
+ "changed",
+ C.GCallback(C.textfieldChanged),
+ C.gpointer(unsafe.Pointer(t)))
+ return t
+}
+
+func newTextField() *textfield {
+ return startNewTextField()
+}
+
+func newPasswordField() *textfield {
+ t := startNewTextField()
+ C.gtk_entry_set_visibility(t.entry, C.FALSE)
+ return t
+}
+
+func (t *textfield) Text() string {
+ return fromgstr(C.gtk_entry_get_text(t.entry))
+}
+
+func (t *textfield) SetText(text string) {
+ ctext := togstr(text)
+ defer freegstr(ctext)
+ C.gtk_entry_set_text(t.entry, ctext)
+}
+
+func (t *textfield) OnChanged(f func()) {
+ t.changed.set(f)
+}
+
+func (t *textfield) Invalid(reason string) {
+ if reason == "" {
+ C.gtk_entry_set_icon_from_stock(t.entry, C.GTK_ENTRY_ICON_SECONDARY, nil)
+ return
+ }
+ C.setErrorIcon(t.entry)
+ creason := togstr(reason)
+ defer freegstr(creason)
+ C.gtk_entry_set_icon_tooltip_text(t.entry, C.GTK_ENTRY_ICON_SECONDARY, creason)
+ C.gtk_widget_error_bell(t._widget)
+}
+
+//export textfieldChanged
+func textfieldChanged(editable *C.GtkEditable, data C.gpointer) {
+ t := (*textfield)(unsafe.Pointer(data))
+ t.changed.fire()
+}