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