+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, " %s is a prefix\n", dir);
+ 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.
+ *
+ * We mark the row as auto-expanded.
+ */
+ ++choose_auto_expanding;
+ gtk_tree_view_expand_row(GTK_TREE_VIEW(choose_view),
+ path,
+ FALSE/*open_all*/);
+ gtk_tree_path_free(path);
+ --choose_auto_expanding;
+ 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 Make @p path visible
+ * @param path Row reference to make visible
+ * @param row_align Row alignment (or -ve)
+ * @return 0 on success, nonzero if @p ref has gone stale
+ *
+ * If @p row_align is negative no row alignemt is performed. Otherwise
+ * it must be between 0 (the top) and 1 (the bottom).
+ */
+static int choose_make_path_visible(GtkTreePath *path,
+ gfloat row_align) {
+ /* Make sure that the target's parents are all expanded */
+ gtk_tree_view_expand_to_path(GTK_TREE_VIEW(choose_view), path);
+ /* Find out what's currently visible */
+ GtkTreePath *startpath, *endpath;
+ choose_get_visible_range(GTK_TREE_VIEW(choose_view), &startpath, &endpath);
+ /* Make sure the target is visible */
+ if(gtk_tree_path_compare(path, startpath) < 0
+ || gtk_tree_path_compare(path, endpath) > 0)
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(choose_view), path, NULL,
+ row_align >= 0.0,
+ row_align,
+ 0);
+ gtk_tree_path_free(startpath);
+ gtk_tree_path_free(endpath);
+ return 0;
+}
+
+/** @brief Make @p ref visible
+ * @param ref Row reference to make visible
+ * @param row_align Row alignment (or -ve)
+ * @return 0 on success, nonzero if @p ref has gone stale
+ *
+ * If @p row_align is negative no row alignemt is performed. Otherwise
+ * it must be between 0 (the top) and 1 (the bottom).
+ */
+static int choose_make_ref_visible(GtkTreeRowReference *ref,
+ gfloat row_align) {
+ GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
+ if(!path)
+ return -1;
+ choose_make_path_visible(path, row_align);
+ 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-more-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_make_ref_visible(choose_search_references[0], 0.5);
+ }