chiark / gitweb /
disobedience/control.c: Handle state-change toggles better.
[disorder] / disobedience / control.c
index 1cdc190b4ea504af212a2b374925b9ee561fcdf6..9153f33b1f5dfc55b0f20148a9ecc5411e1ec87c 100644 (file)
@@ -31,6 +31,24 @@ static void toggled_icon(GtkToggleToolButton *button,
 static void clicked_menu(GtkMenuItem *, gpointer userdata);
 static void toggled_menu(GtkCheckMenuItem *, gpointer userdata);
 
+static int enable_playing(disorder_eclient *c,
+                          disorder_eclient_no_response *completed,
+                          void *v);
+static int disable_playing(disorder_eclient *c,
+                           disorder_eclient_no_response *completed,
+                           void *v);
+static int enable_random(disorder_eclient *c,
+                         disorder_eclient_no_response *completed,
+                         void *v);
+static int disable_random(disorder_eclient *c,
+                          disorder_eclient_no_response *completed,
+                          void *v);
+static int pause_track(disorder_eclient *c,
+                       disorder_eclient_no_response *completed,
+                       void *v);
+static int resume_track(disorder_eclient *c,
+                        disorder_eclient_no_response *completed,
+                        void *v);
 static int enable_rtp(disorder_eclient *c,
                       disorder_eclient_no_response *completed,
                       void *v);
@@ -65,6 +83,20 @@ int suppress_actions = 1;
 /** @brief Toolbar widget */
 static GtkWidget *toolbar;
 
+/** @brief Old state to restore on error
+ *
+ * Here's the problem we're trying to solve.  Suppose the user clicks the
+ * `pause' button.  Immediately, Gtk toggles the button's state.  There's also
+ * a menu item which is @i not yet updated, so we hack @c last_state and call
+ * icon_changed() by hand.  We send a `pause' command to the server.  If all is
+ * well, then we're done: we'll get a state update in the server log which will
+ * match what we're already doing and everything is fine.  If there's an error,
+ * though, we must put @c last_state back the way we found it since there will
+ * be no change reported in the server log to correct the wrong state
+ * information.  Also, we must untoggle the icon and menu items.
+ */
+static unsigned long restore_state;
+
 /** @brief Definition of an icon
  *
  * We have two kinds of icon:
@@ -183,70 +215,70 @@ static int rtp_sensitive(void) {
 /** @brief Table of all icons */
 static struct icon icons[] = {
   {
-    toggle: TRUE,
-    stock: TRUE,
-    icon: GTK_STOCK_MEDIA_PAUSE,
-    label: "Pause",
-    tip_on: "Resume playing track",
-    tip_off: "Pause playing track",
-    menuitem: "<GdisorderMain>/Control/Playing",
-    on: pause_resume_on,
-    sensitive: pause_resume_sensitive,
-    action_go_on: disorder_eclient_pause,
-    action_go_off: disorder_eclient_resume,
-    events: "pause-changed playing-changed rights-changed playing-track-changed",
-    menu_invert: TRUE,
+    .toggle = TRUE,
+    .stock = TRUE,
+    .icon = GTK_STOCK_MEDIA_PAUSE,
+    .label = "Pause",
+    .tip_on = "Resume playing track",
+    .tip_off = "Pause playing track",
+    .menuitem = "<GdisorderMain>/Control/Playing",
+    .on = pause_resume_on,
+    .sensitive = pause_resume_sensitive,
+    .action_go_on = pause_track,
+    .action_go_off = resume_track,
+    .events = "pause-changed playing-changed rights-changed playing-track-changed",
+    .menu_invert = TRUE,
   },
   {
-    stock: TRUE,
-    icon: GTK_STOCK_STOP,
-    label: "Scratch",
-    tip_on: "Cancel playing track",
-    menuitem: "<GdisorderMain>/Control/Scratch",
-    sensitive: scratch_sensitive,
-    action_go_off: disorder_eclient_scratch_playing,
-    events: "playing-track-changed rights-changed",
+    .stock = TRUE,
+    .icon = GTK_STOCK_STOP,
+    .label = "Scratch",
+    .tip_on = "Cancel playing track",
+    .menuitem = "<GdisorderMain>/Control/Scratch",
+    .sensitive = scratch_sensitive,
+    .action_go_off = disorder_eclient_scratch_playing,
+    .events = "playing-track-changed rights-changed",
   },
   {
-    toggle: TRUE,
-    stock: FALSE,
-    icon: "cards24.png",
-    label: "Random",
-    tip_on: "Disable random play",
-    tip_off: "Enable random play",
-    menuitem: "<GdisorderMain>/Control/Random play",
-    on: random_enabled,
-    sensitive: random_sensitive,
-    action_go_on: disorder_eclient_random_enable,
-    action_go_off: disorder_eclient_random_disable,
-    events: "random-changed rights-changed",
+    .toggle = TRUE,
+    .stock = FALSE,
+    .icon = "cards24.png",
+    .label = "Random",
+    .tip_on = "Disable random play",
+    .tip_off = "Enable random play",
+    .menuitem = "<GdisorderMain>/Control/Random play",
+    .on = random_enabled,
+    .sensitive = random_sensitive,
+    .action_go_on = enable_random,
+    .action_go_off = disable_random,
+    .events = "random-changed rights-changed",
   },
   {
-    toggle: TRUE,
-    stock: TRUE,
-    icon: GTK_STOCK_MEDIA_PLAY,
-    label: "Play",
-    tip_on: "Disable play",
-    tip_off: "Enable play",
-    on: playing_enabled,
-    sensitive: playing_sensitive,
-    action_go_on: disorder_eclient_enable,
-    action_go_off: disorder_eclient_disable,
-    events: "enabled-changed rights-changed",
+    .toggle = TRUE,
+    .stock = TRUE,
+    .icon = GTK_STOCK_MEDIA_PLAY,
+    .label = "Play",
+    .tip_on = "Disable play",
+    .tip_off = "Enable play",
+    .on = playing_enabled,
+    .sensitive = playing_sensitive,
+    .action_go_on = enable_playing,
+    .action_go_off = disable_playing,
+    .events = "enabled-changed rights-changed",
   },
   {
-    toggle: TRUE,
-    stock: TRUE,
-    icon: GTK_STOCK_CONNECT,
-    label: "RTP",
-    tip_on: "Stop playing network stream",
-    tip_off: "Play network stream",
-    menuitem: "<GdisorderMain>/Control/Network player",
-    on: rtp_enabled,
-    sensitive: rtp_sensitive,
-    action_go_on: enable_rtp,
-    action_go_off: disable_rtp,
-    events: "rtp-changed",
+    .toggle = TRUE,
+    .stock = TRUE,
+    .icon = GTK_STOCK_CONNECT,
+    .label = "RTP",
+    .tip_on = "Stop playing network stream",
+    .tip_off = "Play network stream",
+    .menuitem = "<GdisorderMain>/Control/Network player",
+    .on = rtp_enabled,
+    .sensitive = rtp_sensitive,
+    .action_go_on = enable_rtp,
+    .action_go_off = disable_rtp,
+    .events = "rtp-changed",
   },
 };
 
@@ -271,8 +303,8 @@ GtkWidget *control_widget(void) {
    * sliders hang down from the toolbar so it unavoidably gets the whole width
    * of the window to play with. */
   gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), FALSE);
-  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), 
-                        full_mode ? GTK_TOOLBAR_BOTH : GTK_TOOLBAR_ICONS);
+  if(full_mode) gtk_toolbar_unset_style(GTK_TOOLBAR(toolbar));
+  else gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
   for(n = 0; n < NICONS; ++n) {
     struct icon *const icon = &icons[n];
     icon->button = (icon->toggle
@@ -364,8 +396,7 @@ GtkWidget *control_widget(void) {
 static int volume_supported(void) {
   /* TODO: if the server doesn't know how to set the volume [but isn't using
    * network play] then we should have volume_supported = FALSE */
-  return (!rtp_supported
-          || (rtp_supported && backend && backend->set_volume));
+  return 1;
 }
 
 /** @brief Update the volume control when it changes */
@@ -429,10 +460,12 @@ static void icon_changed(const char attribute((unused)) *event,
   --suppress_actions;
 }
 
-static void icon_action_completed(void attribute((unused)) *v,
-                                  const char *err) {
-  if(err)
+static void icon_action_completed(void *v, const char *err) {
+  if(err) {
+    last_state = restore_state;
     popup_protocol_error(0, err);
+    icon_changed(0, 0, v);
+  }
 }
 
 static void clicked_icon(GtkToolButton attribute((unused)) *button,
@@ -450,10 +483,12 @@ static void toggled_icon(GtkToggleToolButton attribute((unused)) *button,
 
   if(suppress_actions)
     return;
+  restore_state = last_state;
   if(icon->on())
     icon->action_go_off(client, icon_action_completed, 0);
   else
     icon->action_go_on(client, icon_action_completed, 0);
+  icon_changed(0, 0, user_data);
 }
 
 static void clicked_menu(GtkMenuItem attribute((unused)) *menuitem,
@@ -493,8 +528,7 @@ static void volume_adjusted(GtkAdjustment attribute((unused)) *a,
    * from the log. */
   if(rtp_supported) {
     int l = nearbyint(left(v, b) * 100), r = nearbyint(right(v, b) * 100);
-    if(backend && backend->set_volume)
-      backend->set_volume(&l, &r);
+    rtp_setvol(&l, &r);
   } else
     disorder_eclient_set_volume(client, volume_completed,
                                 nearbyint(left(v, b) * 100),
@@ -585,6 +619,48 @@ static double balance(double l, double r) {
     return 0;
 }
 
+/** @brief Called to enable jukebox playback */
+static int enable_playing(disorder_eclient *c,
+                          disorder_eclient_no_response *completed, void *v) {
+  last_state |= DISORDER_PLAYING;
+  return disorder_eclient_enable(c, completed, v);
+}
+
+/** @brief Called to disable jukebox playback */
+static int disable_playing(disorder_eclient *c,
+                           disorder_eclient_no_response *completed, void *v) {
+  last_state &= ~DISORDER_PLAYING;
+  return disorder_eclient_disable(c, completed, v);
+}
+
+/** @brief Called to enable random selection */
+static int enable_random(disorder_eclient *c,
+                         disorder_eclient_no_response *completed, void *v) {
+  last_state |= DISORDER_RANDOM_ENABLED;
+  return disorder_eclient_random_enable(c, completed, v);
+}
+
+/** @brief Called to disable random selection */
+static int disable_random(disorder_eclient *c,
+                          disorder_eclient_no_response *completed, void *v) {
+  last_state &= ~DISORDER_RANDOM_ENABLED;
+  return disorder_eclient_random_disable(c, completed, v);
+}
+
+/** @brief Called to pause the current track */
+static int pause_track(disorder_eclient *c,
+                       disorder_eclient_no_response *completed, void *v) {
+  last_state |= DISORDER_TRACK_PAUSED;
+  return disorder_eclient_pause(c, completed, v);
+}
+
+/** @brief Called to resume the current track */
+static int resume_track(disorder_eclient *c,
+                        disorder_eclient_no_response *completed, void *v) {
+  last_state &= ~DISORDER_TRACK_PAUSED;
+  return disorder_eclient_resume(c, completed, v);
+}
+
 /** @brief Called to enable RTP play
  *
  * Rather odd signature is to fit in with the other icons which all call @ref
@@ -594,6 +670,7 @@ static int enable_rtp(disorder_eclient attribute((unused)) *c,
                       disorder_eclient_no_response attribute((unused)) *completed,
                       void attribute((unused)) *v) {
   start_rtp();
+  rtp_is_running = 1;
   return 0;
 }
 
@@ -606,6 +683,7 @@ static int disable_rtp(disorder_eclient attribute((unused)) *c,
                        disorder_eclient_no_response attribute((unused)) *completed,
                        void attribute((unused)) *v) {
   stop_rtp();
+  rtp_is_running = 0;
   return 0;
 }
 
@@ -619,8 +697,8 @@ static void control_minimode(const char attribute((unused)) *event,
     gtk_widget_hide(balance_widget);
     gtk_scale_set_value_pos(GTK_SCALE(volume_widget), GTK_POS_RIGHT);
   }
-  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), 
-                        full_mode ? GTK_TOOLBAR_BOTH : GTK_TOOLBAR_ICONS);
+  if(full_mode) gtk_toolbar_unset_style(GTK_TOOLBAR(toolbar));
+  else gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
 }
 
 /*