implement table dnd (GTK) newapi

Wed, 27 Nov 2024 13:28:21 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 27 Nov 2024 13:28:21 +0100
branch
newapi
changeset 394
bedd499b640d
parent 393
3099bf907e21
child 395
b8277deb75b8

implement table dnd (GTK)

ui/gtk/dnd.c file | annotate | diff | comparison | revisions
ui/gtk/dnd.h file | annotate | diff | comparison | revisions
ui/gtk/headerbar.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.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/winui/table.cpp file | annotate | diff | comparison | revisions
--- a/ui/gtk/dnd.c	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/gtk/dnd.c	Wed Nov 27 13:28:21 2024 +0100
@@ -32,6 +32,7 @@
 
 #include "dnd.h"
 #include <cx/buffer.h>
+#include <cx/array_list.h>
 
 #ifdef UI_GTK2LEGACY
 static gboolean selection_data_set_uris(GtkSelectionData *selection_data, char **uris) {
@@ -102,18 +103,193 @@
 }
 */
 
+#if GTK_MAJOR_VERSION >= 4
+
 void ui_selection_settext(UiDnD *sel, char *str, int len) {
+    if(!sel->providers) {
+        return;
+    }
     
+    if(len == -1) {
+        len = strlen(str);
+    }
+    GBytes *bytes = g_bytes_new(str, len);
+    GdkContentProvider *provider = gdk_content_provider_new_for_bytes("text/plain;charset=utf-8", bytes);
+    g_bytes_unref(bytes);
+    
+    cxListAdd(sel->providers, &provider);
 }
 
 void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) {
+    if(!sel->providers) {
+        return;
+    }
     
+    GFile **files = calloc(nelm, sizeof(GFile*));
+    for(int i=0;i<nelm;i++) {
+        GFile *file = uris[i][0] == '/' ? g_file_new_for_path(uris[i]) : g_file_new_for_uri(uris[i]);
+        files[i] = file;
+    }
+    GdkFileList *list = gdk_file_list_new_from_array(files, nelm);
+    
+    GdkContentProvider *provider = gdk_content_provider_new_typed(GDK_TYPE_FILE_LIST, list);
+    cxListAdd(sel->providers, &provider);
+    
+    g_slist_free_full ((GSList*)list, g_object_unref);
+    free(files);
 }
 
 char* ui_selection_gettext(UiDnD *sel) {
+    if(!sel->value) {
+        return NULL;
+    }
+    
+    if(G_VALUE_HOLDS(sel->value, G_TYPE_STRING)) {
+        const char *str = g_value_get_string(sel->value);
+        return str ? strdup(str) : NULL;
+    }
+    
     return NULL;
 }
 
 UiFileList ui_selection_geturis(UiDnD *sel) {
+    if(!sel->value) {
+        return (UiFileList){NULL,0};
+    }
+    
+    if(G_VALUE_HOLDS(sel->value, GDK_TYPE_FILE_LIST)) {
+        GSList *list = g_value_get_boxed(sel->value);
+        if(!list) {
+            return (UiFileList){NULL,0};
+        }
+        guint size = g_slist_length(list);
+        
+        UiFileList flist;
+        flist.nfiles = size;
+        flist.files = calloc(size, sizeof(char*));
+        int i=0;
+        while(list) {
+            GFile *file = list->data;
+            char *uri = g_file_get_uri(file);
+            flist.files[i++] = strdup(uri);
+            g_free(uri);
+            list = list->next;
+        }
+        return flist;
+    }
     return (UiFileList){NULL,0};
 }
+
+
+UiDnD* ui_create_dnd(void) {
+    UiDnD *dnd = malloc(sizeof(UiDnD));
+    memset(dnd, 0, sizeof(UiDnD));
+    dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16);
+    dnd->selected_action = 0;
+    dnd->delete = FALSE;
+    return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+    cxListDestroy(dnd->providers);
+    free(dnd);
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+    switch(dnd->selected_action) {
+        case 0: return UI_DND_ACTION_NONE;
+        case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+        case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+        case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+        default: break;
+    }
+    return UI_DND_ACTION_CUSTOM;
+}
+
+#else
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+    gtk_selection_data_set_text(sel->data, str, len);
+}
+
+void ui_selection_seturis(UiDnD *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);
+    free(uriarray);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+    if(!sel->data) {
+        return NULL;
+    }
+    
+    guchar *text = gtk_selection_data_get_text(sel->data);
+    if(text) {
+        char *textcp = strdup((char*)text);
+        g_free(text);
+        return textcp;
+    }
+    return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+    if(!sel->data) {
+        return (UiFileList){NULL,0};
+    }
+    
+    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++;
+        }
+        g_strfreev(uris);
+        return (UiFileList){array,i};
+    }
+    
+    return (UiFileList){NULL,0};
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+    switch(dnd->selected_action) {
+        case 0: return UI_DND_ACTION_NONE;
+        case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+        case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+        case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+        default: break;
+    }
+    return UI_DND_ACTION_CUSTOM;
+}
+
+
+UiDnD* ui_create_dnd(void) {
+    UiDnD *dnd = malloc(sizeof(UiDnD));
+    memset(dnd, 0, sizeof(UiDnD));
+    return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+    free(dnd);
+}
+
+#endif
+
+UiBool ui_dnd_need_delete(UiDnD *dnd) {
+    return dnd->delete;
+}
+
+void ui_dnd_accept(UiDnD *dnd, UiBool accept) {
+    dnd->accept = accept;
+}
+
--- a/ui/gtk/dnd.h	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/gtk/dnd.h	Wed Nov 27 13:28:21 2024 +0100
@@ -32,12 +32,37 @@
 #include "../ui/dnd.h"
 #include "toolkit.h"
 
+#include <cx/list.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+#if GTK_MAJOR_VERSION >= 4
+    
+struct UiDnD {
+    GtkDropTarget *target;
+    const GValue *value;
+    CxList *providers;
+    GdkDragAction selected_action;
+    gboolean delete;
+    gboolean accept;
+};
+    
+#else
+   
+struct UiDnD {
+    GdkDragContext *context;
+    GtkSelectionData *data;
+    GdkDragAction selected_action;
+    gboolean delete;
+    gboolean accept;
+};
 
+#endif
 
+UiDnD* ui_create_dnd(void);
+void ui_dnd_free(UiDnD *dnd);
 
 #ifdef __cplusplus
 }
--- a/ui/gtk/headerbar.c	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/gtk/headerbar.c	Wed Nov 27 13:28:21 2024 +0100
@@ -171,4 +171,4 @@
     headerbar_add(headerbar, box, menubutton, pos);
 }
     
-#endif
\ No newline at end of file
+#endif // GTK_CHECK_VERSION(3, 10, 0)
--- a/ui/gtk/list.c	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/gtk/list.c	Wed Nov 27 13:28:21 2024 +0100
@@ -35,9 +35,12 @@
 #include "../common/object.h"
 #include "container.h"
 
+#include <cx/array_list.h>
+
 #include "list.h"
 #include "icon.h"
 #include "menu.h"
+#include "dnd.h"
 
 
 void* ui_strmodel_getvalue(void *elm, int column) {
@@ -366,6 +369,12 @@
     tableview->widget = view;
     tableview->var = var;
     tableview->model = model;
+    tableview->ondragstart = args.ondragstart;
+    tableview->ondragstartdata = args.ondragstartdata;
+    tableview->ondragcomplete = args.ondragcomplete;
+    tableview->ondragcompletedata = args.ondragcompletedata;
+    tableview->ondrop = args.ondrop;
+    tableview->ondropdata = args.ondropsdata;
     g_signal_connect(
                 view,
                 "destroy",
@@ -403,6 +412,13 @@
     }
     // TODO: destroy callback
     
+    
+    if(args.ondragstart) {
+        ui_listview_add_dnd(tableview, &args);
+    }
+    if(args.ondrop) {
+        ui_listview_enable_drop(tableview, &args);
+    }
       
     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
     if(args.multiselection) {
@@ -432,6 +448,234 @@
     return scroll_area;
 }
 
+#if GTK_MAJOR_VERSION >= 4
+
+static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
+    //printf("drag prepare\n");
+    UiListView *listview = data;
+    
+    UiDnD *dnd = ui_create_dnd();
+    GdkContentProvider *provider = NULL;
+    
+    
+    if(listview->ondragstart) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = dnd;
+        event.intval = 0;
+        listview->ondragstart(&event, listview->ondragstartdata);
+    }
+    
+    size_t numproviders = cxListSize(dnd->providers);
+    if(numproviders > 0) {
+        GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0);
+        provider = gdk_content_provider_new_union(providers, numproviders);
+    }
+    ui_dnd_free(dnd);
+    
+    return provider;
+}
+
+static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) {
+    //printf("drag begin\n");
+}
+
+static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) {
+    //printf("drag end\n");
+    UiListView *listview = user_data;
+    if(listview->ondragcomplete) {
+        UiDnD dnd;
+        dnd.target = NULL;
+        dnd.value = NULL;
+        dnd.providers = NULL;
+        dnd.selected_action = gdk_drag_get_selected_action(drag);
+        dnd.delete = delete_data;
+        dnd.accept = FALSE;
+        
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragcomplete(&event, listview->ondragcompletedata);
+    }
+}
+
+static gboolean ui_listview_drop(
+        GtkDropTarget *target,
+        const GValue* value,
+        gdouble x,
+        gdouble y,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.providers = NULL;
+    dnd.target = target;
+    dnd.value = value;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondrop) {
+        dnd.accept = TRUE;
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondrop(&event, listview->ondropdata);
+    }
+    
+    return dnd.accept;
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+    GtkDragSource *dragsource = gtk_drag_source_new();
+    gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource));
+    g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview);
+    g_signal_connect(
+            dragsource,
+            "drag-begin",
+            G_CALLBACK(ui_listview_drag_begin),
+            listview);
+    g_signal_connect(
+            dragsource,
+            "drag-end",
+            G_CALLBACK(ui_listview_drag_end),
+            listview);
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+    GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
+    gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target));
+    GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING };
+    gtk_drop_target_set_gtypes(target, default_types, 2);
+    g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview);
+}
+
+#else
+
+static GtkTargetEntry targetentries[] =
+{
+    { "STRING",        0, 0 },
+    { "text/plain",    0, 1 },
+    { "text/uri-list", 0, 2 },
+};
+
+static void ui_listview_drag_getdata(
+        GtkWidget* self,
+        GdkDragContext* context,
+        GtkSelectionData* data,
+        guint info,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = data;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondragstart) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragstart(&event, listview->ondragstartdata);
+    }
+}
+
+static void ui_listview_drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = NULL;
+    dnd.selected_action = gdk_drag_context_get_selected_action(context);
+    dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE;
+    dnd.accept = FALSE;
+    if(listview->ondragcomplete) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragcomplete(&event, listview->ondragcompletedata);
+    }
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(listview->widget),
+            GDK_BUTTON1_MASK,
+            targetentries,
+            2,
+            GDK_ACTION_COPY);
+    
+    g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview);
+    g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview);
+}
+
+
+
+
+static void ui_listview_drag_data_received(
+        GtkWidget *self,
+        GdkDragContext *context,
+        gint x,
+        gint y,
+        GtkSelectionData *data,
+        guint info,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = data;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondrop) {
+        dnd.accept = TRUE;
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondrop(&event, listview->ondropdata);
+    }
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(listview->widget),
+            targetentries,
+            3,
+            GDK_ACTION_COPY);
+    if(listview->ondrop) {
+        g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview);
+    }
+}
+
+#endif
+
 
 GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
     return SCROLLEDWINDOW_GET_CHILD(widget);
--- a/ui/gtk/list.h	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/gtk/list.h	Wed Nov 27 13:28:21 2024 +0100
@@ -41,6 +41,13 @@
     GtkWidget   *widget;
     UiVar       *var;
     UiModel     *model;
+    ui_callback ondragstart;
+    void        *ondragstartdata;
+    ui_callback ondragcomplete;
+    void        *ondragcompletedata;
+    ui_callback ondrop;
+    void        *ondropdata;
+    
 } UiListView;
 
 typedef struct UiTreeEventData {
@@ -78,6 +85,9 @@
         UiTreeEventData *event);
 int ui_tree_path_list_index(GtkTreePath *path);
 
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args);
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);
+
 UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
 GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata);
 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
--- a/ui/ui/dnd.h	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/ui/dnd.h	Wed Nov 27 13:28:21 2024 +0100
@@ -43,6 +43,11 @@
 UIEXPORT char* ui_selection_gettext(UiDnD *sel);
 UIEXPORT UiFileList ui_selection_geturis(UiDnD *sel);
 
+UIEXPORT UiDnDAction ui_dnd_result(UiDnD *dnd);
+UIEXPORT UiBool ui_dnd_need_delete(UiDnD *dnd);
+
+UIEXPORT void ui_dnd_accept(UiDnD *dnd, UiBool accept);
+
 
 #ifdef __cplusplus
 }
--- a/ui/ui/toolkit.h	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/ui/toolkit.h	Wed Nov 27 13:28:21 2024 +0100
@@ -191,11 +191,13 @@
 typedef enum UiTri UiTri;
 typedef enum UiLabelType UiLabelType;
 
+typedef enum UiDnDAction UiDnDAction;
+
 enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
 
 enum UiLabelType { UI_LABEL_DEFAULT, UI_LABEL_TEXT, UI_LABEL_ICON, UI_LABEL_TEXT_ICON };
 
-
+enum UiDnDAction { UI_DND_ACTION_NONE, UI_DND_ACTION_COPY, UI_DND_ACTION_MOVE, UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM };
   
 typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
 
--- a/ui/ui/tree.h	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/ui/tree.h	Wed Nov 27 13:28:21 2024 +0100
@@ -99,11 +99,6 @@
     void *userdata;
 };
 
-struct UiListDnd {
-    UiListSelection selection;
-    UiDnD *dnd;
-};
-
 struct UiListArgs {
     UiTri fill;
     UiBool hexpand;
--- a/ui/winui/table.cpp	Tue Nov 26 15:53:57 2024 +0100
+++ b/ui/winui/table.cpp	Wed Nov 27 13:28:21 2024 +0100
@@ -339,22 +339,14 @@
 						dnd.drageventargs = { nullptr };
 						dnd.data = args.Data();
 
-						UiListDnd dndevt;
-						dndevt.selection = uiselection();
-						dndevt.dnd = &dnd;
-
 						UiEvent evt;
 						evt.obj = this->obj;
 						evt.window = evt.obj->window;
 						evt.document = obj->ctx->document;
-						evt.eventdata = &dndevt;
+						evt.eventdata = &dnd;
 						evt.intval = 0;
 					
 						this->ondragstart(&evt, this->ondragstartdata);
-
-						if (dndevt.selection.rows) {
-							free(dndevt.selection.rows);
-						}
 					});
 				cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) {
 						UiDnD dnd;
@@ -364,23 +356,16 @@
 						dnd.drageventargs = { nullptr };
 						dnd.data = { nullptr };
 
-						UiListDnd dndevt;
-						dndevt.selection = uiselection();
-						dndevt.dnd = &dnd;
-
 						UiEvent evt;
 						evt.obj = this->obj;
 						evt.window = evt.obj->window;
 						evt.document = obj->ctx->document;
-						evt.eventdata = &dndevt;
+						evt.eventdata = &dnd;
 						evt.intval = 0;
 
 						if (this->ondragcomplete) {
 							this->ondragcomplete(&evt, this->ondragcompletedata);
 						}
-						if (dndevt.selection.rows) {
-							free(dndevt.selection.rows);
-						}
 					});
 			}
 			if (ondrop) {
@@ -393,22 +378,14 @@
 						dnd.drageventargs = args;
 						dnd.dataview = args.DataView();
 
-						UiListDnd dndevt;
-						dndevt.selection = uiselection();
-						dndevt.dnd = &dnd;
-
 						UiEvent evt;
 						evt.obj = this->obj;
 						evt.window = evt.obj->window;
 						evt.document = obj->ctx->document;
-						evt.eventdata = &dndevt;
+						evt.eventdata = &dnd;
 						evt.intval = 0;
 
 						this->ondrop(&evt, this->ondropdata);
-
-						if (dndevt.selection.rows) {
-							free(dndevt.selection.rows);
-						}
 					}));
 				cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){
 					args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);

mercurial