# HG changeset patch # User Olaf Wintermann # Date 1732710501 -3600 # Node ID bedd499b640d7e256e035b13105eec12894b7cb5 # Parent 3099bf907e21128b78a3ec434b9edd373e3a535c implement table dnd (GTK) diff -r 3099bf907e21 -r bedd499b640d ui/gtk/dnd.c --- 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 +#include #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;iproviders, &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;idata, 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; +} + diff -r 3099bf907e21 -r bedd499b640d ui/gtk/dnd.h --- 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 + #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 } diff -r 3099bf907e21 -r bedd499b640d ui/gtk/headerbar.c --- 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) diff -r 3099bf907e21 -r bedd499b640d ui/gtk/list.c --- 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 + #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); diff -r 3099bf907e21 -r bedd499b640d ui/gtk/list.h --- 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); diff -r 3099bf907e21 -r bedd499b640d ui/ui/dnd.h --- 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 } diff -r 3099bf907e21 -r bedd499b640d ui/ui/toolkit.h --- 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 */ diff -r 3099bf907e21 -r bedd499b640d ui/ui/tree.h --- 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; diff -r 3099bf907e21 -r bedd499b640d ui/winui/table.cpp --- 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);