int nvec, char **vec);
static void playlist_remove_modify(struct playlist_modify_data *mod,
int nvec, char **vec);
+static gboolean playlist_new_keypress(GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data);
+static gboolean playlist_picker_keypress(GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data);
/** @brief Playlist editing window */
static GtkWidget *playlist_window;
{ "Artist", column_namepart, "artist", COL_EXPAND|COL_ELLIPSIZE },
{ "Album", column_namepart, "album", COL_EXPAND|COL_ELLIPSIZE },
{ "Title", column_namepart, "title", COL_EXPAND|COL_ELLIPSIZE },
- { "Length", column_length, 0, COL_RIGHT }
};
/** @brief Pop-up menu for playlist editor
/* Set initial state of OK button */
playlist_new_changed(0,0,0);
- /* TODO: return should = OK, escape should = cancel */
+ g_signal_connect(playlist_new_window, "key-press-event",
+ G_CALLBACK(playlist_new_keypress), 0);
/* Display the window */
gtk_widget_show_all(playlist_new_window);
}
+/** @brief Keypress handler */
+static gboolean playlist_new_keypress(GtkWidget attribute((unused)) *widget,
+ GdkEventKey *event,
+ gpointer attribute((unused)) user_data) {
+ if(event->state)
+ return FALSE;
+ switch(event->keyval) {
+ case GDK_Return:
+ playlist_new_ok(NULL, NULL);
+ return TRUE;
+ case GDK_Escape:
+ gtk_widget_destroy(playlist_new_window);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
/** @brief Called when 'ok' is clicked in new-playlist popup */
static void playlist_new_ok(GtkButton attribute((unused)) *button,
gpointer attribute((unused)) userdata) {
+ if(playlist_new_valid())
+ return;
gboolean shared, public, private;
char *name, *fullname;
playlist_new_details(&name, &fullname, &shared, &public, &private);
gtk_widget_set_sensitive(playlist_picker_delete_button, 1);
else
gtk_widget_set_sensitive(playlist_picker_delete_button, 0);
+ /* TODO delete should not be sensitive for public playlists owned by other
+ * users */
/* Eliminate no-change cases */
if(!selected && !playlist_picker_selected)
return;
int res;
if(!playlist_picker_selected)
- return; /* shouldn't happen */
+ return;
yesno = gtk_message_dialog_new(GTK_WINDOW(playlist_window),
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
gtk_box_pack_start(GTK_BOX(vbox), scroll_widget(tree), TRUE/*expand*/, TRUE/*fill*/, 0);
gtk_box_pack_start(GTK_BOX(vbox), buttons, FALSE/*expand*/, FALSE, 0);
+ g_signal_connect(tree, "key-press-event",
+ G_CALLBACK(playlist_picker_keypress), 0);
+
return vbox;
}
+static gboolean playlist_picker_keypress(GtkWidget attribute((unused)) *widget,
+ GdkEventKey *event,
+ gpointer attribute((unused)) user_data) {
+ if(event->state)
+ return FALSE;
+ switch(event->keyval) {
+ case GDK_BackSpace:
+ case GDK_Delete:
+ playlist_picker_delete(NULL, NULL);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static void playlist_picker_destroy(void) {
+ playlist_picker_delete_button = NULL;
+ g_object_unref(playlist_picker_list);
+ playlist_picker_list = NULL;
+ playlist_picker_selection = NULL;
+ playlist_picker_selected = NULL;
+}
+
/* Playlist editor ---------------------------------------------------------- */
static GtkWidget *playlists_editor_create(void) {
/* No such playlist, presumably we'll get a deleted event shortly */
return;
/* Translate the list of tracks into queue entries */
- struct queue_entry *newq, **qq = &newq;
+ struct queue_entry *newq, **qq = &newq, *qprev = NULL;
hash *h = hash_new(sizeof(int));
for(int n = 0; n < nvec; ++n) {
struct queue_entry *q = xmalloc(sizeof *q);
+ q->prev = qprev;
q->track = vec[n];
/* Synthesize a unique ID so that the selection survives updates. Tracks
* can appear more than once in the queue so we can't use raw track names,
hash_add(h, vec[n], &serial, HASH_INSERT_OR_REPLACE);
*qq = q;
qq = &q->next;
+ qprev = q;
}
*qq = NULL;
ql_new_queue(&ql_playlist, newq);
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");
+ if(!strcmp(q->id, mod->ids[n]))
return 1;
- }
- fprintf(stderr, "NO it was not.\n");
return 0;
}
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");
+ //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) {
}
/* 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 */
- 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++];
+ /* 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;
}
}
- } else {
- /* This is 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 *));
+ /* 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);
}
return TRUE;
}
-/** @brief Called to play the selected playlist */
+/** @brief Called to remove 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;
struct playlist_modify_data *mod = xmalloc(sizeof *mod);
g_signal_connect(playlist_window, "key-press-event",
G_CALLBACK(playlist_window_keypress), 0);
/* default size is too small */
- gtk_window_set_default_size(GTK_WINDOW(playlist_window), 512, 240);
+ gtk_window_set_default_size(GTK_WINDOW(playlist_window), 640, 320);
GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), playlist_picker_create(),
static void playlist_window_destroyed(GtkWidget attribute((unused)) *widget,
GtkWidget **widget_pointer) {
destroy_queuelike(&ql_playlist);
+ playlist_picker_destroy();
*widget_pointer = NULL;
}