* - the playlist picker (a list of all playlists) TODO should be a tree!
* - an add button
* - a delete button
- * - the playlist view (a drag+drop capable view of the currently picked playlist)
+ * - the playlist editor (a d+d-capable view of the currently picked playlist)
* - a close button TODO
*
* This file also maintains the playlist menu, allowing playlists to be
static void playlist_list_received_playlists(void *v,
const char *err,
int nvec, char **vec);
-static void playlists_fill_tracks(const char *event,
- void *eventdata,
- void *callbackdata);
+static void playlist_editor_fill(const char *event,
+ void *eventdata,
+ void *callbackdata);
+static int playlist_playall_sensitive(void *extra);
+static void playlist_playall_activate(GtkMenuItem *menuitem,
+ gpointer user_data);
+static int playlist_remove_sensitive(void *extra) ;
+static void playlist_remove_activate(GtkMenuItem *menuitem,
+ gpointer user_data);
/** @brief Playlist editing window */
-static GtkWidget *playlists_window;
+static GtkWidget *playlist_window;
/** @brief Columns for the playlist editor */
static const struct queue_column playlist_columns[] = {
{ "Length", column_length, 0, COL_RIGHT }
};
-/** @brief Pop-up menu for playlist editor */
-// TODO some of these may not be generic enough yet - check!
+/** @brief Pop-up menu for playlist editor
+ *
+ * Status:
+ * - track properties works but, bizarrely, raises the main window
+ * - play track works
+ * - play playlist works
+ * - select/deselect all work
+ */
static struct menuitem playlist_menuitems[] = {
{ "Track properties", ql_properties_activate, ql_properties_sensitive, 0, 0 },
{ "Play track", ql_play_activate, ql_play_sensitive, 0, 0 },
- //{ "Play playlist", ql_playall_activate, ql_playall_sensitive, 0, 0 },
- { "Remove track from queue", ql_remove_activate, ql_remove_sensitive, 0, 0 },
+ { "Play playlist", playlist_playall_activate, playlist_playall_sensitive, 0, 0 },
+ { "Remove track from queue", playlist_remove_activate, playlist_remove_sensitive, 0, 0 },
{ "Select all tracks", ql_selectall_activate, ql_selectall_sensitive, 0, 0 },
{ "Deselect all tracks", ql_selectnone_activate, ql_selectnone_sensitive, 0, 0 },
};
/* Playlists menu ----------------------------------------------------------- */
+static void playlist_menu_playing(void attribute((unused)) *v,
+ const char *err) {
+ if(err)
+ popup_protocol_error(0, err);
+}
+
/** @brief Play received playlist contents
*
* Passed as a completion callback by menu_activate_playlist().
return;
}
for(int n = 0; n < nvec; ++n)
- disorder_eclient_play(client, vec[n], NULL, NULL);
+ disorder_eclient_play(client, vec[n], playlist_menu_playing, NULL);
}
/** @brief Called to activate a playlist
/* Window will be modal, suppressing access to other windows */
gtk_window_set_modal(GTK_WINDOW(playlist_new_window), TRUE);
gtk_window_set_transient_for(GTK_WINDOW(playlist_new_window),
- GTK_WINDOW(playlists_window));
+ GTK_WINDOW(playlist_window));
/* Window contents will use a table (grid) layout */
GtkWidget *table = gtk_table_new(3, 3, FALSE/*!homogeneous*/);
void attribute((unused)) *callbackdata) {
GtkTreeIter iter[1];
- if(!playlists_window)
+ if(!playlist_window)
return;
if(!playlist_picker_list)
playlist_picker_list = gtk_list_store_new(1, G_TYPE_STRING);
if(was_selected && !strcmp(was_selected, playlists[n]))
gtk_tree_selection_select_iter(playlist_picker_selection, iter);
}
+ /* TODO deselecting then reselecting the current playlist resets the playlist
+ * editor, which trashes the user's selection. */
}
/** @brief Called when the selection might have changed */
playlist_picker_selected = selected;
/* Re-initalize the queue */
ql_new_queue(&ql_playlist, NULL);
- playlists_fill_tracks(NULL, (void *)playlist_picker_selected, NULL);
+ playlist_editor_fill(NULL, (void *)playlist_picker_selected, NULL);
}
/** @brief Called when the 'add' button is pressed */
if(!playlist_picker_selected)
return; /* shouldn't happen */
- yesno = gtk_message_dialog_new(GTK_WINDOW(playlists_window),
+ yesno = gtk_message_dialog_new(GTK_WINDOW(playlist_window),
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_YES_NO,
return vbox;
}
-/* Playlists window (edit current playlist) --------------------------------- */
+/* Playlist editor ---------------------------------------------------------- */
/** @brief Called with new tracks for the playlist */
-static void playlists_got_new_tracks(void attribute((unused)) *v,
- const char *err,
- int nvec, char **vec) {
- fprintf(stderr, "playlists_got_new_tracks\n");
+static void playlists_editor_received_tracks(void *v,
+ const char *err,
+ int nvec, char **vec) {
+ const char *playlist = v;
if(err) {
popup_protocol_error(0, err);
return;
}
+ if(!playlist_picker_selected
+ || strcmp(playlist, playlist_picker_selected)) {
+ /* The tracks are for the wrong playlist - something must have changed
+ * while the fetch command was in flight. We just ignore this callback,
+ * the right answer will be requested and arrive in due course. */
+ return;
+ }
if(nvec == -1)
/* No such playlist, presumably we'll get a deleted event shortly */
return;
/* 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,
* so we add a serial number to the start. */
- /* TODO but this doesn't work for some reason */
int *serialp = hash_find(h, vec[n]), serial = serialp ? *serialp : 0;
byte_xasprintf((char **)&q->id, "%d-%s", serial++, vec[n]);
- fprintf(stderr, "%s\n", q->id);
hash_add(h, vec[0], &serial, HASH_INSERT_OR_REPLACE);
*qq = q;
qq = &q->next;
}
*qq = NULL;
- fprintf(stderr, "calling ql_new_queue\n");
ql_new_queue(&ql_playlist, newq);
- fprintf(stderr, "back form ql_new_queue\n");
}
/** @brief (Re-)populate the playlist tree model */
-static void playlists_fill_tracks(const char attribute((unused)) *event,
- void *eventdata,
- void attribute((unused)) *callbackdata) {
+static void playlist_editor_fill(const char attribute((unused)) *event,
+ void *eventdata,
+ void attribute((unused)) *callbackdata) {
const char *modified_playlist = eventdata;
- fprintf(stderr, "playlists_fill_tracks: %s\n", modified_playlist);
- if(!playlists_window)
+ if(!playlist_window)
return;
if(!playlist_picker_selected)
return;
if(!strcmp(playlist_picker_selected, modified_playlist))
- disorder_eclient_playlist_get(client, playlists_got_new_tracks,
- playlist_picker_selected, NULL);
+ disorder_eclient_playlist_get(client, playlists_editor_received_tracks,
+ playlist_picker_selected,
+ (void *)playlist_picker_selected);
}
-static GtkWidget *playlists_window_edit(void) {
+static GtkWidget *playlists_editor_create(void) {
assert(ql_playlist.view == NULL); /* better not be set up already */
GtkWidget *w = init_queuelike(&ql_playlist);
/* Initially empty */
return w;
}
+/* Playlist editor right-click menu ---------------------------------------- */
+
+/** @brief Called to determine whether the playlist is playable */
+static int playlist_playall_sensitive(void attribute((unused)) *extra) {
+ /* If there's no playlist obviously we can't play it */
+ if(!playlist_picker_selected)
+ return FALSE;
+ /* If it's empty we can't play it */
+ if(!ql_playlist.q)
+ return FALSE;
+ /* Otherwise we can */
+ return TRUE;
+}
+
+/** @brief Called to play the selected playlist */
+static void playlist_playall_activate(GtkMenuItem attribute((unused)) *menuitem,
+ gpointer attribute((unused)) user_data) {
+ if(!playlist_picker_selected)
+ return;
+ /* Re-use the menu-based activation callback */
+ disorder_eclient_playlist_get(client, playlist_menu_received_content,
+ playlist_picker_selected, NULL);
+}
+
+/** @brief Called to determine whether the playlist is playable */
+static int playlist_remove_sensitive(void attribute((unused)) *extra) {
+ /* If there's no playlist obviously we can't remove from it */
+ if(!playlist_picker_selected)
+ return FALSE;
+ /* If no tracks are selected we cannot remove them */
+ if(!gtk_tree_selection_count_selected_rows(ql_playlist.selection))
+ return FALSE;
+ /* We're good to go */
+ return TRUE;
+}
+
+/** @brief Called to play the selected playlist */
+static void playlist_remove_activate(GtkMenuItem attribute((unused)) *menuitem,
+ gpointer attribute((unused)) user_data) {
+ if(!playlist_picker_selected)
+ return;
+ /* To safely remove rows we must:
+ * - take a lock
+ * - fetch the playlist
+ * - delete the selected rows
+ * - store the playlist
+ * - release the lock
+ */
+ fprintf(stderr, "remove tracks\n"); /* TODO */
+}
+
/* Playlists window --------------------------------------------------------- */
/** @brief Keypress handler */
-static gboolean playlists_keypress(GtkWidget attribute((unused)) *widget,
- GdkEventKey *event,
- gpointer attribute((unused)) user_data) {
+static gboolean playlist_window_keypress(GtkWidget attribute((unused)) *widget,
+ GdkEventKey *event,
+ gpointer attribute((unused)) user_data) {
if(event->state)
return FALSE;
switch(event->keyval) {
case GDK_Escape:
- gtk_widget_destroy(playlists_window);
+ gtk_widget_destroy(playlist_window);
return TRUE;
default:
return FALSE;
}
/** @brief Called when the playlist window is destroyed */
-static void playlists_window_destroyed(GtkWidget attribute((unused)) *widget,
- GtkWidget **widget_pointer) {
+static void playlist_window_destroyed(GtkWidget attribute((unused)) *widget,
+ GtkWidget **widget_pointer) {
destroy_queuelike(&ql_playlist);
*widget_pointer = NULL;
}
*
* Called when the playlists menu item is selected
*/
-void edit_playlists(gpointer attribute((unused)) callback_data,
- guint attribute((unused)) callback_action,
- GtkWidget attribute((unused)) *menu_item) {
+void playlist_window_create(gpointer attribute((unused)) callback_data,
+ guint attribute((unused)) callback_action,
+ GtkWidget attribute((unused)) *menu_item) {
/* If the window already exists, raise it */
- if(playlists_window) {
- gtk_window_present(GTK_WINDOW(playlists_window));
+ if(playlist_window) {
+ gtk_window_present(GTK_WINDOW(playlist_window));
return;
}
/* Create the window */
- playlists_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_widget_set_style(playlists_window, tool_style);
- g_signal_connect(playlists_window, "destroy",
- G_CALLBACK(playlists_window_destroyed), &playlists_window);
- gtk_window_set_title(GTK_WINDOW(playlists_window), "Playlists Management");
+ playlist_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_style(playlist_window, tool_style);
+ g_signal_connect(playlist_window, "destroy",
+ G_CALLBACK(playlist_window_destroyed), &playlist_window);
+ gtk_window_set_title(GTK_WINDOW(playlist_window), "Playlists Management");
/* TODO loads of this is very similar to (copied from!) users.c - can we
* de-dupe? */
/* Keyboard shortcuts */
- g_signal_connect(playlists_window, "key-press-event",
- G_CALLBACK(playlists_keypress), 0);
+ 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(playlists_window), 512, 240);
+ gtk_window_set_default_size(GTK_WINDOW(playlist_window), 512, 240);
GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), playlist_picker_create(),
FALSE/*expand*/, FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), gtk_event_box_new(),
FALSE/*expand*/, FALSE, 2);
- gtk_box_pack_start(GTK_BOX(hbox), playlists_window_edit(),
+ gtk_box_pack_start(GTK_BOX(hbox), playlists_editor_create(),
TRUE/*expand*/, TRUE/*fill*/, 0);
- gtk_container_add(GTK_CONTAINER(playlists_window), frame_widget(hbox, NULL));
- gtk_widget_show_all(playlists_window);
+ gtk_container_add(GTK_CONTAINER(playlist_window), frame_widget(hbox, NULL));
+ gtk_widget_show_all(playlist_window);
}
/** @brief Initialize playlist support */
void playlists_init(void) {
/* We re-get all playlists upon any change... */
event_register("playlist-created", playlist_list_update, 0);
- event_register("playlist-modified", playlist_list_update, 0); /* TODO why? */
event_register("playlist-deleted", playlist_list_update, 0);
/* ...and on reconnection */
event_register("log-connected", playlist_list_update, 0);
/* Update the list of playlists in the edit window when the set changes */
event_register("playlists-updated", playlist_picker_fill, 0);
/* Update the displayed playlist when it is modified */
- event_register("playlist-modified", playlists_fill_tracks, 0);
+ event_register("playlist-modified", playlist_editor_fill, 0);
}
#endif