From: Richard Kettlewell Date: Thu, 12 Jun 2008 12:25:14 +0000 (+0100) Subject: Display track length and playing state in Disobedience choose tab. We X-Git-Tag: 4.1~15^2~38 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/ad47bd4ceb35ce30440fb7ccc5a7d871375ea362 Display track length and playing state in Disobedience choose tab. We enable rules in this tab to allow for easy reading across now that there's more than one column. --- diff --git a/disobedience/Makefile.am b/disobedience/Makefile.am index ab085f2..90a0bd6 100644 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@ -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) diff --git a/disobedience/choose.c b/disobedience/choose.c index 27ba940..e59d5b7 100644 --- a/disobedience/choose.c +++ b/disobedience/choose.c @@ -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); diff --git a/disobedience/choose.h b/disobedience/choose.h index 56964d9..49c9b60 100644 --- a/disobedience/choose.h +++ b/disobedience/choose.h @@ -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 diff --git a/disobedience/disobedience.h b/disobedience/disobedience.h index faf7597..280825c 100644 --- a/disobedience/disobedience.h +++ b/disobedience/disobedience.h @@ -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 index 0000000..2052b30 --- /dev/null +++ b/disobedience/lookup.c @@ -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: +*/ diff --git a/disobedience/queue-generic.c b/disobedience/queue-generic.c index 9c9e549..cc6bf53 100644 --- a/disobedience/queue-generic.c +++ b/disobedience/queue-generic.c @@ -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;