From a98fd5717e59e245fbf543872cfe417b863fd6e1 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Thu, 12 Jun 2008 16:17:29 +0100 Subject: [PATCH] Eliminate choosedata structure, using extra treestore columns instead. Organization: Straylight/Edgeware From: Richard Kettlewell --- disobedience/choose-menu.c | 172 ++++++++++++++++++++----------------- disobedience/choose.c | 141 ++++++++++++++++-------------- disobedience/choose.h | 38 ++++---- 3 files changed, 185 insertions(+), 166 deletions(-) diff --git a/disobedience/choose-menu.c b/disobedience/choose-menu.c index a4e5430..cc1d1a3 100644 --- a/disobedience/choose-menu.c +++ b/disobedience/choose-menu.c @@ -21,55 +21,31 @@ #include "popup.h" #include "choose.h" -VECTOR_TYPE(cdvector, struct choosedata *, xrealloc); - /** @brief Popup menu */ static GtkWidget *choose_menu; -/** @brief Callback for choose_get_selected() */ -static void choose_gather_selected_callback(GtkTreeModel attribute((unused)) *model, - GtkTreePath attribute((unused)) *path, - GtkTreeIter *iter, - gpointer data) { - struct cdvector *v = data; - struct choosedata *cd = choose_iter_to_data(iter); - - if(cd) - cdvector_append(v, cd); -} - -/** @brief Get a list of all selected tracks and directories */ -static struct choosedata **choose_get_selected(int *nselected) { - struct cdvector v[1]; - - cdvector_init(v); - gtk_tree_selection_selected_foreach(choose_selection, - choose_gather_selected_callback, - v); - cdvector_terminate(v); - if(nselected) - *nselected = v->nvec; - return v->vec; -} - /** @brief Recursion step for choose_get_visible() * @param parent A visible node, or NULL for the root - * @param cdv Visible nodes accumulated here + * @param callback Called for each visible node + * @param userdata Passed to @p callback + * + * If @p callback returns nonzero, the walk stops immediately. */ -static void choose_visible_recurse(GtkTreeIter *parent, - void (*callback)(GtkTreeIter *it, - struct choosedata *cd, - void *userdata), - void *userdata) { - struct choosedata *cd; +static int choose_visible_recurse(GtkTreeIter *parent, + int (*callback)(GtkTreeIter *it, + int isfile, + void *userdata), + void *userdata) { int expanded; if(parent) { - cd = choose_iter_to_data(parent); - callback(parent, cd, userdata); - if(cd->type != CHOOSE_DIRECTORY) - /* Only directories can be expanded so we can avoid the more - * expensive test below */ - return; + /* Skip placeholders */ + if(choose_is_placeholder(parent)) + return 0; + const int isfile = choose_is_file(parent); + if(callback(parent, isfile, userdata)) + return 1; + if(isfile) + return 0; /* Files never have children */ GtkTreePath *parent_path = gtk_tree_model_get_path(GTK_TREE_MODEL(choose_store), parent); @@ -86,80 +62,122 @@ static void choose_visible_recurse(GtkTreeIter *parent, it, parent); while(itv) { - choose_visible_recurse(it, callback, userdata); + if(choose_visible_recurse(it, callback, userdata)) + return TRUE; itv = gtk_tree_model_iter_next(GTK_TREE_MODEL(choose_store), it); } } + return 0; } -static void choose_visible_visit(void (*callback)(GtkTreeIter *it, - struct choosedata *cd, - void *userdata), +static void choose_visible_visit(int (*callback)(GtkTreeIter *it, + int isfile, + void *userdata), void *userdata) { choose_visible_recurse(NULL, callback, userdata); } -static void count_choosedatas(struct choosedata **cds, - int counts[2]) { - struct choosedata *cd; - counts[CHOOSE_FILE] = counts[CHOOSE_DIRECTORY] = 0; - while((cd = *cds++)) - ++counts[cd->type]; -} - - -static void choose_selectall_sensitive_callback - (GtkTreeIter attribute((unused)) *it, - struct choosedata *cd, +static int choose_selectall_sensitive_callback + (GtkTreeIter attribute((unused)) *it, + int isfile, void *userdata) { - if(cd->type == CHOOSE_FILE) - ++*(int *)userdata; + if(isfile) { + *(int *)userdata = 1; + return 1; + } + return 0; } +/** @brief Should 'select all' be sensitive? + * + * Yes if there are visible files. + */ static int choose_selectall_sensitive(void attribute((unused)) *extra) { int files = 0; choose_visible_visit(choose_selectall_sensitive_callback, &files); return files > 0; } -static void choose_selectall_activate_callback +static int choose_selectall_activate_callback (GtkTreeIter *it, - struct choosedata *cd, + int isfile, void attribute((unused)) *userdata) { - if(cd->type == CHOOSE_FILE) + if(isfile) gtk_tree_selection_select_iter(choose_selection, it); else gtk_tree_selection_unselect_iter(choose_selection, it); + return 0; } +/** @brief Activate select all + * + * Selects all files and deselects everything else. + */ static void choose_selectall_activate(GtkMenuItem attribute((unused)) *item, gpointer attribute((unused)) userdata) { choose_visible_visit(choose_selectall_activate_callback, 0); } - + +/** @brief Should 'select none' be sensitive + * + * Yes if anything is selected. + */ static int choose_selectnone_sensitive(void attribute((unused)) *extra) { return gtk_tree_selection_count_selected_rows(choose_selection) > 0; } - + +/** @brief Activate select none */ static void choose_selectnone_activate(GtkMenuItem attribute((unused)) *item, gpointer attribute((unused)) userdata) { gtk_tree_selection_unselect_all(choose_selection); } - + +static void choose_play_sensitive_callback(GtkTreeModel attribute((unused)) *model, + GtkTreePath attribute((unused)) *path, + GtkTreeIter *iter, + gpointer data) { + int *filesp = data; + + if(*filesp == -1) + return; + if(choose_is_dir(iter)) + *filesp = -1; + else if(choose_is_file(iter)) + ++*filesp; +} + +/** @brief Should 'play' be sensitive? + * + * Yes if tracks are selected and no directories are */ static int choose_play_sensitive(void attribute((unused)) *extra) { - int counts[2]; - count_choosedatas(choose_get_selected(NULL), counts); - return !counts[CHOOSE_DIRECTORY] && counts[CHOOSE_FILE]; + int files = 0; + + gtk_tree_selection_selected_foreach(choose_selection, + choose_play_sensitive_callback, + &files); + return files > 0; } + +static void choose_gather_selected_files_callback(GtkTreeModel attribute((unused)) *model, + GtkTreePath attribute((unused)) *path, + GtkTreeIter *iter, + gpointer data) { + struct vector *v = data; + + if(choose_is_file(iter)) + vector_append(v, choose_get_track(iter)); +} + static void choose_play_activate(GtkMenuItem attribute((unused)) *item, gpointer attribute((unused)) userdata) { - struct choosedata *cd, **cdp = choose_get_selected(NULL); - while((cd = *cdp++)) { - if(cd->type == CHOOSE_FILE) - disorder_eclient_play(client, xstrdup(cd->track), - choose_play_completed, 0); - } + struct vector v[1]; + vector_init(v); + gtk_tree_selection_selected_foreach(choose_selection, + choose_gather_selected_files_callback, + v); + for(int n = 0; n < v->nvec; ++n) + disorder_eclient_play(client, v->vec[n], choose_play_completed, 0); } static int choose_properties_sensitive(void *extra) { @@ -168,11 +186,11 @@ static int choose_properties_sensitive(void *extra) { static void choose_properties_activate(GtkMenuItem attribute((unused)) *item, gpointer attribute((unused)) userdata) { - struct choosedata *cd, **cdp = choose_get_selected(NULL); struct vector v[1]; vector_init(v); - while((cd = *cdp++)) - vector_append(v, xstrdup(cd->track)); + gtk_tree_selection_selected_foreach(choose_selection, + choose_gather_selected_files_callback, + v); properties(v->nvec, (const char **)v->vec); } diff --git a/disobedience/choose.c b/disobedience/choose.c index b69712d..a564a11 100644 --- a/disobedience/choose.c +++ b/disobedience/choose.c @@ -22,14 +22,11 @@ * * We now use an ordinary GtkTreeStore/GtkTreeView. * - * We have an extra column with per-row data. This isn't referenced from - * anywhere the GC can see so explicit memory management is required. - * (TODO perhaps we could fix this using a gobject?) - * * We don't want to pull the entire tree in memory, but we want directories to * show up as having children. Therefore we give directories a placeholder - * child and replace their children when they are opened. Placeholders have a - * null choosedata pointer. + * child and replace their children when they are opened. Placeholders have + * TRACK_COLUMN="" and ISFILE_COLUMN=FALSE (so that they don't get check boxes, + * lengths, etc). * * TODO We do a period sweep which kills contracted nodes, putting back * placeholders, and updating expanded nodes to keep up with server-side @@ -39,6 +36,7 @@ * - sweep up contracted nodes * - update when content may have changed (e.g. after a rescan) * - searching! + * - proper sorting */ #include "disobedience.h" @@ -56,23 +54,45 @@ GtkTreeSelection *choose_selection; /** @brief Map choosedata types to names */ static const char *const choose_type_map[] = { "track", "dir" }; -/** @brief Return the choosedata given an interator */ -struct choosedata *choose_iter_to_data(GtkTreeIter *iter) { - GValue v[1]; - memset(v, 0, sizeof v); - gtk_tree_model_get_value(GTK_TREE_MODEL(choose_store), iter, CHOOSEDATA_COLUMN, v); - assert(G_VALUE_TYPE(v) == G_TYPE_POINTER); - struct choosedata *const cd = g_value_get_pointer(v); - g_value_unset(v); - return cd; +static char *choose_get_string(GtkTreeIter *iter, int column) { + gchar *gs; + gtk_tree_model_get(GTK_TREE_MODEL(choose_store), iter, + column, &gs, + -1); + char *s = xstrdup(gs); + g_free(gs); + return s; } -struct choosedata *choose_path_to_data(GtkTreePath *path) { - GtkTreeIter it[1]; - gboolean itv = gtk_tree_model_get_iter(GTK_TREE_MODEL(choose_store), - it, path); - assert(itv); - return choose_iter_to_data(it); +char *choose_get_track(GtkTreeIter *iter) { + char *s = choose_get_string(iter, TRACK_COLUMN); + return *s ? s : 0; /* Placeholder -> NULL */ +} + +char *choose_get_sort(GtkTreeIter *iter) { + return choose_get_string(iter, SORT_COLUMN); +} + +int choose_is_file(GtkTreeIter *iter) { + gboolean isfile; + gtk_tree_model_get(GTK_TREE_MODEL(choose_store), iter, + ISFILE_COLUMN, &isfile, + -1); + return isfile; +} + +int choose_is_dir(GtkTreeIter *iter) { + gboolean isfile; + gtk_tree_model_get(GTK_TREE_MODEL(choose_store), iter, + ISFILE_COLUMN, &isfile, + -1); + if(isfile) + return FALSE; + return !choose_is_placeholder(iter); +} + +int choose_is_placeholder(GtkTreeIter *iter) { + return choose_get_string(iter, TRACK_COLUMN)[0] == 0; } /** @brief Remove node @p it and all its children @@ -86,12 +106,6 @@ static gboolean choose_remove_node(GtkTreeIter *it) { it); while(childv) childv = choose_remove_node(child); - struct choosedata *cd = choose_iter_to_data(it); - if(cd) { - g_free(cd->track); - g_free(cd->sort); - g_free(cd); - } return gtk_tree_store_remove(choose_store, it); } @@ -100,11 +114,9 @@ static gboolean choose_set_state_callback(GtkTreeModel attribute((unused)) *mode 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); + if(choose_is_file(it)) { + const char *track = choose_get_track(it); + const long l = namepart_length(track); char length[64]; if(l > 0) byte_snprintf(length, sizeof length, "%ld:%02ld", l / 60, l % 60); @@ -112,7 +124,7 @@ static gboolean choose_set_state_callback(GtkTreeModel attribute((unused)) *mode length[0] = 0; gtk_tree_store_set(choose_store, it, LENGTH_COLUMN, length, - STATE_COLUMN, queued(cd->track), + STATE_COLUMN, queued(track), -1); } return FALSE; /* continue walking */ @@ -131,7 +143,7 @@ static void choose_set_state(const char attribute((unused)) *event, * @param parent_ref Node to populate or NULL to fill root * @param nvec Number of children to add * @param vec Children - * @param dirs True if children are directories + * @param files 1 if children are files, 0 if directories * * Adjusts the set of files (or directories) below @p parent_ref to match those * listed in @p nvec and @p vec. @@ -140,7 +152,7 @@ static void choose_set_state(const char attribute((unused)) *event, */ static void choose_populate(GtkTreeRowReference *parent_ref, int nvec, char **vec, - int type) { + int isfile) { /* Compute parent_* */ GtkTreeIter pit[1], *parent_it; GtkTreePath *parent_path; @@ -167,18 +179,18 @@ static void choose_populate(GtkTreeRowReference *parent_ref, it, parent_it); while(itv) { - struct choosedata *cd = choose_iter_to_data(it); + const char *track = choose_get_track(it); int keep; - if(!cd) { + if(!track) { /* Always kill placeholders */ //fprintf(stderr, " kill a placeholder\n"); keep = 0; - } else if(cd->type == type) { + } else if(choose_is_file(it) == isfile) { /* This is the type we care about */ - //fprintf(stderr, " %s is a %s\n", cd->track, choose_type_map[cd->type]); + //fprintf(stderr, " %s is a %s\n", track, isfile ? "file" : "dir"); int n; - for(n = 0; n < nvec && strcmp(vec[n], cd->track); ++n) + for(n = 0; n < nvec && strcmp(vec[n], track); ++n) ; if(n < nvec) { //fprintf(stderr, " ... and survives\n"); @@ -190,7 +202,7 @@ static void choose_populate(GtkTreeRowReference *parent_ref, } } else { /* Keep wrong-type entries */ - //fprintf(stderr, " %s is a %s\n", cd->track, choose_type_map[cd->type]); + //fprintf(stderr, " %s has wrong type\n", track); keep = 1; } if(keep) @@ -201,22 +213,20 @@ static void choose_populate(GtkTreeRowReference *parent_ref, /* Add nodes we don't have */ int inserted = 0; //fprintf(stderr, " inserting new %s nodes\n", choose_type_map[type]); + const char *typename = isfile ? "track" : "dir"; for(int n = 0; n < nvec; ++n) { if(!found[n]) { //fprintf(stderr, " %s was not found\n", vec[n]); - struct choosedata *cd = g_malloc0(sizeof *cd); - cd->type = type; - cd->track = g_strdup(vec[n]); - cd->sort = g_strdup(trackname_transform(choose_type_map[type], - vec[n], - "sort")); gtk_tree_store_append(choose_store, it, parent_it); gtk_tree_store_set(choose_store, it, - NAME_COLUMN, trackname_transform(choose_type_map[type], + NAME_COLUMN, trackname_transform(typename, vec[n], "display"), - CHOOSEDATA_COLUMN, cd, - ISFILE_COLUMN, type == CHOOSE_FILE, + ISFILE_COLUMN, isfile, + TRACK_COLUMN, vec[n], + SORT_COLUMN, trackname_transform(typename, + vec[n], + "sort"), -1); /* Update length and state; we expect this to kick off length lookups * rather than necessarily get the right value the first time round. */ @@ -224,14 +234,15 @@ static void choose_populate(GtkTreeRowReference *parent_ref, ++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. */ - if(type == CHOOSE_DIRECTORY) { + if(!isfile) { //fprintf(stderr, " inserting a placeholder\n"); GtkTreeIter placeholder[1]; gtk_tree_store_append(choose_store, placeholder, it); gtk_tree_store_set(choose_store, placeholder, NAME_COLUMN, "Waddling...", - CHOOSEDATA_COLUMN, (void *)0, + TRACK_COLUMN, "", + ISFILE_COLUMN, FALSE, -1); } } @@ -255,7 +266,7 @@ static void choose_dirs_completed(void *v, popup_protocol_error(0, error); return; } - choose_populate(v, nvec, vec, CHOOSE_DIRECTORY); + choose_populate(v, nvec, vec, 0/*!isfile*/); } static void choose_files_completed(void *v, @@ -265,7 +276,7 @@ static void choose_files_completed(void *v, popup_protocol_error(0, error); return; } - choose_populate(v, nvec, vec, CHOOSE_FILE); + choose_populate(v, nvec, vec, 1/*isfile*/); } void choose_play_completed(void attribute((unused)) *v, @@ -286,15 +297,12 @@ static void choose_state_toggled path_str); if(!itv) return; - struct choosedata *cd = choose_iter_to_data(it); - if(!cd) - return; - if(cd->type != CHOOSE_FILE) + if(!choose_is_file(it)) return; - if(queued(cd->track)) + const char *track = choose_get_track(it); + if(queued(track)) return; - disorder_eclient_play(client, xstrdup(cd->track), - choose_play_completed, 0); + disorder_eclient_play(client, track, choose_play_completed, 0); } @@ -307,14 +315,14 @@ static void choose_row_expanded(GtkTreeView attribute((unused)) *treeview, /* We update a node's contents whenever it is expanded, even if it was * already populated; the effect is that contracting and expanding a node * suffices to update it to the latest state on the server. */ - struct choosedata *cd = choose_iter_to_data(iter); + const char *track = choose_get_track(iter); disorder_eclient_files(client, choose_files_completed, - xstrdup(cd->track), + track, NULL, gtk_tree_row_reference_new(GTK_TREE_MODEL(choose_store), path)); disorder_eclient_dirs(client, choose_dirs_completed, - xstrdup(cd->track), + track, NULL, gtk_tree_row_reference_new(GTK_TREE_MODEL(choose_store), path)); @@ -324,12 +332,13 @@ static void choose_row_expanded(GtkTreeView attribute((unused)) *treeview, /** @brief Create the choose tab */ GtkWidget *choose_widget(void) { /* Create the tree store. */ - choose_store = gtk_tree_store_new(1 + CHOOSEDATA_COLUMN, + choose_store = gtk_tree_store_new(CHOOSE_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, - G_TYPE_POINTER); + G_TYPE_STRING, + G_TYPE_STRING); /* Create the view */ choose_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(choose_store)); diff --git a/disobedience/choose.h b/disobedience/choose.h index 812d88a..16414a8 100644 --- a/disobedience/choose.h +++ b/disobedience/choose.h @@ -20,32 +20,19 @@ #ifndef CHOOSE_H #define CHOOSE_H -/** @brief Extra data at each node */ -struct choosedata { - /** @brief Node type */ - int type; - - /** @brief Full track or directory name */ - gchar *track; - - /** @brief Sort key */ - gchar *sort; -}; - /** @brief Column numbers */ enum { - STATE_COLUMN, - NAME_COLUMN, - LENGTH_COLUMN, - ISFILE_COLUMN, - CHOOSEDATA_COLUMN -}; - -/** @brief @ref choosedata node is a file */ -#define CHOOSE_FILE 0 + /* Visible columns */ + STATE_COLUMN, /* Track state */ + NAME_COLUMN, /* Track name (display context) */ + LENGTH_COLUMN, /* Track length */ + /* Hidden columns */ + ISFILE_COLUMN, /* TRUE for a track, FALSE for a directory */ + TRACK_COLUMN, /* Full track name, "" for placeholder */ + SORT_COLUMN, /* Sort key */ -/** @brief @ref choosedata node is a directory */ -#define CHOOSE_DIRECTORY 1 + CHOOSE_COLUMNS /* column count */ +}; extern GtkTreeStore *choose_store; extern GtkWidget *choose_view; @@ -59,6 +46,11 @@ gboolean choose_button_event(GtkWidget *widget, gpointer user_data); void choose_play_completed(void attribute((unused)) *v, const char *error); +char *choose_get_track(GtkTreeIter *iter); +char *choose_get_sort(GtkTreeIter *iter); +int choose_is_file(GtkTreeIter *iter); +int choose_is_dir(GtkTreeIter *iter); +int choose_is_placeholder(GtkTreeIter *iter); #endif /* CHOOSE_H */ -- [mdw]