chiark / gitweb /
Display track length and playing state in Disobedience choose tab. We
authorRichard Kettlewell <rjk@greenend.org.uk>
Thu, 12 Jun 2008 12:25:14 +0000 (13:25 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Thu, 12 Jun 2008 12:25:14 +0000 (13:25 +0100)
enable rules in this tab to allow for easy reading across now that
there's more than one column.

disobedience/Makefile.am
disobedience/choose.c
disobedience/choose.h
disobedience/disobedience.h
disobedience/lookup.c [new file with mode: 0644]
disobedience/queue-generic.c

index ab085f2..90a0bd6 100644 (file)
@@ -29,7 +29,7 @@ disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c   \
        recent.c added.c queue-generic.c queue-generic.h queue-menu.c   \
        choose.c choose-menu.c popup.c misc.c control.c properties.c    \
        menu.c log.c progress.c login.c rtp.c help.c ../lib/memgc.c     \
-       settings.c users.c
+       settings.c users.c lookup.c
 disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \
        $(LIBASOUND) $(COREAUDIO) $(LIBDB)
 disobedience_LDFLAGS=$(GTK_LIBS)
index 27ba940..e59d5b7 100644 (file)
@@ -38,8 +38,6 @@
  * TODO:
  * - sweep up contracted nodes
  * - update when content may have changed (e.g. after a rescan)
- * - playing state
- * - display length of tracks
  */
 
 #include "disobedience.h"
@@ -96,6 +94,38 @@ static gboolean choose_remove_node(GtkTreeIter *it) {
   return gtk_tree_store_remove(choose_store, it);
 }
 
+/** @brief Update length and state fields */
+static gboolean choose_set_state_callback(GtkTreeModel attribute((unused)) *model,
+                                          GtkTreePath attribute((unused)) *path,
+                                          GtkTreeIter *it,
+                                          gpointer attribute((unused)) data) {
+  struct choosedata *cd = choose_iter_to_data(it);
+  if(!cd)
+    return FALSE;                       /* Skip placeholders*/
+  if(cd->type == CHOOSE_FILE) {
+    const long l = namepart_length(cd->track);
+    char length[64];
+    if(l > 0)
+      byte_snprintf(length, sizeof length, "%ld:%02ld", l / 60, l % 60);
+    else
+      length[0] = 0;
+    gtk_tree_store_set(choose_store, it,
+                       LENGTH_COLUMN, length,
+                       STATE_COLUMN, queued(cd->track),
+                       -1);
+  }
+  return FALSE;                         /* continue walking */
+}
+
+/** @brief Called when the queue or playing track change */
+static void choose_set_state(const char attribute((unused)) *event,
+                             void attribute((unused)) *eventdata,
+                             void attribute((unused)) *callbackdata) {
+  gtk_tree_model_foreach(GTK_TREE_MODEL(choose_store),
+                         choose_set_state_callback,
+                         NULL);
+}
+
 /** @brief (Re-)populate a node
  * @param parent_ref Node to populate or NULL to fill root
  * @param nvec Number of children to add
@@ -186,6 +216,9 @@ static void choose_populate(GtkTreeRowReference *parent_ref,
                                                           "display"),
                          CHOOSEDATA_COLUMN, cd,
                          -1);
+      /* Update length and state; we expect this to kick off length lookups
+       * rather than necessarily get the right value the first time round. */
+      choose_set_state_callback(0, 0, it, 0);
       ++inserted;
       /* If we inserted a directory, insert a placeholder too, so it appears to
        * have children; it will be deleted when we expand the directory. */
@@ -260,20 +293,52 @@ static void choose_row_expanded(GtkTreeView attribute((unused)) *treeview,
 GtkWidget *choose_widget(void) {
   /* Create the tree store. */
   choose_store = gtk_tree_store_new(1 + CHOOSEDATA_COLUMN,
+                                    G_TYPE_BOOLEAN,
+                                    G_TYPE_STRING,
                                     G_TYPE_STRING,
                                     G_TYPE_POINTER);
 
   /* Create the view */
   choose_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(choose_store));
+  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(choose_view), TRUE);
 
   /* Create cell renderers and columns */
-  GtkCellRenderer *r = gtk_cell_renderer_text_new();
-  GtkTreeViewColumn *c = gtk_tree_view_column_new_with_attributes
-    ("Track",
-     r,
-     "text", 0,
-     (char *)0);
-  gtk_tree_view_append_column(GTK_TREE_VIEW(choose_view), c);
+  /* TODO use a table */
+  {
+    GtkCellRenderer *r = gtk_cell_renderer_text_new();
+    GtkTreeViewColumn *c = gtk_tree_view_column_new_with_attributes
+      ("Track",
+       r,
+       "text", NAME_COLUMN,
+       (char *)0);
+    gtk_tree_view_column_set_resizable(c, TRUE);
+    gtk_tree_view_column_set_reorderable(c, TRUE);
+    g_object_set(c, "expand", TRUE, (char *)0);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(choose_view), c);
+  }
+  {
+    GtkCellRenderer *r = gtk_cell_renderer_text_new();
+    GtkTreeViewColumn *c = gtk_tree_view_column_new_with_attributes
+      ("Length",
+       r,
+       "text", LENGTH_COLUMN,
+       (char *)0);
+    gtk_tree_view_column_set_resizable(c, TRUE);
+    gtk_tree_view_column_set_reorderable(c, TRUE);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(choose_view), c);
+  }
+  {
+    GtkCellRenderer *r = gtk_cell_renderer_toggle_new();
+    GtkTreeViewColumn *c = gtk_tree_view_column_new_with_attributes
+      ("Queued",
+       r,
+       "active", STATE_COLUMN,
+       (char *)0);
+    gtk_tree_view_column_set_resizable(c, TRUE);
+    gtk_tree_view_column_set_reorderable(c, TRUE);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(choose_view), c);
+    /* TODO make checkbox clickable to queue the track */
+  }
   
   /* The selection should support multiple things being selected */
   choose_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(choose_view));
@@ -287,6 +352,9 @@ GtkWidget *choose_widget(void) {
   /* Catch row expansions so we can fill in placeholders */
   g_signal_connect(choose_view, "row-expanded",
                    G_CALLBACK(choose_row_expanded), 0);
+
+  event_register("queue-list-changed", choose_set_state, 0);
+  event_register("playing-track-changed", choose_set_state, 0);
   
   /* Fill the root */
   disorder_eclient_files(client, choose_files_completed, "", NULL, NULL); 
index 56964d9..49c9b60 100644 (file)
@@ -32,11 +32,13 @@ struct choosedata {
   gchar *sort;
 };
 
-/** @brief Track name column number */
-#define NAME_COLUMN 0
-
-/** @brief Hidden column number */
-#define CHOOSEDATA_COLUMN 1
+/** @brief Column numbers */
+enum {
+  STATE_COLUMN,
+  NAME_COLUMN,
+  LENGTH_COLUMN,
+  CHOOSEDATA_COLUMN
+};
 
 /** @brief @ref choosedata node is a file */
 #define CHOOSE_FILE 0
index faf7597..280825c 100644 (file)
@@ -208,13 +208,19 @@ void queue_properties(struct queuelike *ql);
 int queued(const char *track);
 /* Return nonzero iff TRACK is queued or playing */
 
+extern struct queue_entry *playing_track;
+
+/* Lookups */
+const char *namepart(const char *track,
+                     const char *context,
+                     const char *part);
+long namepart_length(const char *track);
+
 void namepart_update(const char *track,
                      const char *context,
                      const char *part);
 /* Called when a namepart might have changed */
 
-extern struct queue_entry *playing_track;
-
 /* Choose */
 
 GtkWidget *choose_widget(void);
diff --git a/disobedience/lookup.c b/disobedience/lookup.c
new file mode 100644 (file)
index 0000000..2052b30
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2008 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+#include "disobedience.h"
+
+static int namepart_lookups_outstanding;
+static const struct cache_type cachetype_string = { 3600 };
+static const struct cache_type cachetype_integer = { 3600 };
+
+/** @brief Called when a namepart lookup has completed or failed
+ *
+ * When there are no lookups in flight a redraw is provoked.  This might well
+ * provoke further lookups.
+ */
+static void namepart_completed_or_failed(void) {
+  --namepart_lookups_outstanding;
+  if(!namepart_lookups_outstanding)
+    /* When all lookups complete, we update any displays that care */
+    event_raise("lookups-completed", 0);
+}
+
+/** @brief Called when a namepart lookup has completed */
+static void namepart_completed(void *v, const char *error, const char *value) {
+  D(("namepart_completed"));
+  if(error) {
+    gtk_label_set_text(GTK_LABEL(report_label), error);
+    value = "?";
+  }
+  const char *key = v;
+  
+  cache_put(&cachetype_string, key, value);
+  namepart_completed_or_failed();
+}
+
+/** @brief Called when a length lookup has completed */
+static void length_completed(void *v, const char *error, long l) {
+  D(("length_completed"));
+  if(error) {
+    gtk_label_set_text(GTK_LABEL(report_label), error);
+    l = -1;
+  }
+  const char *key = v;
+  long *value;
+  
+  D(("namepart_completed"));
+  value = xmalloc(sizeof *value);
+  *value = l;
+  cache_put(&cachetype_integer, key, value);
+  namepart_completed_or_failed();
+}
+
+/** @brief Arrange to fill in a namepart cache entry */
+static void namepart_fill(const char *track,
+                          const char *context,
+                          const char *part,
+                          const char *key) {
+  D(("namepart_fill %s %s %s %s", track, context, part, key));
+  /* We limit the total number of lookups in flight */
+  ++namepart_lookups_outstanding;
+  D(("namepart_lookups_outstanding -> %d\n", namepart_lookups_outstanding));
+  disorder_eclient_namepart(client, namepart_completed,
+                            track, context, part, (void *)key);
+}
+
+/** @brief Look up a namepart
+ * @param track Track name
+ * @param context Context
+ * @param part Name part
+ * @param lookup If nonzero, will schedule a lookup for unknown values
+ *
+ * If it is in the cache then just return its value.  If not then look it up
+ * and arrange for the queues to be updated when its value is available. */
+const char *namepart(const char *track,
+                     const char *context,
+                     const char *part) {
+  char *key;
+  const char *value;
+
+  D(("namepart %s %s %s", track, context, part));
+  byte_xasprintf(&key, "namepart context=%s part=%s track=%s",
+                 context, part, track);
+  value = cache_get(&cachetype_string, key);
+  if(!value) {
+    D(("deferring..."));
+    namepart_fill(track, context, part, key);
+    value = "?";
+  }
+  return value;
+}
+
+/** @brief Called from @ref disobedience/properties.c when we know a name part has changed */
+void namepart_update(const char *track,
+                     const char *context,
+                     const char *part) {
+  char *key;
+
+  byte_xasprintf(&key, "namepart context=%s part=%s track=%s",
+                 context, part, track);
+  /* Only refetch if it's actually in the cache. */
+  if(cache_get(&cachetype_string, key))
+    namepart_fill(track, context, part, key);
+}
+
+/** @brief Look up a track length
+ *
+ * If it is in the cache then just return its value.  If not then look it up
+ * and arrange for the queues to be updated when its value is available. */
+long namepart_length(const char *track) {
+  char *key;
+  const long *value;
+
+  D(("getlength %s", track));
+  byte_xasprintf(&key, "length track=%s", track);
+  value = cache_get(&cachetype_integer, key);
+  if(value)
+    return *value;
+  D(("deferring..."));;
+  ++namepart_lookups_outstanding;
+  D(("namepart_lookups_outstanding -> %d\n", namepart_lookups_outstanding));
+  disorder_eclient_length(client, length_completed, track, key);
+  return -1;
+}
+
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 9c9e549..cc6bf53 100644 (file)
@@ -50,124 +50,11 @@ static struct queuelike *const queuelikes[] = {
 
 /* Track detail lookup ----------------------------------------------------- */
 
-static int namepart_lookups_outstanding;
-static const struct cache_type cachetype_string = { 3600 };
-static const struct cache_type cachetype_integer = { 3600 };
-
-/** @brief Called when a namepart lookup has completed or failed
- *
- * When there are no lookups in flight a redraw is provoked.  This might well
- * provoke further lookups.
- */
-static void namepart_completed_or_failed(void) {
-  --namepart_lookups_outstanding;
-  if(!namepart_lookups_outstanding) {
-    /* There are no more lookups outstanding, so we update the display */
-    for(unsigned n = 0; n < NQUEUELIKES; ++n)
-      ql_update_list_store(queuelikes[n]);
-  }
-}
-
-/** @brief Called when a namepart lookup has completed */
-static void namepart_completed(void *v, const char *error, const char *value) {
-  D(("namepart_completed"));
-  if(error) {
-    gtk_label_set_text(GTK_LABEL(report_label), error);
-    value = "?";
-  }
-  const char *key = v;
-  
-  cache_put(&cachetype_string, key, value);
-  namepart_completed_or_failed();
-}
-
-/** @brief Called when a length lookup has completed */
-static void length_completed(void *v, const char *error, long l) {
-  D(("length_completed"));
-  if(error) {
-    gtk_label_set_text(GTK_LABEL(report_label), error);
-    l = -1;
-  }
-  const char *key = v;
-  long *value;
-  
-  D(("namepart_completed"));
-  value = xmalloc(sizeof *value);
-  *value = l;
-  cache_put(&cachetype_integer, key, value);
-  namepart_completed_or_failed();
-}
-
-/** @brief Arrange to fill in a namepart cache entry */
-static void namepart_fill(const char *track,
-                          const char *context,
-                          const char *part,
-                          const char *key) {
-  D(("namepart_fill %s %s %s %s", track, context, part, key));
-  /* We limit the total number of lookups in flight */
-  ++namepart_lookups_outstanding;
-  D(("namepart_lookups_outstanding -> %d\n", namepart_lookups_outstanding));
-  disorder_eclient_namepart(client, namepart_completed,
-                            track, context, part, (void *)key);
-}
-
-/** @brief Look up a namepart
- * @param track Track name
- * @param context Context
- * @param part Name part
- * @param lookup If nonzero, will schedule a lookup for unknown values
- *
- * If it is in the cache then just return its value.  If not then look it up
- * and arrange for the queues to be updated when its value is available. */
-static const char *namepart(const char *track,
-                            const char *context,
-                            const char *part) {
-  char *key;
-  const char *value;
-
-  D(("namepart %s %s %s", track, context, part));
-  byte_xasprintf(&key, "namepart context=%s part=%s track=%s",
-                 context, part, track);
-  value = cache_get(&cachetype_string, key);
-  if(!value) {
-    D(("deferring..."));
-    namepart_fill(track, context, part, key);
-    value = "?";
-  }
-  return value;
-}
-
-/** @brief Called from @ref disobedience/properties.c when we know a name part has changed */
-void namepart_update(const char *track,
-                     const char *context,
-                     const char *part) {
-  char *key;
-
-  byte_xasprintf(&key, "namepart context=%s part=%s track=%s",
-                 context, part, track);
-  /* Only refetch if it's actually in the cache. */
-  if(cache_get(&cachetype_string, key))
-    namepart_fill(track, context, part, key);
-}
-
-/** @brief Look up a track length
- *
- * If it is in the cache then just return its value.  If not then look it up
- * and arrange for the queues to be updated when its value is available. */
-static long getlength(const char *track) {
-  char *key;
-  const long *value;
-
-  D(("getlength %s", track));
-  byte_xasprintf(&key, "length track=%s", track);
-  value = cache_get(&cachetype_integer, key);
-  if(value)
-    return *value;
-  D(("deferring..."));;
-  ++namepart_lookups_outstanding;
-  D(("namepart_lookups_outstanding -> %d\n", namepart_lookups_outstanding));
-  disorder_eclient_length(client, length_completed, track, key);
-  return -1;
+static void queue_lookups_completed(const char attribute((unused)) *event,
+                                   void attribute((unused)) *eventdata,
+                                   void *callbackdata) {
+  struct queuelike *ql = callbackdata;
+  ql_update_list_store(ql);
 }
 
 /* Column formatting -------------------------------------------------------- */
@@ -229,7 +116,7 @@ const char *column_length(const struct queue_entry *q,
   char *played = 0, *length = 0;
 
   /* Work out what to say for the length */
-  l = getlength(q->track);
+  l = namepart_length(q->track);
   if(l > 0)
     byte_xasprintf(&length, "%ld:%02ld", l / 60, l % 60);
   else
@@ -544,6 +431,9 @@ GtkWidget *init_queuelike(struct queuelike *ql) {
 
   ql->init();
 
+  /* Update display text when lookups complete */
+  event_register("lookups-completed", queue_lookups_completed, ql);
+  
   GtkWidget *scrolled = scroll_widget(ql->view);
   g_object_set_data(G_OBJECT(scrolled), "type", (void *)ql_tabtype(ql));
   return scrolled;