1 /* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
2
3 Copyright 2011 Canonical Ltd.
4
5 Authors:
6 Michael Terry <michael.terry@canonical.com>
7
8 This program is free software: you can redistribute it and/or modify it
9 under the terms of the GNU General Public License version 3, as published
10 by the Free Software Foundation.
11
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranties of
14 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15 PURPOSE. See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License along
18 with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <json-glib/json-glib.h>
26 #include <gdk/gdk.h>
27 #include <gdk/gdkkeysyms.h>
28 #include <glib/gi18n.h>
29 #include <libsoup/soup.h>
30 #include "timezone-completion.h"
31 #include "tz.h"
32
33 enum {
34 LAST_SIGNAL
35 };
36
37 /* static guint signals[LAST_SIGNAL] = { }; */
38
39 struct _CcTimezoneCompletionPrivate
40 {
41 GtkTreeModel * initial_model;
42 GtkEntry * entry;
43 guint queued_request;
44 guint changed_id;
45 guint keypress_id;
46 GCancellable * cancel;
47 gchar * request_text;
48 GHashTable * request_table;
49 SoupSession * soup_session;
50 };
51
52 #define GEONAME_URL "http://geoname-lookup.ubuntu.com/?query=%s&release=%s&lang=%s"
53
54 /* Prototypes */
55 static void cc_timezone_completion_class_init (CcTimezoneCompletionClass *klass);
56 static void cc_timezone_completion_init (CcTimezoneCompletion *self);
57 static void cc_timezone_completion_dispose (GObject *object);
58 static void cc_timezone_completion_finalize (GObject *object);
59
60 G_DEFINE_TYPE (CcTimezoneCompletion, cc_timezone_completion, GTK_TYPE_ENTRY_COMPLETION);
61
62 static gboolean
match_func(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,gpointer user_data)63 match_func (GtkEntryCompletion *completion, const gchar *key,
64 GtkTreeIter *iter, gpointer user_data)
65 {
66 // geonames does the work for us
67 return TRUE;
68 }
69
70 static void
save_and_use_model(CcTimezoneCompletion * completion,GtkTreeModel * model)71 save_and_use_model (CcTimezoneCompletion * completion, GtkTreeModel * model)
72 {
73 CcTimezoneCompletionPrivate * priv = completion->priv;
74
75 g_hash_table_insert (priv->request_table, g_strdup (priv->request_text), g_object_ref_sink (model));
76
77 if (model == priv->initial_model)
78 gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), NULL, NULL, NULL);
79 else
80 gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), match_func, NULL, NULL);
81
82 gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), model);
83
84 if (priv->entry != NULL) {
85 gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion));
86
87 /* By this time, the changed signal has come and gone. We didn't give a
88 model to use, so no popup appeared for user. Poke the entry again to show
89 popup in 300ms. */
90 g_signal_emit_by_name (priv->entry, "changed");
91 }
92 }
93
94 static gint
sort_zone(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)95 sort_zone (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
96 gpointer user_data)
97 {
98 /* Anything that has text as a prefix goes first, in mostly sorted order.
99 Then everything else goes after, in mostly sorted order. */
100 const gchar *casefolded_text = (const gchar *)user_data;
101
102 const gchar *namea = NULL, *nameb = NULL;
103 gtk_tree_model_get (model, a, CC_TIMEZONE_COMPLETION_NAME, &namea, -1);
104 gtk_tree_model_get (model, b, CC_TIMEZONE_COMPLETION_NAME, &nameb, -1);
105
106 gchar *casefolded_namea = NULL, *casefolded_nameb = NULL;
107 casefolded_namea = g_utf8_casefold (namea, -1);
108 casefolded_nameb = g_utf8_casefold (nameb, -1);
109
110 gboolean amatches = FALSE, bmatches = FALSE;
111 amatches = strncmp (casefolded_text, casefolded_namea, strlen(casefolded_text)) == 0;
112 bmatches = strncmp (casefolded_text, casefolded_nameb, strlen(casefolded_text)) == 0;
113
114 gint rv;
115 if (amatches && !bmatches)
116 rv = -1;
117 else if (bmatches && !amatches)
118 rv = 1;
119 else
120 rv = g_utf8_collate (casefolded_namea, casefolded_nameb);
121
122 g_free (casefolded_namea);
123 g_free (casefolded_nameb);
124 return rv;
125 }
126
127 static void
json_parse_ready(GObject * object,GAsyncResult * res,gpointer user_data)128 json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data)
129 {
130 CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (user_data);
131 CcTimezoneCompletionPrivate * priv = completion->priv;
132 GError * error = NULL;
133 const gchar * prev_name = NULL;
134 const gchar * prev_admin1 = NULL;
135 const gchar * prev_country = NULL;
136
137 json_parser_load_from_stream_finish (JSON_PARSER (object), res, &error);
138
139 if (error != NULL)
140 {
141 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
142 save_and_use_model (completion, priv->initial_model);
143 g_warning ("Could not parse geoname JSON data: %s", error->message);
144 g_error_free (error);
145 return;
146 }
147
148 GtkListStore * store = gtk_list_store_new (CC_TIMEZONE_COMPLETION_LAST,
149 G_TYPE_STRING,
150 G_TYPE_STRING,
151 G_TYPE_STRING,
152 G_TYPE_STRING,
153 G_TYPE_STRING,
154 G_TYPE_STRING);
155
156 JsonReader * reader = json_reader_new (json_parser_get_root (JSON_PARSER (object)));
157
158 if (!json_reader_is_array (reader))
159 {
160 g_warning ("Could not parse geoname JSON data");
161 save_and_use_model (completion, priv->initial_model);
162 g_object_unref (G_OBJECT (reader));
163 return;
164 }
165
166 gint i, count = json_reader_count_elements (reader);
167 for (i = 0; i < count; ++i)
168 {
169 if (!json_reader_read_element (reader, i))
170 continue;
171
172 if (json_reader_is_object (reader))
173 {
174 const gchar * name = NULL;
175 const gchar * admin1 = NULL;
176 const gchar * country = NULL;
177 const gchar * longitude = NULL;
178 const gchar * latitude = NULL;
179 gboolean skip = FALSE;
180 if (json_reader_read_member (reader, "name"))
181 {
182 name = json_reader_get_string_value (reader);
183 json_reader_end_member (reader);
184 }
185 if (json_reader_read_member (reader, "admin1"))
186 {
187 admin1 = json_reader_get_string_value (reader);
188 json_reader_end_member (reader);
189 }
190 if (json_reader_read_member (reader, "country"))
191 {
192 country = json_reader_get_string_value (reader);
193 json_reader_end_member (reader);
194 }
195 if (json_reader_read_member (reader, "longitude"))
196 {
197 longitude = json_reader_get_string_value (reader);
198 json_reader_end_member (reader);
199 }
200 if (json_reader_read_member (reader, "latitude"))
201 {
202 latitude = json_reader_get_string_value (reader);
203 json_reader_end_member (reader);
204 }
205
206 if (g_strcmp0(name, prev_name) == 0 &&
207 g_strcmp0(admin1, prev_admin1) == 0 &&
208 g_strcmp0(country, prev_country) == 0)
209 {
210 // Sometimes the data will have duplicate entries that only differ
211 // in longitude and latitude. e.g. "rio de janeiro", "wellington"
212 skip = TRUE;
213 }
214
215 if (!skip)
216 {
217 GtkTreeIter iter;
218 gtk_list_store_append (store, &iter);
219 gtk_list_store_set (store, &iter,
220 CC_TIMEZONE_COMPLETION_ZONE, NULL,
221 CC_TIMEZONE_COMPLETION_NAME, name,
222 CC_TIMEZONE_COMPLETION_ADMIN1, admin1,
223 CC_TIMEZONE_COMPLETION_COUNTRY, country,
224 CC_TIMEZONE_COMPLETION_LONGITUDE, longitude,
225 CC_TIMEZONE_COMPLETION_LATITUDE, latitude,
226 -1);
227 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
228 CC_TIMEZONE_COMPLETION_NAME,
229 sort_zone,
230 g_utf8_casefold(priv->request_text,
231 -1),
232 g_free);
233 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
234 CC_TIMEZONE_COMPLETION_NAME,
235 GTK_SORT_ASCENDING);
236 }
237
238 prev_name = name;
239 prev_admin1 = admin1;
240 prev_country = country;
241 }
242
243 json_reader_end_element (reader);
244 }
245
246 if (strlen (priv->request_text) < 4)
247 {
248 gchar * lower_text = g_ascii_strdown (priv->request_text, -1);
249 if (g_strcmp0 (lower_text, "ut") == 0 ||
250 g_strcmp0 (lower_text, "utc") == 0)
251 {
252 GtkTreeIter iter;
253 gtk_list_store_append (store, &iter);
254 gtk_list_store_set (store, &iter,
255 CC_TIMEZONE_COMPLETION_ZONE, "UTC",
256 CC_TIMEZONE_COMPLETION_NAME, "UTC",
257 -1);
258 }
259 g_free (lower_text);
260 }
261
262 save_and_use_model (completion, GTK_TREE_MODEL (store));
263 g_object_unref (G_OBJECT (reader));
264 }
265
266 static void
geonames_data_ready(GObject * object,GAsyncResult * res,gpointer user_data)267 geonames_data_ready (GObject *object, GAsyncResult *res, gpointer user_data)
268 {
269 CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (user_data);
270 CcTimezoneCompletionPrivate * priv = completion->priv;
271 GError * error = NULL;
272 GInputStream * stream;
273 SoupMessage *message;
274
275 stream = soup_request_send_finish (SOUP_REQUEST (object), res, &error);
276 if (stream == NULL)
277 {
278 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
279 save_and_use_model (completion, priv->initial_model);
280 g_warning ("Could not connect to geoname lookup server: %s",
281 error->message);
282 g_error_free (error);
283 return;
284 }
285
286 message = soup_request_http_get_message (SOUP_REQUEST_HTTP (object));
287 if (message->status_code == SOUP_STATUS_OK)
288 {
289 JsonParser *parser;
290
291 parser = json_parser_new ();
292 json_parser_load_from_stream_async (parser, stream, priv->cancel, json_parse_ready, user_data);
293
294 g_object_unref (parser);
295 }
296 else
297 {
298 g_warning ("Unable to fetch geonames (server responded with: %u %s)",
299 message->status_code, message->reason_phrase);
300 }
301
302 g_object_unref (message);
303 g_object_unref (stream);
304 }
305
306 /* Returns message locale, with possible country info too like en_US */
307 static gchar *
get_locale(void)308 get_locale (void)
309 {
310 /* Check LANGUAGE, LC_ALL, LC_MESSAGES, and LANG, treat as colon-separated */
311 const gchar *env_names[] = {"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG", NULL};
312 const gchar *env = NULL;
313 gint i;
314
315 for (i = 0; env_names[i]; i++)
316 {
317 env = g_getenv (env_names[i]);
318 if (env != NULL && env[0] != 0)
319 break;
320 }
321
322 if (env == NULL)
323 return NULL;
324
325 /* Now, we split on colons as expected, but also on . and @ to filter out
326 extra pieces of locale we don't care about as we only use first chunk. */
327 gchar **split = g_strsplit_set (env, ":.@", 2);
328 if (split == NULL)
329 return NULL;
330
331 if (split[0] == NULL)
332 {
333 g_strfreev (split);
334 return NULL;
335 }
336
337 gchar *locale = g_strdup (split[0]);
338 g_strfreev (split);
339 return locale;
340 }
341
342 static gchar *
get_version(void)343 get_version (void)
344 {
345 static gchar *version = NULL;
346
347 if (version == NULL)
348 {
349 gchar *stdout = NULL;
350 g_spawn_command_line_sync ("lsb_release -rs", &stdout, NULL, NULL, NULL);
351
352 if (stdout != NULL)
353 version = g_strstrip (stdout);
354 else
355 version = g_strdup("");
356 }
357
358 return version;
359 }
360
361 static gboolean
request_zones(CcTimezoneCompletion * completion)362 request_zones (CcTimezoneCompletion * completion)
363 {
364 CcTimezoneCompletionPrivate * priv = completion->priv;
365 SoupRequest *req;
366 GError *error = NULL;
367
368 priv->queued_request = 0;
369
370 if (priv->entry == NULL)
371 {
372 return FALSE;
373 }
374
375 /* Cancel any ongoing request */
376 if (priv->cancel)
377 {
378 g_cancellable_cancel (priv->cancel);
379 g_object_unref (priv->cancel);
380 priv->cancel = g_cancellable_new ();
381 }
382 g_free (priv->request_text);
383
384 const gchar * text = gtk_entry_get_text (priv->entry);
385 priv->request_text = g_strdup (text);
386
387 gchar * escaped = g_uri_escape_string (text, NULL, FALSE);
388 gchar * version = get_version ();
389 gchar * locale = get_locale ();
390 gchar * url = g_strdup_printf (GEONAME_URL, escaped, version, locale);
391 g_free (locale);
392 g_free (escaped);
393
394 req = soup_session_request (priv->soup_session, url, &error);
395 if (req)
396 {
397 soup_request_send_async (req, priv->cancel, geonames_data_ready, completion);
398 g_object_unref (req);
399 }
400 else
401 {
402 g_warning ("%s", error->message);
403 g_error_free (error);
404 }
405
406 g_free (url);
407 return FALSE;
408 }
409
410 static void
entry_changed(GtkEntry * entry,CcTimezoneCompletion * completion)411 entry_changed (GtkEntry * entry, CcTimezoneCompletion * completion)
412 {
413 CcTimezoneCompletionPrivate * priv = completion->priv;
414
415 if (priv->queued_request)
416 {
417 g_source_remove (priv->queued_request);
418 priv->queued_request = 0;
419 }
420
421 /* See if we've already got this one */
422 const gchar * text = gtk_entry_get_text (priv->entry);
423 gpointer data;
424 if (g_hash_table_lookup_extended (priv->request_table, text, NULL, &data))
425 {
426 gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion),
427 GTK_TREE_MODEL (data));
428 }
429 else
430 {
431 priv->queued_request = g_timeout_add (300, (GSourceFunc)request_zones,
432 completion);
433 gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), NULL);
434 }
435 gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion));
436 }
437
438 static GtkWidget *
get_descendent(GtkWidget * parent,GType type)439 get_descendent (GtkWidget * parent, GType type)
440 {
441 if (g_type_is_a (G_OBJECT_TYPE (parent), type))
442 return parent;
443
444 if (GTK_IS_CONTAINER (parent))
445 {
446 GList * children = gtk_container_get_children (GTK_CONTAINER (parent));
447 GList * iter;
448 for (iter = children; iter; iter = iter->next)
449 {
450 GtkWidget * found = get_descendent (GTK_WIDGET (iter->data), type);
451 if (found)
452 {
453 g_list_free (children);
454 return found;
455 }
456 }
457 g_list_free (children);
458 }
459
460 return NULL;
461 }
462
463 /*
464 * The popup window and its GtkTreeView are private to our parent completion
465 * object. We can't get access to discover if there is a highlighted item or
466 * even if the window is showing right now. So this is a super hack to find
467 * it by looking through our toplevel's window group and finding a window with
468 * a GtkTreeView that points at our model. There should be only one ever, so
469 * we'll use the first one we find.
470 */
471 static GtkTreeView *
find_popup_treeview(GtkWidget * widget,GtkTreeModel * model)472 find_popup_treeview (GtkWidget * widget, GtkTreeModel * model)
473 {
474 GtkWidget * toplevel = gtk_widget_get_toplevel (widget);
475 if (!GTK_IS_WINDOW (toplevel))
476 return NULL;
477
478 GtkWindowGroup * group = gtk_window_get_group (GTK_WINDOW (toplevel));
479 GList * windows = gtk_window_group_list_windows (group);
480 GList * iter;
481 for (iter = windows; iter; iter = iter->next)
482 {
483 if (iter->data == toplevel)
484 continue; // Skip our own window, we don't have it
485 GtkWidget * view = get_descendent (GTK_WIDGET (iter->data),
486 GTK_TYPE_TREE_VIEW);
487 if (view != NULL)
488 {
489 GtkTreeModel * tree_model =
490 gtk_tree_view_get_model (GTK_TREE_VIEW (view));
491 if (GTK_IS_TREE_MODEL_FILTER (tree_model))
492 tree_model = gtk_tree_model_filter_get_model (
493 GTK_TREE_MODEL_FILTER (tree_model));
494 if (tree_model == model)
495 {
496 g_list_free (windows);
497 return GTK_TREE_VIEW (view);
498 }
499 }
500 }
501 g_list_free (windows);
502
503 return NULL;
504 }
505
506 static gboolean
entry_keypress(GtkEntry * entry,GdkEventKey * event,CcTimezoneCompletion * completion)507 entry_keypress (GtkEntry * entry, GdkEventKey *event, CcTimezoneCompletion * completion)
508 {
509 if (event->keyval == GDK_KEY_ISO_Enter ||
510 event->keyval == GDK_KEY_KP_Enter ||
511 event->keyval == GDK_KEY_Return)
512 {
513 /* Make sure that user has a selection to choose, otherwise ignore */
514 GtkTreeModel * model = gtk_entry_completion_get_model (
515 GTK_ENTRY_COMPLETION (completion));
516 GtkTreeView * view = find_popup_treeview (GTK_WIDGET (entry), model);
517 if (view == NULL)
518 {
519 // Just beep if popup hasn't appeared yet.
520 gtk_widget_error_bell (GTK_WIDGET (entry));
521 return TRUE;
522 }
523
524 GtkTreeSelection * sel = gtk_tree_view_get_selection (view);
525 GtkTreeModel * sel_model = NULL;
526 if (!gtk_tree_selection_get_selected (sel, &sel_model, NULL))
527 {
528 // No selection, we should help them out and select first item in list
529 GtkTreeIter iter;
530 if (gtk_tree_model_get_iter_first (sel_model, &iter))
531 gtk_tree_selection_select_iter (sel, &iter);
532 // And fall through to normal handler code
533 }
534 }
535
536 return FALSE;
537 }
538
539 void
cc_timezone_completion_watch_entry(CcTimezoneCompletion * completion,GtkEntry * entry)540 cc_timezone_completion_watch_entry (CcTimezoneCompletion * completion, GtkEntry * entry)
541 {
542 CcTimezoneCompletionPrivate * priv = completion->priv;
543
544 if (priv->queued_request)
545 {
546 g_source_remove (priv->queued_request);
547 priv->queued_request = 0;
548 }
549 if (priv->entry)
550 {
551 g_signal_handler_disconnect (priv->entry, priv->changed_id);
552 priv->changed_id = 0;
553 g_signal_handler_disconnect (priv->entry, priv->keypress_id);
554 priv->keypress_id = 0;
555 g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry);
556 gtk_entry_set_completion (priv->entry, NULL);
557 }
558
559 priv->entry = entry;
560
561 if (entry)
562 {
563 guint id = g_signal_connect (entry, "changed",
564 G_CALLBACK (entry_changed), completion);
565 priv->changed_id = id;
566
567 id = g_signal_connect (entry, "key-press-event",
568 G_CALLBACK (entry_keypress), completion);
569 priv->keypress_id = id;
570
571 g_object_add_weak_pointer (G_OBJECT (entry), (gpointer *)&priv->entry);
572
573 gtk_entry_set_completion (entry, GTK_ENTRY_COMPLETION (completion));
574 }
575 }
576
577 static GtkListStore *
get_initial_model(void)578 get_initial_model (void)
579 {
580 TzDB * db = tz_load_db ();
581 GPtrArray * locations = tz_get_locations (db);
582
583 GtkListStore * store = gtk_list_store_new (CC_TIMEZONE_COMPLETION_LAST,
584 G_TYPE_STRING,
585 G_TYPE_STRING,
586 G_TYPE_STRING,
587 G_TYPE_STRING,
588 G_TYPE_STRING,
589 G_TYPE_STRING);
590
591 gint i;
592 for (i = 0; i < locations->len; ++i)
593 {
594 CcTimezoneLocation * loc = g_ptr_array_index (locations, i);
595 GtkTreeIter iter;
596 gtk_list_store_append (store, &iter);
597
598 gchar * zone;
599 gchar * country;
600 gchar * en_name; // FIXME: need something better for non-English locales
601 gdouble longitude;
602 gdouble latitude;
603 g_object_get (loc, "zone", &zone, "country", &country, "en_name", &en_name,
604 "longitude", &longitude, "latitude", &latitude,
605 NULL);
606
607 gchar * longitude_s = g_strdup_printf ("%f", longitude);
608 gchar * latitude_s= g_strdup_printf ("%f", latitude);
609
610 gtk_list_store_set (store, &iter,
611 CC_TIMEZONE_COMPLETION_ZONE, NULL,
612 CC_TIMEZONE_COMPLETION_NAME, en_name,
613 CC_TIMEZONE_COMPLETION_COUNTRY, country,
614 CC_TIMEZONE_COMPLETION_LONGITUDE, longitude_s,
615 CC_TIMEZONE_COMPLETION_LATITUDE, latitude_s,
616 -1);
617
618 g_free (latitude_s);
619 g_free (longitude_s);
620 g_free (en_name);
621 g_free (country);
622 g_free (zone);
623 }
624
625 GtkTreeIter iter;
626 gtk_list_store_append (store, &iter);
627 gtk_list_store_set (store, &iter,
628 CC_TIMEZONE_COMPLETION_ZONE, "UTC",
629 CC_TIMEZONE_COMPLETION_NAME, "UTC",
630 -1);
631
632 tz_db_free (db);
633 return store;
634 }
635
636 static void
data_func(GtkCellLayout * cell_layout,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer user_data)637 data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
638 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
639 {
640 const gchar * name, * admin1, * country;
641
642 gtk_tree_model_get (GTK_TREE_MODEL (tree_model), iter,
643 CC_TIMEZONE_COMPLETION_NAME, &name,
644 CC_TIMEZONE_COMPLETION_ADMIN1, &admin1,
645 CC_TIMEZONE_COMPLETION_COUNTRY, &country,
646 -1);
647
648 gchar * user_name;
649 if (country == NULL || country[0] == 0)
650 {
651 user_name = g_strdup (name);
652 } else if (admin1 == NULL || admin1[0] == 0) {
653 user_name = g_strdup_printf ("%s <small>(%s)</small>", name, country);
654 } else {
655 user_name = g_strdup_printf ("%s <small>(%s, %s)</small>", name, admin1, country);
656 }
657
658 g_object_set (G_OBJECT (cell), "markup", user_name, NULL);
659 }
660
661 static void
cc_timezone_completion_class_init(CcTimezoneCompletionClass * klass)662 cc_timezone_completion_class_init (CcTimezoneCompletionClass *klass)
663 {
664 GObjectClass *object_class = G_OBJECT_CLASS (klass);
665
666 g_type_class_add_private (klass, sizeof (CcTimezoneCompletionPrivate));
667
668 object_class->dispose = cc_timezone_completion_dispose;
669 object_class->finalize = cc_timezone_completion_finalize;
670
671 return;
672 }
673
674 static void
cc_timezone_completion_init(CcTimezoneCompletion * self)675 cc_timezone_completion_init (CcTimezoneCompletion * self)
676 {
677 CcTimezoneCompletionPrivate *priv;
678
679 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
680 CC_TIMEZONE_COMPLETION_TYPE,
681 CcTimezoneCompletionPrivate);
682 priv = self->priv;
683
684 priv->initial_model = GTK_TREE_MODEL (get_initial_model ());
685
686 g_object_set (G_OBJECT (self),
687 "text-column", CC_TIMEZONE_COMPLETION_NAME,
688 "popup-set-width", FALSE,
689 NULL);
690
691 priv->cancel = g_cancellable_new ();
692
693 priv->request_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
694
695 GtkCellRenderer * cell = gtk_cell_renderer_text_new ();
696 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE);
697 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self), cell, data_func, NULL, NULL);
698
699 priv->soup_session = soup_session_new ();
700
701 return;
702 }
703
704 static void
cc_timezone_completion_dispose(GObject * object)705 cc_timezone_completion_dispose (GObject * object)
706 {
707 G_OBJECT_CLASS (cc_timezone_completion_parent_class)->dispose (object);
708
709 CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (object);
710 CcTimezoneCompletionPrivate * priv = completion->priv;
711
712 if (priv->changed_id)
713 {
714 if (priv->entry)
715 g_signal_handler_disconnect (priv->entry, priv->changed_id);
716 priv->changed_id = 0;
717 }
718
719 if (priv->keypress_id)
720 {
721 if (priv->entry)
722 g_signal_handler_disconnect (priv->entry, priv->keypress_id);
723 priv->keypress_id = 0;
724 }
725
726 if (priv->entry != NULL)
727 {
728 gtk_entry_set_completion (priv->entry, NULL);
729 g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry);
730 priv->entry = NULL;
731 }
732
733 if (priv->initial_model != NULL)
734 {
735 g_object_unref (G_OBJECT (priv->initial_model));
736 priv->initial_model = NULL;
737 }
738
739 if (priv->queued_request)
740 {
741 g_source_remove (priv->queued_request);
742 priv->queued_request = 0;
743 }
744
745 if (priv->cancel != NULL)
746 {
747 g_cancellable_cancel (priv->cancel);
748 g_object_unref (priv->cancel);
749 priv->cancel = NULL;
750 }
751
752 if (priv->request_text != NULL)
753 {
754 g_free (priv->request_text);
755 priv->request_text = NULL;
756 }
757
758 if (priv->request_table != NULL)
759 {
760 g_hash_table_destroy (priv->request_table);
761 priv->request_table = NULL;
762 }
763
764 g_clear_object (&priv->soup_session);
765
766 return;
767 }
768
769 static void
cc_timezone_completion_finalize(GObject * object)770 cc_timezone_completion_finalize (GObject * object)
771 {
772 G_OBJECT_CLASS (cc_timezone_completion_parent_class)->finalize (object);
773 return;
774 }
775
776 CcTimezoneCompletion *
cc_timezone_completion_new()777 cc_timezone_completion_new ()
778 {
779 CcTimezoneCompletion * self = g_object_new (CC_TIMEZONE_COMPLETION_TYPE, NULL);
780 return self;
781 }
782
783