Merge lp:~mterry/timezonemap/ship-headers into lp:timezonemap
- ship-headers
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Evan (community) | Approve | ||
Review via email: mp+87925@code.launchpad.net |
Commit message
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.
- 19. By Michael Terry
-
and add in TimezoneCompletion from indicator-datetime
- 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 |
Looks good