* 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
- * changes. (We could trigger the latter off rescan complete notifications?)
- *
* TODO:
- * - sweep up contracted nodes
- * - update when content may have changed (e.g. after a rescan)
+ * - sweep up contracted nodes, replacing their content with a placeholder
*/
#include "disobedience.h"
#include "choose.h"
+#include <gdk/gdkkeysyms.h>
/** @brief The current selection tree */
GtkTreeStore *choose_store;
}
static void choose_dirs_completed(void *v,
- const char *error,
+ const char *err,
int nvec, char **vec) {
- if(error) {
- popup_protocol_error(0, error);
+ if(err) {
+ popup_protocol_error(0, err);
return;
}
choose_populate(v, nvec, vec, 0/*!isfile*/);
}
static void choose_files_completed(void *v,
- const char *error,
+ const char *err,
int nvec, char **vec) {
- if(error) {
- popup_protocol_error(0, error);
+ if(err) {
+ popup_protocol_error(0, err);
return;
}
choose_populate(v, nvec, vec, 1/*isfile*/);
}
void choose_play_completed(void attribute((unused)) *v,
- const char *error) {
- if(error)
- popup_protocol_error(0, error);
+ const char *err) {
+ if(err)
+ popup_protocol_error(0, err);
}
static void choose_state_toggled
}
-static void choose_row_expanded(GtkTreeView attribute((unused)) *treeview,
- GtkTreeIter *iter,
- GtkTreePath *path,
- gpointer attribute((unused)) user_data) {
- /*fprintf(stderr, "row-expanded path=[%s]\n\n",
- gtk_tree_path_to_string(path));*/
- /* 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. */
+/** @brief (Re-)get the children of @p path
+ * @param path Path to target row
+ * @param iter Iterator pointing at target row
+ *
+ * Called from choose_row_expanded() to make sure that the contents are present
+ * and from choose_refill_callback() to (re-)synchronize.
+ */
+static void choose_refill_row(GtkTreePath *path,
+ GtkTreeIter *iter) {
const char *track = choose_get_track(iter);
disorder_eclient_files(client, choose_files_completed,
track,
path));
/* The row references are destroyed in the _completed handlers. */
choose_list_in_flight += 2;
- //fprintf(stderr, "choose_list_in_flight -> %d+\n", choose_list_in_flight);
+}
+
+static void choose_row_expanded(GtkTreeView attribute((unused)) *treeview,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer attribute((unused)) user_data) {
+ /*fprintf(stderr, "row-expanded path=[%s]\n\n",
+ gtk_tree_path_to_string(path));*/
+ /* 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. */
+ choose_refill_row(path, iter);
if(!choose_suppress_set_autocollapse) {
if(choose_auto_expanding) {
/* This was an automatic expansion; mark it the row for auto-collapse. */
0);
}
+/** @brief Called from choose_refill() with each expanded row */
+static void choose_refill_callback(GtkTreeView attribute((unused)) *tree_view,
+ GtkTreePath *path,
+ gpointer attribute((unused)) user_data) {
+ GtkTreeIter it[1];
+
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(choose_store), it, path);
+ choose_refill_row(path, it);
+}
+
+/** @brief Synchronize all visible data with the server
+ *
+ * Called at startup, when a rescan completes, and via periodic_slow().
+ */
+static void choose_refill(const char attribute((unused)) *event,
+ void attribute((unused)) *eventdata,
+ void attribute((unused)) *callbackdata) {
+ //fprintf(stderr, "choose_refill\n");
+ /* Update the root */
+ disorder_eclient_files(client, choose_files_completed, "", NULL, NULL);
+ disorder_eclient_dirs(client, choose_dirs_completed, "", NULL, NULL);
+ choose_list_in_flight += 2;
+ /* Update all expanded rows */
+ gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(choose_view),
+ choose_refill_callback,
+ 0);
+ //fprintf(stderr, "choose_list_in_flight -> %d+\n", choose_list_in_flight);
+}
+
+/** @brief Called for key-*-event on the main view
+ */
+static gboolean choose_key_event(GtkWidget attribute((unused)) *widget,
+ GdkEventKey *event,
+ gpointer user_data) {
+ /*fprintf(stderr, "choose_key_event type=%d state=%#x keyval=%#x\n",
+ event->type, event->state, event->keyval);*/
+ switch(event->keyval) {
+ case GDK_Page_Up:
+ case GDK_Page_Down:
+ case GDK_Up:
+ case GDK_Down:
+ case GDK_Home:
+ case GDK_End:
+ return FALSE; /* We'll take these */
+ case 'f': case 'F':
+ /* ^F is expected to start a search. We implement this by focusing the
+ * search entry box. */
+ if((event->state & ~(GDK_LOCK_MASK|GDK_SHIFT_MASK)) == GDK_CONTROL_MASK
+ && event->type == GDK_KEY_PRESS) {
+ choose_search_new();
+ return TRUE; /* Handled it */
+ }
+ break;
+ case 'g': case 'G':
+ /* ^G is expected to go the next match. We simulate a click on the 'next'
+ * button. */
+ if((event->state & ~(GDK_LOCK_MASK|GDK_SHIFT_MASK)) == GDK_CONTROL_MASK
+ && event->type == GDK_KEY_PRESS) {
+ choose_next_clicked(0, 0);
+ return TRUE; /* Handled it */
+ }
+ break;
+ }
+ /* Anything not handled we redirected to the search entry field */
+ gtk_widget_event(user_data, (GdkEvent *)event);
+ return TRUE; /* Handled it */
+}
+
/** @brief Create the choose tab */
GtkWidget *choose_widget(void) {
/* Create the tree store. */
/* 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);
+ /* Suppress built-in typeahead find, we do our own search support. */
+ gtk_tree_view_set_enable_search(GTK_TREE_VIEW(choose_view), FALSE);
/* Create cell renderers and columns */
/* TODO use a table */
event_register("search-results-changed", choose_set_state, 0);
event_register("lookups-completed", choose_set_state, 0);
- /* Fill the root */
- disorder_eclient_files(client, choose_files_completed, "", NULL, NULL);
- disorder_eclient_dirs(client, choose_dirs_completed, "", NULL, NULL);
- choose_list_in_flight += 2;
- //fprintf(stderr, "choose_list_in_flight -> %d+\n", choose_list_in_flight);
+ /* After a rescan we update the choose tree. We get a rescan-complete
+ * automatically at startup and upon connection too. */
+ event_register("rescan-complete", choose_refill, 0);
/* Make the widget scrollable */
GtkWidget *scrolled = scroll_widget(choose_view);
FALSE/*expand*/, FALSE/*fill*/, 0/*padding*/);
g_object_set_data(G_OBJECT(vbox), "type", (void *)&choose_tabtype);
+
+ /* Redirect keyboard activity to the search widget */
+ g_signal_connect(choose_view, "key-press-event",
+ G_CALLBACK(choose_key_event), choose_search_entry);
+ g_signal_connect(choose_view, "key-release-event",
+ G_CALLBACK(choose_key_event), choose_search_entry);
+
return vbox;
}