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