From 2a9a65e46ee57ffa44bfc796c967b72be39ff85f Mon Sep 17 00:00:00 2001 Message-Id: <2a9a65e46ee57ffa44bfc796c967b72be39ff85f.1715136290.git.mdw@distorted.org.uk> From: Mark Wooding Date: Thu, 12 Jun 2008 20:27:42 +0100 Subject: [PATCH] Make all search results visible. Organization: Straylight/Edgeware From: Richard Kettlewell --- disobedience/choose-search.c | 197 ++++++++++++++++++++++++++++++++++- disobedience/choose.c | 21 +++- 2 files changed, 213 insertions(+), 5 deletions(-) diff --git a/disobedience/choose-search.c b/disobedience/choose-search.c index ed76532..c82ab59 100644 --- a/disobedience/choose-search.c +++ b/disobedience/choose-search.c @@ -34,6 +34,25 @@ static int choose_search_obsolete; /** @brief Hash of all search result */ static hash *choose_search_hash; +/** @brief List of invisible search results + * + * This only lists search results not yet known to be visible, and is + * gradually depleted. + */ +static char **choose_search_results; + +/** @brief Length of @ref choose_search_results */ +static int choose_n_search_results; + +/** @brief Row references for search results */ +static GtkTreeRowReference **choose_search_references; + +/** @brief Length of @ref choose_search_references */ +static int choose_n_search_references; + +/** @brief Event handle for monitoring newly inserted tracks */ +static event_handle choose_inserted_handle; + static void choose_search_entry_changed(GtkEditable *editable, gpointer user_data); @@ -48,10 +67,169 @@ static void choose_clear_clicked(GtkButton attribute((unused)) *button, /* The changed signal will do the rest of the work for us */ } +static int is_prefix(const char *dir, const char *track) { + size_t nd = strlen(dir); + + if(nd < strlen(track) + && track[nd] == '/' + && !strncmp(track, dir, nd)) + return 1; + else + return 0; +} + +/** @brief Do some work towards making @p track visible + * @return True if we made it visible or it was missing + */ +static int choose_make_one_visible(const char *track) { + //fprintf(stderr, " choose_make_one_visible %s\n", track); + /* We walk through nodes at the top level looking for directories that are + * prefixes of the target track. + * + * - if we find one and it's expanded we walk through its children + * - if we find one and it's NOT expanded then we expand it, and arrange + * to be revisited + * - if we don't find one then we're probably out of date + */ + GtkTreeIter it[1]; + gboolean itv = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(choose_store), + it); + while(itv) { + const char *dir = choose_get_track(it); + + //fprintf(stderr, " %s\n", dir); + if(!dir) { + /* Placeholder */ + itv = gtk_tree_model_iter_next(GTK_TREE_MODEL(choose_store), it); + continue; + } + GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(choose_store), + it); + if(!strcmp(dir, track)) { + /* We found the track. If everything above it was expanded, it will be + * too. So we can report it as visible. */ + //fprintf(stderr, " found %s\n", track); + choose_search_references[choose_n_search_references++] + = gtk_tree_row_reference_new(GTK_TREE_MODEL(choose_store), path); + gtk_tree_path_free(path); + return 1; + } + if(is_prefix(dir, track)) { + /* We found a prefix of the target track. */ + //fprintf(stderr, " is a prefix\n"); + const gboolean expanded + = gtk_tree_view_row_expanded(GTK_TREE_VIEW(choose_view), path); + if(expanded) { + //fprintf(stderr, " is apparently expanded\n"); + /* This directory is expanded, let's make like Augustus Gibbons and + * take it to the next level. */ + GtkTreeIter child[1]; /* don't know if parent==iter allowed */ + itv = gtk_tree_model_iter_children(GTK_TREE_MODEL(choose_store), + child, + it); + *it = *child; + if(choose_is_placeholder(it)) { + //fprintf(stderr, " %s is expanded, has a placeholder child\n", dir); + /* We assume that placeholder children of expanded rows are about to + * be replaced */ + gtk_tree_path_free(path); + return 0; + } + } else { + //fprintf(stderr, " requesting expansion of %s\n", dir); + /* Track is below a non-expanded directory. So let's expand it. + * choose_make_visible() will arrange a revisit in due course. */ + gtk_tree_view_expand_row(GTK_TREE_VIEW(choose_view), + path, + FALSE/*open_all*/); + gtk_tree_path_free(path); + /* TODO: the old version would remember which rows had been expanded + * just to show search results and collapse them again. We should + * probably do that. */ + return 0; + } + } else + itv = gtk_tree_model_iter_next(GTK_TREE_MODEL(choose_store), it); + gtk_tree_path_free(path); + } + /* If we reach the end then we didn't find the track at all. */ + fprintf(stderr, "choose_make_one_visible: could not find %s\n", + track); + return 1; +} + +/** @brief Compare two GtkTreeRowReferences + * + * Not very efficient since it does multiple memory operations per + * comparison! + */ +static int choose_compare_references(const void *av, const void *bv) { + GtkTreeRowReference *a = *(GtkTreeRowReference **)av; + GtkTreeRowReference *b = *(GtkTreeRowReference **)bv; + GtkTreePath *pa = gtk_tree_row_reference_get_path(a); + GtkTreePath *pb = gtk_tree_row_reference_get_path(b); + const int rc = gtk_tree_path_compare(pa, pb); + gtk_tree_path_free(pa); + gtk_tree_path_free(pb); + return rc; +} + +/** @brief Move the cursor to @p ref + * @return 0 on success, nonzero if @p ref has gone stale + */ +static int choose_set_cursor(GtkTreeRowReference *ref) { + GtkTreePath *path = gtk_tree_row_reference_get_path(ref); + if(!path) + return -1; + gtk_tree_view_set_cursor(GTK_TREE_VIEW(choose_view), path, NULL, FALSE); + gtk_tree_path_free(path); + return 0; +} + +/** @brief Do some work towards ensuring that all search results are visible + * + * Assumes there's at least one results! + */ +static void choose_make_visible(const char attribute((unused)) *event, + void attribute((unused)) *eventdata, + void attribute((unused)) *callbackdata) { + //fprintf(stderr, "choose_make_visible\n"); + int remaining = 0; + + for(int n = 0; n < choose_n_search_results; ++n) { + if(!choose_search_results[n]) + continue; + if(choose_make_one_visible(choose_search_results[n])) + choose_search_results[n] = 0; + else + ++remaining; + } + //fprintf(stderr, "remaining=%d\n", remaining); + if(remaining) { + /* If there's work left to be done make sure we get a callback when + * something changes */ + if(!choose_inserted_handle) + choose_inserted_handle = event_register("choose-inserted-tracks", + choose_make_visible, 0); + } else { + /* Suppress callbacks if there's nothing more to do */ + event_cancel(choose_inserted_handle); + choose_inserted_handle = 0; + /* We've expanded everything, now we can mess with the cursor */ + //fprintf(stderr, "sort %d references\n", choose_n_search_references); + qsort(choose_search_references, + choose_n_search_references, + sizeof (GtkTreeRowReference *), + choose_compare_references); + choose_set_cursor(choose_search_references[0]); + } +} + /** @brief Called with search results */ static void choose_search_completed(void attribute((unused)) *v, const char *error, int nvec, char **vec) { + //fprintf(stderr, "choose_search_completed\n"); if(error) { popup_protocol_error(0, error); return; @@ -59,14 +237,24 @@ static void choose_search_completed(void attribute((unused)) *v, choose_searching = 0; /* If the search was obsoleted initiate another one */ if(choose_search_obsolete) { + choose_search_obsolete = 0; choose_search_entry_changed(0, 0); return; } - //fprintf(stderr, "%d search results\n", nvec); + //fprintf(stderr, "*** %d search results\n", nvec); choose_search_hash = hash_new(1); - for(int n = 0; n < nvec; ++n) - hash_add(choose_search_hash, vec[n], "", HASH_INSERT); - /* TODO arrange visibility of all search results */ + if(nvec) { + for(int n = 0; n < nvec; ++n) + hash_add(choose_search_hash, vec[n], "", HASH_INSERT); + /* Stash results for choose_make_visible */ + choose_n_search_results = nvec; + choose_search_results = vec; + /* Make a big-enough buffer for the results row reference list */ + choose_n_search_references = 0; + choose_search_references = xcalloc(nvec, sizeof (GtkTreeRowReference *)); + /* Start making rows visible */ + choose_make_visible(0, 0, 0); + } event_raise("search-results-changed", 0); } @@ -74,6 +262,7 @@ static void choose_search_completed(void attribute((unused)) *v, static void choose_search_entry_changed (GtkEditable attribute((unused)) *editable, gpointer attribute((unused)) user_data) { + //fprintf(stderr, "choose_search_entry_changed\n"); /* If a search is in flight don't initiate a new one until it comes back */ if(choose_searching) { choose_search_obsolete = 1; diff --git a/disobedience/choose.c b/disobedience/choose.c index ce3f754..7c96293 100644 --- a/disobedience/choose.c +++ b/disobedience/choose.c @@ -51,6 +51,12 @@ GtkWidget *choose_view; /** @brief The selection tree's selection */ GtkTreeSelection *choose_selection; +/** @brief Count of file listing operations in flight */ +static int choose_list_in_flight; + +/** @brief Count of files inserted in current batch of listing operations */ +static int choose_inserted; + static char *choose_get_string(GtkTreeIter *iter, int column) { gchar *gs; gtk_tree_model_get(GTK_TREE_MODEL(choose_store), iter, @@ -219,7 +225,7 @@ 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]); + //fprintf(stderr, " inserting new %s nodes\n", isfile ? "track" : "dir"); const char *typename = isfile ? "track" : "dir"; for(int n = 0; n < nvec; ++n) { if(!found[n]) { @@ -264,6 +270,18 @@ static void choose_populate(GtkTreeRowReference *parent_ref, gtk_tree_row_reference_free(parent_ref); gtk_tree_path_free(parent_path); } + /* We only notify others that we've inserted tracks when there are no more + * insertions pending, so that they don't have to keep track of how many + * requests they've made. */ + choose_inserted += inserted; + if(--choose_list_in_flight == 0) { + /* Notify interested parties that we inserted some tracks, AFTER making + * sure that the row is properly expanded */ + if(choose_inserted) { + event_raise("choose-inserted-tracks", parent_it); + choose_inserted = 0; + } + } } static void choose_dirs_completed(void *v, @@ -334,6 +352,7 @@ static void choose_row_expanded(GtkTreeView attribute((unused)) *treeview, gtk_tree_row_reference_new(GTK_TREE_MODEL(choose_store), path)); /* The row references are destroyed in the _completed handlers. */ + choose_list_in_flight += 2; } /** @brief Create the choose tab */ -- [mdw]