1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* timezone-menu.c - Timezone-selecting menu
3 *
4 * Copyright 2008, Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #define MATEWEATHER_I_KNOW_THIS_IS_UNSTABLE
26 #include "timezone-menu.h"
27 #include "weather-priv.h"
28
29 #include <string.h>
30
31 /**
32 * SECTION:timezone-menu
33 * @Title: MateWeatherTimezoneMenu
34 *
35 * A #GtkComboBox subclass for choosing a #MateWeatherTimezone
36 */
37
38 G_DEFINE_TYPE (MateWeatherTimezoneMenu, mateweather_timezone_menu, GTK_TYPE_COMBO_BOX)
39
40 enum {
41 PROP_0,
42
43 PROP_TOP,
44 PROP_TZID,
45
46 LAST_PROP
47 };
48
49 static void set_property (GObject *object, guint prop_id,
50 const GValue *value, GParamSpec *pspec);
51 static void get_property (GObject *object, guint prop_id,
52 GValue *value, GParamSpec *pspec);
53
54 static void changed (GtkComboBox *combo);
55
56 static GtkTreeModel *mateweather_timezone_model_new (MateWeatherLocation *top);
57 static gboolean row_separator_func (GtkTreeModel *model, GtkTreeIter *iter,
58 gpointer data);
59 static void is_sensitive (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
60 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data);
61
62 static void
mateweather_timezone_menu_init(MateWeatherTimezoneMenu * menu)63 mateweather_timezone_menu_init (MateWeatherTimezoneMenu *menu)
64 {
65 GtkCellRenderer *renderer;
66
67 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (menu),
68 row_separator_func, NULL, NULL);
69
70 renderer = gtk_cell_renderer_text_new ();
71 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (menu), renderer, TRUE);
72 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (menu), renderer,
73 "markup", 0,
74 NULL);
75 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (menu),
76 renderer, is_sensitive, NULL, NULL);
77 }
78
79 static void
finalize(GObject * object)80 finalize (GObject *object)
81 {
82 MateWeatherTimezoneMenu *menu = MATEWEATHER_TIMEZONE_MENU (object);
83
84 if (menu->zone)
85 mateweather_timezone_unref (menu->zone);
86
87 G_OBJECT_CLASS (mateweather_timezone_menu_parent_class)->finalize (object);
88 }
89
90 static void
mateweather_timezone_menu_class_init(MateWeatherTimezoneMenuClass * timezone_menu_class)91 mateweather_timezone_menu_class_init (MateWeatherTimezoneMenuClass *timezone_menu_class)
92 {
93 GObjectClass *object_class = G_OBJECT_CLASS (timezone_menu_class);
94 GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (timezone_menu_class);
95
96 object_class->finalize = finalize;
97 object_class->set_property = set_property;
98 object_class->get_property = get_property;
99
100 combo_class->changed = changed;
101
102 /* properties */
103 g_object_class_install_property (
104 object_class, PROP_TOP,
105 g_param_spec_pointer ("top",
106 "Top Location",
107 "The MateWeatherLocation whose children will be used to fill in the menu",
108 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
109 g_object_class_install_property (
110 object_class, PROP_TZID,
111 g_param_spec_string ("tzid",
112 "TZID",
113 "The selected TZID",
114 NULL,
115 G_PARAM_READWRITE));
116 }
117
118 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)119 set_property (GObject *object, guint prop_id,
120 const GValue *value, GParamSpec *pspec)
121 {
122 GtkTreeModel *model;
123
124 switch (prop_id) {
125 case PROP_TOP:
126 model = mateweather_timezone_model_new (g_value_get_pointer (value));
127 gtk_combo_box_set_model (GTK_COMBO_BOX (object), model);
128 g_object_unref (model);
129 gtk_combo_box_set_active (GTK_COMBO_BOX (object), 0);
130 break;
131
132 case PROP_TZID:
133 mateweather_timezone_menu_set_tzid (MATEWEATHER_TIMEZONE_MENU (object),
134 g_value_get_string (value));
135 break;
136 default:
137 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
138 break;
139 }
140 }
141
142 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)143 get_property (GObject *object, guint prop_id,
144 GValue *value, GParamSpec *pspec)
145 {
146 MateWeatherTimezoneMenu *menu = MATEWEATHER_TIMEZONE_MENU (object);
147
148 switch (prop_id) {
149 case PROP_TZID:
150 g_value_set_string (value, mateweather_timezone_menu_get_tzid (menu));
151 break;
152 default:
153 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
154 break;
155 }
156 }
157
158 enum {
159 MATEWEATHER_TIMEZONE_MENU_NAME,
160 MATEWEATHER_TIMEZONE_MENU_ZONE
161 };
162
163 static void
changed(GtkComboBox * combo)164 changed (GtkComboBox *combo)
165 {
166 MateWeatherTimezoneMenu *menu = MATEWEATHER_TIMEZONE_MENU (combo);
167 GtkTreeIter iter;
168
169 if (menu->zone)
170 mateweather_timezone_unref (menu->zone);
171
172 gtk_combo_box_get_active_iter (combo, &iter);
173 gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter,
174 MATEWEATHER_TIMEZONE_MENU_ZONE, &menu->zone,
175 -1);
176
177 if (menu->zone)
178 mateweather_timezone_ref (menu->zone);
179
180 g_object_notify (G_OBJECT (combo), "tzid");
181 }
182
183 static void
append_offset(GString * desc,int offset)184 append_offset (GString *desc, int offset)
185 {
186 int hours, minutes;
187
188 hours = offset / 60;
189 minutes = (offset > 0) ? offset % 60 : -offset % 60;
190
191 if (minutes)
192 g_string_append_printf (desc, "GMT%+d:%02d", hours, minutes);
193 else if (hours)
194 g_string_append_printf (desc, "GMT%+d", hours);
195 else
196 g_string_append (desc, "GMT");
197 }
198
199 static char *
get_offset(MateWeatherTimezone * zone)200 get_offset (MateWeatherTimezone *zone)
201 {
202 GString *desc;
203
204 desc = g_string_new (NULL);
205 append_offset (desc, mateweather_timezone_get_offset (zone));
206 if (mateweather_timezone_has_dst (zone)) {
207 g_string_append (desc, " / ");
208 append_offset (desc, mateweather_timezone_get_dst_offset (zone));
209 }
210 return g_string_free (desc, FALSE);
211 }
212
213 static void
insert_location(GtkTreeStore * store,MateWeatherTimezone * zone,const char * loc_name,GtkTreeIter * parent)214 insert_location (GtkTreeStore *store, MateWeatherTimezone *zone, const char *loc_name, GtkTreeIter *parent)
215 {
216 GtkTreeIter iter;
217 char *name, *offset;
218
219 offset = get_offset (zone);
220 name = g_strdup_printf ("%s <small>(%s)</small>",
221 loc_name ? loc_name : mateweather_timezone_get_name (zone),
222 offset);
223 gtk_tree_store_append (store, &iter, parent);
224 gtk_tree_store_set (store, &iter,
225 MATEWEATHER_TIMEZONE_MENU_NAME, name,
226 MATEWEATHER_TIMEZONE_MENU_ZONE, mateweather_timezone_ref (zone),
227 -1);
228 g_free (name);
229 g_free (offset);
230 }
231
232 static void
insert_locations(GtkTreeStore * store,MateWeatherLocation * loc)233 insert_locations (GtkTreeStore *store, MateWeatherLocation *loc)
234 {
235 int i;
236
237 if (mateweather_location_get_level (loc) < MATEWEATHER_LOCATION_COUNTRY) {
238 MateWeatherLocation **children;
239
240 children = mateweather_location_get_children (loc);
241 for (i = 0; children[i]; i++)
242 insert_locations (store, children[i]);
243 mateweather_location_free_children (loc, children);
244 } else {
245 MateWeatherTimezone **zones;
246 GtkTreeIter iter;
247
248 zones = mateweather_location_get_timezones (loc);
249 if (zones[1]) {
250 gtk_tree_store_append (store, &iter, NULL);
251 gtk_tree_store_set (store, &iter,
252 MATEWEATHER_TIMEZONE_MENU_NAME, mateweather_location_get_name (loc),
253 -1);
254
255 for (i = 0; zones[i]; i++) {
256 insert_location (store, zones[i], NULL, &iter);
257 }
258 } else if (zones[0]) {
259 insert_location (store, zones[0], mateweather_location_get_name (loc), NULL);
260 }
261
262 mateweather_location_free_timezones (loc, zones);
263 }
264 }
265
266 static GtkTreeModel *
mateweather_timezone_model_new(MateWeatherLocation * top)267 mateweather_timezone_model_new (MateWeatherLocation *top)
268 {
269 GtkTreeStore *store;
270 GtkTreeModel *model;
271 GtkTreeIter iter;
272 char *unknown;
273 MateWeatherTimezone *utc;
274
275 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
276 model = GTK_TREE_MODEL (store);
277
278 unknown = g_markup_printf_escaped ("<i>%s</i>", C_("timezone", "Unknown"));
279
280 gtk_tree_store_append (store, &iter, NULL);
281 gtk_tree_store_set (store, &iter,
282 MATEWEATHER_TIMEZONE_MENU_NAME, unknown,
283 MATEWEATHER_TIMEZONE_MENU_ZONE, NULL,
284 -1);
285
286 utc = mateweather_timezone_get_utc ();
287 if (utc) {
288 insert_location (store, utc, NULL, NULL);
289 mateweather_timezone_unref (utc);
290 }
291
292 gtk_tree_store_append (store, &iter, NULL);
293
294 g_free (unknown);
295
296 insert_locations (store, top);
297
298 return model;
299 }
300
301 static gboolean
row_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)302 row_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
303 {
304 char *name;
305
306 gtk_tree_model_get (model, iter,
307 MATEWEATHER_TIMEZONE_MENU_NAME, &name,
308 -1);
309 if (name) {
310 g_free (name);
311 return FALSE;
312 } else
313 return TRUE;
314 }
315
316 static void
is_sensitive(GtkCellLayout * cell_layout,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)317 is_sensitive (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
318 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
319 {
320 gboolean sensitive;
321
322 sensitive = !gtk_tree_model_iter_has_child (tree_model, iter);
323 g_object_set (cell, "sensitive", sensitive, NULL);
324 }
325
326 /**
327 * mateweather_timezone_menu_new:
328 * @top: the top-level location for the menu.
329 *
330 * Creates a new #MateWeatherTimezoneMenu.
331 *
332 * @top will normally be a location returned from
333 * mateweather_location_new_world(), but you can create a menu that
334 * contains the timezones from a smaller set of locations if you want.
335 *
336 * Return value: the new #MateWeatherTimezoneMenu
337 **/
338 GtkWidget *
mateweather_timezone_menu_new(MateWeatherLocation * top)339 mateweather_timezone_menu_new (MateWeatherLocation *top)
340 {
341 return g_object_new (MATEWEATHER_TYPE_TIMEZONE_MENU,
342 "top", top,
343 NULL);
344 }
345
346 typedef struct {
347 GtkComboBox *combo;
348 const char *tzid;
349 } SetTimezoneData;
350
351 static gboolean
check_tzid(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)352 check_tzid (GtkTreeModel *model, GtkTreePath *path,
353 GtkTreeIter *iter, gpointer data)
354 {
355 SetTimezoneData *tzd = data;
356 MateWeatherTimezone *zone;
357
358 gtk_tree_model_get (model, iter,
359 MATEWEATHER_TIMEZONE_MENU_ZONE, &zone,
360 -1);
361 if (!zone)
362 return FALSE;
363
364 if (!strcmp (mateweather_timezone_get_tzid (zone), tzd->tzid)) {
365 gtk_combo_box_set_active_iter (tzd->combo, iter);
366 return TRUE;
367 } else
368 return FALSE;
369 }
370
371 /**
372 * mateweather_timezone_menu_set_tzid:
373 * @menu: a #MateWeatherTimezoneMenu
374 * @tzid: (allow-none): a tzdata id (eg, "America/New_York")
375 *
376 * Sets @menu to the given @tzid. If @tzid is %NULL, sets @menu to
377 * "Unknown".
378 **/
379 void
mateweather_timezone_menu_set_tzid(MateWeatherTimezoneMenu * menu,const char * tzid)380 mateweather_timezone_menu_set_tzid (MateWeatherTimezoneMenu *menu,
381 const char *tzid)
382 {
383 SetTimezoneData tzd;
384
385 g_return_if_fail (MATEWEATHER_IS_TIMEZONE_MENU (menu));
386
387 if (!tzid) {
388 gtk_combo_box_set_active (GTK_COMBO_BOX (menu), 0);
389 return;
390 }
391
392 tzd.combo = GTK_COMBO_BOX (menu);
393 tzd.tzid = tzid;
394 gtk_tree_model_foreach (gtk_combo_box_get_model (tzd.combo),
395 check_tzid, &tzd);
396 }
397
398 /**
399 * mateweather_timezone_menu_get_tzid:
400 * @menu: a #MateWeatherTimezoneMenu
401 *
402 * Gets @menu's timezone id.
403 *
404 * Return value: (allow-none): @menu's tzid, or %NULL if no timezone
405 * is selected.
406 **/
407 const char *
mateweather_timezone_menu_get_tzid(MateWeatherTimezoneMenu * menu)408 mateweather_timezone_menu_get_tzid (MateWeatherTimezoneMenu *menu)
409 {
410 g_return_val_if_fail (MATEWEATHER_IS_TIMEZONE_MENU (menu), NULL);
411
412 if (!menu->zone)
413 return NULL;
414 return mateweather_timezone_get_tzid (menu->zone);
415 }
416
417