+/** @brief Called when the playlist has been updated */
+static void playlist_modify_updated(void attribute((unused)) *v,
+ const char *err) {
+ if(err)
+ popup_protocol_error(0, err);
+ disorder_eclient_playlist_unlock(client, playlist_modify_unlocked, NULL);
+}
+
+/** @brief Called when the playlist has been unlocked */
+static void playlist_modify_unlocked(void attribute((unused)) *v,
+ const char *err) {
+ if(err)
+ popup_protocol_error(0, err);
+}
+
+/* Drop tracks into a playlist ---------------------------------------------- */
+
+static void playlist_drop(struct queuelike attribute((unused)) *ql,
+ int ntracks,
+ char **tracks, char **ids,
+ struct queue_entry *after_me) {
+ struct playlist_modify_data *mod = xmalloc(sizeof *mod);
+
+ mod->playlist = playlist_picker_selected;
+ mod->modify = playlist_drop_modify;
+ mod->ntracks = ntracks;
+ mod->tracks = tracks;
+ mod->ids = ids;
+ mod->after_me = after_me;
+ 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;
+
+ /* Find the q corresponding to i, so we can get the ID */
+ for(q = ql_playlist.q; i; q = q->next, --i)
+ ;
+ /* 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]))
+ return 1;
+ return 0;
+}
+
+static void playlist_drop_modify(struct playlist_modify_data *mod,
+ int nvec, char **vec) {
+ char **newvec;
+ int nnewvec;
+
+ //fprintf(stderr, "\nplaylist_drop_modify\n");
+ /* 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. */
+#if 0
+ fprintf(stderr, "ins = %d = %s\n",
+ ins, ins < nvec ? vec[ins] : "NULL");
+ for(int n = 0; n < nvec; ++n)
+ fprintf(stderr, "%d: %s %s\n", n, n == ins ? "->" : " ", vec[n]);
+ fprintf(stderr, "nvec = %d\n", nvec);
+#endif
+ if(mod->ids) {
+ /* This is a rearrangement */
+ /* We have:
+ * - vec[], the current layout
+ * - ins, pointing into vec
+ * - mod->tracks[], a subset of vec[] which is to be moved
+ *
+ * ins is the insertion point BUT it is in terms of the whole
+ * array, i.e. before mod->tracks[] have been removed. The first
+ * step then is to remove everything in mod->tracks[] and adjust
+ * ins downwards as necessary.
+ */
+ /* First zero out anything that's moved */
+ int before_ins = 0;
+ for(int n = 0; n < nvec; ++n) {
+ if(playlist_drop_is_moved(mod, n)) {
+ vec[n] = NULL;
+ if(n < ins)
+ ++before_ins;
+ }
+ }
+ /* Now collapse down the array */
+ int i = 0;
+ for(int n = 0; n < nvec; ++n) {
+ if(vec[n])
+ vec[i++] = vec[n];
+ }
+ assert(i + mod->ntracks == nvec);
+ nvec = i;
+ /* Adjust the insertion point to take account of things moved from before
+ * it */
+ ins -= before_ins;
+ /* The effect is now the same as an insertion */
+ }
+ /* This is (now) an insertion */
+ nnewvec = nvec + mod->ntracks;
+ newvec = xcalloc(nnewvec, sizeof (char *));
+ memcpy(newvec, vec,
+ ins * sizeof (char *));
+ memcpy(newvec + ins, mod->tracks,
+ mod->ntracks * sizeof (char *));
+ memcpy(newvec + ins + mod->ntracks, vec + ins,
+ (nvec - ins) * sizeof (char *));
+ disorder_eclient_playlist_set(client, playlist_modify_updated, mod->playlist,
+ newvec, nnewvec, mod);