X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/7a3eb8eb516c8cdb35eaf2e41030e802b623e713..a6712ea8e5d17b646625bba5fc1141927c5d6ad9:/disobedience/playlists.c diff --git a/disobedience/playlists.c b/disobedience/playlists.c index 37bb0ea..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 ---------------------------------------- */ @@ -756,7 +776,7 @@ static void playlists_editor_received_tracks(void *v, * so we add a serial number to the start. */ int *serialp = hash_find(h, vec[n]), serial = serialp ? *serialp : 0; byte_xasprintf((char **)&q->id, "%d-%s", serial++, vec[n]); - hash_add(h, vec[0], &serial, HASH_INSERT_OR_REPLACE); + hash_add(h, vec[n], &serial, HASH_INSERT_OR_REPLACE); *qq = q; qq = &q->next; } @@ -768,6 +788,16 @@ static void playlists_editor_received_tracks(void *v, /** @brief State structure for guarded playlist modification * + * To safely move, insert or delete rows we must: + * - take a lock + * - fetch the playlist + * - verify it's not changed + * - update the playlist contents + * - store the playlist + * - release the lock + * + * The playlist_modify_ functions do just that. + * * To kick things off create one of these and disorder_eclient_playlist_lock() * with playlist_modify_locked() as its callback. @c modify will be called; it * should disorder_eclient_playlist_set() to set the new state with @@ -870,38 +900,76 @@ static void playlist_drop(struct queuelike attribute((unused)) *ql, mod->tracks = tracks; mod->ids = ids; mod->after_me = after_me; - /* To safely move or insert rows we must: - * - take a lock - * - fetch the playlist - * - verify it's not changed - * - update the playlist contents - * - store the playlist - * - release the lock - * - */ disorder_eclient_playlist_lock(client, playlist_modify_locked, 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++]; } } + } else { + /* This is an insertion */ nnewvec = nvec + mod->ntracks; newvec = xcalloc(nnewvec, sizeof (char *)); memcpy(newvec, vec, @@ -954,20 +1022,9 @@ static int playlist_remove_sensitive(void attribute((unused)) *extra) { /** @brief Called to play the selected playlist */ static void playlist_remove_activate(GtkMenuItem attribute((unused)) *menuitem, gpointer attribute((unused)) user_data) { + /* TODO backspace should work too */ if(!playlist_picker_selected) return; - /* To safely remove rows we must: - * - take a lock - * - fetch the playlist - * - verify it's not changed - * - delete the selected rows from the retrieved version - * - store the playlist - * - release the lock - * - * In addition we careful check that the selected playlist hasn't changed - * underfoot, and avoid leaving the playlist locked if we bail out at any - * point. - */ struct playlist_modify_data *mod = xmalloc(sizeof *mod); mod->playlist = playlist_picker_selected;