Merge lp:~mterry/timezonemap/ship-headers into lp:timezonemap

Proposed by Michael Terry
Status: Merged
Merged at revision: 16
Proposed branch: lp:~mterry/timezonemap/ship-headers
Merge into: lp:timezonemap
Diff against target: 959 lines (+825/-11)
10 files modified
configure.ac (+3/-1)
debian/changelog (+17/-1)
debian/control (+3/-0)
debian/gir1.2-timezonemap-1.0.install (+0/-1)
debian/libtimezonemap1-dev.install (+4/-2)
src/Makefile.am (+17/-3)
src/timezone-completion.c (+701/-0)
src/timezone-completion.h (+66/-0)
src/timezonemap.pc.in (+11/-0)
src/tz.c (+3/-3)
To merge this branch: bzr merge lp:~mterry/timezonemap/ship-headers
Reviewer Review Type Date Requested Status
Evan (community) Approve
Review via email: mp+87925@code.launchpad.net

Description of the change

Ships header files and a .pc file so that C-based libraries can use it.

Also fixes a crasher when freeing CcTimezoneLocation

To post a comment you must log in.
lp:~mterry/timezonemap/ship-headers updated
19. By Michael Terry

and add in TimezoneCompletion from indicator-datetime

Revision history for this message
Evan (ev) wrote :

Looks good

review: Approve
lp:~mterry/timezonemap/ship-headers updated
20. By Michael Terry

and fix breaks/replaces version to be 0.3

21. By Michael Terry

add missing libjson-glib-dev dependency to debian/control

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2011-07-29 08:36:49 +0000
3+++ configure.ac 2012-01-09 14:43:24 +0000
4@@ -52,7 +52,8 @@
5 GIO_REQUIRED_VERSION=2.5.11
6
7 PKG_CHECK_MODULES(LIBTIMEZONEMAP, gio-2.0 >= $GIO_REQUIRED_VERSION
8- gtk+-3.0 >= $GTK3_REQUIRED_VERSION)
9+ gtk+-3.0 >= $GTK3_REQUIRED_VERSION
10+ json-glib-1.0)
11
12 GOBJECT_INTROSPECTION_CHECK([0.6.7])
13
14@@ -76,6 +77,7 @@
15 AC_CONFIG_FILES([
16 Makefile
17 src/Makefile
18+src/timezonemap.pc
19 ])
20 AC_OUTPUT
21
22
23=== modified file 'debian/changelog'
24--- debian/changelog 2011-08-09 09:23:44 +0000
25+++ debian/changelog 2012-01-09 14:43:24 +0000
26@@ -1,4 +1,20 @@
27-libtimezonemap (0.2) UNRELEASED; urgency=low
28+libtimezonemap (0.3) precise; urgency=low
29+
30+ * Fix crash when freeing CcTimezoneLocation objects
31+ * Ship a .pc file
32+ * Ship include headers
33+ * Move .gir file from gir1.2-* package to *-dev package
34+ * Add CcTimezoneCompletion from indicator-datetime
35+
36+ -- Michael Terry <mterry@ubuntu.com> Mon, 09 Jan 2012 12:58:29 +0000
37+
38+libtimezonemap (0.2.1) precise; urgency=low
39+
40+ * Link with libm.
41+
42+ -- Matthias Klose <doko@ubuntu.com> Sun, 04 Dec 2011 21:55:46 +0100
43+
44+libtimezonemap (0.2) oneiric; urgency=low
45
46 * Expose TzLocations as GObjects.
47
48
49=== modified file 'debian/control'
50--- debian/control 2011-08-03 09:09:02 +0000
51+++ debian/control 2012-01-09 14:43:24 +0000
52@@ -12,6 +12,7 @@
53 libglib2.0-dev (>= 2.25.0),
54 libgtk-3-dev (>= 3.1.4),
55 libcairo2-dev (>= 1.10),
56+ libjson-glib-dev,
57 dh-autoreconf
58 Standards-Version: 3.9.2
59 Vcs-Bzr: http://bazaar.launchpad.net/~timezonemap-team/timezonemap/trunk
60@@ -41,6 +42,8 @@
61 Depends: ${shlibs:Depends},
62 ${misc:Depends},
63 libtimezonemap1 (= ${binary:Version}),
64+Replaces: gir1.2-timezonemap-1.0 (<< 0.3)
65+Breaks: gir1.2-timezonemap-1.0 (<< 0.3)
66 Description: GTK+3 timezone map widget - development files
67 Timezone map widget for GTK+3
68 .
69
70=== modified file 'debian/gir1.2-timezonemap-1.0.install'
71--- debian/gir1.2-timezonemap-1.0.install 2011-07-29 08:36:49 +0000
72+++ debian/gir1.2-timezonemap-1.0.install 2012-01-09 14:43:24 +0000
73@@ -1,2 +1,1 @@
74 debian/tmp/usr/lib/girepository-1.0/* /usr/lib/girepository-1.0/
75-debian/tmp/usr/share/gir-1.0/* /usr/share/gir-1.0/
76
77=== modified file 'debian/libtimezonemap1-dev.install'
78--- debian/libtimezonemap1-dev.install 2011-07-29 08:36:49 +0000
79+++ debian/libtimezonemap1-dev.install 2012-01-09 14:43:24 +0000
80@@ -1,2 +1,4 @@
81-#debian/tmp/usr/lib/libtimezonemap.a /usr/lib/
82-debian/tmp/usr/lib/libtimezonemap.so /usr/lib/
83+debian/tmp/usr/lib/libtimezonemap.so
84+debian/tmp/usr/share/gir-1.0
85+debian/tmp/usr/include
86+debian/tmp/usr/lib/pkgconfig
87
88=== modified file 'src/Makefile.am'
89--- src/Makefile.am 2011-07-29 09:01:38 +0000
90+++ src/Makefile.am 2012-01-09 14:43:24 +0000
91@@ -48,6 +48,9 @@
92 tzdatadir = $(pkgdatadir)/
93 dist_tzdata_DATA = backward
94
95+pkgconfigdir = $(libdir)/pkgconfig
96+dist_pkgconfig_DATA = timezonemap.pc
97+
98 AM_CPPFLAGS = \
99 $(LIBTIMEZONEMAP_CFLAGS) \
100 -DGNOMELOCALEDIR="\"$(datadir)/locale\"" \
101@@ -58,7 +61,7 @@
102 noinst_PROGRAMS = test-timezone
103
104 test_timezone_SOURCES = test-timezone.c tz.c tz.h
105-test_timezone_LDADD = $(LIBTIMEZONEMAP_LIBS)
106+test_timezone_LDADD = $(LIBTIMEZONEMAP_LIBS) -lm
107 test_timezone_CFLAGS = $(LIBTIMEZONEMAP_CFLAGS)
108
109 all-local: check-local
110@@ -72,9 +75,20 @@
111 libtimezonemap_la_SOURCES = \
112 cc-timezone-map.c \
113 cc-timezone-map.h \
114+ timezone-completion.c \
115+ timezone-completion.h \
116 tz.c tz.h
117
118-libtimezonemap_la_LIBADD = $(LIBTIMEZONEMAP_LIBS)
119+# Specify 'timezonemap' twice: once for package (so we could eventually add
120+# a timezonemap-gtk4 for example), and once for namespacing inside code so
121+# that users do "#include <timezonemap/tz.h>" instead of "#include <tz.h>"
122+timezonemapincludesdir = $(includedir)/timezonemap/timezonemap
123+timezonemapincludes_HEADERS = \
124+ cc-timezone-map.h \
125+ timezone-completion.h \
126+ tz.h
127+
128+libtimezonemap_la_LIBADD = $(LIBTIMEZONEMAP_LIBS) -lm
129 libtimezonemap_la_LDFLAGS = \
130 -version-info 1:0:0 \
131 -no-undefined \
132@@ -90,7 +104,7 @@
133 introspection_sources = $(libtimezonemap_la_SOURCES)
134
135 TimezoneMap-1.0.gir: libtimezonemap.la
136-TimezoneMap_1_0_gir_INCLUDES = GObject-2.0 Gtk-3.0
137+TimezoneMap_1_0_gir_INCLUDES = GObject-2.0 Gtk-3.0 Json-1.0
138 TimezoneMap_1_0_gir_CFLAGS = $(INCLUDES) --identifier-prefix=Cc
139 TimezoneMap_1_0_gir_LIBS = libtimezonemap.la
140 TimezoneMap_1_0_gir_FILES = $(introspection_sources)
141
142=== added file 'src/timezone-completion.c'
143--- src/timezone-completion.c 1970-01-01 00:00:00 +0000
144+++ src/timezone-completion.c 2012-01-09 14:43:24 +0000
145@@ -0,0 +1,701 @@
146+/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
147+
148+Copyright 2011 Canonical Ltd.
149+
150+Authors:
151+ Michael Terry <michael.terry@canonical.com>
152+
153+This program is free software: you can redistribute it and/or modify it
154+under the terms of the GNU General Public License version 3, as published
155+by the Free Software Foundation.
156+
157+This program is distributed in the hope that it will be useful, but
158+WITHOUT ANY WARRANTY; without even the implied warranties of
159+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
160+PURPOSE. See the GNU General Public License for more details.
161+
162+You should have received a copy of the GNU General Public License along
163+with this program. If not, see <http://www.gnu.org/licenses/>.
164+*/
165+
166+#ifdef HAVE_CONFIG_H
167+#include "config.h"
168+#endif
169+
170+#include <json-glib/json-glib.h>
171+#include <gdk/gdk.h>
172+#include <gdk/gdkkeysyms.h>
173+#include <glib/gi18n.h>
174+#include "timezone-completion.h"
175+#include "tz.h"
176+
177+enum {
178+ LAST_SIGNAL
179+};
180+
181+/* static guint signals[LAST_SIGNAL] = { }; */
182+
183+struct _CcTimezoneCompletionPrivate
184+{
185+ GtkTreeModel * initial_model;
186+ GtkEntry * entry;
187+ guint queued_request;
188+ guint changed_id;
189+ guint keypress_id;
190+ GCancellable * cancel;
191+ gchar * request_text;
192+ GHashTable * request_table;
193+};
194+
195+#define GEONAME_URL "http://geoname-lookup.ubuntu.com/?query=%s&release=%s&lang=%s"
196+
197+/* Prototypes */
198+static void cc_timezone_completion_class_init (CcTimezoneCompletionClass *klass);
199+static void cc_timezone_completion_init (CcTimezoneCompletion *self);
200+static void cc_timezone_completion_dispose (GObject *object);
201+static void cc_timezone_completion_finalize (GObject *object);
202+
203+G_DEFINE_TYPE (CcTimezoneCompletion, cc_timezone_completion, GTK_TYPE_ENTRY_COMPLETION);
204+
205+static gboolean
206+match_func (GtkEntryCompletion *completion, const gchar *key,
207+ GtkTreeIter *iter, gpointer user_data)
208+{
209+ // geonames does the work for us
210+ return TRUE;
211+}
212+
213+static void
214+save_and_use_model (CcTimezoneCompletion * completion, GtkTreeModel * model)
215+{
216+ CcTimezoneCompletionPrivate * priv = completion->priv;
217+
218+ g_hash_table_insert (priv->request_table, g_strdup (priv->request_text), g_object_ref_sink (model));
219+
220+ if (model == priv->initial_model)
221+ gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), NULL, NULL, NULL);
222+ else
223+ gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), match_func, NULL, NULL);
224+
225+ gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), model);
226+
227+ if (priv->entry != NULL) {
228+ gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion));
229+
230+ /* By this time, the changed signal has come and gone. We didn't give a
231+ model to use, so no popup appeared for user. Poke the entry again to show
232+ popup in 300ms. */
233+ g_signal_emit_by_name (priv->entry, "changed");
234+ }
235+}
236+
237+static gint
238+sort_zone (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
239+ gpointer user_data)
240+{
241+ /* Anything that has text as a prefix goes first, in mostly sorted order.
242+ Then everything else goes after, in mostly sorted order. */
243+ const gchar *casefolded_text = (const gchar *)user_data;
244+
245+ const gchar *namea = NULL, *nameb = NULL;
246+ gtk_tree_model_get (model, a, CC_TIMEZONE_COMPLETION_NAME, &namea, -1);
247+ gtk_tree_model_get (model, b, CC_TIMEZONE_COMPLETION_NAME, &nameb, -1);
248+
249+ gchar *casefolded_namea = NULL, *casefolded_nameb = NULL;
250+ casefolded_namea = g_utf8_casefold (namea, -1);
251+ casefolded_nameb = g_utf8_casefold (nameb, -1);
252+
253+ gboolean amatches = FALSE, bmatches = FALSE;
254+ amatches = strncmp (casefolded_text, casefolded_namea, strlen(casefolded_text)) == 0;
255+ bmatches = strncmp (casefolded_text, casefolded_nameb, strlen(casefolded_text)) == 0;
256+
257+ gint rv;
258+ if (amatches && !bmatches)
259+ rv = -1;
260+ else if (bmatches && !amatches)
261+ rv = 1;
262+ else
263+ rv = g_utf8_collate (casefolded_namea, casefolded_nameb);
264+
265+ g_free (casefolded_namea);
266+ g_free (casefolded_nameb);
267+ return rv;
268+}
269+
270+static void
271+json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data)
272+{
273+ CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (user_data);
274+ CcTimezoneCompletionPrivate * priv = completion->priv;
275+ GError * error = NULL;
276+ const gchar * prev_name = NULL;
277+ const gchar * prev_admin1 = NULL;
278+ const gchar * prev_country = NULL;
279+
280+ json_parser_load_from_stream_finish (JSON_PARSER (object), res, &error);
281+
282+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && priv->cancel) {
283+ g_cancellable_reset (priv->cancel);
284+ }
285+
286+ if (error != NULL) {
287+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
288+ save_and_use_model (completion, priv->initial_model);
289+ g_warning ("Could not parse geoname JSON data: %s", error->message);
290+ g_error_free (error);
291+ return;
292+ }
293+
294+ GtkListStore * store = gtk_list_store_new (CC_TIMEZONE_COMPLETION_LAST,
295+ G_TYPE_STRING,
296+ G_TYPE_STRING,
297+ G_TYPE_STRING,
298+ G_TYPE_STRING,
299+ G_TYPE_STRING,
300+ G_TYPE_STRING);
301+
302+ JsonReader * reader = json_reader_new (json_parser_get_root (JSON_PARSER (object)));
303+
304+ if (!json_reader_is_array (reader)) {
305+ g_warning ("Could not parse geoname JSON data");
306+ save_and_use_model (completion, priv->initial_model);
307+ g_object_unref (G_OBJECT (reader));
308+ return;
309+ }
310+
311+ gint i, count = json_reader_count_elements (reader);
312+ for (i = 0; i < count; ++i) {
313+ if (!json_reader_read_element (reader, i))
314+ continue;
315+
316+ if (json_reader_is_object (reader)) {
317+ const gchar * name = NULL;
318+ const gchar * admin1 = NULL;
319+ const gchar * country = NULL;
320+ const gchar * longitude = NULL;
321+ const gchar * latitude = NULL;
322+ gboolean skip = FALSE;
323+ if (json_reader_read_member (reader, "name")) {
324+ name = json_reader_get_string_value (reader);
325+ json_reader_end_member (reader);
326+ }
327+ if (json_reader_read_member (reader, "admin1")) {
328+ admin1 = json_reader_get_string_value (reader);
329+ json_reader_end_member (reader);
330+ }
331+ if (json_reader_read_member (reader, "country")) {
332+ country = json_reader_get_string_value (reader);
333+ json_reader_end_member (reader);
334+ }
335+ if (json_reader_read_member (reader, "longitude")) {
336+ longitude = json_reader_get_string_value (reader);
337+ json_reader_end_member (reader);
338+ }
339+ if (json_reader_read_member (reader, "latitude")) {
340+ latitude = json_reader_get_string_value (reader);
341+ json_reader_end_member (reader);
342+ }
343+
344+ if (g_strcmp0(name, prev_name) == 0 &&
345+ g_strcmp0(admin1, prev_admin1) == 0 &&
346+ g_strcmp0(country, prev_country) == 0) {
347+ // Sometimes the data will have duplicate entries that only differ
348+ // in longitude and latitude. e.g. "rio de janeiro", "wellington"
349+ skip = TRUE;
350+ }
351+
352+ if (!skip) {
353+ GtkTreeIter iter;
354+ gtk_list_store_append (store, &iter);
355+ gtk_list_store_set (store, &iter,
356+ CC_TIMEZONE_COMPLETION_ZONE, NULL,
357+ CC_TIMEZONE_COMPLETION_NAME, name,
358+ CC_TIMEZONE_COMPLETION_ADMIN1, admin1,
359+ CC_TIMEZONE_COMPLETION_COUNTRY, country,
360+ CC_TIMEZONE_COMPLETION_LONGITUDE, longitude,
361+ CC_TIMEZONE_COMPLETION_LATITUDE, latitude,
362+ -1);
363+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
364+ CC_TIMEZONE_COMPLETION_NAME, sort_zone,
365+ g_utf8_casefold(priv->request_text, -1),
366+ g_free);
367+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
368+ CC_TIMEZONE_COMPLETION_NAME,
369+ GTK_SORT_ASCENDING);
370+ }
371+
372+ prev_name = name;
373+ prev_admin1 = admin1;
374+ prev_country = country;
375+ }
376+
377+ json_reader_end_element (reader);
378+ }
379+
380+ if (strlen (priv->request_text) < 4) {
381+ gchar * lower_text = g_ascii_strdown (priv->request_text, -1);
382+ if (g_strcmp0 (lower_text, "ut") == 0 ||
383+ g_strcmp0 (lower_text, "utc") == 0) {
384+ GtkTreeIter iter;
385+ gtk_list_store_append (store, &iter);
386+ gtk_list_store_set (store, &iter,
387+ CC_TIMEZONE_COMPLETION_ZONE, "UTC",
388+ CC_TIMEZONE_COMPLETION_NAME, "UTC",
389+ -1);
390+ }
391+ g_free (lower_text);
392+ }
393+
394+ save_and_use_model (completion, GTK_TREE_MODEL (store));
395+ g_object_unref (G_OBJECT (reader));
396+}
397+
398+static void
399+geonames_data_ready (GObject *object, GAsyncResult *res, gpointer user_data)
400+{
401+ CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (user_data);
402+ CcTimezoneCompletionPrivate * priv = completion->priv;
403+ GError * error = NULL;
404+ GFileInputStream * stream;
405+
406+ stream = g_file_read_finish (G_FILE (object), res, &error);
407+
408+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && priv->cancel) {
409+ g_cancellable_reset (priv->cancel);
410+ }
411+
412+ if (error != NULL) {
413+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
414+ save_and_use_model (completion, priv->initial_model);
415+ g_warning ("Could not connect to geoname lookup server: %s", error->message);
416+ g_error_free (error);
417+ return;
418+ }
419+
420+ JsonParser * parser = json_parser_new ();
421+ json_parser_load_from_stream_async (parser, G_INPUT_STREAM (stream), priv->cancel,
422+ json_parse_ready, user_data);
423+}
424+
425+/* Returns message locale, with possible country info too like en_US */
426+static gchar *
427+get_locale (void)
428+{
429+ /* Check LANGUAGE, LC_ALL, LC_MESSAGES, and LANG, treat as colon-separated */
430+ const gchar *env_names[] = {"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG", NULL};
431+ const gchar *env = NULL;
432+ gint i;
433+
434+ for (i = 0; env_names[i]; i++) {
435+ env = g_getenv (env_names[i]);
436+ if (env != NULL && env[0] != 0)
437+ break;
438+ }
439+
440+ if (env == NULL)
441+ return NULL;
442+
443+ /* Now, we split on colons as expected, but also on . and @ to filter out
444+ extra pieces of locale we don't care about as we only use first chunk. */
445+ gchar **split = g_strsplit_set (env, ":.@", 2);
446+ if (split == NULL)
447+ return NULL;
448+
449+ if (split[0] == NULL) {
450+ g_strfreev (split);
451+ return NULL;
452+ }
453+
454+ gchar *locale = g_strdup (split[0]);
455+ g_strfreev (split);
456+ return locale;
457+}
458+
459+static gchar *
460+get_version (void)
461+{
462+ static gchar *version = NULL;
463+
464+ if (version == NULL) {
465+ gchar *stdout = NULL;
466+ g_spawn_command_line_sync ("lsb_release -rs", &stdout, NULL, NULL, NULL);
467+
468+ if (stdout != NULL)
469+ version = g_strstrip (stdout);
470+ else
471+ version = g_strdup("");
472+ }
473+
474+ return version;
475+}
476+
477+static gboolean
478+request_zones (CcTimezoneCompletion * completion)
479+{
480+ CcTimezoneCompletionPrivate * priv = completion->priv;
481+
482+ priv->queued_request = 0;
483+
484+ if (priv->entry == NULL) {
485+ return FALSE;
486+ }
487+
488+ /* Cancel any ongoing request */
489+ if (priv->cancel) {
490+ g_cancellable_cancel (priv->cancel);
491+ g_cancellable_reset (priv->cancel);
492+ }
493+ g_free (priv->request_text);
494+
495+ const gchar * text = gtk_entry_get_text (priv->entry);
496+ priv->request_text = g_strdup (text);
497+
498+ gchar * escaped = g_uri_escape_string (text, NULL, FALSE);
499+ gchar * version = get_version ();
500+ gchar * locale = get_locale ();
501+ gchar * url = g_strdup_printf (GEONAME_URL, escaped, version, locale);
502+ g_free (locale);
503+ g_free (escaped);
504+
505+ GFile * file = g_file_new_for_uri (url);
506+ g_free (url);
507+
508+ g_file_read_async (file, G_PRIORITY_DEFAULT, priv->cancel,
509+ geonames_data_ready, completion);
510+
511+ return FALSE;
512+}
513+
514+static void
515+entry_changed (GtkEntry * entry, CcTimezoneCompletion * completion)
516+{
517+ CcTimezoneCompletionPrivate * priv = completion->priv;
518+
519+ if (priv->queued_request) {
520+ g_source_remove (priv->queued_request);
521+ priv->queued_request = 0;
522+ }
523+
524+ /* See if we've already got this one */
525+ const gchar * text = gtk_entry_get_text (priv->entry);
526+ gpointer data;
527+ if (g_hash_table_lookup_extended (priv->request_table, text, NULL, &data)) {
528+ gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), GTK_TREE_MODEL (data));
529+ }
530+ else {
531+ priv->queued_request = g_timeout_add (300, (GSourceFunc)request_zones, completion);
532+ gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), NULL);
533+ }
534+ gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion));
535+}
536+
537+static GtkWidget *
538+get_descendent (GtkWidget * parent, GType type)
539+{
540+ if (g_type_is_a (G_OBJECT_TYPE (parent), type))
541+ return parent;
542+
543+ if (GTK_IS_CONTAINER (parent)) {
544+ GList * children = gtk_container_get_children (GTK_CONTAINER (parent));
545+ GList * iter;
546+ for (iter = children; iter; iter = iter->next) {
547+ GtkWidget * found = get_descendent (GTK_WIDGET (iter->data), type);
548+ if (found) {
549+ g_list_free (children);
550+ return found;
551+ }
552+ }
553+ g_list_free (children);
554+ }
555+
556+ return NULL;
557+}
558+
559+/**
560+ * The popup window and its GtkTreeView are private to our parent completion
561+ * object. We can't get access to discover if there is a highlighted item or
562+ * even if the window is showing right now. So this is a super hack to find
563+ * it by looking through our toplevel's window group and finding a window with
564+ * a GtkTreeView that points at our model. There should be only one ever, so
565+ * we'll use the first one we find.
566+ */
567+static GtkTreeView *
568+find_popup_treeview (GtkWidget * widget, GtkTreeModel * model)
569+{
570+ GtkWidget * toplevel = gtk_widget_get_toplevel (widget);
571+ if (!GTK_IS_WINDOW (toplevel))
572+ return NULL;
573+
574+ GtkWindowGroup * group = gtk_window_get_group (GTK_WINDOW (toplevel));
575+ GList * windows = gtk_window_group_list_windows (group);
576+ GList * iter;
577+ for (iter = windows; iter; iter = iter->next) {
578+ if (iter->data == toplevel)
579+ continue; // Skip our own window, we don't have it
580+ GtkWidget * view = get_descendent (GTK_WIDGET (iter->data), GTK_TYPE_TREE_VIEW);
581+ if (view != NULL) {
582+ GtkTreeModel * tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
583+ if (GTK_IS_TREE_MODEL_FILTER (tree_model))
584+ tree_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (tree_model));
585+ if (tree_model == model) {
586+ g_list_free (windows);
587+ return GTK_TREE_VIEW (view);
588+ }
589+ }
590+ }
591+ g_list_free (windows);
592+
593+ return NULL;
594+}
595+
596+static gboolean
597+entry_keypress (GtkEntry * entry, GdkEventKey *event, CcTimezoneCompletion * completion)
598+{
599+ if (event->keyval == GDK_KEY_ISO_Enter ||
600+ event->keyval == GDK_KEY_KP_Enter ||
601+ event->keyval == GDK_KEY_Return) {
602+ /* Make sure that user has a selection to choose, otherwise ignore */
603+ GtkTreeModel * model = gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (completion));
604+ GtkTreeView * view = find_popup_treeview (GTK_WIDGET (entry), model);
605+ if (view == NULL) {
606+ // Just beep if popup hasn't appeared yet.
607+ gtk_widget_error_bell (GTK_WIDGET (entry));
608+ return TRUE;
609+ }
610+
611+ GtkTreeSelection * sel = gtk_tree_view_get_selection (view);
612+ GtkTreeModel * sel_model = NULL;
613+ if (!gtk_tree_selection_get_selected (sel, &sel_model, NULL)) {
614+ // No selection, we should help them out and select first item in list
615+ GtkTreeIter iter;
616+ if (gtk_tree_model_get_iter_first (sel_model, &iter))
617+ gtk_tree_selection_select_iter (sel, &iter);
618+ // And fall through to normal handler code
619+ }
620+ }
621+
622+ return FALSE;
623+}
624+
625+void
626+cc_timezone_completion_watch_entry (CcTimezoneCompletion * completion, GtkEntry * entry)
627+{
628+ CcTimezoneCompletionPrivate * priv = completion->priv;
629+
630+ if (priv->queued_request) {
631+ g_source_remove (priv->queued_request);
632+ priv->queued_request = 0;
633+ }
634+ if (priv->entry) {
635+ g_signal_handler_disconnect (priv->entry, priv->changed_id);
636+ priv->changed_id = 0;
637+ g_signal_handler_disconnect (priv->entry, priv->keypress_id);
638+ priv->keypress_id = 0;
639+ g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry);
640+ gtk_entry_set_completion (priv->entry, NULL);
641+ }
642+
643+ priv->entry = entry;
644+
645+ if (entry) {
646+ guint id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion);
647+ priv->changed_id = id;
648+
649+ id = g_signal_connect (entry, "key-press-event", G_CALLBACK (entry_keypress), completion);
650+ priv->keypress_id = id;
651+
652+ g_object_add_weak_pointer (G_OBJECT (entry), (gpointer *)&priv->entry);
653+
654+ gtk_entry_set_completion (entry, GTK_ENTRY_COMPLETION (completion));
655+ }
656+}
657+
658+static GtkListStore *
659+get_initial_model (void)
660+{
661+ TzDB * db = tz_load_db ();
662+ GPtrArray * locations = tz_get_locations (db);
663+
664+ GtkListStore * store = gtk_list_store_new (CC_TIMEZONE_COMPLETION_LAST,
665+ G_TYPE_STRING,
666+ G_TYPE_STRING,
667+ G_TYPE_STRING,
668+ G_TYPE_STRING,
669+ G_TYPE_STRING,
670+ G_TYPE_STRING);
671+
672+ gint i;
673+ for (i = 0; i < locations->len; ++i) {
674+ CcTimezoneLocation * loc = g_ptr_array_index (locations, i);
675+ GtkTreeIter iter;
676+ gtk_list_store_append (store, &iter);
677+
678+ gchar * zone;
679+ gchar * country;
680+ g_object_get (loc, "zone", &zone, "country", &country, NULL);
681+
682+ /* FIXME: need something better than below for non-English locales */
683+ const gchar * last_bit = ((const gchar *)strrchr (zone, '/')) + 1;
684+ if (last_bit == NULL)
685+ last_bit = zone;
686+ gchar * name = g_strdup (last_bit);
687+ gchar * underscore;
688+ while ((underscore = strchr (name, '_'))) {
689+ *underscore = ' ';
690+ }
691+
692+ gtk_list_store_set (store, &iter,
693+ CC_TIMEZONE_COMPLETION_ZONE, zone,
694+ CC_TIMEZONE_COMPLETION_NAME, name,
695+ CC_TIMEZONE_COMPLETION_COUNTRY, country,
696+ -1);
697+
698+ g_free (name);
699+ g_free (zone);
700+ }
701+
702+ GtkTreeIter iter;
703+ gtk_list_store_append (store, &iter);
704+ gtk_list_store_set (store, &iter,
705+ CC_TIMEZONE_COMPLETION_ZONE, "UTC",
706+ CC_TIMEZONE_COMPLETION_NAME, "UTC",
707+ -1);
708+
709+ tz_db_free (db);
710+ return store;
711+}
712+
713+static void
714+data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
715+ GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
716+{
717+ const gchar * name, * admin1, * country;
718+
719+ gtk_tree_model_get (GTK_TREE_MODEL (tree_model), iter,
720+ CC_TIMEZONE_COMPLETION_NAME, &name,
721+ CC_TIMEZONE_COMPLETION_ADMIN1, &admin1,
722+ CC_TIMEZONE_COMPLETION_COUNTRY, &country,
723+ -1);
724+
725+ gchar * user_name;
726+ if (country == NULL || country[0] == 0) {
727+ user_name = g_strdup (name);
728+ } else if (admin1 == NULL || admin1[0] == 0) {
729+ user_name = g_strdup_printf ("%s <small>(%s)</small>", name, country);
730+ } else {
731+ user_name = g_strdup_printf ("%s <small>(%s, %s)</small>", name, admin1, country);
732+ }
733+
734+ g_object_set (G_OBJECT (cell), "markup", user_name, NULL);
735+}
736+
737+static void
738+cc_timezone_completion_class_init (CcTimezoneCompletionClass *klass)
739+{
740+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
741+
742+ g_type_class_add_private (klass, sizeof (CcTimezoneCompletionPrivate));
743+
744+ object_class->dispose = cc_timezone_completion_dispose;
745+ object_class->finalize = cc_timezone_completion_finalize;
746+
747+ return;
748+}
749+
750+static void
751+cc_timezone_completion_init (CcTimezoneCompletion * self)
752+{
753+ CcTimezoneCompletionPrivate *priv;
754+
755+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
756+ CC_TIMEZONE_COMPLETION_TYPE,
757+ CcTimezoneCompletionPrivate);
758+ priv = self->priv;
759+
760+ priv->initial_model = GTK_TREE_MODEL (get_initial_model ());
761+
762+ g_object_set (G_OBJECT (self),
763+ "text-column", CC_TIMEZONE_COMPLETION_NAME,
764+ "popup-set-width", FALSE,
765+ NULL);
766+
767+ priv->cancel = g_cancellable_new ();
768+
769+ priv->request_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
770+
771+ GtkCellRenderer * cell = gtk_cell_renderer_text_new ();
772+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE);
773+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self), cell, data_func, NULL, NULL);
774+
775+ return;
776+}
777+
778+static void
779+cc_timezone_completion_dispose (GObject * object)
780+{
781+ G_OBJECT_CLASS (cc_timezone_completion_parent_class)->dispose (object);
782+
783+ CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (object);
784+ CcTimezoneCompletionPrivate * priv = completion->priv;
785+
786+ if (priv->changed_id) {
787+ if (priv->entry)
788+ g_signal_handler_disconnect (priv->entry, priv->changed_id);
789+ priv->changed_id = 0;
790+ }
791+
792+ if (priv->keypress_id) {
793+ if (priv->entry)
794+ g_signal_handler_disconnect (priv->entry, priv->keypress_id);
795+ priv->keypress_id = 0;
796+ }
797+
798+ if (priv->entry != NULL) {
799+ gtk_entry_set_completion (priv->entry, NULL);
800+ g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry);
801+ priv->entry = NULL;
802+ }
803+
804+ if (priv->initial_model != NULL) {
805+ g_object_unref (G_OBJECT (priv->initial_model));
806+ priv->initial_model = NULL;
807+ }
808+
809+ if (priv->queued_request) {
810+ g_source_remove (priv->queued_request);
811+ priv->queued_request = 0;
812+ }
813+
814+ if (priv->cancel != NULL) {
815+ g_cancellable_cancel (priv->cancel);
816+ g_object_unref (priv->cancel);
817+ priv->cancel = NULL;
818+ }
819+
820+ if (priv->request_text != NULL) {
821+ g_free (priv->request_text);
822+ priv->request_text = NULL;
823+ }
824+
825+ if (priv->request_table != NULL) {
826+ g_hash_table_destroy (priv->request_table);
827+ priv->request_table = NULL;
828+ }
829+
830+ return;
831+}
832+
833+static void
834+cc_timezone_completion_finalize (GObject * object)
835+{
836+ G_OBJECT_CLASS (cc_timezone_completion_parent_class)->finalize (object);
837+ return;
838+}
839+
840+CcTimezoneCompletion *
841+cc_timezone_completion_new ()
842+{
843+ CcTimezoneCompletion * self = g_object_new (CC_TIMEZONE_COMPLETION_TYPE, NULL);
844+ return self;
845+}
846+
847
848=== added file 'src/timezone-completion.h'
849--- src/timezone-completion.h 1970-01-01 00:00:00 +0000
850+++ src/timezone-completion.h 2012-01-09 14:43:24 +0000
851@@ -0,0 +1,66 @@
852+/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
853+
854+Copyright 2011 Canonical Ltd.
855+
856+Authors:
857+ Michael Terry <michael.terry@canonical.com>
858+
859+This program is free software: you can redistribute it and/or modify it
860+under the terms of the GNU General Public License version 3, as published
861+by the Free Software Foundation.
862+
863+This program is distributed in the hope that it will be useful, but
864+WITHOUT ANY WARRANTY; without even the implied warranties of
865+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
866+PURPOSE. See the GNU General Public License for more details.
867+
868+You should have received a copy of the GNU General Public License along
869+with this program. If not, see <http://www.gnu.org/licenses/>.
870+*/
871+
872+#ifndef __CC_TIMEZONE_COMPLETION_H__
873+#define __CC_TIMEZONE_COMPLETION_H__
874+
875+#include <glib.h>
876+#include <glib-object.h>
877+#include <gtk/gtk.h>
878+
879+G_BEGIN_DECLS
880+
881+#define CC_TIMEZONE_COMPLETION_TYPE (cc_timezone_completion_get_type ())
882+#define CC_TIMEZONE_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TIMEZONE_COMPLETION_TYPE, CcTimezoneCompletion))
883+#define CC_TIMEZONE_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TIMEZONE_COMPLETION_TYPE, CcTimezoneCompletionClass))
884+#define IS_CC_TIMEZONE_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TIMEZONE_COMPLETION_TYPE))
885+#define IS_CC_TIMEZONE_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TIMEZONE_COMPLETION_TYPE))
886+#define CC_TIMEZONE_COMPLETION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TIMEZONE_COMPLETION_TYPE, CcTimezoneCompletionClass))
887+
888+typedef struct _CcTimezoneCompletion CcTimezoneCompletion;
889+typedef struct _CcTimezoneCompletionPrivate CcTimezoneCompletionPrivate;
890+typedef struct _CcTimezoneCompletionClass CcTimezoneCompletionClass;
891+
892+struct _CcTimezoneCompletion {
893+ GtkEntryCompletion parent;
894+
895+ CcTimezoneCompletionPrivate *priv;
896+};
897+
898+struct _CcTimezoneCompletionClass {
899+ GtkEntryCompletionClass parent_class;
900+};
901+
902+#define CC_TIMEZONE_COMPLETION_ZONE 0
903+#define CC_TIMEZONE_COMPLETION_NAME 1
904+#define CC_TIMEZONE_COMPLETION_ADMIN1 2
905+#define CC_TIMEZONE_COMPLETION_COUNTRY 3
906+#define CC_TIMEZONE_COMPLETION_LONGITUDE 4
907+#define CC_TIMEZONE_COMPLETION_LATITUDE 5
908+#define CC_TIMEZONE_COMPLETION_LAST 6
909+
910+GType cc_timezone_completion_get_type (void) G_GNUC_CONST;
911+CcTimezoneCompletion * cc_timezone_completion_new ();
912+void cc_timezone_completion_watch_entry (CcTimezoneCompletion * completion, GtkEntry * entry);
913+
914+G_END_DECLS
915+
916+#endif /* __CC_TIMEZONE_COMPLETION_H__ */
917+
918
919=== added file 'src/timezonemap.pc.in'
920--- src/timezonemap.pc.in 1970-01-01 00:00:00 +0000
921+++ src/timezonemap.pc.in 2012-01-09 14:43:24 +0000
922@@ -0,0 +1,11 @@
923+prefix=@prefix@
924+exec_prefix=@exec_prefix@
925+libdir=@libdir@
926+includedir=@includedir@
927+
928+Name: @PACKAGE_NAME@
929+Description: GTK+3 timezone map widget
930+Version: @VERSION@
931+Libs: -L${libdir} -ltimezonemap -lm
932+Cflags: -I${includedir}/timezonemap
933+Requires: gio-2.0 gtk+-3.0 json-glib-1.0
934
935=== modified file 'src/tz.c'
936--- src/tz.c 2011-08-02 08:01:29 +0000
937+++ src/tz.c 2012-01-09 14:43:24 +0000
938@@ -138,19 +138,19 @@
939
940 if (priv->country)
941 {
942- g_object_unref (priv->country);
943+ g_free (priv->country);
944 priv->country = NULL;
945 }
946
947 if (priv->zone)
948 {
949- g_object_unref (priv->zone);
950+ g_free (priv->zone);
951 priv->zone = NULL;
952 }
953
954 if (priv->comment)
955 {
956- g_object_unref (priv->comment);
957+ g_free (priv->comment);
958 priv->comment = NULL;
959 }
960

Subscribers

People subscribed via source and target branches