+static void vf_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
+{
+ auto *vf = static_cast<ViewFile *>(data);
+
+ switch (vf->type)
+ {
+ case FILEVIEW_LIST: vflist_dnd_end(vf, context); break;
+ case FILEVIEW_ICON: vficon_dnd_end(vf, context); break;
+ }
+}
+
+static FileData *vf_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *iter)
+{
+ switch (vf->type)
+ {
+ case FILEVIEW_LIST: return vflist_find_data_by_coord(vf, x, y, iter);
+ case FILEVIEW_ICON: return vficon_find_data_by_coord(vf, x, y, iter);
+ }
+
+ return nullptr;
+}
+
+static void vf_drag_data_received(GtkWidget *, GdkDragContext *,
+ int x, int y, GtkSelectionData *selection,
+ guint info, guint, gpointer data)
+{
+ if (info != TARGET_TEXT_PLAIN) return;
+
+ auto *vf = static_cast<ViewFile *>(data);
+
+ FileData *fd = vf_find_data_by_coord(vf, x, y, nullptr);
+ if (!fd) return;
+
+ /* Add keywords to file */
+ auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
+ GList *kw_list = string_to_keywords_list(str);
+
+ metadata_append_list(fd, KEYWORD_KEY, kw_list);
+
+ g_list_free_full(kw_list, g_free);
+ g_free(str);
+}
+
+static void vf_dnd_init(ViewFile *vf)
+{
+ gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
+ dnd_file_drag_types, dnd_file_drag_types_count,
+ static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
+ gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
+ dnd_file_drag_types, dnd_file_drag_types_count,
+ static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
+
+ g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
+ G_CALLBACK(vf_dnd_get), vf);
+ g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
+ G_CALLBACK(vf_dnd_begin), vf);
+ g_signal_connect(G_OBJECT(vf->listview), "drag_end",
+ G_CALLBACK(vf_dnd_end), vf);
+ g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
+ G_CALLBACK(vf_drag_data_received), vf);
+}
+