From deafbc1da6b3c29e04455e46414342bcb9841c2a Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Tue, 22 Feb 2011 15:17:48 -0600 Subject: beginnings of completion support --- src/timezone-completion.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/timezone-completion.c (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c new file mode 100644 index 0000000..d98654a --- /dev/null +++ b/src/timezone-completion.c @@ -0,0 +1,131 @@ +/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- + +Copyright 2011 Canonical Ltd. + +Authors: + Michael Terry + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "timezone-completion.h" +#include "tz.h" + +enum { + LAST_SIGNAL +}; + +/* static guint signals[LAST_SIGNAL] = { }; */ + +typedef struct _TimezoneCompletionPrivate TimezoneCompletionPrivate; +struct _TimezoneCompletionPrivate +{ + void * placeholder; +}; + +#define TIMEZONE_COMPLETION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TIMEZONE_COMPLETION_TYPE, TimezoneCompletionPrivate)) + +/* Prototypes */ +static void timezone_completion_class_init (TimezoneCompletionClass *klass); +static void timezone_completion_init (TimezoneCompletion *self); +static void timezone_completion_dispose (GObject *object); +static void timezone_completion_finalize (GObject *object); + +G_DEFINE_TYPE (TimezoneCompletion, timezone_completion, GTK_TYPE_ENTRY_COMPLETION); + +static GtkListStore * +get_initial_model (void) +{ + TzDB * db = tz_load_db (); + GPtrArray * locations = tz_get_locations (db); + + GtkListStore * store = gtk_list_store_new (TIMEZONE_COMPLETION_LAST, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + + gint i; + for (i = 0; i < locations->len; ++i) { + TzLocation * loc = g_ptr_array_index (locations, i); + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + + /* FIXME: need something better than below for non-English locales */ + const gchar * last_bit = ((const gchar *)strrchr (loc->zone, '/')) + 1; + if (last_bit == NULL) + last_bit = loc->zone; + gchar * name = g_strdup (last_bit); + gchar * underscore; + while ((underscore = strchr (name, '_'))) { + *underscore = ' '; + } + + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, loc->zone, + TIMEZONE_COMPLETION_NAME, name, + TIMEZONE_COMPLETION_COUNTRY, loc->country, + -1); + + g_free (name); + } + + tz_db_free (db); + return store; +} + +static void +timezone_completion_class_init (TimezoneCompletionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TimezoneCompletionPrivate)); + + object_class->dispose = timezone_completion_dispose; + object_class->finalize = timezone_completion_finalize; + + return; +} + +static void +timezone_completion_init (TimezoneCompletion *self) +{ + GtkListStore * model = get_initial_model (); + gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (self), GTK_TREE_MODEL (model)); + gtk_entry_completion_set_text_column (GTK_ENTRY_COMPLETION (self), TIMEZONE_COMPLETION_NAME); + return; +} + +static void +timezone_completion_dispose (GObject *object) +{ + G_OBJECT_CLASS (timezone_completion_parent_class)->dispose (object); + return; +} + +static void +timezone_completion_finalize (GObject *object) +{ + G_OBJECT_CLASS (timezone_completion_parent_class)->finalize (object); + return; +} + +TimezoneCompletion * +timezone_completion_new () +{ + TimezoneCompletion * self = g_object_new (TIMEZONE_COMPLETION_TYPE, NULL); + return self; +} + -- cgit v1.2.3 From b4a4c9682ca2413175386ad36d06fc4e1032badc Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Feb 2011 13:28:53 -0500 Subject: grab timezone names from geomaps; flesh out support for timezone completion in main map and locations dialog; show times in locations dialog --- src/timezone-completion.c | 227 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 221 insertions(+), 6 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index d98654a..a1b4d00 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -22,6 +22,7 @@ with this program. If not, see . #include "config.h" #endif +#include #include #include #include "timezone-completion.h" @@ -36,10 +37,17 @@ enum { typedef struct _TimezoneCompletionPrivate TimezoneCompletionPrivate; struct _TimezoneCompletionPrivate { - void * placeholder; + GtkEntry * entry; + guint queued_request; + guint changed_id; + GCancellable * cancel; + gchar * request_text; + GHashTable * request_table; }; -#define TIMEZONE_COMPLETION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TIMEZONE_COMPLETION_TYPE, TimezoneCompletionPrivate)) +#define TIMEZONE_COMPLETION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), TIMEZONE_COMPLETION_TYPE, TimezoneCompletionPrivate)) + +#define GEONAME_URL "http://geoname-lookup.ubuntu.com/?query=%s&release=%s" /* Prototypes */ static void timezone_completion_class_init (TimezoneCompletionClass *klass); @@ -49,13 +57,186 @@ static void timezone_completion_finalize (GObject *object); G_DEFINE_TYPE (TimezoneCompletion, timezone_completion, GTK_TYPE_ENTRY_COMPLETION); +static void +json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) +{ + TimezoneCompletion * completion = TIMEZONE_COMPLETION (user_data); + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE(completion); + GError * error = NULL; + + json_parser_load_from_stream_finish (JSON_PARSER (object), res, &error); + + if (priv->cancel && (error == NULL || error->code != G_IO_ERROR_CANCELLED)) { + g_cancellable_reset (priv->cancel); + } + + if (error != NULL) { + g_warning ("Could not parse geoname JSON data: %s", error->message); + g_error_free (error); + return; + } + +g_print("got json\n"); + GtkListStore * store = GTK_LIST_STORE (gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (completion))); + JsonReader * reader = json_reader_new (json_parser_get_root (JSON_PARSER (object))); + + if (!json_reader_is_array (reader)) + return; + + gint i, count = json_reader_count_elements (reader); + for (i = 0; i < count; ++i) { + if (!json_reader_read_element (reader, i)) + continue; + + if (json_reader_is_object (reader)) { + const gchar * name = NULL; + const gchar * admin1 = NULL; + const gchar * country = NULL; + const gchar * longitude = NULL; + const gchar * latitude = NULL; + if (json_reader_read_member (reader, "name")) { + name = json_reader_get_string_value (reader); + json_reader_end_member (reader); + } + if (json_reader_read_member (reader, "admin1")) { + admin1 = json_reader_get_string_value (reader); + json_reader_end_member (reader); + } + if (json_reader_read_member (reader, "country")) { + country = json_reader_get_string_value (reader); + json_reader_end_member (reader); + } + if (json_reader_read_member (reader, "longitude")) { + longitude = json_reader_get_string_value (reader); + json_reader_end_member (reader); + } + if (json_reader_read_member (reader, "latitude")) { + latitude = json_reader_get_string_value (reader); + json_reader_end_member (reader); + } + +g_print("adding %s\n", name); + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, NULL, + TIMEZONE_COMPLETION_NAME, name, + TIMEZONE_COMPLETION_ADMIN1, admin1, + TIMEZONE_COMPLETION_COUNTRY, country, + TIMEZONE_COMPLETION_LONGITUDE, longitude, + TIMEZONE_COMPLETION_LATITUDE, latitude, + -1); + } + + json_reader_end_element (reader); + } + + g_hash_table_insert (priv->request_table, g_strdup (priv->request_text), NULL); +} + +static void +geonames_data_ready (GObject *object, GAsyncResult *res, gpointer user_data) +{ + TimezoneCompletion * completion = TIMEZONE_COMPLETION (user_data); + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); + GError * error = NULL; + GFileInputStream * stream; + + stream = g_file_read_finish (G_FILE (object), res, &error); + + if (priv->cancel && (error == NULL || error->code != G_IO_ERROR_CANCELLED)) { + g_cancellable_reset (priv->cancel); + } + + if (error != NULL) { + g_warning ("Could not connect to geoname lookup server: %s", error->message); + g_error_free (error); + return; + } + + JsonParser * parser = json_parser_new (); + json_parser_load_from_stream_async (parser, G_INPUT_STREAM (stream), priv->cancel, + json_parse_ready, user_data); +} + +static gboolean +request_zones (TimezoneCompletion * completion) +{ + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); + + priv->queued_request = 0; + +g_print("requesting json?\n"); + if (priv->entry == NULL) { + return FALSE; + } + + const gchar * text = gtk_entry_get_text (priv->entry); + + if (g_hash_table_lookup_extended (priv->request_table, text, NULL, NULL)) + return FALSE; // already looked this up + + /* Cancel any ongoing request */ + if (priv->cancel) { + g_cancellable_cancel (priv->cancel); + g_cancellable_reset (priv->cancel); + } + g_free (priv->request_text); + + priv->request_text = g_strdup (text); + +g_print("requesting json now\n"); + gchar * escaped = g_uri_escape_string (text, NULL, FALSE); + gchar * url = g_strdup_printf (GEONAME_URL, escaped, "11.04"); + + GFile * file = g_file_new_for_uri (url); + g_file_read_async (file, G_PRIORITY_DEFAULT, priv->cancel, + geonames_data_ready, completion); + + return FALSE; +} + +static void +entry_changed (GtkEntry * entry, TimezoneCompletion * completion) +{ + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); + + if (priv->queued_request) { + g_source_remove (priv->queued_request); + } + priv->queued_request = g_timeout_add (300, (GSourceFunc)request_zones, completion); +} + +void +timezone_completion_watch_entry (TimezoneCompletion * completion, GtkEntry * entry) +{ + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); + + if (priv->entry) { + g_source_remove (priv->changed_id); + g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); + } + + guint id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion); + priv->changed_id = id; + + priv->entry = entry; + g_object_add_weak_pointer (G_OBJECT (entry), (gpointer *)&priv->entry); +} + static GtkListStore * get_initial_model (void) { TzDB * db = tz_load_db (); GPtrArray * locations = tz_get_locations (db); - GtkListStore * store = gtk_list_store_new (TIMEZONE_COMPLETION_LAST, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + GtkListStore * store = gtk_list_store_new (TIMEZONE_COMPLETION_LAST, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); gint i; for (i = 0; i < locations->len; ++i) { @@ -100,23 +281,57 @@ timezone_completion_class_init (TimezoneCompletionClass *klass) } static void -timezone_completion_init (TimezoneCompletion *self) +timezone_completion_init (TimezoneCompletion * self) { + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (self); + GtkListStore * model = get_initial_model (); gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (self), GTK_TREE_MODEL (model)); gtk_entry_completion_set_text_column (GTK_ENTRY_COMPLETION (self), TIMEZONE_COMPLETION_NAME); + + priv->cancel = g_cancellable_new (); + + priv->request_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + return; } static void -timezone_completion_dispose (GObject *object) +timezone_completion_dispose (GObject * object) { G_OBJECT_CLASS (timezone_completion_parent_class)->dispose (object); + + TimezoneCompletion * completion = TIMEZONE_COMPLETION (object); + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); + + if (priv->changed_id) { + g_source_remove (priv->changed_id); + priv->changed_id = 0; + } + + if (priv->entry != NULL) { + g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); + } + + if (priv->queued_request) { + g_source_remove (priv->queued_request); + priv->queued_request = 0; + } + + if (priv->cancel != NULL) { + g_cancellable_cancel (priv->cancel); + g_object_unref (priv->cancel); + priv->cancel = NULL; + } + + g_free (priv->request_text); + g_hash_table_destroy (priv->request_table); + return; } static void -timezone_completion_finalize (GObject *object) +timezone_completion_finalize (GObject * object) { G_OBJECT_CLASS (timezone_completion_parent_class)->finalize (object); return; -- cgit v1.2.3 From 66f988fe7a494e1aa12c161e415660fba247aaf4 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Feb 2011 15:30:30 -0500 Subject: some cleanup; when map changes, update entry too --- src/timezone-completion.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index a1b4d00..840b3e4 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -76,7 +76,6 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) return; } -g_print("got json\n"); GtkListStore * store = GTK_LIST_STORE (gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (completion))); JsonReader * reader = json_reader_new (json_parser_get_root (JSON_PARSER (object))); @@ -115,7 +114,6 @@ g_print("got json\n"); json_reader_end_member (reader); } -g_print("adding %s\n", name); GtkTreeIter iter; gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, @@ -166,7 +164,6 @@ request_zones (TimezoneCompletion * completion) priv->queued_request = 0; -g_print("requesting json?\n"); if (priv->entry == NULL) { return FALSE; } @@ -185,7 +182,6 @@ g_print("requesting json?\n"); priv->request_text = g_strdup (text); -g_print("requesting json now\n"); gchar * escaped = g_uri_escape_string (text, NULL, FALSE); gchar * url = g_strdup_printf (GEONAME_URL, escaped, "11.04"); -- cgit v1.2.3 From d38ff688556788a5d6c8e3f033b460c8ac849039 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Feb 2011 14:46:46 -0600 Subject: skip duplicate completions --- src/timezone-completion.c | 55 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 10 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index 840b3e4..7f5ad68 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -114,16 +114,51 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) json_reader_end_member (reader); } + /* See if we have this in our store already */ GtkTreeIter iter; - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, - TIMEZONE_COMPLETION_ZONE, NULL, - TIMEZONE_COMPLETION_NAME, name, - TIMEZONE_COMPLETION_ADMIN1, admin1, - TIMEZONE_COMPLETION_COUNTRY, country, - TIMEZONE_COMPLETION_LONGITUDE, longitude, - TIMEZONE_COMPLETION_LATITUDE, latitude, - -1); + gboolean skip = FALSE; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { + do { + GValue value = {0}; + + gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, TIMEZONE_COMPLETION_NAME, &value); + if (g_strcmp0 (g_value_get_string (&value), name) != 0) { + g_value_unset (&value); + continue; + } + g_value_unset (&value); + + gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, TIMEZONE_COMPLETION_ADMIN1, &value); + if (g_strcmp0 (g_value_get_string (&value), admin1) != 0) { + g_value_unset (&value); + continue; + } + g_value_unset (&value); + + gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, TIMEZONE_COMPLETION_COUNTRY, &value); + if (g_strcmp0 (g_value_get_string (&value), country) != 0) { + g_value_unset (&value); + continue; + } + g_value_unset (&value); + + /* Must be the same, skip this one */ + skip = TRUE; + break; + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + } + + if (!skip) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, NULL, + TIMEZONE_COMPLETION_NAME, name, + TIMEZONE_COMPLETION_ADMIN1, admin1, + TIMEZONE_COMPLETION_COUNTRY, country, + TIMEZONE_COMPLETION_LONGITUDE, longitude, + TIMEZONE_COMPLETION_LATITUDE, latitude, + -1); + } } json_reader_end_element (reader); @@ -183,7 +218,7 @@ request_zones (TimezoneCompletion * completion) priv->request_text = g_strdup (text); gchar * escaped = g_uri_escape_string (text, NULL, FALSE); - gchar * url = g_strdup_printf (GEONAME_URL, escaped, "11.04"); + gchar * url = g_strdup_printf (GEONAME_URL, escaped, "11.04"); // FIXME: don't hardcode GFile * file = g_file_new_for_uri (url); g_file_read_async (file, G_PRIORITY_DEFAULT, priv->cancel, -- cgit v1.2.3 From e2fa62d9ad271f26f2a8e4d9297190e0250bb967 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 23 Feb 2011 15:13:11 -0600 Subject: show admin1 and country in completion dropdown --- src/timezone-completion.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index 7f5ad68..ab7ae5e 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -298,6 +298,35 @@ get_initial_model (void) return store; } +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); + + gchar * user_name; + 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); + } + + 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 timezone_completion_class_init (TimezoneCompletionClass *klass) { @@ -318,12 +347,16 @@ timezone_completion_init (TimezoneCompletion * self) GtkListStore * model = get_initial_model (); gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (self), GTK_TREE_MODEL (model)); - gtk_entry_completion_set_text_column (GTK_ENTRY_COMPLETION (self), TIMEZONE_COMPLETION_NAME); + g_object_set (G_OBJECT (self), "text-column", TIMEZONE_COMPLETION_NAME, NULL); priv->cancel = g_cancellable_new (); priv->request_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + GtkCellRenderer * cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self), cell, data_func, NULL, NULL); + return; } -- cgit v1.2.3 From ef1648eeba51b9c2e29a3890a6057b569c67b543 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Thu, 24 Feb 2011 11:41:37 -0500 Subject: make sure user always sees new model from geonames when we get it --- src/timezone-completion.c | 122 ++++++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 54 deletions(-) (limited to 'src/timezone-completion.c') diff --git a/src/timezone-completion.c b/src/timezone-completion.c index ab7ae5e..f570c33 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -37,6 +37,7 @@ enum { typedef struct _TimezoneCompletionPrivate TimezoneCompletionPrivate; struct _TimezoneCompletionPrivate { + GtkTreeModel * initial_model; GtkEntry * entry; guint queued_request; guint changed_id; @@ -57,6 +58,16 @@ static void timezone_completion_finalize (GObject *object); G_DEFINE_TYPE (TimezoneCompletion, timezone_completion, GTK_TYPE_ENTRY_COMPLETION); +static void +save_and_use_model (TimezoneCompletion * completion, GtkTreeModel * model) +{ + TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE(completion); + + g_hash_table_insert (priv->request_table, g_strdup (priv->request_text), g_object_ref (model)); + gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), model); + gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); +} + static void json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) { @@ -73,10 +84,18 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) if (error != NULL) { g_warning ("Could not parse geoname JSON data: %s", error->message); g_error_free (error); + save_and_use_model (completion, priv->initial_model); return; } - GtkListStore * store = GTK_LIST_STORE (gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (completion))); + GtkListStore * store = gtk_list_store_new (TIMEZONE_COMPLETION_LAST, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + JsonReader * reader = json_reader_new (json_parser_get_root (JSON_PARSER (object))); if (!json_reader_is_array (reader)) @@ -114,57 +133,23 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) json_reader_end_member (reader); } - /* See if we have this in our store already */ GtkTreeIter iter; - gboolean skip = FALSE; - if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { - do { - GValue value = {0}; - - gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, TIMEZONE_COMPLETION_NAME, &value); - if (g_strcmp0 (g_value_get_string (&value), name) != 0) { - g_value_unset (&value); - continue; - } - g_value_unset (&value); - - gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, TIMEZONE_COMPLETION_ADMIN1, &value); - if (g_strcmp0 (g_value_get_string (&value), admin1) != 0) { - g_value_unset (&value); - continue; - } - g_value_unset (&value); - - gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, TIMEZONE_COMPLETION_COUNTRY, &value); - if (g_strcmp0 (g_value_get_string (&value), country) != 0) { - g_value_unset (&value); - continue; - } - g_value_unset (&value); - - /* Must be the same, skip this one */ - skip = TRUE; - break; - } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); - } - - if (!skip) { - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, - TIMEZONE_COMPLETION_ZONE, NULL, - TIMEZONE_COMPLETION_NAME, name, - TIMEZONE_COMPLETION_ADMIN1, admin1, - TIMEZONE_COMPLETION_COUNTRY, country, - TIMEZONE_COMPLETION_LONGITUDE, longitude, - TIMEZONE_COMPLETION_LATITUDE, latitude, - -1); - } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, NULL, + TIMEZONE_COMPLETION_NAME, name, + TIMEZONE_COMPLETION_ADMIN1, admin1, + TIMEZONE_COMPLETION_COUNTRY, country, + TIMEZONE_COMPLETION_LONGITUDE, longitude, + TIMEZONE_COMPLETION_LATITUDE, latitude, + -1); } json_reader_end_element (reader); } - g_hash_table_insert (priv->request_table, g_strdup (priv->request_text), NULL); + save_and_use_model (completion, GTK_TREE_MODEL (store)); + g_object_unref (G_OBJECT (store)); } static void @@ -184,6 +169,7 @@ geonames_data_ready (GObject *object, GAsyncResult *res, gpointer user_data) if (error != NULL) { 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; } @@ -205,8 +191,12 @@ request_zones (TimezoneCompletion * completion) const gchar * text = gtk_entry_get_text (priv->entry); - if (g_hash_table_lookup_extended (priv->request_table, text, NULL, NULL)) - return FALSE; // already looked this up + gpointer data; + if (g_hash_table_lookup_extended (priv->request_table, text, NULL, &data)) { + gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), GTK_TREE_MODEL (data)); + gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); + return FALSE; + } /* Cancel any ongoing request */ if (priv->cancel) { @@ -327,6 +317,14 @@ data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell, g_value_unset (&country_val); } +static gboolean +match_func (GtkEntryCompletion *completion, const gchar *key, + GtkTreeIter *iter, gpointer user_data) +{ + // geonames does the work for us + return TRUE; +} + static void timezone_completion_class_init (TimezoneCompletionClass *klass) { @@ -345,13 +343,17 @@ timezone_completion_init (TimezoneCompletion * self) { TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (self); - GtkListStore * model = get_initial_model (); - gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (self), GTK_TREE_MODEL (model)); - g_object_set (G_OBJECT (self), "text-column", TIMEZONE_COMPLETION_NAME, NULL); + priv->initial_model = GTK_TREE_MODEL (get_initial_model ()); + + gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (self), match_func, NULL, NULL); + g_object_set (G_OBJECT (self), + "text-column", TIMEZONE_COMPLETION_NAME, + "popup-set-width", FALSE, + NULL); priv->cancel = g_cancellable_new (); - priv->request_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + priv->request_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); GtkCellRenderer * cell = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE); @@ -377,6 +379,11 @@ timezone_completion_dispose (GObject * object) g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); } + if (priv->initial_model != NULL) { + g_object_unref (G_OBJECT (priv->initial_model)); + priv->initial_model = NULL; + } + if (priv->queued_request) { g_source_remove (priv->queued_request); priv->queued_request = 0; @@ -388,8 +395,15 @@ timezone_completion_dispose (GObject * object) priv->cancel = NULL; } - g_free (priv->request_text); - g_hash_table_destroy (priv->request_table); + if (priv->request_text != NULL) { + g_free (priv->request_text); + priv->request_text = NULL; + } + + if (priv->request_table != NULL) { + g_hash_table_destroy (priv->request_table); + priv->request_table = NULL; + } return; } -- cgit v1.2.3