diff --git a/common/GNUmakeinc.mk b/common/GNUmakeinc.mk index e7fb9522..aea8e601 100644 --- a/common/GNUmakeinc.mk +++ b/common/GNUmakeinc.mk @@ -7,6 +7,7 @@ CFILES += \ common/menu.c \ common/ptrarray.c \ common/shouldquit.c \ + common/tablesubscriptions.c \ common/types.c HFILES += \ diff --git a/common/tablesubscriptions.c b/common/tablesubscriptions.c new file mode 100644 index 00000000..1089890a --- /dev/null +++ b/common/tablesubscriptions.c @@ -0,0 +1,54 @@ +// 18 october 2015 +#include "../ui.h" +#include "uipriv.h" + +struct uiTableSubscriptions { + struct ptrArray *tables; +}; + +uiTableSubscriptions *uiTableNewSubscriptions(void) +{ + uiTableSubscriptions *s; + + s = uiNew(uiTableSubscriptions); + s->tables = newPtrArray(); + return s; +} + +void uiTableFreeSubscriptions(uiTableSubscriptions *s) +{ + ptrArrayDestroy(s->tables); + uiFree(s); +} + +// TODO make more robust: complain if table already subscribed +void uiTableSubscriptionsSubscribe(uiTableSubscriptions *s, uiTable *t) +{ + ptrArrayAppend(s->tables, t); +} + +void uiTableSubscriptionsUnsubscribe(uiTableSubscriptions *s, uiTable *t) +{ + uintmax_t i; + uiTable *table; + + for (i = 0; i < s->tables->len; i++) { + table = ptrArrayIndex(s->tables, uiTable *, i); + if (table == t) { + ptrArrayDelete(s->tables, i); + return; + } + } + complain("attempt to unsubscribe table that isn't subscribed in uiTableSubscriptionsUnsubscribe()"); +} + +void uiTableSubscriptionsNotify(uiTableSubscriptions *s, uiTableNotification n, intmax_t row, intmax_t column) +{ + uintmax_t i; + uiTable *table; + + for (i = 0; i < s->tables->len; i++) { + table = ptrArrayIndex(s->tables, uiTable *, i); + tableNotify(table, n, row, column); + } +} diff --git a/common/uipriv.h b/common/uipriv.h index a8cf9273..bb7f7baa 100644 --- a/common/uipriv.h +++ b/common/uipriv.h @@ -70,3 +70,6 @@ extern void fallbackScale(uiDrawMatrix *, double, double, double, double); extern void fallbackMultiply(uiDrawMatrix *, uiDrawMatrix *); extern void fallbackTransformPoint(uiDrawMatrix *, double *, double *); extern void fallbackTransformSize(uiDrawMatrix *, double *, double *); + +// each table implementation provides this +extern void tableNotify(uiTable *, uiTableNotification, intmax_t, intmax_t); diff --git a/ui.h b/ui.h index d95ef434..0fa85840 100644 --- a/ui.h +++ b/ui.h @@ -559,9 +559,7 @@ _UI_EXTERN void uiTableSubscriptionsUnsubscribe(uiTableSubscriptions *s, uiTable _UI_EXTERN void uiTableSubscriptionsNotify(uiTableSubscriptions *s, uiTableNotification n, intmax_t row, intmax_t column); #define uiTableModelFromBool(b) ((void *) (b)) -#define uiTableModelToBool(v) ((int) (v)) _UI_EXTERN void *uiTableModelFromString(const char *str); -#define uiTableModelToString(v) ((const char *) (v)) struct uiTableColumnParams { const char *Name; diff --git a/unix/GNUmakeinc.mk b/unix/GNUmakeinc.mk index 972c471e..7149970b 100644 --- a/unix/GNUmakeinc.mk +++ b/unix/GNUmakeinc.mk @@ -23,6 +23,7 @@ CFILES += \ unix/spinbox.c \ unix/stddialogs.c \ unix/tab.c \ + unix/table.c \ unix/text.c \ unix/util.c \ unix/window.c diff --git a/unix/table.c b/unix/table.c new file mode 100644 index 00000000..2a1f69a1 --- /dev/null +++ b/unix/table.c @@ -0,0 +1,294 @@ +// 18 october 2015 +#include "uipriv_unix.h" + +#define tableGTKModelType (tableGTKModel_get_type()) +#define tableGTKModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), tableGTKModelType, tableGTKModel)) +#define isAreaWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), tableGTKModelType)) +#define tableGTKModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), tableGTKModelType, tableGTKModelClass)) +#define isAreaWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), tableGTKModel)) +#define getAreaWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), tableGTKModelType, tableGTKModelClass)) + +typedef struct tableGTKModel tableGTKModel; +typedef struct tableGTKModelClass tableGTKModelClass; + +struct uiTable { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *container; + GtkScrolledWindow *sw; + GtkWidget *treeWidget; + GtkTreeView *treeview; + GtkTreeSelection *selection; + tableGTKModel *model; +}; + +struct tableGTKModel { + GObject parent_instance; + uiTableModel *model; +}; + +struct tableGTKModelClass { + GObjectClass parent_class; +}; + +static void tableGTKModel_treeModel_init(GtkTreeModel *); + +G_DEFINE_TYPE_WITH_CODE(tableGTKModel, tableGTKModel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, tableGTKModel_treeModel_init)) + +static void tableGTKModel_init(tableGTKModel *m) +{ + // do nothing +} + +static void tableGTKModel_dispose(GObject *obj) +{ + G_OBJECT_CLASS(tableGTKModel_parent_class)->dispose(obj); +} + +static void tableGTKModel_finalize(GObject *obj) +{ + G_OBJECT_CLASS(tableGTKModel_parent_class)->finalize(obj); +} + +static GtkTreeModelFlags tableGTKModel_get_flags(GtkTreeModel *mb) +{ + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint tableGTKModel_get_n_columns(GtkTreeModel *mb) +{ + tableGTKModel *m = tableGTKModel(mb); + + return (*(m->model->NumColumns))(m->model); +} + +static GType tableGTKModel_get_column_type(GtkTreeModel *mb, gint index) +{ + tableGTKModel *m = tableGTKModel(mb); + uiTableColumnType type; + + type = (*(m->model->ColumnType))(m->model, index); + switch (type) { + case uiTableColumnText: + return G_TYPE_STRING; +//TODO case uiTableColumnImage: +//TODO return GDK_TYPE_PIXBUF; + case uiTableColumnCheckbox: + return G_TYPE_BOOLEAN; + } + complain("unknown column type %d in tableGTKModel_get_column_type()", type); + return G_TYPE_INVALID; // make compiler happy +} + +/* +how our GtkTreeIters are stored: + stamp: either GOOD_STAMP or BAD_STAMP + user_data: row index +Thanks to Company in irc.gimp.net/#gtk+ for suggesting the GSIZE_TO_POINTER() t +rick. +*/ +#define GOOD_STAMP 0x1234 +#define BAD_STAMP 0x5678 +#define FROM(x) ((gint) GPOINTER_TO_SIZE((x))) +#define TO(x) GSIZE_TO_POINTER((gsize) (x)) + +static gboolean tableGTKModel_get_iter(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreePath *path) +{ + tableGTKModel *m = tableGTKModel(mb); + gint index; + + if (gtk_tree_path_get_depth(path) != 1) + goto bad; + index = gtk_tree_path_get_indices(path)[0]; + if (index < 0) + goto bad; + if (index >= (*(m->model->NumRows))(m->model)) + goto bad; + iter->stamp = GOOD_STAMP; + iter->user_data = TO(index); + return TRUE; +bad: + iter->stamp = BAD_STAMP; + return FALSE; +} + +static GtkTreePath *tableGTKModel_get_path(GtkTreeModel *mb, GtkTreeIter *iter) +{ + // note: from this point forward, the GOOD_STAMP checks ensure that the index stored in iter is nonnegative + if (iter->stamp != GOOD_STAMP) + return NULL; // this is what both GtkListStore and GtkTreeStore do + return gtk_tree_path_new_from_indices(FROM(iter->user_data), -1); +} + +void *uiTableModelFromString(const char *str) +{ + return g_strdup(str); +} + +#define toBool(v) ((int) (v)) +#define toStr(v) ((const char *) (v)) + +static void tableGTKModel_get_value(GtkTreeModel *mb, GtkTreeIter *iter, gint column, GValue *value) +{ + tableGTKModel *m = tableGTKModel(mb); + void *v; + uiTableColumnType type; + + if (iter->stamp != GOOD_STAMP) + return; // this is what both GtkListStore and GtkTreeStore do + v = (*(m->model->CellValue))(m->model, FROM(iter->user_data), column); + type = (*(m->model->ColumnType))(m->model, column); + switch (type) { + case uiTableColumnText: + g_value_init(value, G_TYPE_STRING); + g_value_take_string(value, toStr(v)); + // the GValue now manages the memory of the string that was g_strdup()'d before + return; +//TODO case uiTableColumnImage: + // TODO + case uiTableColumnCheckbox: + g_value_init(value, G_TYPE_BOOLEAN); + g_value_set_boolean(value, toBool(v)); + return; + } + complain("unknown column type %d in tableGTKModel_get_value()", type); +} + +static gboolean tableGTKModel_iter_next(GtkTreeModel *mb, GtkTreeIter *iter) +{ + tableGTKModel *m = tableGTKModel(mb); + gint index; + + if (iter->stamp != GOOD_STAMP) + return FALSE; // this is what both GtkListStore and GtkTreeStore do + index = FROM(iter->user_data); + index++; + if (index >= (*(m->model->NumRows))(m->model)) { + iter->stamp = BAD_STAMP; + return FALSE; + } + iter->user_data = TO(index); + return TRUE; +} + +static gboolean tableGTKModel_iter_previous(GtkTreeModel *mb, GtkTreeIter *iter) +{ + tableGTKModel *m = tableGTKModel(mb); + gint index; + + if (iter->stamp != GOOD_STAMP) + return FALSE; // this is what both GtkListStore and GtkTreeStore do + index = FROM(iter->user_data); + if (index <= 0) { + iter->stamp = BAD_STAMP; + return FALSE; + } + index--; + iter->user_data = TO(index); + return TRUE; +} + +static gboolean tableGTKModel_iter_children(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *parent) +{ + tableGTKModel *m = tableGTKModel(mb); + + if (parent == NULL && (*(m->model->NumRows))(m->model) > 0) { + child->stamp = GOOD_STAMP; + child->user_data = 0; + return TRUE; + } + child->stamp = BAD_STAMP; + return FALSE; +} + +static gboolean tableGTKModel_iter_has_child(GtkTreeModel *mb, GtkTreeIter *iter) +{ + return FALSE; +} + +static gint tableGTKModel_iter_n_children(GtkTreeModel *mb, GtkTreeIter *iter) +{ + tableGTKModel *m = tableGTKModel(mb); + + if (iter == NULL) + return (*(m->model->NumRows))(m->model); + return 0; +} + +static gboolean tableGTKModel_iter_nth_child(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + tableGTKModel *m = tableGTKModel(mb); + + if (parent == NULL && n >= 0 && n < (*(m->model->NumRows))(m->model)) { + child->stamp = GOOD_STAMP; + child->user_data = TO(n); + return TRUE; + } + child->stamp = BAD_STAMP; + return FALSE; +} + +static gboolean tableGTKModel_iter_parent(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *child) +{ + parent->stamp = BAD_STAMP; + return FALSE; +} + +static void tableGTKModel_class_init(tableGTKModelClass *class) +{ + G_OBJECT_CLASS(class)->dispose = tableGTKModel_dispose; + G_OBJECT_CLASS(class)->finalize = tableGTKModel_finalize; +} + +static void tableGTKModel_treeModel_init(GtkTreeModel *iface) +{ + iface->get_flags = tableGTKModel_get_flags; + iface->get_n_columns = tableGTKModel_get_n_columns; + iface->get_column_type = tableGTKModel_get_column_type; + iface->get_iter = tableGTKModel_get_iter; + iface->get_path = tableGTKModel_get_path; + iface->get_value = tableGTKModel_get_value; + iface->iter_next = tableGTKModel_iter_next; + iface->iter_previous = tableGTKModel_iter_previous; + iface->iter_children = tableGTKModel_iter_children; + iface->iter_has_child = tableGTKModel_iter_has_child; + iface->iter_n_children = tableGTKModel_iter_n_children; + iface->iter_nth_child = tableGTKModel_iter_nth_child; + iface->iter_parent = tableGTKModel_iter_parent; + // no need for ref_node or unref_node +} + +static void newGTKModel(uiTableModel *model) +{ + tableGTKModel *m; + + m = tableGTKModel(g_object_new(tableGTKModelType, NULL)); + m->model = model; + return m; +} + +void tableNotify(uiTable *t, uiTableNotification n, intmax_t row, intmax_t column) +{ + // TODO +} + +void uiTableSetModel(uiTable *t, uiTableModel *m) +{ + if (t->model != NULL) + (*(t->model->model->Unsubscribe))(t->model->model, t); + t->model = newGTKModel(m); + (*(t->model->model->Subscribe))(t->model->model, t); + // TODO will this free the old model? + gtk_tree_view_set_model(t->treeview, GTK_TREE_MODEL(t->moel)); +} + +void uiTableAppendColumn(uiTable *t, uiTableColumnParams *p) +{ + // TODO +} + +uiTable *uiNewTable(void) +{ + uiTable *t; +}