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  *   Michael Zucchi <notzed@ximian.com>
17  *   Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  * Copyright (C) 2009 Intel Corporation
21  *
22  */
23 
24 #include "evolution-config.h"
25 
26 #include <string.h>
27 #include <sys/types.h>
28 
29 #include <libxml/parser.h>
30 #include <libxml/xmlmemory.h>
31 
32 #include <gtk/gtk.h>
33 #include <glib/gi18n.h>
34 
35 #include <libedataserver/libedataserver.h>
36 
37 #include "e-alert.h"
38 #include "e-alert-sink.h"
39 
40 #define d(x)
41 
42 #define E_ALERT_GET_PRIVATE(obj) \
43 	(G_TYPE_INSTANCE_GET_PRIVATE \
44 	((obj), E_TYPE_ALERT, EAlertPrivate))
45 
46 typedef struct _EAlertButton EAlertButton;
47 
48 struct _e_alert {
49 	const gchar *id;
50 	GtkMessageType message_type;
51 	gint default_response;
52 	const gchar *primary_text;
53 	const gchar *secondary_text;
54 	EAlertButton *buttons;
55 };
56 
57 struct _e_alert_table {
58 	const gchar *domain;
59 	const gchar *translation_domain;
60 	GHashTable *alerts;
61 };
62 
63 struct _EAlertButton {
64 	EAlertButton *next;
65 	const gchar *stock_id;
66 	const gchar *label;
67 	gint response_id;
68 	gboolean destructive;
69 };
70 
71 static GHashTable *alert_table;
72 
73 /* ********************************************************************** */
74 
75 static EAlertButton default_ok_button = {
76 	NULL, NULL, NULL, GTK_RESPONSE_OK
77 };
78 
79 static struct _e_alert default_alerts[] = {
80 	{ "error", GTK_MESSAGE_ERROR, GTK_RESPONSE_OK,
81 	  "{0}", "{1}", &default_ok_button },
82 	{ "warning", GTK_MESSAGE_WARNING, GTK_RESPONSE_OK,
83 	  "{0}", "{1}", &default_ok_button }
84 };
85 
86 /* ********************************************************************** */
87 
88 struct _EAlertPrivate {
89 	gchar *tag;
90 	GPtrArray *args;
91 	gchar *primary_text;
92 	gchar *secondary_text;
93 	struct _e_alert *definition;
94 	GtkMessageType message_type;
95 	gint default_response;
96 	guint timeout_id;
97 
98 	/* It may occur to one that we could use a GtkActionGroup here,
99 	 * but we need to preserve the button order and GtkActionGroup
100 	 * uses a hash table, which does not preserve order. */
101 	GQueue actions;
102 
103 	GQueue widgets;
104 };
105 
106 enum {
107 	PROP_0,
108 	PROP_ARGS,
109 	PROP_TAG,
110 	PROP_MESSAGE_TYPE,
111 	PROP_PRIMARY_TEXT,
112 	PROP_SECONDARY_TEXT
113 };
114 
115 enum {
116 	RESPONSE,
117 	LAST_SIGNAL
118 };
119 
120 static gulong signals[LAST_SIGNAL];
121 
G_DEFINE_TYPE(EAlert,e_alert,G_TYPE_OBJECT)122 G_DEFINE_TYPE (
123 	EAlert,
124 	e_alert,
125 	G_TYPE_OBJECT)
126 
127 static gint
128 map_response (const gchar *name)
129 {
130 	GEnumClass *class;
131 	GEnumValue *value;
132 
133 	class = g_type_class_ref (GTK_TYPE_RESPONSE_TYPE);
134 	value = g_enum_get_value_by_name (class, name);
135 	g_type_class_unref (class);
136 
137 	return (value != NULL) ? value->value : 0;
138 }
139 
140 static GtkMessageType
map_type(const gchar * nick)141 map_type (const gchar *nick)
142 {
143 	GEnumClass *class;
144 	GEnumValue *value;
145 
146 	class = g_type_class_ref (GTK_TYPE_MESSAGE_TYPE);
147 	value = g_enum_get_value_by_nick (class, nick);
148 	g_type_class_unref (class);
149 
150 	return (value != NULL) ? value->value : GTK_MESSAGE_ERROR;
151 }
152 
153 /*
154  * XML format:
155  *
156  * <error id="error-id" type="info|warning|question|error"?
157  *      response="default_response"? >
158  *  <primary> Primary error text.</primary>?
159  *  <secondary> Secondary error text.</secondary>?
160  *  <button stock="stock-button-id"? label="button label"?
161  *      response="response_id"? /> *
162  * </error>
163  */
164 
165 static void
e_alert_load(const gchar * path)166 e_alert_load (const gchar *path)
167 {
168 	xmlDocPtr doc = NULL;
169 	xmlNodePtr root, error, scan;
170 	struct _e_alert *e;
171 	EAlertButton *lastbutton;
172 	struct _e_alert_table *table;
173 	gchar *tmp;
174 
175 	d (printf ("loading error file %s\n", path));
176 
177 	doc = e_xml_parse_file (path);
178 	if (doc == NULL) {
179 		g_warning ("Error file '%s' not found", path);
180 		return;
181 	}
182 
183 	root = xmlDocGetRootElement (doc);
184 	if (root == NULL
185 	    || strcmp ((gchar *) root->name, "error-list") != 0
186 	    || (tmp = (gchar *) xmlGetProp (root, (const guchar *)"domain")) == NULL) {
187 		g_warning ("Error file '%s' invalid format", path);
188 		xmlFreeDoc (doc);
189 		return;
190 	}
191 
192 	table = g_hash_table_lookup (alert_table, tmp);
193 	if (table == NULL) {
194 		gchar *tmp2;
195 
196 		table = g_malloc0 (sizeof (*table));
197 		table->domain = g_strdup (tmp);
198 		table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
199 		g_hash_table_insert (alert_table, (gpointer) table->domain, table);
200 
201 		tmp2 = (gchar *) xmlGetProp (
202 			root, (const guchar *) "translation-domain");
203 		if (tmp2) {
204 			table->translation_domain = g_strdup (tmp2);
205 			xmlFree (tmp2);
206 
207 			tmp2 = (gchar *) xmlGetProp (
208 				root, (const guchar *) "translation-localedir");
209 			if (tmp2) {
210 				bindtextdomain (table->translation_domain, tmp2);
211 				xmlFree (tmp2);
212 			}
213 		}
214 	} else
215 		g_warning (
216 			"Error file '%s', domain '%s' "
217 			"already used, merging", path, tmp);
218 	xmlFree (tmp);
219 
220 	for (error = root->children; error; error = error->next) {
221 		if (!strcmp ((gchar *) error->name, "error")) {
222 			tmp = (gchar *) xmlGetProp (error, (const guchar *)"id");
223 			if (tmp == NULL)
224 				continue;
225 
226 			e = g_malloc0 (sizeof (*e));
227 			e->id = g_strdup (tmp);
228 
229 			xmlFree (tmp);
230 			lastbutton = (EAlertButton *) &e->buttons;
231 
232 			tmp = (gchar *) xmlGetProp (error, (const guchar *)"type");
233 			e->message_type = map_type (tmp);
234 			if (tmp)
235 				xmlFree (tmp);
236 
237 			tmp = (gchar *) xmlGetProp (error, (const guchar *)"default");
238 			if (tmp) {
239 				e->default_response = map_response (tmp);
240 				xmlFree (tmp);
241 			}
242 
243 			for (scan = error->children; scan; scan = scan->next) {
244 				if (!strcmp ((gchar *) scan->name, "primary")) {
245 					if ((tmp = (gchar *) xmlNodeGetContent (scan))) {
246 						e->primary_text = g_strdup (
247 							dgettext (table->
248 							translation_domain, tmp));
249 						xmlFree (tmp);
250 					}
251 				} else if (!strcmp ((gchar *) scan->name, "secondary")) {
252 					if ((tmp = (gchar *) xmlNodeGetContent (scan))) {
253 						e->secondary_text = g_strdup (
254 							dgettext (table->
255 							translation_domain, tmp));
256 						xmlFree (tmp);
257 					}
258 				} else if (!strcmp ((gchar *) scan->name, "button")) {
259 					EAlertButton *button;
260 					gchar *label = NULL;
261 					gchar *stock_id = NULL;
262 
263 					button = g_new0 (EAlertButton, 1);
264 					tmp = (gchar *) xmlGetProp (scan, (const guchar *)"stock");
265 					if (tmp) {
266 						stock_id = g_strdup (tmp);
267 						button->stock_id = stock_id;
268 						xmlFree (tmp);
269 					}
270 					tmp = (gchar *) xmlGetProp (
271 						scan, (xmlChar *) "label");
272 					if (tmp) {
273 						label = g_strdup (
274 							dgettext (table->
275 							translation_domain,
276 							tmp));
277 						button->label = label;
278 						xmlFree (tmp);
279 					}
280 					tmp = (gchar *) xmlGetProp (
281 						scan, (xmlChar *) "response");
282 					if (tmp) {
283 						button->response_id =
284 							map_response (tmp);
285 						xmlFree (tmp);
286 					}
287 					tmp = (gchar *) xmlGetProp (scan, (xmlChar *) "destructive");
288 					if (g_strcmp0 (tmp, "1") == 0 || g_strcmp0 (tmp, "true") == 0)
289 						button->destructive = TRUE;
290 					if (tmp)
291 						xmlFree (tmp);
292 
293 					if (stock_id == NULL && label == NULL) {
294 						g_warning (
295 							"Error file '%s': "
296 							"missing button "
297 							"details in error "
298 							"'%s'", path, e->id);
299 						g_free (stock_id);
300 						g_free (label);
301 						g_free (button);
302 					} else {
303 						lastbutton->next = button;
304 						lastbutton = button;
305 					}
306 				}
307 			}
308 
309 			g_hash_table_insert (table->alerts, (gpointer) e->id, e);
310 		}
311 	}
312 
313 	xmlFreeDoc (doc);
314 }
315 
316 static void
e_alert_load_directory(const gchar * dirname)317 e_alert_load_directory (const gchar *dirname)
318 {
319 	GDir *dir;
320 	const gchar *d;
321 
322 	dir = g_dir_open (dirname, 0, NULL);
323 	if (dir == NULL) {
324 		return;
325 	}
326 
327 	while ((d = g_dir_read_name (dir))) {
328 		gchar *path;
329 
330 		if (d[0] == '.')
331 			continue;
332 
333 		path = g_build_filename (dirname, d, NULL);
334 		e_alert_load (path);
335 		g_free (path);
336 	}
337 
338 	g_dir_close (dir);
339 }
340 
341 static void
e_alert_load_tables(void)342 e_alert_load_tables (void)
343 {
344 	GPtrArray *variants;
345 	gchar *base;
346 	struct _e_alert_table *table;
347 	guint ii;
348 
349 	if (alert_table != NULL)
350 		return;
351 
352 	alert_table = g_hash_table_new (g_str_hash, g_str_equal);
353 
354 	/* setup system alert types */
355 	table = g_malloc0 (sizeof (*table));
356 	table->domain = "builtin";
357 	table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
358 	for (ii = 0; ii < G_N_ELEMENTS (default_alerts); ii++)
359 		g_hash_table_insert (
360 			table->alerts, (gpointer)
361 			default_alerts[ii].id, &default_alerts[ii]);
362 	g_hash_table_insert (alert_table, (gpointer) table->domain, table);
363 
364 	/* look for installed alert tables */
365 	base = g_build_filename (EVOLUTION_PRIVDATADIR, "errors", NULL);
366 	variants = e_util_get_directory_variants (base, EVOLUTION_PREFIX, TRUE);
367 	if (variants) {
368 		for (ii = 0; ii < variants->len; ii++) {
369 			const gchar *dirname = g_ptr_array_index (variants, ii);
370 
371 			if (dirname && *dirname)
372 				e_alert_load_directory (dirname);
373 		}
374 		g_ptr_array_unref (variants);
375 	} else {
376 		e_alert_load_directory (base);
377 	}
378 	g_free (base);
379 }
380 
381 static void
alert_action_activate(EAlert * alert,GtkAction * action)382 alert_action_activate (EAlert *alert,
383                        GtkAction *action)
384 {
385 	GObject *object;
386 	gpointer data;
387 
388 	object = G_OBJECT (action);
389 	data = g_object_get_data (object, "e-alert-response-id");
390 	e_alert_response (alert, GPOINTER_TO_INT (data));
391 }
392 
393 static gchar *
alert_format_string(const gchar * format,GPtrArray * args)394 alert_format_string (const gchar *format,
395                      GPtrArray *args)
396 {
397 	GString *string;
398 	const gchar *end, *newstart;
399 	gint id;
400 
401 	string = g_string_sized_new (strlen (format));
402 
403 	while (format
404 	       && (newstart = strchr (format, '{'))
405 	       && (end = strchr (newstart + 1, '}'))) {
406 		g_string_append_len (string, format, newstart - format);
407 		id = atoi (newstart + 1);
408 		if (id < args->len) {
409 			g_string_append (string, args->pdata[id]);
410 		} else
411 			g_warning (
412 				"Error references argument %d "
413 				"not supplied by caller", id);
414 		format = end + 1;
415 	}
416 
417 	g_string_append (string, format);
418 
419 	return g_string_free (string, FALSE);
420 }
421 
422 static void
alert_set_tag(EAlert * alert,const gchar * tag)423 alert_set_tag (EAlert *alert,
424                const gchar *tag)
425 {
426 	struct _e_alert *definition;
427 	struct _e_alert_table *table;
428 	gchar *domain, *id;
429 
430 	alert->priv->tag = g_strdup (tag);
431 
432 	g_return_if_fail (alert_table);
433 
434 	domain = g_alloca (strlen (tag) + 1);
435 	strcpy (domain, tag);
436 	id = strchr (domain, ':');
437 	if (id)
438 		*id++ = 0;
439 	else {
440 		g_warning ("Alert tag '%s' is missing a domain", tag);
441 		return;
442 	}
443 
444 	table = g_hash_table_lookup (alert_table, domain);
445 	g_return_if_fail (table);
446 
447 	definition = g_hash_table_lookup (table->alerts, id);
448 	g_warn_if_fail (definition);
449 
450 	alert->priv->definition = definition;
451 }
452 
453 static gboolean
alert_timeout_cb(gpointer user_data)454 alert_timeout_cb (gpointer user_data)
455 {
456 	EAlert *alert = user_data;
457 
458 	if (g_source_is_destroyed (g_main_current_source ()))
459 		return FALSE;
460 
461 	g_return_val_if_fail (E_IS_ALERT (alert), FALSE);
462 
463 	if (g_source_get_id (g_main_current_source ()) == alert->priv->timeout_id)
464 		alert->priv->timeout_id = 0;
465 
466 	e_alert_response (alert, alert->priv->default_response);
467 
468 	return FALSE;
469 }
470 
471 static void
alert_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)472 alert_set_property (GObject *object,
473                     guint property_id,
474                     const GValue *value,
475                     GParamSpec *pspec)
476 {
477 	EAlert *alert = (EAlert *) object;
478 
479 	switch (property_id) {
480 		case PROP_TAG:
481 			alert_set_tag (
482 				E_ALERT (object),
483 				g_value_get_string (value));
484 			return;
485 
486 		case PROP_ARGS:
487 			alert->priv->args = g_value_dup_boxed (value);
488 			return;
489 
490 		case PROP_MESSAGE_TYPE:
491 			e_alert_set_message_type (
492 				E_ALERT (object),
493 				g_value_get_enum (value));
494 			return;
495 
496 		case PROP_PRIMARY_TEXT:
497 			e_alert_set_primary_text (
498 				E_ALERT (object),
499 				g_value_get_string (value));
500 			return;
501 
502 		case PROP_SECONDARY_TEXT:
503 			e_alert_set_secondary_text (
504 				E_ALERT (object),
505 				g_value_get_string (value));
506 			return;
507 	}
508 
509 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
510 }
511 
512 static void
alert_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)513 alert_get_property (GObject *object,
514                     guint property_id,
515                     GValue *value,
516                     GParamSpec *pspec)
517 {
518 	EAlert *alert = (EAlert *) object;
519 
520 	switch (property_id) {
521 		case PROP_TAG:
522 			g_value_set_string (value, alert->priv->tag);
523 			return;
524 
525 		case PROP_ARGS:
526 			g_value_set_boxed (value, alert->priv->args);
527 			return;
528 
529 		case PROP_MESSAGE_TYPE:
530 			g_value_set_enum (
531 				value, e_alert_get_message_type (
532 				E_ALERT (object)));
533 			return;
534 
535 		case PROP_PRIMARY_TEXT:
536 			g_value_set_string (
537 				value, e_alert_get_primary_text (
538 				E_ALERT (object)));
539 			return;
540 
541 		case PROP_SECONDARY_TEXT:
542 			g_value_set_string (
543 				value, e_alert_get_secondary_text (
544 				E_ALERT (object)));
545 			return;
546 	}
547 
548 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
549 }
550 
551 static void
alert_dispose(GObject * object)552 alert_dispose (GObject *object)
553 {
554 	EAlert *alert = E_ALERT (object);
555 
556 	if (alert->priv->timeout_id > 0) {
557 		g_source_remove (alert->priv->timeout_id);
558 		alert->priv->timeout_id = 0;
559 	}
560 
561 	while (!g_queue_is_empty (&alert->priv->actions)) {
562 		GtkAction *action;
563 
564 		action = g_queue_pop_head (&alert->priv->actions);
565 		g_signal_handlers_disconnect_by_func (
566 			action, G_CALLBACK (alert_action_activate), object);
567 		g_object_unref (action);
568 	}
569 
570 	while (!g_queue_is_empty (&alert->priv->widgets)) {
571 		GtkWidget *widget;
572 
573 		widget = g_queue_pop_head (&alert->priv->widgets);
574 		g_object_unref (widget);
575 	}
576 
577 	/* Chain up to parent's dispose() method. */
578 	G_OBJECT_CLASS (e_alert_parent_class)->dispose (object);
579 }
580 
581 static void
alert_finalize(GObject * object)582 alert_finalize (GObject *object)
583 {
584 	EAlertPrivate *priv;
585 
586 	priv = E_ALERT_GET_PRIVATE (object);
587 
588 	g_free (priv->tag);
589 	g_free (priv->primary_text);
590 	g_free (priv->secondary_text);
591 
592 	g_ptr_array_free (priv->args, TRUE);
593 
594 	/* Chain up to parent's finalize() method. */
595 	G_OBJECT_CLASS (e_alert_parent_class)->finalize (object);
596 }
597 
598 static void
alert_constructed(GObject * object)599 alert_constructed (GObject *object)
600 {
601 	EAlert *alert;
602 	EAlertButton *button;
603 	struct _e_alert *definition;
604 	gint ii = 0;
605 
606 	alert = E_ALERT (object);
607 	definition = alert->priv->definition;
608 	g_return_if_fail (definition != NULL);
609 
610 	e_alert_set_message_type (alert, definition->message_type);
611 	e_alert_set_default_response (alert, definition->default_response);
612 
613 	/* Build actions out of the button definitions. */
614 	button = definition->buttons;
615 	while (button != NULL) {
616 		GtkAction *action;
617 		gchar *action_name;
618 
619 		action_name = g_strdup_printf ("alert-response-%d", ii++);
620 
621 		if (button->stock_id != NULL) {
622 			action = gtk_action_new (
623 				action_name, NULL, NULL, button->stock_id);
624 			e_alert_add_action (
625 				alert, action, button->response_id, button->destructive);
626 			g_object_unref (action);
627 
628 		} else if (button->label != NULL) {
629 			action = gtk_action_new (
630 				action_name, button->label, NULL, NULL);
631 			e_alert_add_action (
632 				alert, action, button->response_id, button->destructive);
633 			g_object_unref (action);
634 		}
635 
636 		g_free (action_name);
637 
638 		button = button->next;
639 	}
640 
641 	/* Chain up to parent's constructed() method. */
642 	G_OBJECT_CLASS (e_alert_parent_class)->constructed (object);
643 }
644 
645 static void
e_alert_class_init(EAlertClass * class)646 e_alert_class_init (EAlertClass *class)
647 {
648 	GObjectClass *object_class = G_OBJECT_CLASS (class);
649 
650 	g_type_class_add_private (class, sizeof (EAlertPrivate));
651 
652 	object_class->set_property = alert_set_property;
653 	object_class->get_property = alert_get_property;
654 	object_class->dispose = alert_dispose;
655 	object_class->finalize = alert_finalize;
656 	object_class->constructed = alert_constructed;
657 
658 	g_object_class_install_property (
659 		object_class,
660 		PROP_ARGS,
661 		g_param_spec_boxed (
662 			"args",
663 			"Arguments",
664 			"Arguments for formatting the alert",
665 			G_TYPE_PTR_ARRAY,
666 			G_PARAM_READWRITE |
667 			G_PARAM_CONSTRUCT_ONLY |
668 			G_PARAM_STATIC_STRINGS));
669 
670 	g_object_class_install_property (
671 		object_class,
672 		PROP_TAG,
673 		g_param_spec_string (
674 			"tag",
675 			"alert tag",
676 			"A tag describing the alert",
677 			"",
678 			G_PARAM_READWRITE |
679 			G_PARAM_CONSTRUCT_ONLY |
680 			G_PARAM_STATIC_STRINGS));
681 
682 	g_object_class_install_property (
683 		object_class,
684 		PROP_MESSAGE_TYPE,
685 		g_param_spec_enum (
686 			"message-type",
687 			NULL,
688 			NULL,
689 			GTK_TYPE_MESSAGE_TYPE,
690 			GTK_MESSAGE_ERROR,
691 			G_PARAM_READWRITE |
692 			G_PARAM_STATIC_STRINGS));
693 
694 	g_object_class_install_property (
695 		object_class,
696 		PROP_PRIMARY_TEXT,
697 		g_param_spec_string (
698 			"primary-text",
699 			NULL,
700 			NULL,
701 			NULL,
702 			G_PARAM_READWRITE |
703 			G_PARAM_STATIC_STRINGS));
704 
705 	g_object_class_install_property (
706 		object_class,
707 		PROP_SECONDARY_TEXT,
708 		g_param_spec_string (
709 			"secondary-text",
710 			NULL,
711 			NULL,
712 			NULL,
713 			G_PARAM_READWRITE |
714 			G_PARAM_STATIC_STRINGS));
715 
716 	signals[RESPONSE] = g_signal_new (
717 		"response",
718 		G_OBJECT_CLASS_TYPE (object_class),
719 		G_SIGNAL_RUN_LAST,
720 		G_STRUCT_OFFSET (EAlertClass, response),
721 		NULL, NULL,
722 		g_cclosure_marshal_VOID__INT,
723 		G_TYPE_NONE, 1,
724 		G_TYPE_INT);
725 
726 	e_alert_load_tables ();
727 }
728 
729 static void
e_alert_init(EAlert * alert)730 e_alert_init (EAlert *alert)
731 {
732 	alert->priv = E_ALERT_GET_PRIVATE (alert);
733 
734 	g_queue_init (&alert->priv->actions);
735 	g_queue_init (&alert->priv->widgets);
736 }
737 
738 /**
739  * e_alert_new:
740  * @tag: alert identifier
741  * @...: %NULL-terminated argument list
742  *
743  * Creates a new EAlert.  The @tag argument is used to determine
744  * which alert to use, it is in the format domain:alert-id.  The NULL
745  * terminated list of arguments is used to fill out the alert definition.
746  *
747  * Returns: a new #EAlert
748  **/
749 EAlert *
e_alert_new(const gchar * tag,...)750 e_alert_new (const gchar *tag,
751              ...)
752 {
753 	EAlert *e;
754 	va_list va;
755 
756 	va_start (va, tag);
757 	e = e_alert_new_valist (tag, va);
758 	va_end (va);
759 
760 	return e;
761 }
762 
763 EAlert *
e_alert_new_valist(const gchar * tag,va_list va)764 e_alert_new_valist (const gchar *tag,
765                     va_list va)
766 {
767 	EAlert *alert;
768 	GPtrArray *args;
769 	gchar *tmp;
770 
771 	args = g_ptr_array_new_with_free_func (g_free);
772 
773 	tmp = va_arg (va, gchar *);
774 	while (tmp) {
775 		g_ptr_array_add (args, g_strdup (tmp));
776 		tmp = va_arg (va, gchar *);
777 	}
778 
779 	alert = e_alert_new_array (tag, args);
780 
781 	g_ptr_array_unref (args);
782 
783 	return alert;
784 }
785 
786 EAlert *
e_alert_new_array(const gchar * tag,GPtrArray * args)787 e_alert_new_array (const gchar *tag,
788                    GPtrArray *args)
789 {
790 	return g_object_new (E_TYPE_ALERT, "tag", tag, "args", args, NULL);
791 }
792 
793 gint
e_alert_get_default_response(EAlert * alert)794 e_alert_get_default_response (EAlert *alert)
795 {
796 	g_return_val_if_fail (E_IS_ALERT (alert), 0);
797 
798 	return alert->priv->default_response;
799 }
800 
801 void
e_alert_set_default_response(EAlert * alert,gint response_id)802 e_alert_set_default_response (EAlert *alert,
803                               gint response_id)
804 {
805 	g_return_if_fail (E_IS_ALERT (alert));
806 
807 	alert->priv->default_response = response_id;
808 }
809 
810 GtkMessageType
e_alert_get_message_type(EAlert * alert)811 e_alert_get_message_type (EAlert *alert)
812 {
813 	g_return_val_if_fail (E_IS_ALERT (alert), GTK_MESSAGE_OTHER);
814 
815 	return alert->priv->message_type;
816 }
817 
818 void
e_alert_set_message_type(EAlert * alert,GtkMessageType message_type)819 e_alert_set_message_type (EAlert *alert,
820                           GtkMessageType message_type)
821 {
822 	g_return_if_fail (E_IS_ALERT (alert));
823 
824 	if (alert->priv->message_type == message_type)
825 		return;
826 
827 	alert->priv->message_type = message_type;
828 
829 	g_object_notify (G_OBJECT (alert), "message-type");
830 }
831 
832 const gchar *
e_alert_get_primary_text(EAlert * alert)833 e_alert_get_primary_text (EAlert *alert)
834 {
835 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
836 
837 	if (alert->priv->primary_text != NULL)
838 		goto exit;
839 
840 	if (alert->priv->definition == NULL)
841 		goto exit;
842 
843 	if (alert->priv->definition->primary_text == NULL)
844 		goto exit;
845 
846 	if (alert->priv->args == NULL)
847 		goto exit;
848 
849 	alert->priv->primary_text = alert_format_string (
850 		alert->priv->definition->primary_text,
851 		alert->priv->args);
852 
853 exit:
854 	return alert->priv->primary_text;
855 }
856 
857 void
e_alert_set_primary_text(EAlert * alert,const gchar * primary_text)858 e_alert_set_primary_text (EAlert *alert,
859                             const gchar *primary_text)
860 {
861 	g_return_if_fail (E_IS_ALERT (alert));
862 
863 	if (g_strcmp0 (alert->priv->primary_text, primary_text) == 0)
864 		return;
865 
866 	g_free (alert->priv->primary_text);
867 	alert->priv->primary_text = g_strdup (primary_text);
868 
869 	g_object_notify (G_OBJECT (alert), "primary-text");
870 }
871 
872 const gchar *
e_alert_get_secondary_text(EAlert * alert)873 e_alert_get_secondary_text (EAlert *alert)
874 {
875 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
876 
877 	if (alert->priv->secondary_text != NULL)
878 		goto exit;
879 
880 	if (alert->priv->definition == NULL)
881 		goto exit;
882 
883 	if (alert->priv->definition->secondary_text == NULL)
884 		goto exit;
885 
886 	if (alert->priv->args == NULL)
887 		goto exit;
888 
889 	alert->priv->secondary_text = alert_format_string (
890 		alert->priv->definition->secondary_text,
891 		alert->priv->args);
892 
893 exit:
894 	return alert->priv->secondary_text;
895 }
896 
897 void
e_alert_set_secondary_text(EAlert * alert,const gchar * secondary_text)898 e_alert_set_secondary_text (EAlert *alert,
899                             const gchar *secondary_text)
900 {
901 	g_return_if_fail (E_IS_ALERT (alert));
902 
903 	if (g_strcmp0 (alert->priv->secondary_text, secondary_text) == 0)
904 		return;
905 
906 	g_free (alert->priv->secondary_text);
907 	alert->priv->secondary_text = g_strdup (secondary_text);
908 
909 	g_object_notify (G_OBJECT (alert), "secondary-text");
910 }
911 
912 const gchar *
e_alert_get_icon_name(EAlert * alert)913 e_alert_get_icon_name (EAlert *alert)
914 {
915 	const gchar *icon_name;
916 
917 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
918 
919 	switch (e_alert_get_message_type (alert)) {
920 		case GTK_MESSAGE_INFO:
921 			icon_name = "dialog-information";
922 			break;
923 		case GTK_MESSAGE_WARNING:
924 			icon_name = "dialog-warning";
925 			break;
926 		case GTK_MESSAGE_QUESTION:
927 			icon_name = "dialog-question";
928 			break;
929 		case GTK_MESSAGE_ERROR:
930 			icon_name = "dialog-error";
931 			break;
932 		default:
933 			icon_name = "image-missing";
934 			g_warn_if_reached ();
935 			break;
936 	}
937 
938 	return icon_name;
939 }
940 
941 void
e_alert_add_action(EAlert * alert,GtkAction * action,gint response_id,gboolean is_destructive)942 e_alert_add_action (EAlert *alert,
943                     GtkAction *action,
944                     gint response_id,
945 		    gboolean is_destructive)
946 {
947 	g_return_if_fail (E_IS_ALERT (alert));
948 	g_return_if_fail (GTK_IS_ACTION (action));
949 
950 	g_object_set_data (
951 		G_OBJECT (action), "e-alert-response-id",
952 		GINT_TO_POINTER (response_id));
953 	g_object_set_data (
954 		G_OBJECT (action), "e-alert-is-destructive",
955 		GINT_TO_POINTER (is_destructive ? 1 : 0));
956 
957 	g_signal_connect_swapped (
958 		action, "activate",
959 		G_CALLBACK (alert_action_activate), alert);
960 
961 	g_queue_push_tail (&alert->priv->actions, g_object_ref (action));
962 }
963 
964 GList *
e_alert_peek_actions(EAlert * alert)965 e_alert_peek_actions (EAlert *alert)
966 {
967 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
968 
969 	return g_queue_peek_head_link (&alert->priv->actions);
970 }
971 
972 /* The widget is consumed by this function */
973 void
e_alert_add_widget(EAlert * alert,GtkWidget * widget)974 e_alert_add_widget (EAlert *alert,
975 		    GtkWidget *widget)
976 {
977 	g_return_if_fail (E_IS_ALERT (alert));
978 	g_return_if_fail (GTK_IS_WIDGET (widget));
979 
980 	g_queue_push_tail (&alert->priv->widgets, g_object_ref_sink (widget));
981 }
982 
983 GList *
e_alert_peek_widgets(EAlert * alert)984 e_alert_peek_widgets (EAlert *alert)
985 {
986 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
987 
988 	return g_queue_peek_head_link (&alert->priv->widgets);
989 }
990 
991 GtkWidget *
e_alert_create_image(EAlert * alert,GtkIconSize size)992 e_alert_create_image (EAlert *alert,
993                       GtkIconSize size)
994 {
995 	const gchar *icon_name;
996 
997 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
998 
999 	icon_name = e_alert_get_icon_name (alert);
1000 
1001 	return gtk_image_new_from_icon_name (icon_name, size);
1002 }
1003 
1004 void
e_alert_response(EAlert * alert,gint response_id)1005 e_alert_response (EAlert *alert,
1006                   gint response_id)
1007 {
1008 	g_return_if_fail (E_IS_ALERT (alert));
1009 
1010 	g_signal_emit (alert, signals[RESPONSE], 0, response_id);
1011 }
1012 
1013 /**
1014  * e_alert_start_timer:
1015  * @alert: an #EAlert
1016  * @seconds: seconds until timeout occurs
1017  *
1018  * Starts an internal timer for @alert.  When the timer expires, @alert
1019  * will emit the default response.  There is only one timer per #EAlert,
1020  * so calling this function repeatedly on the same #EAlert will restart
1021  * its timer each time.  If @seconds is zero, the timer is cancelled and
1022  * no response will be emitted.
1023  **/
1024 void
e_alert_start_timer(EAlert * alert,guint seconds)1025 e_alert_start_timer (EAlert *alert,
1026                      guint seconds)
1027 {
1028 	g_return_if_fail (E_IS_ALERT (alert));
1029 
1030 	if (alert->priv->timeout_id > 0) {
1031 		g_source_remove (alert->priv->timeout_id);
1032 		alert->priv->timeout_id = 0;
1033 	}
1034 
1035 	if (seconds > 0) {
1036 		alert->priv->timeout_id =
1037 			e_named_timeout_add_seconds (
1038 			seconds, alert_timeout_cb, alert);
1039 	}
1040 }
1041 
1042 void
e_alert_submit(EAlertSink * alert_sink,const gchar * tag,...)1043 e_alert_submit (EAlertSink *alert_sink,
1044                 const gchar *tag,
1045                 ...)
1046 {
1047 	va_list va;
1048 
1049 	va_start (va, tag);
1050 	e_alert_submit_valist (alert_sink, tag, va);
1051 	va_end (va);
1052 }
1053 
1054 void
e_alert_submit_valist(EAlertSink * alert_sink,const gchar * tag,va_list va)1055 e_alert_submit_valist (EAlertSink *alert_sink,
1056                        const gchar *tag,
1057                        va_list va)
1058 {
1059 	EAlert *alert;
1060 
1061 	g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
1062 	g_return_if_fail (tag != NULL);
1063 
1064 	alert = e_alert_new_valist (tag, va);
1065 	e_alert_sink_submit_alert (alert_sink, alert);
1066 	g_object_unref (alert);
1067 }
1068 
1069 void
e_alert_update_destructive_action_style(GtkAction * for_action,GtkWidget * button)1070 e_alert_update_destructive_action_style (GtkAction *for_action,
1071 					 GtkWidget *button)
1072 {
1073 	GtkStyleContext *style_context;
1074 
1075 	g_return_if_fail (GTK_IS_ACTION (for_action));
1076 	g_return_if_fail (GTK_IS_WIDGET (button));
1077 
1078 	style_context = gtk_widget_get_style_context (button);
1079 
1080 	if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (for_action), "e-alert-is-destructive")) != 0)
1081 		gtk_style_context_add_class (style_context, "destructive-action");
1082 	else
1083 		gtk_style_context_remove_class (style_context, "destructive-action");
1084 }
1085