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)
* 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"
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
"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. */
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));
/* 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);
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
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);
--- /dev/null
+/*
+ * 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:
+*/
/* 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 -------------------------------------------------------- */
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
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;