#include "multidrag.h"
#include "autoscroll.h"
-static const GtkTargetEntry queuelike_targets[] = {
- {
- (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 ----------------------------------------------------- */
static void queue_lookups_completed(const char attribute((unused)) *event,
}
}
+/** @brief Old and new queue data
+ *
+ * Used when updating a @ref queuelike with new data from the server.
+ */
struct newqueue_data {
struct queue_entry *old, *new;
};
hash_add(h, id, empty, HASH_INSERT);
nqd = hash_find(h, id);
}
- if(old)
+ if(old) {
+#if DEBUG_QUEUE
+ fprintf(stderr, " old: %s\n", id);
+#endif
nqd->old = old;
- if(new)
+ }
+ if(new) {
+#if DEBUG_QUEUE
+ fprintf(stderr, " new: %s\n", id);
+#endif
nqd->new = new;
+ }
}
-#if 0
+#if DEBUG_QUEUE
static void dump_queue(struct queue_entry *head, struct queue_entry *mark) {
for(struct queue_entry *q = head; q; q = q->next) {
if(q == mark)
- fprintf(stderr, "!");
- fprintf(stderr, "%s", q->id);
- if(q->next)
- fprintf(stderr, " ");
+ fprintf(stderr, " !");
+ fprintf(stderr, " %s\n", q->id);
}
- fprintf(stderr, "\n");
}
static void dump_rows(struct queuelike *ql) {
while(it) {
struct queue_entry *q = ql_iter_to_q(GTK_TREE_MODEL(ql->store), iter);
it = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), iter);
- fprintf(stderr, "%s", q->id);
- if(it)
- fprintf(stderr, " ");
+ fprintf(stderr, " %s\n", q->id);
}
- fprintf(stderr, "\n");
}
#endif
++suppress_actions;
/* Tell every queue entry which queue owns it */
- //fprintf(stderr, "%s: filling in q->ql\n", ql->name);
+#if DEBUG_QUEUE
+ fprintf(stderr, "%s: filling in q->ql\n", ql->name);
+#endif
for(struct queue_entry *q = newq; q; q = q->next)
q->ql = ql;
- //fprintf(stderr, "%s: constructing h\n", ql->name);
+#if DEBUG_QUEUE
+ fprintf(stderr, "%s: constructing h\n", ql->name);
+#endif
/* Construct map from id to new and old structures */
hash *h = hash_new(sizeof(struct newqueue_data));
for(struct queue_entry *q = ql->q; q; q = q->next)
/* The easy bit: delete rows not present any more. In the same pass we
* update the secret column containing the queue_entry pointer. */
- //fprintf(stderr, "%s: deleting rows...\n", ql->name);
+#if DEBUG_QUEUE
+ fprintf(stderr, "%s: deleting rows...\n", ql->name);
+#endif
GtkTreeIter iter[1];
gboolean it = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ql->store),
iter);
ql->ncolumns + QUEUEPOINTER_COLUMN, nqd->new,
-1);
it = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), iter);
+ /* We'll need the new start time */
+ nqd->new->when = q->when;
++kept;
} else {
/* Delete this row (and move iter to the next one) */
- //fprintf(stderr, " delete %s", q->id);
+#if DEBUG_QUEUE
+ fprintf(stderr, " delete %s\n", q->id);
+#endif
it = gtk_list_store_remove(ql->store, iter);
++deleted;
}
/* We're going to have to support arbitrary rearrangements, so we might as
* well add new elements at the end. */
- //fprintf(stderr, "%s: adding rows...\n", ql->name);
+#if DEBUG_QUEUE
+ fprintf(stderr, "%s: adding rows...\n", ql->name);
+#endif
struct queue_entry *after = 0;
for(struct queue_entry *q = newq; q; q = q->next) {
const struct newqueue_data *nqd = hash_find(h, q->id);
gtk_list_store_set(ql->store, iter,
ql->ncolumns + QUEUEPOINTER_COLUMN, q,
-1);
- //fprintf(stderr, " add %s", q->id);
+#if DEBUG_QUEUE
+ fprintf(stderr, " add %s\n", q->id);
+#endif
++inserted;
}
after = newq;
* The current code is simple but amounts to a bubble-sort - we might easily
* called gtk_tree_model_iter_next a couple of thousand times.
*/
- //fprintf(stderr, "%s: rearranging rows\n", ql->name);
- //fprintf(stderr, "%s: queue state: ", ql->name);
- //dump_queue(newq, 0);
- //fprintf(stderr, "%s: row state: ", ql->name);
- //dump_rows(ql);
- it = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ql->store),
- iter);
- struct queue_entry *rq = newq; /* r for 'right, correct' */
+#if DEBUG_QUEUE
+ fprintf(stderr, "%s: rearranging rows\n", ql->name);
+ fprintf(stderr, "%s: target state:\n", ql->name);
+ dump_queue(newq, 0);
+ fprintf(stderr, "%s: current state:\n", ql->name);
+ dump_rows(ql);
+#endif
+ it = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ql->store), iter);
+ struct queue_entry *tq = newq; /* t-for-target */
int swaps = 0, searches = 0;
+ int row = 0;
while(it) {
- struct queue_entry *q = ql_iter_to_q(GTK_TREE_MODEL(ql->store), iter);
- //fprintf(stderr, " rq = %p, q = %p\n", rq, q);
- //fprintf(stderr, " rq->id = %s, q->id = %s\n", rq->id, q->id);
-
- if(q != rq) {
- //fprintf(stderr, " mismatch\n");
+ struct queue_entry *cq = ql_iter_to_q(GTK_TREE_MODEL(ql->store), iter);
+ /* c-for-current */
+
+ /* Everything has the right queue pointer (see above) so it's sufficient to
+ * compare pointers to detect mismatches */
+ if(cq != tq) {
+#if DEBUG_QUEUE
+ fprintf(stderr, " pointer mismatch at row %d\n", row);
+ fprintf(stderr, " target id %s\n", tq->id);
+ fprintf(stderr, " actual id %s\n", cq->id);
+#endif
+ /* Start looking for the target row fromn the next row */
GtkTreeIter next[1] = { *iter };
gboolean nit = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), next);
while(nit) {
struct queue_entry *nq = ql_iter_to_q(GTK_TREE_MODEL(ql->store), next);
- //fprintf(stderr, " candidate: %s\n", nq->id);
- if(nq == rq)
+#if DEBUG_QUEUE
+ fprintf(stderr, " candidate: %s\n", nq->id);
+#endif
+ if(nq == tq)
break;
nit = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), next);
++searches;
}
+ /* Note that this assertion will fail in the face of duplicate IDs.
+ * q->id really does need to be unique. */
assert(nit);
- //fprintf(stderr, " found it\n");
gtk_list_store_swap(ql->store, iter, next);
*iter = *next;
- //fprintf(stderr, "%s: new row state: ", ql->name);
- //dump_rows(ql);
+#if DEBUG_QUEUE
+ fprintf(stderr, "%s: found it. new row state:\n", ql->name);
+ dump_rows(ql);
+#endif
++swaps;
}
/* ...and onto the next one */
it = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), iter);
- rq = rq->next;
+ tq = tq->next;
+ ++row;
}
-#if 0
+#if DEBUG_QUEUE
fprintf(stderr, "%6s: %3d kept %3d inserted %3d deleted %3d swaps %4d searches\n", ql->name,
kept, inserted, deleted, swaps, searches);
+ fprintf(stderr, "done\n");
#endif
- //fprintf(stderr, "done\n");
ql->q = newq;
/* Set the rest of the columns in new rows */
ql_update_list_store(ql);
return path;
}
+#if 0
+static const char *act(GdkDragAction action) {
+ struct dynstr d[1];
+
+ dynstr_init(d);
+ if(action & GDK_ACTION_DEFAULT)
+ dynstr_append_string(d, "|DEFAULT");
+ if(action & GDK_ACTION_COPY)
+ dynstr_append_string(d, "|COPY");
+ if(action & GDK_ACTION_MOVE)
+ dynstr_append_string(d, "|MOVE");
+ if(action & GDK_ACTION_LINK)
+ dynstr_append_string(d, "|LINK");
+ if(action & GDK_ACTION_PRIVATE)
+ dynstr_append_string(d, "|PRIVATE");
+ if(action & GDK_ACTION_ASK)
+ dynstr_append_string(d, "|ASK");
+ dynstr_terminate(d);
+ return d->nvec ? d->vec + 1 : "";
+}
+#endif
+
/** @brief Called when a drag moves within a candidate destination
* @param w Destination widget
* @param dc Drag context
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);*/
+ /* TODO this comes up with the wrong answer sometimes. If we are in the
+ * middle of a rearrange then the suggested action will be COPY, which we'll
+ * take, even though MOVE would actually be appropriate. The drag still
+ * seems to work, but it _is_ wrong. The answer is to take the target into
+ * account. */
+ /*fprintf(stderr, "suggested %s actions %s result %s\n",
+ act(dc->suggested_action), act(dc->actions), act(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)
* @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 info Target @c info parameter
* @param time_ Time data requested (for some reason not a @c time_t)
* @param user_data The queuelike
*
static void ql_drag_data_get(GtkWidget attribute((unused)) *w,
GdkDragContext attribute((unused)) *dc,
GtkSelectionData *data,
- guint attribute((unused)) info_,
+ guint attribute((unused)) info,
guint attribute((unused)) time_,
gpointer user_data) {
struct queuelike *const ql = user_data;
struct dynstr result[1];
+ //fprintf(stderr, "ql_drag_data_get %s info=%d\n", ql->name, info);
dynstr_init(result);
gtk_tree_selection_selected_foreach(ql->selection,
ql_drag_data_get_collect,
gint x,
gint y,
GtkSelectionData *data,
- guint attribute((unused)) info_,
+ guint info_,
guint attribute((unused)) time_,
gpointer user_data) {
struct queuelike *const ql = user_data;
struct vector ids[1], tracks[1];
int parity = 0;
- //fprintf(stderr, "drag-data-received: %d,%d info_=%u\n", x, y, info_);
+ //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) {
GtkTreePath *path = ql_drop_path(w, GTK_TREE_MODEL(ql->store), x, y, &pos);
if(path) {
q = ql_path_to_q(GTK_TREE_MODEL(ql->store), path);
+ //fprintf(stderr, " drop path: %s q=%p pos=%d\n",
+ // gtk_tree_path_to_string(path), q, pos);
} else {
/* This generally means a drop past the end of the queue. We find the last
* element in the queue and ask to move after that. */
for(q = ql->q; q && q->next; q = q->next)
;
+ //fprintf(stderr, " after end. q=%p. pos=%d\n", q, pos);
}
switch(pos) {
case GTK_TREE_VIEW_DROP_BEFORE:
case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
if(q) {
q = q->prev;
- //fprintf(stderr, " ...but we like to drop near %s\n",
+ //fprintf(stderr, " but we like to drop near %s\n",
// q ? q->id : "NULL");
}
break;
/* Note that q->id can match one of ids[]. This doesn't matter for
* moveafter but TODO may matter for playlist support. */
switch(info_) {
- case 0:
- /* Rearrangement. Send ID and track data. */
+ case QUEUED_TRACKS_ID:
+ case PLAYLIST_TRACKS_ID:
+ /* Rearrangement within some widget. Send ID and track data. */
ql->drop(ql, tracks->nvec, tracks->vec, ids->vec, q);
break;
- case 1:
+ case PLAYABLE_TRACKS_ID:
/* Copying between widgets. IDs mean nothing so don't send them. */
ql->drop(ql, tracks->nvec, tracks->vec, NULL, q);
break;
gtk_tree_path_free(path);
}
+static int count_drag_targets(const GtkTargetEntry *targets) {
+ const GtkTargetEntry *t = targets;
+
+ while(t->target)
+ ++t;
+ return t - targets;
+}
+
/** @brief Initialize a @ref queuelike */
GtkWidget *init_queuelike(struct queuelike *ql) {
D(("init_queuelike"));
(ql->columns[n].name,
r,
"text", n,
- "background", ql->ncolumns + BACKGROUND_COLUMN,
+ "cell-background", ql->ncolumns + BACKGROUND_COLUMN,
"foreground", ql->ncolumns + FOREGROUND_COLUMN,
(char *)0);
gtk_tree_view_column_set_resizable(c, TRUE);
/* This view will act as a drag source */
gtk_drag_source_set(ql->view,
GDK_BUTTON1_MASK,
- queuelike_targets,
- sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE);
+ ql->drag_source_targets,
+ count_drag_targets(ql->drag_source_targets),
+ ql->drag_dest_actions);
/* This view will act as a drag destination */
gtk_drag_dest_set(ql->view,
GTK_DEST_DEFAULT_HIGHLIGHT|GTK_DEST_DEFAULT_DROP,
- queuelike_targets,
- sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE|GDK_ACTION_COPY);
+ ql->drag_dest_targets,
+ count_drag_targets(ql->drag_dest_targets),
+ ql->drag_dest_actions);
g_signal_connect(ql->view, "drag-motion",
G_CALLBACK(ql_drag_motion), ql);
g_signal_connect(ql->view, "drag-leave",
/* 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);
+ ql->drag_source_targets,
+ count_drag_targets(ql->drag_source_targets),
+ ql->drag_source_actions);
g_signal_connect(ql->view, "drag-data-get",
G_CALLBACK(ql_drag_data_get), ql);
make_treeview_multidrag(ql->view, NULL);