From ff18efce04ac61f70943dbdb6273c7663a1bb936 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 15 Nov 2009 12:48:15 +0000 Subject: [PATCH] Implement dragging from the choose tab. This adds a new parameter to multidrag, a predicate to allow the draggable rows to be selected. In this case, choose uses it to suppress directories. Organization: Straylight/Edgeware From: Richard Kettlewell --- disobedience/choose.c | 75 ++++++++++++++++++++++++++++++++++++ disobedience/multidrag.c | 36 +++++++++++++---- disobedience/multidrag.h | 11 +++++- disobedience/queue-generic.c | 16 +++++--- 4 files changed, 123 insertions(+), 15 deletions(-) diff --git a/disobedience/choose.c b/disobedience/choose.c index b9a9e7e..9829f19 100644 --- a/disobedience/choose.c +++ b/disobedience/choose.c @@ -32,8 +32,18 @@ #include "disobedience.h" #include "choose.h" +#include "multidrag.h" #include +/** @brief Drag types */ +static const GtkTargetEntry choose_targets[] = { + { + (char *)"text/x-disorder-playable-tracks", /* drag type */ + GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */ + 1 /* ID value */ + }, +}; + /** @brief The current selection tree */ GtkTreeStore *choose_store; @@ -538,6 +548,60 @@ static gboolean choose_key_event(GtkWidget attribute((unused)) *widget, return TRUE; /* Handled it */ } +static gboolean choose_multidrag_predicate(GtkTreePath attribute((unused)) *path, + GtkTreeIter *iter) { + return choose_is_file(iter); +} + + +/** @brief Callback to add selected tracks to the selection data + * + * Called from choose_drag_data_get(). + */ +static void choose_drag_data_get_collect(GtkTreeModel attribute((unused)) *model, + GtkTreePath attribute((unused)) *path, + GtkTreeIter *iter, + gpointer data) { + struct dynstr *const result = data; + + if(choose_is_file(iter)) { /* no diretories */ + dynstr_append_string(result, ""); /* no ID */ + dynstr_append(result, '\n'); + dynstr_append_string(result, choose_get_track(iter)); + dynstr_append(result, '\n'); + } +} + +/** @brief Called to extract the dragged data from the choose view + * @param w Source widget (the tree view) + * @param dc Drag context + * @param data Where to put the answer + * @param info_ Target @c info parameter + * @param time_ Time data requested (for some reason not a @c time_t) + * @param user_data The queuelike + * + * Closely analogous to ql_drag_data_get(), and uses the same data format. + * IDs are sent as empty strings. + */ +static void choose_drag_data_get(GtkWidget *w, + GdkDragContext attribute((unused)) *dc, + GtkSelectionData *data, + guint attribute((unused)) info_, + guint attribute((unused)) time_, + gpointer attribute((unused)) user_data) { + struct dynstr result[1]; + GtkTreeSelection *sel; + + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w)); + dynstr_init(result); + gtk_tree_selection_selected_foreach(sel, + choose_drag_data_get_collect, + result); + gtk_selection_data_set(data, + GDK_TARGET_STRING, + 8, (guchar *)result->vec, result->nvec); +} + /** @brief Create the choose tab */ GtkWidget *choose_widget(void) { /* Create the tree store. */ @@ -643,6 +707,17 @@ GtkWidget *choose_widget(void) { g_signal_connect(choose_view, "key-release-event", G_CALLBACK(choose_key_event), choose_search_entry); + /* Enable dragging of tracks out */ + gtk_drag_source_set(choose_view, + GDK_BUTTON1_MASK, + choose_targets, + sizeof choose_targets / sizeof *choose_targets, + GDK_ACTION_COPY); + g_signal_connect(choose_view, "drag-data-get", + G_CALLBACK(choose_drag_data_get), NULL); + make_treeview_multidrag(choose_view, + choose_multidrag_predicate); + return vbox; } diff --git a/disobedience/multidrag.c b/disobedience/multidrag.c index 6014e07..2f58ae1 100644 --- a/disobedience/multidrag.c +++ b/disobedience/multidrag.c @@ -138,6 +138,7 @@ static gboolean multidrag_button_release_event(GtkWidget *w, /** @brief State for multidrag_begin() and its callbacks */ struct multidrag_begin_state { GtkTreeView *view; + multidrag_row_predicate *predicate; int rows; int index; GdkPixmap **pixmaps; @@ -146,22 +147,24 @@ struct multidrag_begin_state { /** @brief Callback to construct a row pixmap */ static void multidrag_make_row_pixmaps(GtkTreeModel attribute((unused)) *model, GtkTreePath *path, - GtkTreeIter attribute((unused)) *iter, + GtkTreeIter *iter, gpointer data) { struct multidrag_begin_state *qdbs = data; - qdbs->pixmaps[qdbs->index++] - = gtk_tree_view_create_row_drag_icon(qdbs->view, path); + if(qdbs->predicate(path, iter)) { + qdbs->pixmaps[qdbs->index++] + = gtk_tree_view_create_row_drag_icon(qdbs->view, path); + } } /** @brief Called when a drag operation starts * @param w Source widget (the tree view) * @param dc Drag context - * @param user_data Not used + * @param user_data Row predicate */ static void multidrag_drag_begin(GtkWidget *w, GdkDragContext attribute((unused)) *dc, - gpointer attribute((unused)) user_data) { + gpointer user_data) { struct multidrag_begin_state qdbs[1]; GdkPixmap *icon; GtkTreeSelection *sel; @@ -169,6 +172,7 @@ static void multidrag_drag_begin(GtkWidget *w, //fprintf(stderr, "drag-begin\n"); memset(qdbs, 0, sizeof *qdbs); qdbs->view = GTK_TREE_VIEW(w); + qdbs->predicate = (multidrag_row_predicate *)user_data; sel = gtk_tree_view_get_selection(qdbs->view); /* Find out how many rows there are */ if(!(qdbs->rows = gtk_tree_selection_count_selected_rows(sel))) @@ -178,6 +182,8 @@ static void multidrag_drag_begin(GtkWidget *w, gtk_tree_selection_selected_foreach(sel, multidrag_make_row_pixmaps, qdbs); + /* Might not have used all rows */ + qdbs->rows = qdbs->index; /* Determine the size of the final icon */ int height = 0, width = 0; for(int n = 0; n < qdbs->rows; ++n) { @@ -217,18 +223,32 @@ static void multidrag_drag_begin(GtkWidget *w, NULL); } +static gboolean multidrag_default_predicate(GtkTreePath attribute((unused)) *path, + GtkTreeIter attribute((unused)) *iter) { + return TRUE; +} + /** @brief Allow multi-row drag for @p w * @param w A GtkTreeView widget + * @param predicate Function called to test rows for draggability, or NULL + * + * Suppresses the restriction of selections when a drag is started, and + * intercepts drag-begin to construct an icon. * - * Suppresses the restriction of selections when a drag is started. + * @p predicate should return TRUE for draggable rows and FALSE otherwise, to + * control what goes in the icon. If NULL, equivalent to a function that + * always returns TRUE. */ -void make_treeview_multidrag(GtkWidget *w) { +void make_treeview_multidrag(GtkWidget *w, + multidrag_row_predicate *predicate) { + if(!predicate) + predicate = multidrag_default_predicate; g_signal_connect(w, "button-press-event", G_CALLBACK(multidrag_button_press_event), NULL); g_signal_connect(w, "button-release-event", G_CALLBACK(multidrag_button_release_event), NULL); g_signal_connect(w, "drag-begin", - G_CALLBACK(multidrag_drag_begin), NULL); + G_CALLBACK(multidrag_drag_begin), predicate); } /* diff --git a/disobedience/multidrag.h b/disobedience/multidrag.h index 6101542..82eafa9 100644 --- a/disobedience/multidrag.h +++ b/disobedience/multidrag.h @@ -21,7 +21,16 @@ #ifndef MULTIDRAG_H #define MULTIDRAG_H -void make_treeview_multidrag(GtkWidget *w); +/** @brief Predicate type for rows to drag + * @param path Path to row + * @param iter Iterator pointing at row + * @return TRUE if row is draggable else FALSE + */ +typedef gboolean multidrag_row_predicate(GtkTreePath *path, + GtkTreeIter *iter); + +void make_treeview_multidrag(GtkWidget *w, + multidrag_row_predicate *predicate); #endif /* MULTIDRAG_H */ diff --git a/disobedience/queue-generic.c b/disobedience/queue-generic.c index 179998a..5a2a8ea 100644 --- a/disobedience/queue-generic.c +++ b/disobedience/queue-generic.c @@ -534,6 +534,13 @@ static void ql_drag_data_get_collect(GtkTreeModel *model, * @param info_ Target @c info parameter * @param time_ Time data requested (for some reason not a @c time_t) * @param user_data The queuelike + * + * The list of tracks is converted into a single string, consisting of IDs + * and track names. Each is terminated by a newline. Including both ID and + * track name means that the receiver can use whichever happens to be more + * convenient. + * + * If there are no IDs for rows in this widget then the ID half is undefined. */ static void ql_drag_data_get(GtkWidget attribute((unused)) *w, GdkDragContext attribute((unused)) *dc, @@ -544,10 +551,6 @@ static void ql_drag_data_get(GtkWidget attribute((unused)) *w, struct queuelike *const ql = user_data; struct dynstr result[1]; - /* The list of tracks is converted into a single string, consisting of IDs - * and track names. Each is terminated by a newline. Including both ID and - * track name means that the receiver can use whichever happens to be more - * convenient. */ dynstr_init(result); gtk_tree_selection_selected_foreach(ql->selection, ql_drag_data_get_collect, @@ -747,7 +750,8 @@ GtkWidget *init_queuelike(struct queuelike *ql) { G_CALLBACK(ql_drag_data_get), ql); g_signal_connect(ql->view, "drag-data-received", G_CALLBACK(ql_drag_data_received), ql); - make_treeview_multidrag(ql->view); + make_treeview_multidrag(ql->view, NULL); + // TODO playing track should be refused by predicate arg } else { /* For queues that cannot accept a drop we still accept a copy out */ gtk_drag_source_set(ql->view, @@ -757,7 +761,7 @@ GtkWidget *init_queuelike(struct queuelike *ql) { GDK_ACTION_COPY); g_signal_connect(ql->view, "drag-data-get", G_CALLBACK(ql_drag_data_get), ql); - make_treeview_multidrag(ql->view); + make_treeview_multidrag(ql->view, NULL); } /* TODO style? */ -- [mdw]