#include "disobedience.h"
#include "popup.h"
#include "queue-generic.h"
-
-static struct queuelike *const queuelikes[] = {
- &ql_queue, &ql_recent, &ql_added
-};
-#define NQUEUELIKES (sizeof queuelikes / sizeof *queuelikes)
+#include "multidrag.h"
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 ----------------------------------------------------- */
--suppress_actions;
}
-/** @brief State for ql_drag_begin() and its callbacks */
-struct ql_drag_begin_state {
- struct queuelike *ql;
- int rows;
- int index;
- GdkPixmap **pixmaps;
-};
-
-/** @brief Callback to construct a row pixmap */
-static void ql_drag_make_row_pixmaps(GtkTreeModel attribute((unused)) *model,
- GtkTreePath *path,
- GtkTreeIter attribute((unused)) *iter,
- gpointer data) {
- struct ql_drag_begin_state *qdbs = data;
-
- qdbs->pixmaps[qdbs->index++]
- = gtk_tree_view_create_row_drag_icon(GTK_TREE_VIEW(qdbs->ql->view),
- path);
-}
-
-/** @brief Called when a drag operation from this queuelike begins
- * @param w Source widget (the tree view)
+/** @brief Called when a drag moves within a candidate destination
+ * @param w Destination widget
* @param dc Drag context
- * @param user_data The queuelike
+ * @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 void ql_drag_begin(GtkWidget attribute((unused)) *w,
- GdkDragContext attribute((unused)) *dc,
- gpointer user_data) {
- struct queuelike *const ql = user_data;
- struct ql_drag_begin_state qdbs[1];
- GdkPixmap *icon;
-
- //fprintf(stderr, "drag-begin\n");
- memset(qdbs, 0, sizeof *qdbs);
- qdbs->ql = ql;
- /* Find out how many rows there are */
- if(!(qdbs->rows = gtk_tree_selection_count_selected_rows(ql->selection)))
- return; /* doesn't make sense */
- /* Generate a pixmap for each row */
- qdbs->pixmaps = xcalloc(qdbs->rows, sizeof *qdbs->pixmaps);
- gtk_tree_selection_selected_foreach(ql->selection,
- ql_drag_make_row_pixmaps,
- qdbs);
- /* Determine the size of the final icon */
- int height = 0, width = 0;
- for(int n = 0; n < qdbs->rows; ++n) {
- int pxw, pxh;
- gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
- if(pxw > width)
- width = pxw;
- height += pxh;
- }
- if(!width || !height)
- return; /* doesn't make sense */
- /* Construct the icon */
- icon = gdk_pixmap_new(qdbs->pixmaps[0], width, height, -1);
- GdkGC *gc = gdk_gc_new(icon);
- gdk_gc_set_colormap(gc, gtk_widget_get_colormap(ql->view));
- int y = 0;
- for(int n = 0; n < qdbs->rows; ++n) {
- int pxw, pxh;
- gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
- gdk_draw_drawable(icon,
- gc,
- qdbs->pixmaps[n],
- 0, 0, /* source coords */
- 0, y, /* dest coords */
- pxw, pxh); /* size */
- y += pxh;
- }
- // TODO scale down a bit, the resulting icons are currently a bit on the
- // large side.
- gtk_drag_source_set_icon(ql->view,
- gtk_widget_get_colormap(ql->view),
- icon,
- NULL);
-}
-
static gboolean ql_drag_motion(GtkWidget *w,
GdkDragContext *dc,
gint x,
// 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.
- // Currently we can only accept _MOVE. But in the future we will
- // need to accept _COPY in some cases.
if(dc->suggested_action) {
- if(dc->suggested_action == GDK_ACTION_MOVE)
+ if(dc->suggested_action & (GDK_ACTION_MOVE|GDK_ACTION_COPY))
action = dc->suggested_action;
} else if(dc->actions & GDK_ACTION_MOVE)
- action = dc->actions;
+ 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))
+ if(gtk_drag_dest_find_target(w, dc, NULL) == GDK_NONE)
action = 0;
}
// Report the status
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_,
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 */
GTK_DEST_DEFAULT_HIGHLIGHT|GTK_DEST_DEFAULT_DROP,
queuelike_targets,
sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE);
- g_signal_connect(ql->view, "drag-begin",
- G_CALLBACK(ql_drag_begin), ql);
+ GDK_ACTION_MOVE|GDK_ACTION_COPY);
g_signal_connect(ql->view, "drag-motion",
G_CALLBACK(ql_drag_motion), ql);
g_signal_connect(ql->view, "drag-leave",
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-data-get",
+ G_CALLBACK(ql_drag_data_get), ql);
+ make_treeview_multidrag(ql->view);
}
/* TODO style? */