From a6712ea8e5d17b646625bba5fc1141927c5d6ad9 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Tue, 24 Nov 2009 18:03:13 +0000 Subject: [PATCH] Drag targets are now specified separately for each queuelike. Organization: Straylight/Edgeware From: Richard Kettlewell Playlist rearrangement now implemented. There's still a bug in playlist d+d handling somewhere, e.g. make a 4-element playlist and rearrange the first three elements to after themselves twice in a row. The first time it succeeds (but using an odd after_me) the second time it blows up. --- disobedience/added.c | 2 + disobedience/choose.c | 12 +++-- disobedience/disobedience.h | 2 + disobedience/playlists.c | 90 ++++++++++++++++++++++++++++++------ disobedience/queue-generic.c | 84 +++++++++++++++++++++------------ disobedience/queue-generic.h | 22 +++++++++ disobedience/queue.c | 22 ++++++++- disobedience/recent.c | 2 + 8 files changed, 187 insertions(+), 49 deletions(-) diff --git a/disobedience/added.c b/disobedience/added.c index 7f654ee..aa15f48 100644 --- a/disobedience/added.c +++ b/disobedience/added.c @@ -93,6 +93,8 @@ struct queuelike ql_added = { .ncolumns = sizeof added_columns / sizeof *added_columns, .menuitems = added_menuitems, .nmenuitems = sizeof added_menuitems / sizeof *added_menuitems, + .drag_source_targets = choose_targets, + .drag_source_actions = GDK_ACTION_COPY, }; GtkWidget *added_widget(void) { diff --git a/disobedience/choose.c b/disobedience/choose.c index 9829f19..a1d50c1 100644 --- a/disobedience/choose.c +++ b/disobedience/choose.c @@ -33,15 +33,19 @@ #include "disobedience.h" #include "choose.h" #include "multidrag.h" +#include "queue-generic.h" #include /** @brief Drag types */ -static const GtkTargetEntry choose_targets[] = { +const GtkTargetEntry choose_targets[] = { { - (char *)"text/x-disorder-playable-tracks", /* drag type */ + PLAYABLE_TRACKS, /* drag type */ GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */ - 1 /* ID value */ + PLAYABLE_TRACKS_ID /* ID value */ }, + { + .target = NULL + } }; /** @brief The current selection tree */ @@ -711,7 +715,7 @@ GtkWidget *choose_widget(void) { gtk_drag_source_set(choose_view, GDK_BUTTON1_MASK, choose_targets, - sizeof choose_targets / sizeof *choose_targets, + 1, GDK_ACTION_COPY); g_signal_connect(choose_view, "drag-data-get", G_CALLBACK(choose_drag_data_get), NULL); diff --git a/disobedience/disobedience.h b/disobedience/disobedience.h index e8b2d02..29a278e 100644 --- a/disobedience/disobedience.h +++ b/disobedience/disobedience.h @@ -214,6 +214,8 @@ void choose_update(void); void play_completed(void *v, const char *err); +extern const GtkTargetEntry choose_targets[]; + /* Login details */ void login_box(void); diff --git a/disobedience/playlists.c b/disobedience/playlists.c index 92772d7..f46195d 100644 --- a/disobedience/playlists.c +++ b/disobedience/playlists.c @@ -130,6 +130,22 @@ static struct menuitem playlist_menuitems[] = { { "Deselect all tracks", ql_selectnone_activate, ql_selectnone_sensitive, 0, 0 }, }; +static const GtkTargetEntry playlist_targets[] = { + { + PLAYLIST_TRACKS, /* drag type */ + GTK_TARGET_SAME_WIDGET, /* rearrangement within a widget */ + PLAYLIST_TRACKS_ID /* ID value */ + }, + { + PLAYABLE_TRACKS, /* drag type */ + GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */ + PLAYABLE_TRACKS_ID, /* ID value */ + }, + { + .target = NULL + } +}; + /** @brief Queuelike for editing a playlist */ static struct queuelike ql_playlist = { .name = "playlist", @@ -137,7 +153,11 @@ static struct queuelike ql_playlist = { .ncolumns = sizeof playlist_columns / sizeof *playlist_columns, .menuitems = playlist_menuitems, .nmenuitems = sizeof playlist_menuitems / sizeof *playlist_menuitems, - .drop = playlist_drop + .drop = playlist_drop, + .drag_source_targets = playlist_targets, + .drag_source_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY, + .drag_dest_targets = playlist_targets, + .drag_dest_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY, }; /* Maintaining the list of playlists ---------------------------------------- */ @@ -884,30 +904,72 @@ static void playlist_drop(struct queuelike attribute((unused)) *ql, mod->playlist, mod); } +/** @brief Return true if track @p i is in the moved set */ +static int playlist_drop_is_moved(struct playlist_modify_data *mod, + int i) { + struct queue_entry *q; + + fprintf(stderr, "is %d moved?\n", i); + /* Find the q corresponding to i, so we can get the ID */ + for(q = ql_playlist.q; i; q = q->next, --i) + ; + fprintf(stderr, "id is %s\n", q->id); + /* See if track i matches any of the moved set by ID */ + for(int n = 0; n < mod->ntracks; ++n) + if(!strcmp(q->id, mod->ids[n])) { + fprintf(stderr, "YES, it was moved.\n"); + return 1; + } + fprintf(stderr, "NO it was not.\n"); + return 0; +} + static void playlist_drop_modify(struct playlist_modify_data *mod, int nvec, char **vec) { char **newvec; int nnewvec; + /* after_me is the queue_entry to insert after, or NULL to insert at the + * beginning (including the case when the playlist is empty) */ fprintf(stderr, "after_me = %s\n", mod->after_me ? mod->after_me->track : "NULL"); + struct queue_entry *q = ql_playlist.q; + int ins = 0; + if(mod->after_me) { + ++ins; + while(q && q != mod->after_me) { + q = q->next; + ++ins; + } + } + /* Now ins is the index to insert at; equivalently, the row to insert before, + * and so equal to nvec to append. */ + fprintf(stderr, "ins = %d = %s\n", + ins, ins < nvec ? vec[ins] : "NULL"); + fprintf(stderr, "nvec = %d\n", nvec); if(mod->ids) { /* This is a rearrangement */ - /* TODO what if it's a drag from the queue? */ - abort(); - } else { - /* This is an insertion */ - struct queue_entry *q = ql_playlist.q; - int ins = 0; - if(mod->after_me) { - ++ins; - while(q && q != mod->after_me) { - q = q->next; - ++ins; + nnewvec = nvec; + newvec = xcalloc(nnewvec, sizeof (char *)); + int i = 0; + /* For each destination slot decide what will go there */ + for(int n = 0; n < nnewvec; ++n) { + fprintf(stderr, "n=%d i=%d\n", n, i); + if(n >= ins && n < ins + mod->ntracks) { + fprintf(stderr, "inside insertion range\n"); + newvec[n] = mod->tracks[n - ins]; + } else { + /* Pick the next track from vec[] that is not mentioned in mod->ids[] */ + while(playlist_drop_is_moved(mod, i)) { + ++i; + --ins; + fprintf(stderr, "now: i=%d ins=%d\n", i, ins); + } + newvec[n] = vec[i++]; } } - fprintf(stderr, "ins = %d = %s\n", - ins, ins < nvec ? vec[ins] : "NULL"); + } else { + /* This is an insertion */ nnewvec = nvec + mod->ntracks; newvec = xcalloc(nnewvec, sizeof (char *)); memcpy(newvec, vec, diff --git a/disobedience/queue-generic.c b/disobedience/queue-generic.c index e5f76a9..e2fd48a 100644 --- a/disobedience/queue-generic.c +++ b/disobedience/queue-generic.c @@ -43,19 +43,6 @@ #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, @@ -518,6 +505,28 @@ static GtkTreePath *ql_drop_path(GtkWidget *w, 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 @@ -552,8 +561,13 @@ static gboolean ql_drag_motion(GtkWidget *w, 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) @@ -636,12 +650,13 @@ static void ql_drag_data_get_collect(GtkTreeModel *model, static void ql_drag_data_get(GtkWidget attribute((unused)) *w, GdkDragContext attribute((unused)) *dc, GtkSelectionData *data, - guint attribute((unused)) info_, + guint 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, @@ -674,7 +689,7 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w, 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; @@ -682,7 +697,7 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w, 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) { @@ -739,11 +754,12 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w, /* 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; @@ -752,6 +768,14 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w, 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")); @@ -820,15 +844,15 @@ GtkWidget *init_queuelike(struct queuelike *ql) { /* 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", @@ -843,9 +867,9 @@ GtkWidget *init_queuelike(struct queuelike *ql) { /* 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); diff --git a/disobedience/queue-generic.h b/disobedience/queue-generic.h index 2b8b8ef..efd6400 100644 --- a/disobedience/queue-generic.h +++ b/disobedience/queue-generic.h @@ -104,8 +104,30 @@ struct queuelike { void (*drop)(struct queuelike *ql, int ntracks, char **tracks, char **ids, struct queue_entry *after_me); + /** @brief Source target list */ + const GtkTargetEntry *drag_source_targets; + + /** @brief Drag source actions */ + GdkDragAction drag_source_actions; + + /** @brief Destination target list */ + const GtkTargetEntry *drag_dest_targets; + + /** @brief Drag destination actions */ + GdkDragAction drag_dest_actions; + }; +enum { + PLAYABLE_TRACKS_ID, + QUEUED_TRACKS_ID, + PLAYLIST_TRACKS_ID +}; + +#define PLAYABLE_TRACKS (char *)"text/x-disorder-playable-tracks" +#define QUEUED_TRACKS (char *)"text/x-disorder-queued-tracks" +#define PLAYLIST_TRACKS (char *)"text/x-disorder-playlist-tracks" + enum { QUEUEPOINTER_COLUMN, FOREGROUND_COLUMN, diff --git a/disobedience/queue.c b/disobedience/queue.c index 80b163a..0db90d7 100644 --- a/disobedience/queue.c +++ b/disobedience/queue.c @@ -225,6 +225,22 @@ static struct menuitem queue_menuitems[] = { { "Adopt track", ql_adopt_activate, ql_adopt_sensitive, 0, 0 }, }; +static const GtkTargetEntry queue_targets[] = { + { + QUEUED_TRACKS, /* drag type */ + GTK_TARGET_SAME_WIDGET, /* rearrangement within a widget */ + QUEUED_TRACKS_ID /* ID value */ + }, + { + PLAYABLE_TRACKS, /* drag type */ + GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */ + PLAYABLE_TRACKS_ID, /* ID value */ + }, + { + .target = NULL + } +}; + struct queuelike ql_queue = { .name = "queue", .init = queue_init, @@ -232,7 +248,11 @@ struct queuelike ql_queue = { .ncolumns = sizeof queue_columns / sizeof *queue_columns, .menuitems = queue_menuitems, .nmenuitems = sizeof queue_menuitems / sizeof *queue_menuitems, - .drop = queue_drop + .drop = queue_drop, + .drag_source_targets = queue_targets, + .drag_source_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY, + .drag_dest_targets = queue_targets, + .drag_dest_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY, }; /** @brief Called when a key is pressed in the queue tree view */ diff --git a/disobedience/recent.c b/disobedience/recent.c index f53e631..0e74feb 100644 --- a/disobedience/recent.c +++ b/disobedience/recent.c @@ -91,6 +91,8 @@ struct queuelike ql_recent = { .ncolumns = sizeof recent_columns / sizeof *recent_columns, .menuitems = recent_menuitems, .nmenuitems = sizeof recent_menuitems / sizeof *recent_menuitems, + .drag_source_targets = choose_targets, + .drag_source_actions = GDK_ACTION_COPY, }; GtkWidget *recent_widget(void) { -- [mdw]