From 64d76856cb691d998c070086d31ad51f18d68cdf Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Mar 2011 16:03:29 -0500 Subject: sort locations by name, starting with prefixes --- src/timezone-completion.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index aaf6bdc..2cd2340 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -87,6 +87,39 @@ save_and_use_model (TimezoneCompletion * completion, GtkTreeModel * model) g_signal_emit_by_name (priv->entry, "changed"); } +static gint +sort_zone (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, + gpointer user_data) +{ + /* Anything that has text as a prefix goes first, in mostly sorted order. + Then everything else goes after, in mostly sorted order. */ + const gchar *casefolded_text = (const gchar *)user_data; + + const gchar *namea = NULL, *nameb = NULL; + gtk_tree_model_get (model, a, TIMEZONE_COMPLETION_NAME, &namea, -1); + gtk_tree_model_get (model, b, TIMEZONE_COMPLETION_NAME, &nameb, -1); + + gchar *casefolded_namea = NULL, *casefolded_nameb = NULL; + casefolded_namea = g_utf8_casefold (namea, -1); + casefolded_nameb = g_utf8_casefold (nameb, -1); + + gboolean amatches = FALSE, bmatches = FALSE; + amatches = strncmp (casefolded_text, casefolded_namea, strlen(casefolded_text)) == 0; + bmatches = strncmp (casefolded_text, casefolded_nameb, strlen(casefolded_text)) == 0; + + gint rv; + if (amatches && !bmatches) + rv = -1; + else if (bmatches && !amatches) + rv = 1; + else + rv = g_utf8_collate (casefolded_namea, casefolded_nameb); + + g_free (casefolded_namea); + g_free (casefolded_nameb); + return rv; +} + static void json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) { @@ -179,6 +212,13 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) TIMEZONE_COMPLETION_LONGITUDE, longitude, TIMEZONE_COMPLETION_LATITUDE, latitude, -1); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), + TIMEZONE_COMPLETION_NAME, sort_zone, + g_utf8_casefold(priv->request_text, -1), + g_free); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + TIMEZONE_COMPLETION_NAME, + GTK_SORT_ASCENDING); } prev_name = name; -- cgit v1.2.3 From 7df6b79140cf5184e95169682534af60e947d6bd Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Mar 2011 16:05:07 -0500 Subject: cleanup another instance of GValue --- src/timezone-completion.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index 2cd2340..a2fa643 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -380,16 +380,13 @@ static void data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) { - GValue name_val = {0}, admin1_val = {0}, country_val = {0}; const gchar * name, * admin1, * country; - gtk_tree_model_get_value (GTK_TREE_MODEL (tree_model), iter, TIMEZONE_COMPLETION_NAME, &name_val); - gtk_tree_model_get_value (GTK_TREE_MODEL (tree_model), iter, TIMEZONE_COMPLETION_ADMIN1, &admin1_val); - gtk_tree_model_get_value (GTK_TREE_MODEL (tree_model), iter, TIMEZONE_COMPLETION_COUNTRY, &country_val); - - name = g_value_get_string (&name_val); - admin1 = g_value_get_string (&admin1_val); - country = g_value_get_string (&country_val); + gtk_tree_model_get (GTK_TREE_MODEL (tree_model), iter, + TIMEZONE_COMPLETION_NAME, &name, + TIMEZONE_COMPLETION_ADMIN1, &admin1, + TIMEZONE_COMPLETION_COUNTRY, &country, + -1); gchar * user_name; if (admin1 == NULL || admin1[0] == 0) { @@ -399,10 +396,6 @@ data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell, } g_object_set (G_OBJECT (cell), "markup", user_name, NULL); - - g_value_unset (&name_val); - g_value_unset (&admin1_val); - g_value_unset (&country_val); } static void -- cgit v1.2.3 From 2021a9554f85a9f78f1b65de30cb78572dab2752 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Mar 2011 16:37:20 -0500 Subject: add utc to relevant results --- src/timezone-completion.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index a2fa643..c1ffb42 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -229,6 +229,20 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) json_reader_end_element (reader); } + if (strlen (priv->request_text) < 4) { + gchar * lower_text = g_ascii_strdown (priv->request_text, -1); + if (g_strcmp0 (lower_text, "ut") == 0 || + g_strcmp0 (lower_text, "utc") == 0) { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, "UTC", + TIMEZONE_COMPLETION_NAME, "UTC", + -1); + } + g_free (lower_text); + } + save_and_use_model (completion, GTK_TREE_MODEL (store)); g_object_unref (G_OBJECT (reader)); } @@ -372,6 +386,13 @@ get_initial_model (void) g_free (name); } + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, "UTC", + TIMEZONE_COMPLETION_NAME, "UTC", + -1); + tz_db_free (db); return store; } @@ -389,7 +410,9 @@ data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell, -1); gchar * user_name; - if (admin1 == NULL || admin1[0] == 0) { + if (country == NULL || country[0] == 0) { + user_name = g_strdup (name); + } else if (admin1 == NULL || admin1[0] == 0) { user_name = g_strdup_printf ("%s (%s)", name, country); } else { user_name = g_strdup_printf ("%s (%s, %s)", name, admin1, country); -- cgit v1.2.3 From 47f806242850d509e1919780fa3eb2ec3598b356 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Thu, 24 Mar 2011 16:54:00 -0400 Subject: don't access priv pointer if we've cancelled an async operation --- src/timezone-completion.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index aaf6bdc..dddc003 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -99,14 +99,15 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) json_parser_load_from_stream_finish (JSON_PARSER (object), res, &error); - if (priv->cancel && (error == NULL || error->code != G_IO_ERROR_CANCELLED)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && priv->cancel) { g_cancellable_reset (priv->cancel); } if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + save_and_use_model (completion, priv->initial_model); g_warning ("Could not parse geoname JSON data: %s", error->message); g_error_free (error); - save_and_use_model (completion, priv->initial_model); return; } @@ -203,14 +204,15 @@ geonames_data_ready (GObject *object, GAsyncResult *res, gpointer user_data) stream = g_file_read_finish (G_FILE (object), res, &error); - if (priv->cancel && (error == NULL || error->code != G_IO_ERROR_CANCELLED)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && priv->cancel) { g_cancellable_reset (priv->cancel); } if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + save_and_use_model (completion, priv->initial_model); g_warning ("Could not connect to geoname lookup server: %s", error->message); g_error_free (error); - save_and_use_model (completion, priv->initial_model); return; } -- cgit v1.2.3 From 3f00f0b2b0790d6d8429d6d6812fcb78dcad9d7b Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Mon, 28 Mar 2011 15:21:46 -0400 Subject: Don't allow enter presses to leave non-timezone content in the model --- src/timezone-completion.c | 109 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index c1ffb42..ab74219 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -24,6 +24,7 @@ with this program. If not, see . #include #include +#include #include #include "timezone-completion.h" #include "tz.h" @@ -41,6 +42,7 @@ struct _TimezoneCompletionPrivate GtkEntry * entry; guint queued_request; guint changed_id; + guint keypress_id; GCancellable * cancel; gchar * request_text; GHashTable * request_table; @@ -311,6 +313,7 @@ entry_changed (GtkEntry * entry, TimezoneCompletion * completion) if (priv->queued_request) { g_source_remove (priv->queued_request); + priv->queued_request = 0; } /* See if we've already got this one */ @@ -326,6 +329,94 @@ entry_changed (GtkEntry * entry, TimezoneCompletion * completion) gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); } +static GtkWidget * +get_descendent (GtkWidget * parent, GType type) +{ + if (g_type_is_a (G_OBJECT_TYPE (parent), type)) + return parent; + + if (GTK_IS_CONTAINER (parent)) { + GList * children = gtk_container_get_children (GTK_CONTAINER (parent)); + GList * iter; + for (iter = children; iter; iter = iter->next) { + GtkWidget * found = get_descendent (GTK_WIDGET (iter->data), type); + if (found) { + g_list_free (children); + return found; + } + } + g_list_free (children); + } + + return NULL; +} + +/** + * The popup window and its GtkTreeView are private to our parent completion + * object. We can't get access to discover if there is a highlighted item or + * even if the window is showing right now. So this is a super hack to find + * it by looking through our toplevel's window group and finding a window with + * a GtkTreeView that points at our model. There should be only one ever, so + * we'll use the first one we find. + */ +static GtkTreeView * +find_popup_treeview (GtkWidget * widget, GtkTreeModel * model) +{ + GtkWidget * toplevel = gtk_widget_get_toplevel (widget); + if (!GTK_IS_WINDOW (toplevel)) + return NULL; + + GtkWindowGroup * group = gtk_window_get_group (GTK_WINDOW (toplevel)); + GList * windows = gtk_window_group_list_windows (group); + GList * iter; + for (iter = windows; iter; iter = iter->next) { + if (iter->data == toplevel) + continue; // Skip our own window, we don't have it + GtkWidget * view = get_descendent (GTK_WIDGET (iter->data), GTK_TYPE_TREE_VIEW); + if (view != NULL) { + GtkTreeModel * tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + if (GTK_IS_TREE_MODEL_FILTER (tree_model)) + tree_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (tree_model)); + if (tree_model == model) { + g_list_free (windows); + return GTK_TREE_VIEW (view); + } + } + } + g_list_free (windows); + + return NULL; +} + +static gboolean +entry_keypress (GtkEntry * entry, GdkEventKey *event, TimezoneCompletion * completion) +{ + if (event->keyval == GDK_ISO_Enter || + event->keyval == GDK_KP_Enter || + event->keyval == GDK_Return) { + /* Make sure that user has a selection to choose, otherwise ignore */ + GtkTreeModel * model = gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (completion)); + GtkTreeView * view = find_popup_treeview (GTK_WIDGET (entry), model); + if (view == NULL) { + // Just beep if popup hasn't appeared yet. + gtk_widget_error_bell (GTK_WIDGET (entry)); + return TRUE; + } + + GtkTreeSelection * sel = gtk_tree_view_get_selection (view); + GtkTreeModel * sel_model = NULL; + if (!gtk_tree_selection_get_selected (sel, &sel_model, NULL)) { + // No selection, we should help them out and select first item in list + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (sel_model, &iter)) + gtk_tree_selection_select_iter (sel, &iter); + // And fall through to normal handler code + } + } + + return FALSE; +} + void timezone_completion_watch_entry (TimezoneCompletion * completion, GtkEntry * entry) { @@ -336,15 +427,22 @@ timezone_completion_watch_entry (TimezoneCompletion * completion, GtkEntry * ent priv->queued_request = 0; } if (priv->entry) { - g_source_remove (priv->changed_id); + g_signal_handler_disconnect (priv->entry, priv->changed_id); + g_signal_handler_disconnect (priv->entry, priv->keypress_id); g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); + gtk_entry_set_completion (priv->entry, NULL); } guint id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion); priv->changed_id = id; + id = g_signal_connect (entry, "key-press-event", G_CALLBACK (entry_keypress), completion); + priv->keypress_id = id; + priv->entry = entry; g_object_add_weak_pointer (G_OBJECT (entry), (gpointer *)&priv->entry); + + gtk_entry_set_completion (entry, GTK_ENTRY_COMPLETION (completion)); } static GtkListStore * @@ -466,10 +564,17 @@ timezone_completion_dispose (GObject * object) TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); if (priv->changed_id) { - g_source_remove (priv->changed_id); + if (priv->entry) + g_signal_handler_disconnect (priv->entry, priv->changed_id); priv->changed_id = 0; } + if (priv->keypress_id) { + if (priv->entry) + g_signal_handler_disconnect (priv->entry, priv->keypress_id); + priv->keypress_id = 0; + } + if (priv->entry != NULL) { g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); } -- cgit v1.2.3 From 37106c9e4c1d59285fdc0185ea2f55891d19c4cf Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Tue, 29 Mar 2011 15:18:55 -0400 Subject: don't call complete() unless we have an entry --- src/timezone-completion.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index 2e1afc0..1c47bc0 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -79,12 +79,15 @@ save_and_use_model (TimezoneCompletion * completion, GtkTreeModel * model) gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), match_func, NULL, NULL); gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), model); - gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); - /* By this time, the changed signal has come and gone. We didn't give a - model to use, so no popup appeared for user. Poke the entry again to show - popup in 300ms. */ - g_signal_emit_by_name (priv->entry, "changed"); + if (priv->entry != NULL) { + gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); + + /* By this time, the changed signal has come and gone. We didn't give a + model to use, so no popup appeared for user. Poke the entry again to show + popup in 300ms. */ + g_signal_emit_by_name (priv->entry, "changed"); + } } static gint -- cgit v1.2.3