1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Damon Chaplin <damon@ximian.com>
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 /*
23  * ETimezoneEntry - a field for setting a timezone. It shows the timezone in
24  * a GtkEntry with a '...' button beside it which shows a dialog for changing
25  * the timezone. The dialog contains a map of the world with a point for each
26  * timezone, and an option menu as an alternative way of selecting the
27  * timezone.
28  */
29 
30 #include "evolution-config.h"
31 
32 #include "e-timezone-entry.h"
33 
34 #include <glib/gi18n.h>
35 
36 #include "e-util/e-util.h"
37 
38 #define E_TIMEZONE_ENTRY_GET_PRIVATE(obj) \
39 	(G_TYPE_INSTANCE_GET_PRIVATE \
40 	((obj), E_TYPE_TIMEZONE_ENTRY, ETimezoneEntryPrivate))
41 
42 struct _ETimezoneEntryPrivate {
43 	/* The current timezone, set in e_timezone_entry_set_timezone()
44 	 * or from the timezone dialog. Note that we don't copy it or
45 	 * use a ref count - we assume it is never destroyed for the
46 	 * lifetime of this widget. */
47 	ICalTimezone *timezone;
48 	gboolean allow_none;
49 
50 	GtkWidget *entry;
51 	GtkWidget *button;
52 };
53 
54 enum {
55 	PROP_0,
56 	PROP_TIMEZONE
57 };
58 
59 enum {
60 	CHANGED,
61 	LAST_SIGNAL
62 };
63 
64 static guint signals[LAST_SIGNAL];
65 
G_DEFINE_TYPE(ETimezoneEntry,e_timezone_entry,GTK_TYPE_BOX)66 G_DEFINE_TYPE (ETimezoneEntry, e_timezone_entry, GTK_TYPE_BOX)
67 
68 static void
69 timezone_entry_emit_changed (ETimezoneEntry *timezone_entry)
70 {
71 	g_signal_emit (timezone_entry, signals[CHANGED], 0);
72 }
73 
74 static void
timezone_entry_update_entry(ETimezoneEntry * timezone_entry)75 timezone_entry_update_entry (ETimezoneEntry *timezone_entry)
76 {
77 	const gchar *display_name;
78 	gchar *name_buffer;
79 	ICalTimezone *timezone;
80 
81 	timezone = e_timezone_entry_get_timezone (timezone_entry);
82 
83 	if (timezone != NULL) {
84 		display_name = i_cal_timezone_get_display_name (timezone);
85 
86 		/* We check if it is one of our builtin timezone
87 		 * names, in which case we call gettext to translate
88 		 * it. If it isn't a builtin timezone name, we don't. */
89 		if (i_cal_timezone_get_builtin_timezone (display_name))
90 			display_name = _(display_name);
91 	} else if (timezone_entry->priv->allow_none) {
92 		display_name = C_("timezone", "None");
93 	} else
94 		display_name = "";
95 
96 	name_buffer = g_strdup (display_name);
97 
98 	gtk_entry_set_text (GTK_ENTRY (timezone_entry->priv->entry), name_buffer);
99 
100 	/* XXX Do we need to hide the timezone entry at all?  I know
101 	 *     this overrules the previous case of hiding the timezone
102 	 *     entry field when we select the default timezone. */
103 	gtk_widget_show (timezone_entry->priv->entry);
104 
105 	g_free (name_buffer);
106 }
107 static void
timezone_entry_add_relation(ETimezoneEntry * timezone_entry)108 timezone_entry_add_relation (ETimezoneEntry *timezone_entry)
109 {
110 	AtkObject *a11y_timezone_entry;
111 	AtkObject *a11y_widget;
112 	AtkRelationSet *set;
113 	AtkRelation *relation;
114 	GtkWidget *widget;
115 	GPtrArray *target;
116 	gpointer target_object;
117 
118 	/* add a labelled_by relation for widget for accessibility */
119 
120 	widget = GTK_WIDGET (timezone_entry);
121 	a11y_timezone_entry = gtk_widget_get_accessible (widget);
122 
123 	widget = timezone_entry->priv->entry;
124 	a11y_widget = gtk_widget_get_accessible (widget);
125 
126 	set = atk_object_ref_relation_set (a11y_widget);
127 	if (set != NULL) {
128 		relation = atk_relation_set_get_relation_by_type (
129 			set, ATK_RELATION_LABELLED_BY);
130 		/* check whether has a labelled_by relation already */
131 		if (relation != NULL) {
132 			g_object_unref (set);
133 			return;
134 		}
135 	}
136 
137 	g_clear_object (&set);
138 
139 	set = atk_object_ref_relation_set (a11y_timezone_entry);
140 	if (!set)
141 		return;
142 
143 	relation = atk_relation_set_get_relation_by_type (
144 		set, ATK_RELATION_LABELLED_BY);
145 	if (relation != NULL) {
146 		target = atk_relation_get_target (relation);
147 		target_object = g_ptr_array_index (target, 0);
148 		if (ATK_IS_OBJECT (target_object)) {
149 			atk_object_add_relationship (
150 				a11y_widget,
151 				ATK_RELATION_LABELLED_BY,
152 				ATK_OBJECT (target_object));
153 		}
154 	}
155 
156 	g_clear_object (&set);
157 }
158 
159 /* The arrow button beside the date field has been clicked, so we show the
160  * popup with the ECalendar in. */
161 static void
timezone_entry_button_clicked_cb(ETimezoneEntry * timezone_entry)162 timezone_entry_button_clicked_cb (ETimezoneEntry *timezone_entry)
163 {
164 	ETimezoneDialog *timezone_dialog;
165 	GtkWidget *toplevel;
166 	GtkWidget *dialog;
167 	ICalTimezone *timezone;
168 
169 	timezone_dialog = e_timezone_dialog_new ();
170 	e_timezone_dialog_set_allow_none (timezone_dialog, e_timezone_entry_get_allow_none (timezone_entry));
171 
172 	timezone = e_timezone_entry_get_timezone (timezone_entry);
173 	e_timezone_dialog_set_timezone (timezone_dialog, timezone);
174 
175 	dialog = e_timezone_dialog_get_toplevel (timezone_dialog);
176 
177 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (timezone_entry));
178 	if (GTK_IS_WINDOW (toplevel))
179 		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (toplevel));
180 
181 	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT)
182 		goto exit;
183 
184 	timezone = e_timezone_dialog_get_timezone (timezone_dialog);
185 	e_timezone_entry_set_timezone (timezone_entry, timezone);
186 	timezone_entry_update_entry (timezone_entry);
187 
188 exit:
189 	g_object_unref (timezone_dialog);
190 }
191 
192 static void
timezone_entry_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)193 timezone_entry_set_property (GObject *object,
194                              guint property_id,
195                              const GValue *value,
196                              GParamSpec *pspec)
197 {
198 	switch (property_id) {
199 		case PROP_TIMEZONE:
200 			e_timezone_entry_set_timezone (
201 				E_TIMEZONE_ENTRY (object),
202 				g_value_get_object (value));
203 			return;
204 	}
205 
206 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
207 }
208 
209 static void
timezone_entry_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)210 timezone_entry_get_property (GObject *object,
211                              guint property_id,
212                              GValue *value,
213                              GParamSpec *pspec)
214 {
215 	switch (property_id) {
216 		case PROP_TIMEZONE:
217 			g_value_set_object (
218 				value, e_timezone_entry_get_timezone (
219 				E_TIMEZONE_ENTRY (object)));
220 			return;
221 	}
222 
223 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
224 }
225 
226 static void
timezone_entry_get_finalize(GObject * object)227 timezone_entry_get_finalize (GObject *object)
228 {
229 	ETimezoneEntry *tzentry = E_TIMEZONE_ENTRY (object);
230 
231 	g_clear_object (&tzentry->priv->timezone);
232 
233 	/* Chain up to parent's method. */
234 	G_OBJECT_CLASS (e_timezone_entry_parent_class)->finalize (object);
235 }
236 
237 static gboolean
timezone_entry_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)238 timezone_entry_mnemonic_activate (GtkWidget *widget,
239                                   gboolean group_cycling)
240 {
241 	ETimezoneEntryPrivate *priv;
242 
243 	priv = E_TIMEZONE_ENTRY_GET_PRIVATE (widget);
244 
245 	if (gtk_widget_get_can_focus (widget)) {
246 		if (priv->button != NULL)
247 			gtk_widget_grab_focus (priv->button);
248 	}
249 
250 	return TRUE;
251 }
252 
253 static gboolean
timezone_entry_focus(GtkWidget * widget,GtkDirectionType direction)254 timezone_entry_focus (GtkWidget *widget,
255                       GtkDirectionType direction)
256 {
257 	ETimezoneEntryPrivate *priv;
258 
259 	priv = E_TIMEZONE_ENTRY_GET_PRIVATE (widget);
260 
261 	if (direction == GTK_DIR_TAB_FORWARD) {
262 		if (gtk_widget_has_focus (priv->entry))
263 			gtk_widget_grab_focus (priv->button);
264 		else if (gtk_widget_has_focus (priv->button))
265 			return FALSE;
266 		else if (gtk_widget_get_visible (priv->entry))
267 			gtk_widget_grab_focus (priv->entry);
268 		else
269 			gtk_widget_grab_focus (priv->button);
270 
271 	} else if (direction == GTK_DIR_TAB_BACKWARD) {
272 		if (gtk_widget_has_focus (priv->entry))
273 			return FALSE;
274 		else if (gtk_widget_has_focus (priv->button)) {
275 			if (gtk_widget_get_visible (priv->entry))
276 				gtk_widget_grab_focus (priv->entry);
277 			else
278 				return FALSE;
279 		} else
280 			gtk_widget_grab_focus (priv->button);
281 	} else
282 		return FALSE;
283 
284 	return TRUE;
285 }
286 
287 static void
e_timezone_entry_class_init(ETimezoneEntryClass * class)288 e_timezone_entry_class_init (ETimezoneEntryClass *class)
289 {
290 	GObjectClass *object_class;
291 	GtkWidgetClass *widget_class;
292 
293 	g_type_class_add_private (class, sizeof (ETimezoneEntryPrivate));
294 
295 	object_class = G_OBJECT_CLASS (class);
296 	object_class->set_property = timezone_entry_set_property;
297 	object_class->get_property = timezone_entry_get_property;
298 	object_class->finalize = timezone_entry_get_finalize;
299 
300 	widget_class = GTK_WIDGET_CLASS (class);
301 	widget_class->mnemonic_activate = timezone_entry_mnemonic_activate;
302 	widget_class->focus = timezone_entry_focus;
303 
304 	g_object_class_install_property (
305 		object_class,
306 		PROP_TIMEZONE,
307 		g_param_spec_object (
308 			"timezone",
309 			"Timezone",
310 			NULL,
311 			I_CAL_TYPE_TIMEZONE,
312 			G_PARAM_READWRITE));
313 
314 	signals[CHANGED] = g_signal_new (
315 		"changed",
316 		G_TYPE_FROM_CLASS (object_class),
317 		G_SIGNAL_RUN_LAST,
318 		G_STRUCT_OFFSET (ETimezoneEntryClass, changed),
319 		NULL, NULL,
320 		g_cclosure_marshal_VOID__VOID,
321 		G_TYPE_NONE, 0);
322 }
323 
324 static void
e_timezone_entry_init(ETimezoneEntry * timezone_entry)325 e_timezone_entry_init (ETimezoneEntry *timezone_entry)
326 {
327 	AtkObject *a11y;
328 	GtkWidget *widget;
329 
330 	timezone_entry->priv = E_TIMEZONE_ENTRY_GET_PRIVATE (timezone_entry);
331 	timezone_entry->priv->allow_none = FALSE;
332 
333 	gtk_widget_set_can_focus (GTK_WIDGET (timezone_entry), TRUE);
334 	gtk_orientable_set_orientation (GTK_ORIENTABLE (timezone_entry), GTK_ORIENTATION_HORIZONTAL);
335 
336 	widget = gtk_entry_new ();
337 	gtk_editable_set_editable (GTK_EDITABLE (widget), FALSE);
338 	gtk_box_pack_start (GTK_BOX (timezone_entry), widget, TRUE, TRUE, 0);
339 	timezone_entry->priv->entry = widget;
340 	gtk_widget_show (widget);
341 
342 	g_signal_connect_swapped (
343 		widget, "changed",
344 		G_CALLBACK (timezone_entry_emit_changed), timezone_entry);
345 
346 	widget = gtk_button_new_with_label (_("Select…"));
347 	gtk_box_pack_start (GTK_BOX (timezone_entry), widget, FALSE, FALSE, 6);
348 	timezone_entry->priv->button = widget;
349 	gtk_widget_show (widget);
350 
351 	g_signal_connect_swapped (
352 		widget, "clicked",
353 		G_CALLBACK (timezone_entry_button_clicked_cb), timezone_entry);
354 
355 	a11y = gtk_widget_get_accessible (timezone_entry->priv->button);
356 	if (a11y != NULL)
357 		atk_object_set_name (a11y, _("Select Timezone"));
358 }
359 
360 GtkWidget *
e_timezone_entry_new(void)361 e_timezone_entry_new (void)
362 {
363 	return g_object_new (E_TYPE_TIMEZONE_ENTRY, NULL);
364 }
365 
366 ICalTimezone *
e_timezone_entry_get_timezone(ETimezoneEntry * timezone_entry)367 e_timezone_entry_get_timezone (ETimezoneEntry *timezone_entry)
368 {
369 	g_return_val_if_fail (E_IS_TIMEZONE_ENTRY (timezone_entry), NULL);
370 
371 	return timezone_entry->priv->timezone;
372 }
373 
374 void
e_timezone_entry_set_timezone(ETimezoneEntry * timezone_entry,const ICalTimezone * timezone)375 e_timezone_entry_set_timezone (ETimezoneEntry *timezone_entry,
376 			       const ICalTimezone *timezone)
377 {
378 	g_return_if_fail (E_IS_TIMEZONE_ENTRY (timezone_entry));
379 
380 	if (timezone_entry->priv->timezone == timezone)
381 		return;
382 
383 	g_clear_object (&timezone_entry->priv->timezone);
384 	if (timezone)
385 		timezone_entry->priv->timezone = e_cal_util_copy_timezone (timezone);
386 
387 	timezone_entry_update_entry (timezone_entry);
388 	timezone_entry_add_relation (timezone_entry);
389 
390 	g_object_notify (G_OBJECT (timezone_entry), "timezone");
391 }
392 
393 gboolean
e_timezone_entry_get_allow_none(ETimezoneEntry * timezone_entry)394 e_timezone_entry_get_allow_none (ETimezoneEntry *timezone_entry)
395 {
396 	g_return_val_if_fail (E_IS_TIMEZONE_ENTRY (timezone_entry), FALSE);
397 
398 	return timezone_entry->priv->allow_none;
399 }
400 
401 void
e_timezone_entry_set_allow_none(ETimezoneEntry * timezone_entry,gboolean allow_none)402 e_timezone_entry_set_allow_none (ETimezoneEntry *timezone_entry,
403 				 gboolean allow_none)
404 {
405 	g_return_if_fail (E_IS_TIMEZONE_ENTRY (timezone_entry));
406 
407 	if ((timezone_entry->priv->allow_none ? 1 : 0) == (allow_none ? 1 : 0))
408 		return;
409 
410 	timezone_entry->priv->allow_none = allow_none;
411 }
412