adds simple drag and drop support (GTK)

Sun, 19 Nov 2017 09:00:16 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 19 Nov 2017 09:00:16 +0100
changeset 147
2e384acc89a6
parent 146
dd0ae1c62a72
child 148
4e0b38bbd6c4

adds simple drag and drop support (GTK)

application/main.c file | annotate | diff | comparison | revisions
ui/gtk/dnd.c file | annotate | diff | comparison | revisions
ui/gtk/dnd.h file | annotate | diff | comparison | revisions
ui/gtk/model.c file | annotate | diff | comparison | revisions
ui/gtk/model.h file | annotate | diff | comparison | revisions
ui/gtk/objs.mk file | annotate | diff | comparison | revisions
ui/gtk/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.h file | annotate | diff | comparison | revisions
ui/gtk/tree.c file | annotate | diff | comparison | revisions
ui/gtk/tree.h file | annotate | diff | comparison | revisions
ui/ui/dnd.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/tree.h file | annotate | diff | comparison | revisions
ui/ui/ui.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Thu Nov 16 12:04:10 2017 +0100
+++ b/application/main.c	Sun Nov 19 09:00:16 2017 +0100
@@ -82,6 +82,16 @@
     fflush(stdout);
 }
 
+void list_data_get(UiEvent *event, UiSelection *selection, UiList *l, int i) {
+    ui_selection_settext(selection, "Hello World!", -1);
+}
+
+void list_drop(UiEvent *event, UiSelection *selection, UiList *l, int i) {
+    char *text = ui_selection_gettext(selection);
+    printf("dnd drop: {%s}\n", text);
+    free(text);
+}
+
 Document* create_doc(char *str) {
     Document *doc = ui_document_new(sizeof(Document));
     UiContext *ctx = ui_document_context(doc);
@@ -163,8 +173,11 @@
     
     UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "name", UI_STRING, "desc", -1);
     model->getvalue = (ui_getvaluefunc)model_get;
+    model->drop = list_drop;
+    model->data_get = list_data_get;
     UiListCallbacks cb = {NULL, NULL, NULL};
-    ui_table_nv(obj, "list", model, cb);
+    UIWIDGET table = ui_table_nv(obj, "list", model, cb);
+    ui_table_dragsource(table, 0, "text/plain", NULL);
     
     //ui_spinner_setrange(ui_spinnerf_nv(obj, 1, 0, "d"), 0, 1000);
     ui_spinnerr_nv(obj, "r");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/dnd.c	Sun Nov 19 09:00:16 2017 +0100
@@ -0,0 +1,77 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dnd.h"
+
+void ui_selection_settext(UiSelection *sel, char *str, int len) {
+    if(!gtk_selection_data_set_text(sel->data, str, len));
+}
+
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
+    char **uriarray = calloc(nelm+1, sizeof(char*));
+    for(int i=0;i<nelm;i++) {
+        uriarray[i] = uris[i];
+    }
+    uriarray[nelm] = NULL;
+    gtk_selection_data_set_uris(sel->data, uriarray);
+}
+
+char* ui_selection_gettext(UiSelection *sel) {
+    guchar *text = gtk_selection_data_get_text(sel->data);
+    if(text) {
+        char *textcp = strdup((char*)text);
+        g_free(text);
+        return textcp;
+    }
+    return NULL;
+}
+
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
+    gchar **uris = gtk_selection_data_get_uris(sel->data);
+    if(uris) {
+        size_t al = 32;
+        char **array = malloc(al * sizeof(char*));
+        size_t i = 0;
+        while(uris[i] != NULL) {
+            if(i >= al) {
+                al *= 2;
+                array = realloc(array, al * sizeof(char*));
+            }
+            array[i] = strdup((char*)uris[i]);
+            i++;
+        }
+        *nelm = i;
+        g_strfreev(uris);
+        return array;
+    }
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/dnd.h	Sun Nov 19 09:00:16 2017 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- a/ui/gtk/model.c	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/model.c	Sun Nov 19 09:00:16 2017 +0100
@@ -31,6 +31,7 @@
 
 #include "model.h"
 #include "image.h"
+#include "toolkit.h"
 
 #define IS_UI_LIST_MODEL(obj) \
         (G_TYPE_CHECK_INSTANCE_TYPE((obj), list_model_type))
@@ -41,6 +42,9 @@
 static void list_model_interface_init(GtkTreeModelIface *i, gpointer data);
 static void list_model_init(UiListModel *instance, GObjectClass *cl);
 
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data);
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data);
+
 static GObjectClass list_model_class;
 static const GTypeInfo list_model_info = {
     sizeof(GObjectClass),
@@ -60,6 +64,17 @@
 };
 static GType list_model_type;
 
+static const GInterfaceInfo list_model_dnd_dest_interface_info = {
+    (GInterfaceInitFunc)list_model_dnd_dest_interface_init,
+    NULL,
+    NULL
+};
+static const GInterfaceInfo list_model_dnd_src_interface_info = {
+    (GInterfaceInitFunc)list_model_dnd_src_interface_init,
+    NULL,
+    NULL
+};
+
 void ui_list_init() {
     list_model_type = g_type_register_static(
             G_TYPE_OBJECT,
@@ -70,10 +85,19 @@
             list_model_type,
             GTK_TYPE_TREE_MODEL,
             &list_model_interface_info);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_DRAG_DEST,
+            &list_model_dnd_dest_interface_info);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_DRAG_SOURCE,
+            &list_model_dnd_src_interface_info);
 }
 
 static void list_model_class_init(GObjectClass *cl, gpointer data) {
     //cl->finalize = ...; // TODO
+    
 }
 
 static void list_model_interface_init(GtkTreeModelIface *i, gpointer data) {
@@ -91,6 +115,17 @@
     i->iter_parent     = ui_list_model_iter_parent;
 }
 
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data) {
+    i->drag_data_received = ui_list_model_drag_data_received;
+    i->row_drop_possible = ui_list_model_row_drop_possible;
+}
+
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data) {
+    i->drag_data_delete = ui_list_model_drag_data_delete;
+    i->drag_data_get = ui_list_model_drag_data_get;
+    i->row_draggable = ui_list_model_row_draggable;
+}
+
 static void list_model_init(UiListModel *instance, GObjectClass *cl) {
     instance->columntypes = NULL;
     instance->var = NULL;
@@ -128,9 +163,10 @@
     value->g_type = G_TYPE_INVALID; 
 }
 
-UiListModel* ui_list_model_new(UiVar *var, UiModel *info) {
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info) {
     UiListModel *model = g_object_new(list_model_type, NULL);
-    model->info = info;
+    model->obj = obj;
+    model->model = info;
     model->var = var;
     model->columntypes = calloc(sizeof(GType), 2 * info->columns);
     int ncol = 0;
@@ -249,8 +285,8 @@
     list->iter = iter->user_data;
     //list->index = (int)(intptr_t)iter->user_data2;
     //list->current = iter->user_data3;
-    if(model->info->getvalue) {
-        void *data = model->info->getvalue(iter->user_data3, column);
+    if(model->model->getvalue) {
+        void *data = model->model->getvalue(iter->user_data3, column);
         if(model->columntypes[column] == G_TYPE_OBJECT) {
             UiImage *img = data;
             ui_model_set_value(model->columntypes[column], img->pixbuf, value);
@@ -379,3 +415,114 @@
 {
     return FALSE;
 }
+
+// ****** dnd ******
+
+gboolean ui_list_model_drag_data_received(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+        GtkSelectionData  *selection_data)
+{
+    //printf("drag received\n");
+    UiListModel *model = UI_LIST_MODEL(drag_dest);
+    if(model->model->drop) {
+        gint *indices = gtk_tree_path_get_indices(dest_path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        model->model->drop(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_row_drop_possible(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+	GtkSelectionData  *selection_data)
+{
+    //printf("row_drop_possible\n");
+    UiListModel *model = UI_LIST_MODEL(drag_dest);
+    if(model->model->candrop) {
+        gint *indices = gtk_tree_path_get_indices(dest_path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        return model->model->candrop(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_row_draggable(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path)
+{
+    //printf("row_draggable\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->candrag) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        return model->model->candrag(&e, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_drag_data_get(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path,
+        GtkSelectionData    *selection_data)
+{
+    //printf("drag_data_get\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->data_get) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        model->model->data_get(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_drag_data_delete(
+        GtkTreeDragSource *drag_source,
+        GtkTreePath       *path)
+{
+    //printf("drag_data_delete\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->data_get) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        model->model->data_delete(&e, model->var->value, row);
+    }
+    return TRUE;
+}
--- a/ui/gtk/model.h	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/model.h	Sun Nov 19 09:00:16 2017 +0100
@@ -43,12 +43,13 @@
  * UiList to GtkTreeModel wrapper
  */
 struct UiListModel {
-    GObject object;
-    UiModel *info;
-    UiVar   *var;
-    GType   *columntypes;
-    int     numcolumns;
-    int     stamp;
+    GObject  object;
+    UiObject *obj;
+    UiModel  *model;
+    UiVar    *var;
+    GType    *columntypes;
+    int      numcolumns;
+    int      stamp;
 };
 
 /*
@@ -59,7 +60,7 @@
 /*
  * Creates a UiListModel for a given UiList
  */
-UiListModel* ui_list_model_new(UiVar *var, UiModel *info);
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info);
 
 
 // interface functions
@@ -113,6 +114,31 @@
         GtkTreeIter *iter,
         GtkTreeIter *child);
 
+/* dnd */
+
+gboolean ui_list_model_drag_data_received(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest,
+        GtkSelectionData  *selection_data);
+
+gboolean ui_list_model_row_drop_possible(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+	GtkSelectionData  *selection_data);
+
+gboolean ui_list_model_row_draggable(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path);
+
+gboolean ui_list_model_drag_data_get(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path,
+        GtkSelectionData    *selection_data);
+
+gboolean ui_list_model_drag_data_delete(
+        GtkTreeDragSource *drag_source,
+        GtkTreePath       *path);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/gtk/objs.mk	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/objs.mk	Sun Nov 19 09:00:16 2017 +0100
@@ -44,6 +44,7 @@
 GTKOBJ += graphics.o
 GTKOBJ += range.o
 GTKOBJ += entry.o
+GTKOBJ += dnd.o
 
 TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
 TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- a/ui/gtk/toolbar.c	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/toolbar.c	Sun Nov 19 09:00:16 2017 +0100
@@ -360,7 +360,7 @@
 void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) {
     UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
     modelinfo->getvalue = cb->getvalue;
-    UiListModel *model = ui_list_model_new(cb->var, modelinfo);
+    UiListModel *model = ui_list_model_new(obj, cb->var, modelinfo);
     
     GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
     GtkToolItem *item = gtk_tool_item_new();
@@ -373,7 +373,7 @@
     if(var) {
         UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
         modelinfo->getvalue = cb->getvalue;
-        UiListModel *model = ui_list_model_new(var, modelinfo);
+        UiListModel *model = ui_list_model_new(obj, var, modelinfo);
         
         GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
         GtkToolItem *item = gtk_tool_item_new();
--- a/ui/gtk/toolkit.h	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/toolkit.h	Sun Nov 19 09:00:16 2017 +0100
@@ -59,6 +59,10 @@
     void          *finish_data;
 } UiJob;
 
+struct UiSelection {
+    GtkSelectionData *data;
+};
+
 typedef enum UiOrientation UiOrientation;
 enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
 
--- a/ui/gtk/tree.c	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/tree.c	Sun Nov 19 09:00:16 2017 +0100
@@ -29,6 +29,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "../common/context.h"
 #include "../common/object.h"
@@ -67,11 +68,11 @@
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
     model->getvalue = getvalue;
     UiList *list = var->value;
-    UiListModel *listmodel = ui_list_model_new(var, model);
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
     gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
     
     UiListView *listview = malloc(sizeof(UiListView));
-    listview->ctx = obj->ctx;
+    listview->obj = obj;
     listview->widget = view;
     listview->var = var;
     listview->model = model;
@@ -135,10 +136,42 @@
     return NULL;
 }
 
+static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
+    printf("drag begin\n");
+    
+}
+
+static void drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer udata)
+{
+    printf("drag end\n");
+    
+}
+
+static gboolean drag_failed(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        GtkDragResult result,
+        gpointer udata)
+{
+    printf("drag failed: %d\n", result);
+    
+}
+
+static GtkTargetEntry targetentries[] =
+    {
+      { "STRING",        0, 0 },
+      { "text/plain",    0, 1 },
+      { "text/uri-list", 0, 2 },
+    };
 
 UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
     // create treeview
     GtkWidget *view = gtk_tree_view_new();
+    
     int addi = 0;
     for(int i=0;i<model->columns;i++) {
         GtkTreeViewColumn *column = NULL;
@@ -178,13 +211,17 @@
 #endif
     
     UiList *list = var->value;
-    UiListModel *listmodel = ui_list_model_new(var, model);
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
     gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
     
+    //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
+    //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
+    //g_signal_connect(view, "drag-failed", G_CALLBACK(drag_failed), NULL);
+       
     // add TreeView as observer to the UiList to update the TreeView if the
     // data changes
     UiListView *tableview = malloc(sizeof(UiListView));
-    tableview->ctx = obj->ctx;
+    tableview->obj = obj;
     tableview->widget = view;
     tableview->var = var;
     tableview->model = model;
@@ -260,15 +297,90 @@
     return NULL;
 }
 
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+    GList *c = gtk_container_get_children(GTK_CONTAINER(widget));
+    if(c) {
+        return c->data;
+    }
+    return NULL;
+}
+
+static char** targets2array(char *target0, va_list ap, int *nelm) {
+    int al = 16;
+    char **targets = calloc(16, sizeof(char*));
+    targets[0] = target0;
+    
+    int i = 1;
+    char *target;
+    while((target = va_arg(ap, char*)) != NULL) {
+        if(i >= al) {
+            al *= 2;
+            targets = realloc(targets, al*sizeof(char*));
+        }
+        targets[i] = target;
+        i++;
+    }
+    
+    *nelm = i;
+    return targets;
+}
+
+static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
+    GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
+    for(int i=0;i<nelm;i++) {
+        targets[i].target = str[i];
+    }
+    return targets;
+}
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 
+    va_list ap;
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            GDK_BUTTON1_MASK,
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
+    va_list ap;
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragdest_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
 void ui_listview_update(UiList *list, int i) {
     UiListView *view = list->obj;
-    UiListModel *model = ui_list_model_new(view->var, view->model);
+    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
     gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model));
     // TODO: free old model
 }
 
 void ui_listview_destroy(GtkWidget *w, UiListView *v) {
-    ui_destroy_boundvar(v->ctx, v->var);
+    ui_destroy_boundvar(v->obj->ctx, v->var);
     // TODO: destroy model?
     free(v);
 }
@@ -375,7 +487,7 @@
 UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
     model->getvalue = getvalue;
-    UiListModel *listmodel = ui_list_model_new(var, model);
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
     
     GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata);
     UiContainer *ct = uic_get_current_container(obj);
@@ -386,10 +498,10 @@
     GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
     
     UiListView *uicbox = malloc(sizeof(UiListView));
-    uicbox->ctx = obj->ctx;
+    uicbox->obj = obj;
     uicbox->widget = combobox;
     uicbox->var = model->var;
-    uicbox->model = model->info;
+    uicbox->model = model->model;
     
     g_signal_connect(
                 combobox,
@@ -442,7 +554,7 @@
 
 void ui_combobox_modelupdate(UiList *list, int i) {
     UiListView *view = list->obj;
-    UiListModel *model = ui_list_model_new(view->var, view->model);
+    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
     gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model));
 }
 
--- a/ui/gtk/tree.h	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/gtk/tree.h	Sun Nov 19 09:00:16 2017 +0100
@@ -38,7 +38,8 @@
 #endif
 
 typedef struct UiListView {
-    UiContext   *ctx;
+    UiObject    *obj;
+    //UiContext   *ctx;
     GtkWidget   *widget;
     UiVar       *var;
     UiModel     *model;
@@ -56,6 +57,8 @@
 UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
 UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
 
+GtkWidget* ui_get_tree_widget(UIWIDGET widget);
+
 void ui_listview_update(UiList *list, int i);
 void ui_listview_destroy(GtkWidget *w, UiListView *v);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/dnd.h	Sun Nov 19 09:00:16 2017 +0100
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_DND_H
+#define UI_DND_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#define UI_DND_FILE_TARGET "XdndDirectSave0"
+    
+void ui_selection_settext(UiSelection *sel, char *str, int len);
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm);
+
+char* ui_selection_gettext(UiSelection *sel);
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_DND_H */
+
--- a/ui/ui/toolkit.h	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/ui/toolkit.h	Sun Nov 19 09:00:16 2017 +0100
@@ -108,6 +108,8 @@
 
 typedef struct UiIcon       UiIcon;
 typedef struct UiImage      UiImage;
+
+typedef struct UiSelection  UiSelection;
 /* end opaque types */
 
 typedef struct UiTabbedPane UiTabbedPane;
--- a/ui/ui/tree.h	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/ui/tree.h	Sun Nov 19 09:00:16 2017 +0100
@@ -73,6 +73,12 @@
      * TODO: return
      */
     void*(*getvalue)(void*, int);
+    
+    UiBool(*candrop)(UiEvent*, UiSelection*, UiList*, int);
+    void(*drop)(UiEvent*, UiSelection*, UiList*, int);
+    UiBool(*candrag)(UiEvent*, UiList*, int);
+    void(*data_get)(UiEvent*, UiSelection*, UiList*, int);
+    void(*data_delete)(UiEvent*, UiList*, int);
 };
 
 struct UiListCallbacks {
@@ -87,7 +93,7 @@
     ui_callback selection;
     
     /*
-     * userdata for both callback
+     * userdata for all callbacks
      */
     void *userdata;
 };
@@ -114,6 +120,11 @@
 UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb);
 UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb);
 
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+
 UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
 UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata);
 UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
--- a/ui/ui/ui.h	Thu Nov 16 12:04:10 2017 +0100
+++ b/ui/ui/ui.h	Sun Nov 19 09:00:16 2017 +0100
@@ -43,6 +43,7 @@
 #include "entry.h"
 #include "range.h"
 #include "image.h"
+#include "dnd.h"
 
 #endif	/* UI_H */
 

mercurial