aboutsummaryrefslogtreecommitdiff
path: root/src/timezone-completion.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/timezone-completion.c')
-rw-r--r--src/timezone-completion.c688
1 files changed, 688 insertions, 0 deletions
diff --git a/src/timezone-completion.c b/src/timezone-completion.c
new file mode 100644
index 0000000..7dcc28e
--- /dev/null
+++ b/src/timezone-completion.c
@@ -0,0 +1,688 @@
+/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
+
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Michael Terry <michael.terry@canonical.com>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <json-glib/json-glib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include "timezone-completion.h"
+#include "tz.h"
+
+enum {
+ LAST_SIGNAL
+};
+
+/* static guint signals[LAST_SIGNAL] = { }; */
+
+typedef struct _TimezoneCompletionPrivate TimezoneCompletionPrivate;
+struct _TimezoneCompletionPrivate
+{
+ GtkTreeModel * initial_model;
+ GtkEntry * entry;
+ guint queued_request;
+ guint changed_id;
+ guint keypress_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 GEONAME_URL "http://geoname-lookup.ubuntu.com/?query=%s&release=%s&lang=%s"
+
+/* 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 gboolean
+match_func (GtkEntryCompletion *completion, const gchar *key,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ // geonames does the work for us
+ return TRUE;
+}
+
+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_sink (model));
+
+ if (model == priv->initial_model)
+ gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), NULL, NULL, NULL);
+ else
+ gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), match_func, NULL, NULL);
+
+ gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), model);
+
+ 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
+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)
+{
+ TimezoneCompletion * completion = TIMEZONE_COMPLETION (user_data);
+ TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE(completion);
+ GError * error = NULL;
+ const gchar * prev_name = NULL;
+ const gchar * prev_admin1 = NULL;
+ const gchar * prev_country = NULL;
+
+ json_parser_load_from_stream_finish (JSON_PARSER (object), res, &error);
+
+ 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);
+ return;
+ }
+
+ 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)) {
+ g_warning ("Could not parse geoname JSON data");
+ save_and_use_model (completion, priv->initial_model);
+ g_object_unref (G_OBJECT (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;
+ gboolean skip = FALSE;
+ 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);
+ }
+
+ if (g_strcmp0(name, prev_name) == 0 &&
+ g_strcmp0(admin1, prev_admin1) == 0 &&
+ g_strcmp0(country, prev_country) == 0) {
+ // Sometimes the data will have duplicate entries that only differ
+ // in longitude and latitude. e.g. "rio de janeiro", "wellington"
+ skip = TRUE;
+ }
+
+ if (!skip) {
+ 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);
+ 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;
+ prev_admin1 = admin1;
+ prev_country = country;
+ }
+
+ 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));
+}
+
+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 (!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);
+ return;
+ }
+
+ JsonParser * parser = json_parser_new ();
+ json_parser_load_from_stream_async (parser, G_INPUT_STREAM (stream), priv->cancel,
+ json_parse_ready, user_data);
+}
+
+/* Returns message locale, with possible country info too like en_US */
+static gchar *
+get_locale (void)
+{
+ /* Check LANGUAGE, LC_ALL, LC_MESSAGES, and LANG, treat as colon-separated */
+ const gchar *env_names[] = {"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG", NULL};
+ const gchar *env = NULL;
+ gint i;
+
+ for (i = 0; env_names[i]; i++) {
+ env = g_getenv (env_names[i]);
+ if (env != NULL && env[0] != 0)
+ break;
+ }
+
+ if (env == NULL)
+ return NULL;
+
+ /* Now, we split on colons as expected, but also on . and @ to filter out
+ extra pieces of locale we don't care about as we only use first chunk. */
+ gchar **split = g_strsplit_set (env, ":.@", 2);
+ if (split == NULL)
+ return NULL;
+
+ if (split[0] == NULL) {
+ g_strfreev (split);
+ return NULL;
+ }
+
+ gchar *locale = g_strdup (split[0]);
+ g_strfreev (split);
+ return locale;
+}
+
+static gchar *
+get_version (void)
+{
+ static gchar *version = NULL;
+
+ if (version == NULL) {
+ gchar *stdout = NULL;
+ g_spawn_command_line_sync ("lsb_release -rs", &stdout, NULL, NULL, NULL);
+
+ if (stdout != NULL)
+ version = g_strstrip (stdout);
+ else
+ version = g_strdup("");
+ }
+
+ return version;
+}
+
+static gboolean
+request_zones (TimezoneCompletion * completion)
+{
+ TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion);
+
+ priv->queued_request = 0;
+
+ if (priv->entry == NULL) {
+ return FALSE;
+ }
+
+ /* Cancel any ongoing request */
+ if (priv->cancel) {
+ g_cancellable_cancel (priv->cancel);
+ g_cancellable_reset (priv->cancel);
+ }
+ g_free (priv->request_text);
+
+ const gchar * text = gtk_entry_get_text (priv->entry);
+ priv->request_text = g_strdup (text);
+
+ gchar * escaped = g_uri_escape_string (text, NULL, FALSE);
+ gchar * version = get_version ();
+ gchar * locale = get_locale ();
+ gchar * url = g_strdup_printf (GEONAME_URL, escaped, version, locale);
+ g_free (locale);
+ g_free (version);
+ g_free (escaped);
+
+ GFile * file = g_file_new_for_uri (url);
+ g_free (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 = 0;
+ }
+
+ /* See if we've already got this one */
+ const gchar * text = gtk_entry_get_text (priv->entry);
+ 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));
+ }
+ else {
+ priv->queued_request = g_timeout_add (300, (GSourceFunc)request_zones, completion);
+ gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), NULL);
+ }
+ 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)
+{
+ TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion);
+
+ if (priv->queued_request) {
+ g_source_remove (priv->queued_request);
+ priv->queued_request = 0;
+ }
+ if (priv->entry) {
+ 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 *
+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,
+ 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);
+ }
+
+ 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;
+}
+
+static void
+data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
+ GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
+{
+ const gchar * name, * admin1, * country;
+
+ 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 (country == NULL || country[0] == 0) {
+ user_name = g_strdup (name);
+ } else if (admin1 == NULL || admin1[0] == 0) {
+ user_name = g_strdup_printf ("%s <small>(%s)</small>", name, country);
+ } else {
+ user_name = g_strdup_printf ("%s <small>(%s, %s)</small>", name, admin1, country);
+ }
+
+ g_object_set (G_OBJECT (cell), "markup", user_name, NULL);
+}
+
+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)
+{
+ TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (self);
+
+ priv->initial_model = GTK_TREE_MODEL (get_initial_model ());
+
+ 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, g_object_unref);
+
+ 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;
+}
+
+static void
+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) {
+ 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);
+ }
+
+ 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;
+ }
+
+ if (priv->cancel != NULL) {
+ g_cancellable_cancel (priv->cancel);
+ g_object_unref (priv->cancel);
+ priv->cancel = NULL;
+ }
+
+ 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;
+}
+
+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;
+}
+