#include "popup.h"
#include "queue-generic.h"
-static struct queuelike *const queuelikes[] = {
- &ql_queue, &ql_recent, &ql_added
-};
-#define NQUEUELIKES (sizeof queuelikes / sizeof *queuelikes)
-
static const GtkTargetEntry queuelike_targets[] = {
{
- (char *)"text/x-disorder-track-data", /* drag type */
- GTK_TARGET_SAME_WIDGET, /* rearrangement only for now */
+ (char *)"text/x-disorder-queued-tracks", /* drag type */
+ GTK_TARGET_SAME_WIDGET, /* rearrangement within a widget */
0 /* ID value */
},
+ {
+ (char *)"text/x-disorder-playable-tracks", /* drag type */
+ GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */
+ 1 /* ID value */
+ },
};
/* Track detail lookup ----------------------------------------------------- */
0, y, /* dest coords */
pxw, pxh); /* size */
y += pxh;
+ gdk_drawable_unref(qdbs->pixmaps[n]);
+ qdbs->pixmaps[n] = NULL;
}
// TODO scale down a bit, the resulting icons are currently a bit on the
// large side.
NULL);
}
+/** @brief Called when a drag moves within a candidate destination
+ * @param w Destination widget
+ * @param dc Drag context
+ * @param x Current pointer location
+ * @param y Current pointer location
+ * @param time_ Current time
+ * @param user_data Pointer to queuelike
+ * @return TRUE in a dropzone, otherwise FALSE
+ */
+static gboolean ql_drag_motion(GtkWidget *w,
+ GdkDragContext *dc,
+ gint x,
+ gint y,
+ guint time_,
+ gpointer attribute((unused)) user_data) {
+ //struct queuelike *const ql = user_data;
+ GdkDragAction action = 0;
+
+ // GTK_DEST_DEFAULT_MOTION vets actions as follows:
+ // 1) if dc->suggested_action is in the gtk_drag_dest_set actions
+ // then dc->suggested_action is taken as the action.
+ // 2) otherwise if dc->actions intersects the gtk_drag_dest_set actions
+ // then the lowest-numbered member of the intersection is chosen.
+ // 3) otherwise no member is chosen and gdk_drag_status() is called
+ // with action=0 to refuse the drop.
+ if(dc->suggested_action) {
+ if(dc->suggested_action & (GDK_ACTION_MOVE|GDK_ACTION_COPY))
+ action = dc->suggested_action;
+ } else if(dc->actions & GDK_ACTION_MOVE)
+ action = GDK_ACTION_MOVE;
+ else if(dc->actions & GDK_ACTION_COPY)
+ action = GDK_ACTION_COPY;
+ /*fprintf(stderr, "suggested %#x actions %#x result %#x\n",
+ dc->suggested_action, dc->actions, action);*/
+ if(action) {
+ // If the action is acceptable then we see if this widget is acceptable
+ if(gtk_drag_dest_find_target(w, dc, NULL) == GDK_NONE)
+ action = 0;
+ }
+ // Report the status
+ gdk_drag_status(dc, action, time_);
+ if(action) {
+ // Highlight the drop area
+ GtkTreePath *path;
+ GtkTreeViewDropPosition pos;
+
+ if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(w),
+ x, y,
+ &path,
+ &pos)) {
+ //fprintf(stderr, "gtk_tree_view_get_dest_row_at_pos() -> TRUE\n");
+ // Normalize drop position
+ switch(pos) {
+ case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
+ pos = GTK_TREE_VIEW_DROP_BEFORE;
+ break;
+ case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
+ pos = GTK_TREE_VIEW_DROP_AFTER;
+ break;
+ default: break;
+ }
+ // Highlight drop target
+ gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), path, pos);
+ } else {
+ //fprintf(stderr, "gtk_tree_view_get_dest_row_at_pos() -> FALSE\n");
+ gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), NULL, 0);
+ }
+ }
+ return TRUE;
+}
+
+/** @brief Called when a drag moves leaves a candidate destination
+ * @param w Destination widget
+ * @param dc Drag context
+ * @param time_ Current time
+ * @param user_data Pointer to queuelike
+ */
+static void ql_drag_leave(GtkWidget *w,
+ GdkDragContext attribute((unused)) *dc,
+ guint attribute((unused)) time_,
+ gpointer attribute((unused)) user_data) {
+ //struct queuelike *const ql = user_data;
+
+ gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), NULL, 0);
+}
+
/** @brief Callback to add selected tracks to the selection data
*
* Called from ql_drag_data_get().
gtk_tree_selection_selected_foreach(ql->selection,
ql_drag_data_get_collect,
result);
+ // TODO must not be able to drag playing track!
//fprintf(stderr, "drag-data-get: %.*s\n",
// result->nvec, result->vec);
/* gtk_selection_data_set_text() insists that data->target is one of a
struct vector ids[1], tracks[1];
int parity = 0;
- //fprintf(stderr, "drag-data-received: %d,%d\n", x, y);
+ //fprintf(stderr, "drag-data-received: %d,%d info_=%u\n", x, y, info_);
/* Get the selection string */
p = result = (char *)gtk_selection_data_get_text(data);
if(!result) {
break;
}
}
+ /* Guarantee we never drop an empty list */
+ if(!tracks->nvec)
+ return;
/* Note that q->id can match one of ids[]. This doesn't matter for
* moveafter but TODO may matter for playlist support. */
- ql->drop(ql, tracks->nvec, tracks->vec, ids->vec, q);
+ switch(info_) {
+ case 0:
+ /* Rearrangement. Send ID and track data. */
+ ql->drop(ql, tracks->nvec, tracks->vec, ids->vec, q);
+ break;
+ case 1:
+ /* Copying between widgets. IDs mean nothing so don't send them. */
+ ql->drop(ql, tracks->nvec, tracks->vec, NULL, q);
+ break;
+ }
}
/** @brief Initialize a @ref queuelike */
queuelike_targets,
sizeof queuelike_targets / sizeof *queuelike_targets,
GDK_ACTION_MOVE);
- /* This view will act as a drag destination
- *
- * GTK_DEST_DEFAULT_ALL is going to have to change; GTK_DEST_DEFAULT_DROP
- * does not allow us to detect illegal drops up front. However it will do
- * for now.
- */
+ /* This view will act as a drag destination */
gtk_drag_dest_set(ql->view,
- GTK_DEST_DEFAULT_ALL,
+ GTK_DEST_DEFAULT_HIGHLIGHT|GTK_DEST_DEFAULT_DROP,
queuelike_targets,
sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE);
+ GDK_ACTION_MOVE|GDK_ACTION_COPY);
g_signal_connect(ql->view, "drag-begin",
G_CALLBACK(ql_drag_begin), ql);
+ g_signal_connect(ql->view, "drag-motion",
+ G_CALLBACK(ql_drag_motion), ql);
+ g_signal_connect(ql->view, "drag-leave",
+ G_CALLBACK(ql_drag_leave), ql);
g_signal_connect(ql->view, "drag-data-get",
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);
} else {
- /* TODO: support copy-dragging out of non-rearrangeable queues. Will need
- * to support copy dropping into the rearrangeable ones. */
+ /* For queues that cannot accept a drop we still accept a copy out */
+ gtk_drag_source_set(ql->view,
+ GDK_BUTTON1_MASK,
+ queuelike_targets,
+ sizeof queuelike_targets / sizeof *queuelike_targets,
+ GDK_ACTION_COPY);
+ g_signal_connect(ql->view, "drag-begin",
+ G_CALLBACK(ql_drag_begin), ql);
+ g_signal_connect(ql->view, "drag-data-get",
+ G_CALLBACK(ql_drag_data_get), ql);
+ make_treeview_multidrag(ql->view);
}
/* TODO style? */