chiark / gitweb /
Work on playlist right-click menu
[disorder] / disobedience / playlists.c
index 3fd2f551b1872c0338bef9f1b450701695b0d506..2673e1c526ddb325ab6ced81a212790dc6a00a35 100644 (file)
@@ -24,7 +24,7 @@
  * - 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[] = {
@@ -60,13 +66,19 @@ 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 },
 };
@@ -144,6 +156,12 @@ static void playlist_list_received_playlists(void attribute((unused)) *v,
 
 /* 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().
@@ -156,7 +174,7 @@ static void playlist_menu_received_content(void attribute((unused)) *v,
     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
@@ -414,7 +432,7 @@ static void playlist_new_playlist(void) {
   /* 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*/);
@@ -491,7 +509,7 @@ static void playlist_picker_fill(const char attribute((unused)) *event,
                                  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);
@@ -506,6 +524,8 @@ static void playlist_picker_fill(const char attribute((unused)) *event,
     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 */
@@ -538,7 +558,7 @@ static void playlist_picker_selection_changed(GtkTreeSelection attribute((unused
   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 */
@@ -564,7 +584,7 @@ static void playlist_picker_delete(GtkButton attribute((unused)) *button,
 
   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,
@@ -635,17 +655,24 @@ static GtkWidget *playlist_picker_create(void) {
   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;
@@ -658,53 +685,100 @@ static void playlists_got_new_tracks(void attribute((unused)) *v,
     /* 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;
@@ -712,8 +786,8 @@ static gboolean playlists_keypress(GtkWidget attribute((unused)) *widget,
 }
 
 /** @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;
 }
@@ -722,45 +796,44 @@ static void playlists_window_destroyed(GtkWidget attribute((unused)) *widget,
  *
  * 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);
@@ -776,7 +849,7 @@ void playlists_init(void) {
   /* 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