evolution-3.6.4/widgets/e-timezone-dialog/e-timezone-dialog.c

No issues found

  1 /*
  2  * This program is free software; you can redistribute it and/or
  3  * modify it under the terms of the GNU Lesser General Public
  4  * License as published by the Free Software Foundation; either
  5  * version 2 of the License, or (at your option) version 3.
  6  *
  7  * This program is distributed in the hope that it will be useful,
  8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 10  * Lesser General Public License for more details.
 11  *
 12  * You should have received a copy of the GNU Lesser General Public
 13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 14  *
 15  *
 16  * Authors:
 17  *		Damon Chaplin <damon@ximian.com>
 18  *
 19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 20  *
 21  */
 22 
 23 #ifdef HAVE_CONFIG_H
 24 #include <config.h>
 25 #endif
 26 
 27 #include <time.h>
 28 #include <string.h>
 29 #include <glib/gi18n.h>
 30 #include <misc/e-map.h>
 31 
 32 #include <libecal/libecal.h>
 33 
 34 #include "e-util/e-util.h"
 35 #include "e-util/e-util-private.h"
 36 
 37 #include "e-timezone-dialog.h"
 38 
 39 #ifdef G_OS_WIN32
 40 #ifdef gmtime_r
 41 #undef gmtime_r
 42 #endif
 43 #ifdef localtime_r
 44 #undef localtime_r
 45 #endif
 46 
 47 /* The gmtime() and localtime() in Microsoft's C library are MT-safe */
 48 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
 49 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
 50 #endif
 51 
 52 #define E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA 0xc070a0ff
 53 #define E_TIMEZONE_DIALOG_MAP_POINT_HOVER_RGBA 0xffff60ff
 54 #define E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_1_RGBA 0xff60e0ff
 55 #define E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_2_RGBA 0x000000ff
 56 
 57 #define E_TIMEZONE_DIALOG_GET_PRIVATE(obj) \
 58 	(G_TYPE_INSTANCE_GET_PRIVATE \
 59 	((obj), E_TYPE_TIMEZONE_DIALOG, ETimezoneDialogPrivate))
 60 
 61 struct _ETimezoneDialogPrivate {
 62 	/* The selected timezone. May be NULL for a 'local time' (i.e. when
 63 	 * the displayed name is ""). */
 64 	icaltimezone *zone;
 65 
 66 	GtkBuilder *builder;
 67 
 68 	EMapPoint *point_selected;
 69 	EMapPoint *point_hover;
 70 
 71 	EMap *map;
 72 
 73 	/* The timeout used to flash the nearest point. */
 74 	guint timeout_id;
 75 
 76 	/* Widgets from the UI file */
 77 	GtkWidget *app;
 78 	GtkWidget *table;
 79 	GtkWidget *map_window;
 80 	GtkWidget *timezone_combo;
 81 	GtkWidget *preview_label;
 82 };
 83 
 84 static void e_timezone_dialog_dispose		(GObject	*object);
 85 
 86 static gboolean get_widgets			(ETimezoneDialog *etd);
 87 static gboolean on_map_timeout			(gpointer	 data);
 88 static gboolean on_map_motion			(GtkWidget	*widget,
 89 						 GdkEventMotion *event,
 90 						 gpointer	 data);
 91 static gboolean on_map_leave			(GtkWidget	*widget,
 92 						 GdkEventCrossing *event,
 93 						 gpointer	 data);
 94 static gboolean on_map_visibility_changed	(GtkWidget	*w,
 95 						 GdkEventVisibility *event,
 96 						 gpointer	 data);
 97 static gboolean on_map_button_pressed		(GtkWidget	*w,
 98 						 GdkEventButton *event,
 99 						 gpointer	 data);
100 
101 static icaltimezone * get_zone_from_point	(ETimezoneDialog *etd,
102 						 EMapPoint	*point);
103 static void	set_map_timezone		(ETimezoneDialog *etd,
104 						 icaltimezone    *zone);
105 static void	on_combo_changed		(GtkComboBox	*combo,
106 						 ETimezoneDialog *etd);
107 
108 static void timezone_combo_get_active_text	(GtkComboBox *combo,
109 						 gchar **zone_name);
110 static gboolean timezone_combo_set_active_text	(GtkComboBox *combo,
111 						 const gchar *zone_name);
112 
113 static void	map_destroy_cb			(gpointer data,
114 						 GObject *where_object_was);
115 
116 G_DEFINE_TYPE (ETimezoneDialog, e_timezone_dialog, G_TYPE_OBJECT)
117 
118 /* Class initialization function for the event editor */
119 static void
120 e_timezone_dialog_class_init (ETimezoneDialogClass *class)
121 {
122 	GObjectClass *object_class;
123 
124 	g_type_class_add_private (class, sizeof (ETimezoneDialogPrivate));
125 
126 	object_class = G_OBJECT_CLASS (class);
127 	object_class->dispose  = e_timezone_dialog_dispose;
128 }
129 
130 /* Object initialization function for the event editor */
131 static void
132 e_timezone_dialog_init (ETimezoneDialog *etd)
133 {
134 	etd->priv = E_TIMEZONE_DIALOG_GET_PRIVATE (etd);
135 }
136 
137 /* Dispose handler for the event editor */
138 static void
139 e_timezone_dialog_dispose (GObject *object)
140 {
141 	ETimezoneDialogPrivate *priv;
142 
143 	priv = E_TIMEZONE_DIALOG_GET_PRIVATE (object);
144 
145 	/* Destroy the actual dialog. */
146 	if (priv->app != NULL) {
147 		gtk_widget_destroy (priv->app);
148 		priv->app = NULL;
149 	}
150 
151 	if (priv->timeout_id) {
152 		g_source_remove (priv->timeout_id);
153 		priv->timeout_id = 0;
154 	}
155 
156 	if (priv->builder) {
157 		g_object_unref (priv->builder);
158 		priv->builder = NULL;
159 	}
160 
161 	/* Chain up to parent's dispose() method. */
162 	G_OBJECT_CLASS (e_timezone_dialog_parent_class)->dispose (object);
163 }
164 
165 static void
166 e_timezone_dialog_add_timezones (ETimezoneDialog *etd)
167 {
168 	ETimezoneDialogPrivate *priv;
169 	icalarray *zones;
170 	GtkComboBox *combo;
171 	GList *l, *list_items = NULL;
172 	GtkListStore *list_store;
173 	GtkTreeIter iter;
174 	GtkCellRenderer *cell;
175 	GHashTable *index;
176 	gint i;
177 
178 	priv = etd->priv;
179 
180 	/* Get the array of builtin timezones. */
181 	zones = icaltimezone_get_builtin_timezones ();
182 
183 	for (i = 0; i < zones->num_elements; i++) {
184 		icaltimezone *zone;
185 		gchar *location;
186 
187 		zone = icalarray_element_at (zones, i);
188 
189 		location = _(icaltimezone_get_location (zone));
190 
191 		e_map_add_point (
192 			priv->map, location,
193 			icaltimezone_get_longitude (zone),
194 			icaltimezone_get_latitude (zone),
195 			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
196 
197 		list_items = g_list_prepend (list_items, location);
198 	}
199 
200 	list_items = g_list_sort (list_items, (GCompareFunc) g_utf8_collate);
201 
202 	/* Put the "UTC" entry at the top of the combo's list. */
203 	list_items = g_list_prepend (list_items, _("UTC"));
204 
205 	combo = GTK_COMBO_BOX (priv->timezone_combo);
206 
207 	cell = gtk_cell_renderer_text_new ();
208 	gtk_cell_layout_pack_start ((GtkCellLayout *) combo, cell, TRUE);
209 	gtk_cell_layout_set_attributes ((GtkCellLayout *) combo, cell, "text", 0, NULL);
210 
211 	list_store = gtk_list_store_new (1, G_TYPE_STRING);
212 	index = g_hash_table_new (g_str_hash, g_str_equal);
213 	for (l = list_items, i = 0; l != NULL; l = l->next, ++i) {
214 		gtk_list_store_append (list_store, &iter);
215 		gtk_list_store_set (list_store, &iter, 0, (gchar *)(l->data), -1);
216 		g_hash_table_insert (index, (gchar *)(l->data), GINT_TO_POINTER (i));
217 	}
218 
219 	g_object_set_data_full (
220 		G_OBJECT (list_store), "index", index,
221 		(GDestroyNotify) g_hash_table_destroy);
222 
223 	gtk_combo_box_set_model (combo, (GtkTreeModel *) list_store);
224 
225 	gtk_rc_parse_string (
226 		"style \"e-timezone-combo-style\" {\n"
227 		"  GtkComboBox::appears-as-list = 1\n"
228 		"}\n"
229 		"\n"
230 		"widget \"*.e-timezone-dialog-combo\" style \"e-timezone-combo-style\"");
231 
232 	gtk_widget_set_name (priv->timezone_combo, "e-timezone-dialog-combo");
233 
234 	g_list_free (list_items);
235 }
236 
237 ETimezoneDialog *
238 e_timezone_dialog_construct (ETimezoneDialog *etd)
239 {
240 	ETimezoneDialogPrivate *priv;
241 	GtkWidget *widget;
242 	GtkWidget *map;
243 
244 	g_return_val_if_fail (etd != NULL, NULL);
245 	g_return_val_if_fail (E_IS_TIMEZONE_DIALOG (etd), NULL);
246 
247 	priv = etd->priv;
248 
249 	/* Load the content widgets */
250 
251 	priv->builder = gtk_builder_new ();
252 	e_load_ui_builder_definition (priv->builder, "e-timezone-dialog.ui");
253 
254 	if (!get_widgets (etd)) {
255 		g_message (
256 			"%s(): Could not find all widgets in the XML file!",
257 			G_STRFUNC);
258 		goto error;
259 	}
260 
261 	widget = gtk_dialog_get_content_area (GTK_DIALOG (priv->app));
262 	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
263 
264 	widget = gtk_dialog_get_action_area (GTK_DIALOG (priv->app));
265 	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
266 
267 	priv->map = e_map_new ();
268 	map = GTK_WIDGET (priv->map);
269 
270 	g_object_weak_ref (G_OBJECT (map), map_destroy_cb, priv);
271 
272 	gtk_widget_set_events (
273 		map,
274 		gtk_widget_get_events (map) |
275 		GDK_LEAVE_NOTIFY_MASK |
276 		GDK_VISIBILITY_NOTIFY_MASK);
277 
278 	e_timezone_dialog_add_timezones (etd);
279 
280 	gtk_container_add (GTK_CONTAINER (priv->map_window), map);
281 	gtk_widget_show (map);
282 
283 	/* Ensure a reasonable minimum amount of map is visible */
284 	gtk_widget_set_size_request (priv->map_window, 200, 200);
285 
286 	g_signal_connect (
287 		map, "motion-notify-event",
288 		G_CALLBACK (on_map_motion), etd);
289 	g_signal_connect (
290 		map, "leave-notify-event",
291 		G_CALLBACK (on_map_leave), etd);
292 	g_signal_connect (
293 		map, "visibility-notify-event",
294 		G_CALLBACK (on_map_visibility_changed), etd);
295 	g_signal_connect (
296 		map, "button-press-event",
297 		G_CALLBACK (on_map_button_pressed), etd);
298 
299 	g_signal_connect (
300 		priv->timezone_combo, "changed",
301 		G_CALLBACK (on_combo_changed), etd);
302 
303 	return etd;
304 
305  error:
306 
307 	g_object_unref (etd);
308 	return NULL;
309 }
310 
311 #if 0
312 static gint
313 get_local_offset (void)
314 {
315 	time_t now = time (NULL), t_gmt, t_local;
316 	struct tm gmt, local;
317 	gint diff;
318 
319 	gmtime_r (&now, &gmt);
320 	localtime_r (&now, &local);
321 	t_gmt = mktime (&gmt);
322 	t_local = mktime (&local);
323 	diff = t_local - t_gmt;
324 
325 	return diff;
326 }
327 #endif
328 
329 static icaltimezone *
330 get_local_timezone (void)
331 {
332 	icaltimezone *zone;
333 	gchar *location;
334 
335 	tzset ();
336 	location = e_cal_system_timezone_get_location ();
337 
338 	if (location)
339 		zone = icaltimezone_get_builtin_timezone (location);
340 	else
341 		zone = icaltimezone_get_utc_timezone ();
342 
343 	g_free (location);
344 
345 	return zone;
346 }
347 
348 /* Gets the widgets from the XML file and returns if they are all available.
349  * For the widgets whose values can be simply set with e-dialog-utils, it does
350  * that as well.
351  */
352 static gboolean
353 get_widgets (ETimezoneDialog *etd)
354 {
355 	ETimezoneDialogPrivate *priv;
356 	GtkBuilder *builder;
357 
358 	priv = etd->priv;
359 	builder = etd->priv->builder;
360 
361 	priv->app = e_builder_get_widget (builder, "timezone-dialog");
362 	priv->map_window = e_builder_get_widget (builder, "map-window");
363 	priv->timezone_combo = e_builder_get_widget (builder, "timezone-combo");
364 	priv->table = e_builder_get_widget (builder, "timezone-table");
365 	priv->preview_label = e_builder_get_widget (builder, "preview-label");
366 
367 	return (priv->app
368 		&& priv->map_window
369 		&& priv->timezone_combo
370 		&& priv->table
371 		&& priv->preview_label);
372 }
373 
374 /**
375  * e_timezone_dialog_new:
376  *
377  * Creates a new event editor dialog.
378  *
379  * Return value: A newly-created event editor dialog, or NULL if the event
380  * editor could not be created.
381  **/
382 ETimezoneDialog *
383 e_timezone_dialog_new (void)
384 {
385 	ETimezoneDialog *etd;
386 
387 	etd = E_TIMEZONE_DIALOG (g_object_new (E_TYPE_TIMEZONE_DIALOG, NULL));
388 	return e_timezone_dialog_construct (E_TIMEZONE_DIALOG (etd));
389 }
390 
391 static void
392 format_utc_offset (gint utc_offset,
393                    gchar *buffer)
394 {
395 	const gchar *sign = "+";
396 	gint hours, minutes, seconds;
397 
398 	if (utc_offset < 0) {
399 		utc_offset = -utc_offset;
400 		sign = "-";
401 	}
402 
403 	hours = utc_offset / 3600;
404 	minutes = (utc_offset % 3600) / 60;
405 	seconds = utc_offset % 60;
406 
407 	/* Sanity check. Standard timezone offsets shouldn't be much more
408 	 * than 12 hours, and daylight saving shouldn't change it by more
409 	 * than a few hours.  (The maximum offset is 15 hours 56 minutes
410 	 * at present.) */
411 	if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
412 	    || seconds < 0 || seconds >= 60) {
413 		fprintf (
414 			stderr, "Warning: Strange timezone offset: "
415 			"H:%i M:%i S:%i\n", hours, minutes, seconds);
416 	}
417 
418 	if (hours == 0 && minutes == 0 && seconds == 0)
419 		strcpy (buffer, _("UTC"));
420 	else if (seconds == 0)
421 		sprintf (
422 			buffer, "%s %s%02i:%02i",
423 			_("UTC"), sign, hours, minutes);
424 	else
425 		sprintf (
426 			buffer, "%s %s%02i:%02i:%02i",
427 			_("UTC"), sign, hours, minutes, seconds);
428 }
429 
430 static gchar *
431 zone_display_name_with_offset (icaltimezone *zone)
432 {
433 	const gchar *display_name;
434 	struct tm local;
435 	struct icaltimetype tt;
436 	gint offset;
437 	gchar buffer[100];
438 	time_t now = time (NULL);
439 
440 	gmtime_r ((const time_t *) &now, &local);
441 	tt = tm_to_icaltimetype (&local, TRUE);
442 	offset = icaltimezone_get_utc_offset (zone, &tt, NULL);
443 
444 	format_utc_offset (offset, buffer);
445 
446 	display_name = icaltimezone_get_display_name (zone);
447 	if (icaltimezone_get_builtin_timezone (display_name))
448 		display_name = _(display_name);
449 
450 	return g_strdup_printf ("%s (%s)", display_name, buffer);
451 }
452 
453 static const gchar *
454 zone_display_name (icaltimezone *zone)
455 {
456 	const gchar *display_name;
457 
458 	display_name = icaltimezone_get_display_name (zone);
459 	if (icaltimezone_get_builtin_timezone (display_name))
460 		display_name = _(display_name);
461 
462 	return display_name;
463 }
464 
465 /* This flashes the currently selected timezone in the map. */
466 static gboolean
467 on_map_timeout (gpointer data)
468 {
469 	ETimezoneDialog *etd;
470 	ETimezoneDialogPrivate *priv;
471 
472 	etd = E_TIMEZONE_DIALOG (data);
473 	priv = etd->priv;
474 
475 	if (!priv->point_selected)
476 		return TRUE;
477 
478 	if (e_map_point_get_color_rgba (priv->point_selected)
479 	    == E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_1_RGBA)
480 		e_map_point_set_color_rgba (
481 			priv->map, priv->point_selected,
482 			E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_2_RGBA);
483 	else
484 		e_map_point_set_color_rgba (
485 			priv->map, priv->point_selected,
486 			E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_1_RGBA);
487 
488 	return TRUE;
489 }
490 
491 static gboolean
492 on_map_motion (GtkWidget *widget,
493                GdkEventMotion *event,
494                gpointer data)
495 {
496 	ETimezoneDialog *etd;
497 	ETimezoneDialogPrivate *priv;
498 	gdouble longitude, latitude;
499 	icaltimezone *new_zone;
500 	gchar *display = NULL;
501 
502 	etd = E_TIMEZONE_DIALOG (data);
503 	priv = etd->priv;
504 
505 	e_map_window_to_world (
506 		priv->map, (gdouble) event->x, (gdouble) event->y,
507 		&longitude, &latitude);
508 
509 	if (priv->point_hover && priv->point_hover != priv->point_selected)
510 		e_map_point_set_color_rgba (
511 			priv->map, priv->point_hover,
512 			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
513 
514 	priv->point_hover = e_map_get_closest_point (
515 		priv->map, longitude,
516 		latitude, TRUE);
517 
518 	if (priv->point_hover != priv->point_selected)
519 		e_map_point_set_color_rgba (
520 			priv->map, priv->point_hover,
521 			E_TIMEZONE_DIALOG_MAP_POINT_HOVER_RGBA);
522 
523 	new_zone = get_zone_from_point (etd, priv->point_hover);
524 
525 	display = zone_display_name_with_offset (new_zone);
526 	gtk_label_set_text (GTK_LABEL (priv->preview_label), display);
527 
528 	g_free (display);
529 
530 	return TRUE;
531 }
532 
533 static gboolean
534 on_map_leave (GtkWidget *widget,
535               GdkEventCrossing *event,
536               gpointer data)
537 {
538 	ETimezoneDialog *etd;
539 	ETimezoneDialogPrivate *priv;
540 
541 	etd = E_TIMEZONE_DIALOG (data);
542 	priv = etd->priv;
543 
544 	/* We only want to reset the hover point if this is a normal leave
545 	 * event. For some reason we are getting leave events when the
546 	 * button is pressed in the map, which causes problems. */
547 	if (event->mode != GDK_CROSSING_NORMAL)
548 		return FALSE;
549 
550 	if (priv->point_hover && priv->point_hover != priv->point_selected)
551 		e_map_point_set_color_rgba (
552 			priv->map, priv->point_hover,
553 			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
554 
555 	timezone_combo_set_active_text (
556 		GTK_COMBO_BOX (priv->timezone_combo),
557 		zone_display_name (priv->zone));
558 	gtk_label_set_text (GTK_LABEL (priv->preview_label), "");
559 
560 	priv->point_hover = NULL;
561 
562 	return FALSE;
563 }
564 
565 static gboolean
566 on_map_visibility_changed (GtkWidget *w,
567                            GdkEventVisibility *event,
568                            gpointer data)
569 {
570 	ETimezoneDialog *etd;
571 	ETimezoneDialogPrivate *priv;
572 
573 	etd = E_TIMEZONE_DIALOG (data);
574 	priv = etd->priv;
575 
576 	if (event->state != GDK_VISIBILITY_FULLY_OBSCURED) {
577 		/* Map is visible, at least partly, so make sure we flash the
578 		 * selected point. */
579 		if (!priv->timeout_id)
580 			priv->timeout_id = g_timeout_add (100, on_map_timeout, etd);
581 	} else {
582 		/* Map is invisible, so don't waste resources on the timeout.*/
583 		if (priv->timeout_id) {
584 			g_source_remove (priv->timeout_id);
585 			priv->timeout_id = 0;
586 		}
587 	}
588 
589 	return FALSE;
590 }
591 
592 static gboolean
593 on_map_button_pressed (GtkWidget *w,
594                        GdkEventButton *event,
595                        gpointer data)
596 {
597 	ETimezoneDialog *etd;
598 	ETimezoneDialogPrivate *priv;
599 	gdouble longitude, latitude;
600 
601 	etd = E_TIMEZONE_DIALOG (data);
602 	priv = etd->priv;
603 
604 	e_map_window_to_world (
605 		priv->map, (gdouble) event->x, (gdouble) event->y,
606 		&longitude, &latitude);
607 
608 	if (event->button != 1) {
609 		e_map_zoom_out (priv->map);
610 	} else {
611 		if (e_map_get_magnification (priv->map) <= 1.0)
612 			e_map_zoom_to_location (
613 				priv->map, longitude, latitude);
614 
615 		if (priv->point_selected)
616 			e_map_point_set_color_rgba (
617 				priv->map,
618 				priv->point_selected,
619 				E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
620 		priv->point_selected = priv->point_hover;
621 
622 		priv->zone = get_zone_from_point (etd, priv->point_selected);
623 		timezone_combo_set_active_text (
624 			GTK_COMBO_BOX (priv->timezone_combo),
625 			zone_display_name (priv->zone));
626 	}
627 
628 	return TRUE;
629 }
630 
631 /* Returns the translated timezone location of the given EMapPoint,
632  * e.g. "Europe/London". */
633 static icaltimezone *
634 get_zone_from_point (ETimezoneDialog *etd,
635                      EMapPoint *point)
636 {
637 	icalarray *zones;
638 	gdouble longitude, latitude;
639 	gint i;
640 
641 	if (point == NULL)
642 		return NULL;
643 
644 	e_map_point_get_location (point, &longitude, &latitude);
645 
646 	/* Get the array of builtin timezones. */
647 	zones = icaltimezone_get_builtin_timezones ();
648 
649 	for (i = 0; i < zones->num_elements; i++) {
650 		icaltimezone *zone;
651 		gdouble zone_longitude, zone_latitude;
652 
653 		zone = icalarray_element_at (zones, i);
654 		zone_longitude = icaltimezone_get_longitude (zone);
655 		zone_latitude = icaltimezone_get_latitude (zone);
656 
657 		if (zone_longitude - 0.005 <= longitude &&
658 		    zone_longitude + 0.005 >= longitude &&
659 		    zone_latitude - 0.005 <= latitude &&
660 		    zone_latitude + 0.005 >= latitude)
661 		{
662 			return zone;
663 		}
664 	}
665 
666 	g_return_val_if_reached (NULL);
667 }
668 
669 /**
670  * e_timezone_dialog_get_timezone:
671  * @etd: the timezone dialog
672  *
673  * Return value: the currently-selected timezone, or %NULL if no timezone
674  * is selected.
675  **/
676 icaltimezone *
677 e_timezone_dialog_get_timezone (ETimezoneDialog *etd)
678 {
679 	ETimezoneDialogPrivate *priv;
680 
681 	g_return_val_if_fail (E_IS_TIMEZONE_DIALOG (etd), NULL);
682 
683 	priv = etd->priv;
684 
685 	return priv->zone;
686 }
687 
688 /**
689  * e_timezone_dialog_set_timezone:
690  * @etd: the timezone dialog
691  * @zone: the timezone
692  *
693  * Sets the timezone of @etd to @zone. Updates the display name and
694  * selected location. The caller must ensure that @zone is not freed
695  * before @etd is destroyed.
696  **/
697 
698 void
699 e_timezone_dialog_set_timezone (ETimezoneDialog *etd,
700                                 icaltimezone *zone)
701 {
702 	ETimezoneDialogPrivate *priv;
703 	gchar *display = NULL;
704 
705 	g_return_if_fail (E_IS_TIMEZONE_DIALOG (etd));
706 
707 	if (!zone)
708 		zone = get_local_timezone ();
709 
710 	if (zone)
711 		display = zone_display_name_with_offset (zone);
712 
713 	priv = etd->priv;
714 
715 	priv->zone = zone;
716 
717 	gtk_label_set_text (
718 		GTK_LABEL (priv->preview_label),
719 		zone ? display : "");
720 	timezone_combo_set_active_text (
721 		GTK_COMBO_BOX (priv->timezone_combo),
722 		zone ? zone_display_name (zone) : "");
723 
724 	set_map_timezone (etd, zone);
725 	g_free (display);
726 }
727 
728 GtkWidget *
729 e_timezone_dialog_get_toplevel (ETimezoneDialog *etd)
730 {
731 	ETimezoneDialogPrivate *priv;
732 
733 	g_return_val_if_fail (etd != NULL, NULL);
734 	g_return_val_if_fail (E_IS_TIMEZONE_DIALOG (etd), NULL);
735 
736 	priv = etd->priv;
737 
738 	return priv->app;
739 }
740 
741 static void
742 set_map_timezone (ETimezoneDialog *etd,
743                   icaltimezone *zone)
744 {
745 	ETimezoneDialogPrivate *priv;
746 	EMapPoint *point;
747 	gdouble zone_longitude, zone_latitude;
748 
749 	priv = etd->priv;
750 
751 	if (zone) {
752 		zone_longitude = icaltimezone_get_longitude (zone);
753 		zone_latitude = icaltimezone_get_latitude (zone);
754 		point = e_map_get_closest_point (
755 			priv->map,
756 			zone_longitude,
757 			zone_latitude,
758 			FALSE);
759 	} else
760 		point = NULL;
761 
762 	if (priv->point_selected)
763 		e_map_point_set_color_rgba (
764 			priv->map, priv->point_selected,
765 			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
766 
767 	priv->point_selected = point;
768 }
769 
770 static void
771 on_combo_changed (GtkComboBox *combo_box,
772                   ETimezoneDialog *etd)
773 {
774 	ETimezoneDialogPrivate *priv;
775 	gchar *new_zone_name;
776 	icalarray *zones;
777 	icaltimezone *map_zone = NULL;
778 	gchar *location;
779 	gint i;
780 
781 	priv = etd->priv;
782 
783 	timezone_combo_get_active_text (
784 		GTK_COMBO_BOX (priv->timezone_combo), &new_zone_name);
785 
786 	if (!new_zone_name || !*new_zone_name)
787 		priv->zone = NULL;
788 	else if (!g_utf8_collate (new_zone_name, _("UTC")))
789 		priv->zone = icaltimezone_get_utc_timezone ();
790 	else {
791 		priv->zone = NULL;
792 
793 		zones = icaltimezone_get_builtin_timezones ();
794 		for (i = 0; i < zones->num_elements; i++) {
795 			map_zone = icalarray_element_at (zones, i);
796 			location = _(icaltimezone_get_location (map_zone));
797 			if (!g_utf8_collate (new_zone_name, location)) {
798 				priv->zone = map_zone;
799 				break;
800 			}
801 		}
802 	}
803 
804 	set_map_timezone (etd, map_zone);
805 
806 	g_free (new_zone_name);
807 }
808 
809 static void
810 timezone_combo_get_active_text (GtkComboBox *combo,
811                                 gchar **zone_name)
812 {
813 	GtkTreeModel *list_store;
814 	GtkTreeIter iter;
815 
816 	list_store = gtk_combo_box_get_model (combo);
817 
818 	/* Get the active iter in the list */
819 	if (gtk_combo_box_get_active_iter (combo, &iter))
820 		gtk_tree_model_get (list_store, &iter, 0, zone_name, -1);
821 	else
822 		*zone_name = NULL;
823 }
824 
825 static gboolean
826 timezone_combo_set_active_text (GtkComboBox *combo,
827                                 const gchar *zone_name)
828 {
829 	GtkTreeModel *list_store;
830 	GHashTable *index;
831 	gpointer id = NULL;
832 
833 	list_store = gtk_combo_box_get_model (combo);
834 	index = (GHashTable *) g_object_get_data (G_OBJECT (list_store), "index");
835 
836 	if (zone_name && *zone_name)
837 		id = g_hash_table_lookup (index, zone_name);
838 
839 	gtk_combo_box_set_active (combo, GPOINTER_TO_INT (id));
840 
841 	return (id != NULL);
842 }
843 
844 static void
845 map_destroy_cb (gpointer data,
846                 GObject *where_object_was)
847 {
848 
849 	ETimezoneDialogPrivate *priv = data;
850 	if (priv->timeout_id) {
851 		g_source_remove (priv->timeout_id);
852 		priv->timeout_id = 0;
853 	}
854 	return;
855 }