evolution-3.6.4/libevolution-utils/e-alert.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  *   Michael Zucchi <notzed@ximian.com>
  18  *   Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
  19  *
  20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  21  * Copyright (C) 2009 Intel Corporation
  22  *
  23  */
  24 
  25 #ifdef HAVE_CONFIG_H
  26 #include <config.h>
  27 #endif
  28 
  29 #include <string.h>
  30 #include <sys/types.h>
  31 
  32 #include <libxml/parser.h>
  33 #include <libxml/xmlmemory.h>
  34 
  35 #include <gtk/gtk.h>
  36 #include <glib/gi18n.h>
  37 
  38 #include <libedataserver/libedataserver.h>
  39 
  40 #include "e-alert.h"
  41 #include "e-alert-sink.h"
  42 
  43 #define d(x)
  44 
  45 #define E_ALERT_GET_PRIVATE(obj) \
  46 	(G_TYPE_INSTANCE_GET_PRIVATE \
  47 	((obj), E_TYPE_ALERT, EAlertPrivate))
  48 
  49 typedef struct _EAlertButton EAlertButton;
  50 
  51 struct _e_alert {
  52 	const gchar *id;
  53 	GtkMessageType message_type;
  54 	gint default_response;
  55 	const gchar *primary_text;
  56 	const gchar *secondary_text;
  57 	EAlertButton *buttons;
  58 };
  59 
  60 struct _e_alert_table {
  61 	const gchar *domain;
  62 	const gchar *translation_domain;
  63 	GHashTable *alerts;
  64 };
  65 
  66 struct _EAlertButton {
  67 	EAlertButton *next;
  68 	const gchar *stock_id;
  69 	const gchar *label;
  70 	gint response_id;
  71 };
  72 
  73 static GHashTable *alert_table;
  74 
  75 /* ********************************************************************** */
  76 
  77 static EAlertButton default_ok_button = {
  78 	NULL, GTK_STOCK_OK, NULL, GTK_RESPONSE_OK
  79 };
  80 
  81 static struct _e_alert default_alerts[] = {
  82 	{ "error", GTK_MESSAGE_ERROR, GTK_RESPONSE_OK,
  83 	  "{0}", "{1}", &default_ok_button },
  84 	{ "warning", GTK_MESSAGE_WARNING, GTK_RESPONSE_OK,
  85 	  "{0}", "{1}", &default_ok_button }
  86 };
  87 
  88 /* ********************************************************************** */
  89 
  90 struct _EAlertPrivate {
  91 	gchar *tag;
  92 	GPtrArray *args;
  93 	gchar *primary_text;
  94 	gchar *secondary_text;
  95 	struct _e_alert *definition;
  96 	GtkMessageType message_type;
  97 	gint default_response;
  98 	guint timeout_id;
  99 
 100 	/* It may occur to one that we could use a GtkActionGroup here,
 101 	 * but we need to preserve the button order and GtkActionGroup
 102 	 * uses a hash table, which does not preserve order. */
 103 	GQueue actions;
 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 
 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
 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
 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 
 288 					if (stock_id == NULL && label == NULL) {
 289 						g_warning (
 290 							"Error file '%s': "
 291 							"missing button "
 292 							"details in error "
 293 							"'%s'", path, e->id);
 294 						g_free (stock_id);
 295 						g_free (label);
 296 						g_free (button);
 297 					} else {
 298 						lastbutton->next = button;
 299 						lastbutton = button;
 300 					}
 301 				}
 302 			}
 303 
 304 			g_hash_table_insert (table->alerts, (gpointer) e->id, e);
 305 		}
 306 	}
 307 
 308 	xmlFreeDoc (doc);
 309 }
 310 
 311 static void
 312 e_alert_load_tables (void)
 313 {
 314 	GDir *dir;
 315 	const gchar *d;
 316 	gchar *base;
 317 	struct _e_alert_table *table;
 318 	gint i;
 319 
 320 	if (alert_table != NULL)
 321 		return;
 322 
 323 	alert_table = g_hash_table_new (g_str_hash, g_str_equal);
 324 
 325 	/* setup system alert types */
 326 	table = g_malloc0 (sizeof (*table));
 327 	table->domain = "builtin";
 328 	table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
 329 	for (i = 0; i < G_N_ELEMENTS (default_alerts); i++)
 330 		g_hash_table_insert (
 331 			table->alerts, (gpointer)
 332 			default_alerts[i].id, &default_alerts[i]);
 333 	g_hash_table_insert (alert_table, (gpointer) table->domain, table);
 334 
 335 	/* look for installed alert tables */
 336 	base = g_build_filename (EVOLUTION_PRIVDATADIR, "errors", NULL);
 337 	dir = g_dir_open (base, 0, NULL);
 338 	if (dir == NULL) {
 339 		g_free (base);
 340 		return;
 341 	}
 342 
 343 	while ((d = g_dir_read_name (dir))) {
 344 		gchar *path;
 345 
 346 		if (d[0] == '.')
 347 			continue;
 348 
 349 		path = g_build_filename (base, d, NULL);
 350 		e_alert_load (path);
 351 		g_free (path);
 352 	}
 353 
 354 	g_dir_close (dir);
 355 	g_free (base);
 356 }
 357 
 358 static void
 359 alert_action_activate (EAlert *alert,
 360                        GtkAction *action)
 361 {
 362 	GObject *object;
 363 	gpointer data;
 364 
 365 	object = G_OBJECT (action);
 366 	data = g_object_get_data (object, "e-alert-response-id");
 367 	e_alert_response (alert, GPOINTER_TO_INT (data));
 368 }
 369 
 370 static gchar *
 371 alert_format_string (const gchar *format,
 372                      GPtrArray *args)
 373 {
 374 	GString *string;
 375 	const gchar *end, *newstart;
 376 	gint id;
 377 
 378 	string = g_string_sized_new (strlen (format));
 379 
 380 	while (format
 381 	       && (newstart = strchr (format, '{'))
 382 	       && (end = strchr (newstart + 1, '}'))) {
 383 		g_string_append_len (string, format, newstart - format);
 384 		id = atoi (newstart + 1);
 385 		if (id < args->len) {
 386 			g_string_append (string, args->pdata[id]);
 387 		} else
 388 			g_warning (
 389 				"Error references argument %d "
 390 				"not supplied by caller", id);
 391 		format = end + 1;
 392 	}
 393 
 394 	g_string_append (string, format);
 395 
 396 	return g_string_free (string, FALSE);
 397 }
 398 
 399 static void
 400 alert_set_tag (EAlert *alert,
 401                const gchar *tag)
 402 {
 403 	struct _e_alert *definition;
 404 	struct _e_alert_table *table;
 405 	gchar *domain, *id;
 406 
 407 	alert->priv->tag = g_strdup (tag);
 408 
 409 	g_return_if_fail (alert_table);
 410 
 411 	domain = g_alloca (strlen (tag) + 1);
 412 	strcpy (domain, tag);
 413 	id = strchr (domain, ':');
 414 	if (id)
 415 		*id++ = 0;
 416 	else {
 417 		g_warning ("Alert tag '%s' is missing a domain", tag);
 418 		return;
 419 	}
 420 
 421 	table = g_hash_table_lookup (alert_table, domain);
 422 	g_return_if_fail (table);
 423 
 424 	definition = g_hash_table_lookup (table->alerts, id);
 425 	g_warn_if_fail (definition);
 426 
 427 	alert->priv->definition = definition;
 428 }
 429 
 430 static gboolean
 431 alert_timeout_cb (EAlert *alert)
 432 {
 433 	e_alert_response (alert, alert->priv->default_response);
 434 
 435 	return FALSE;
 436 }
 437 
 438 static void
 439 alert_set_property (GObject *object,
 440                     guint property_id,
 441                     const GValue *value,
 442                     GParamSpec *pspec)
 443 {
 444 	EAlert *alert = (EAlert *) object;
 445 
 446 	switch (property_id) {
 447 		case PROP_TAG:
 448 			alert_set_tag (
 449 				E_ALERT (object),
 450 				g_value_get_string (value));
 451 			return;
 452 
 453 		case PROP_ARGS:
 454 			alert->priv->args = g_value_dup_boxed (value);
 455 			return;
 456 
 457 		case PROP_MESSAGE_TYPE:
 458 			e_alert_set_message_type (
 459 				E_ALERT (object),
 460 				g_value_get_enum (value));
 461 			return;
 462 
 463 		case PROP_PRIMARY_TEXT:
 464 			e_alert_set_primary_text (
 465 				E_ALERT (object),
 466 				g_value_get_string (value));
 467 			return;
 468 
 469 		case PROP_SECONDARY_TEXT:
 470 			e_alert_set_secondary_text (
 471 				E_ALERT (object),
 472 				g_value_get_string (value));
 473 			return;
 474 	}
 475 
 476 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 477 }
 478 
 479 static void
 480 alert_get_property (GObject *object,
 481                     guint property_id,
 482                     GValue *value,
 483                     GParamSpec *pspec)
 484 {
 485 	EAlert *alert = (EAlert *) object;
 486 
 487 	switch (property_id) {
 488 		case PROP_TAG:
 489 			g_value_set_string (value, alert->priv->tag);
 490 			return;
 491 
 492 		case PROP_ARGS:
 493 			g_value_set_boxed (value, alert->priv->args);
 494 			return;
 495 
 496 		case PROP_MESSAGE_TYPE:
 497 			g_value_set_enum (
 498 				value, e_alert_get_message_type (
 499 				E_ALERT (object)));
 500 			return;
 501 
 502 		case PROP_PRIMARY_TEXT:
 503 			g_value_set_string (
 504 				value, e_alert_get_primary_text (
 505 				E_ALERT (object)));
 506 			return;
 507 
 508 		case PROP_SECONDARY_TEXT:
 509 			g_value_set_string (
 510 				value, e_alert_get_secondary_text (
 511 				E_ALERT (object)));
 512 			return;
 513 	}
 514 
 515 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 516 }
 517 
 518 static void
 519 alert_dispose (GObject *object)
 520 {
 521 	EAlert *alert = E_ALERT (object);
 522 
 523 	if (alert->priv->timeout_id > 0) {
 524 		g_source_remove (alert->priv->timeout_id);
 525 		alert->priv->timeout_id = 0;
 526 	}
 527 
 528 	while (!g_queue_is_empty (&alert->priv->actions)) {
 529 		GtkAction *action;
 530 
 531 		action = g_queue_pop_head (&alert->priv->actions);
 532 		g_signal_handlers_disconnect_by_func (
 533 			action, G_CALLBACK (alert_action_activate), object);
 534 		g_object_unref (action);
 535 	}
 536 
 537 	/* Chain up to parent's dispose() method. */
 538 	G_OBJECT_CLASS (e_alert_parent_class)->dispose (object);
 539 }
 540 
 541 static void
 542 alert_finalize (GObject *object)
 543 {
 544 	EAlertPrivate *priv;
 545 
 546 	priv = E_ALERT_GET_PRIVATE (object);
 547 
 548 	g_free (priv->tag);
 549 	g_free (priv->primary_text);
 550 	g_free (priv->secondary_text);
 551 
 552 	g_ptr_array_free (priv->args, TRUE);
 553 
 554 	/* Chain up to parent's finalize() method. */
 555 	G_OBJECT_CLASS (e_alert_parent_class)->finalize (object);
 556 }
 557 
 558 static void
 559 alert_constructed (GObject *object)
 560 {
 561 	EAlert *alert;
 562 	EAlertButton *button;
 563 	struct _e_alert *definition;
 564 	gint ii = 0;
 565 
 566 	alert = E_ALERT (object);
 567 	definition = alert->priv->definition;
 568 	g_return_if_fail (definition != NULL);
 569 
 570 	e_alert_set_message_type (alert, definition->message_type);
 571 	e_alert_set_default_response (alert, definition->default_response);
 572 
 573 	/* Build actions out of the button definitions. */
 574 	button = definition->buttons;
 575 	while (button != NULL) {
 576 		GtkAction *action;
 577 		gchar *action_name;
 578 
 579 		action_name = g_strdup_printf ("alert-response-%d", ii++);
 580 
 581 		if (button->stock_id != NULL) {
 582 			action = gtk_action_new (
 583 				action_name, NULL, NULL, button->stock_id);
 584 			e_alert_add_action (
 585 				alert, action, button->response_id);
 586 			g_object_unref (action);
 587 
 588 		} else if (button->label != NULL) {
 589 			action = gtk_action_new (
 590 				action_name, button->label, NULL, NULL);
 591 			e_alert_add_action (
 592 				alert, action, button->response_id);
 593 			g_object_unref (action);
 594 		}
 595 
 596 		g_free (action_name);
 597 
 598 		button = button->next;
 599 	}
 600 
 601 	/* Chain up to parent's constructed() method. */
 602 	G_OBJECT_CLASS (e_alert_parent_class)->constructed (object);
 603 }
 604 
 605 static void
 606 e_alert_class_init (EAlertClass *class)
 607 {
 608 	GObjectClass *object_class = G_OBJECT_CLASS (class);
 609 
 610 	g_type_class_add_private (class, sizeof (EAlertPrivate));
 611 
 612 	object_class->set_property = alert_set_property;
 613 	object_class->get_property = alert_get_property;
 614 	object_class->dispose = alert_dispose;
 615 	object_class->finalize = alert_finalize;
 616 	object_class->constructed = alert_constructed;
 617 
 618 	g_object_class_install_property (
 619 		object_class,
 620 		PROP_ARGS,
 621 		g_param_spec_boxed (
 622 			"args",
 623 			"Arguments",
 624 			"Arguments for formatting the alert",
 625 			G_TYPE_PTR_ARRAY,
 626 			G_PARAM_READWRITE |
 627 			G_PARAM_CONSTRUCT_ONLY |
 628 			G_PARAM_STATIC_STRINGS));
 629 
 630 	g_object_class_install_property (
 631 		object_class,
 632 		PROP_TAG,
 633 		g_param_spec_string (
 634 			"tag",
 635 			"alert tag",
 636 			"A tag describing the alert",
 637 			"",
 638 			G_PARAM_READWRITE |
 639 			G_PARAM_CONSTRUCT_ONLY |
 640 			G_PARAM_STATIC_STRINGS));
 641 
 642 	g_object_class_install_property (
 643 		object_class,
 644 		PROP_MESSAGE_TYPE,
 645 		g_param_spec_enum (
 646 			"message-type",
 647 			NULL,
 648 			NULL,
 649 			GTK_TYPE_MESSAGE_TYPE,
 650 			GTK_MESSAGE_ERROR,
 651 			G_PARAM_READWRITE |
 652 			G_PARAM_STATIC_STRINGS));
 653 
 654 	g_object_class_install_property (
 655 		object_class,
 656 		PROP_PRIMARY_TEXT,
 657 		g_param_spec_string (
 658 			"primary-text",
 659 			NULL,
 660 			NULL,
 661 			NULL,
 662 			G_PARAM_READWRITE |
 663 			G_PARAM_STATIC_STRINGS));
 664 
 665 	g_object_class_install_property (
 666 		object_class,
 667 		PROP_SECONDARY_TEXT,
 668 		g_param_spec_string (
 669 			"secondary-text",
 670 			NULL,
 671 			NULL,
 672 			NULL,
 673 			G_PARAM_READWRITE |
 674 			G_PARAM_STATIC_STRINGS));
 675 
 676 	signals[RESPONSE] = g_signal_new (
 677 		"response",
 678 		G_OBJECT_CLASS_TYPE (object_class),
 679 		G_SIGNAL_RUN_LAST,
 680 		G_STRUCT_OFFSET (EAlertClass, response),
 681 		NULL, NULL,
 682 		g_cclosure_marshal_VOID__INT,
 683 		G_TYPE_NONE, 1,
 684 		G_TYPE_INT);
 685 
 686 	e_alert_load_tables ();
 687 }
 688 
 689 static void
 690 e_alert_init (EAlert *alert)
 691 {
 692 	alert->priv = E_ALERT_GET_PRIVATE (alert);
 693 
 694 	g_queue_init (&alert->priv->actions);
 695 }
 696 
 697 /**
 698  * e_alert_new:
 699  * @tag: alert identifier
 700  * @arg0: The first argument for the alert formatter.  The list must
 701  * be NULL terminated.
 702  *
 703  * Creates a new EAlert.  The @tag argument is used to determine
 704  * which alert to use, it is in the format domain:alert-id.  The NULL
 705  * terminated list of arguments, starting with @arg0 is used to fill
 706  * out the alert definition.
 707  *
 708  * Returns: a new #EAlert
 709  **/
 710 EAlert *
 711 e_alert_new (const gchar *tag,
 712              ...)
 713 {
 714 	EAlert *e;
 715 	va_list va;
 716 
 717 	va_start (va, tag);
 718 	e = e_alert_new_valist (tag, va);
 719 	va_end (va);
 720 
 721 	return e;
 722 }
 723 
 724 EAlert *
 725 e_alert_new_valist (const gchar *tag,
 726                     va_list va)
 727 {
 728 	EAlert *alert;
 729 	GPtrArray *args;
 730 	gchar *tmp;
 731 
 732 	args = g_ptr_array_new_with_free_func (g_free);
 733 
 734 	tmp = va_arg (va, gchar *);
 735 	while (tmp) {
 736 		g_ptr_array_add (args, g_strdup (tmp));
 737 		tmp = va_arg (va, gchar *);
 738 	}
 739 
 740 	alert = e_alert_new_array (tag, args);
 741 
 742 	g_ptr_array_unref (args);
 743 
 744 	return alert;
 745 }
 746 
 747 EAlert *
 748 e_alert_new_array (const gchar *tag,
 749                    GPtrArray *args)
 750 {
 751 	return g_object_new (E_TYPE_ALERT, "tag", tag, "args", args, NULL);
 752 }
 753 
 754 gint
 755 e_alert_get_default_response (EAlert *alert)
 756 {
 757 	g_return_val_if_fail (E_IS_ALERT (alert), 0);
 758 
 759 	return alert->priv->default_response;
 760 }
 761 
 762 void
 763 e_alert_set_default_response (EAlert *alert,
 764                               gint response_id)
 765 {
 766 	g_return_if_fail (E_IS_ALERT (alert));
 767 
 768 	alert->priv->default_response = response_id;
 769 }
 770 
 771 GtkMessageType
 772 e_alert_get_message_type (EAlert *alert)
 773 {
 774 	g_return_val_if_fail (E_IS_ALERT (alert), GTK_MESSAGE_OTHER);
 775 
 776 	return alert->priv->message_type;
 777 }
 778 
 779 void
 780 e_alert_set_message_type (EAlert *alert,
 781                           GtkMessageType message_type)
 782 {
 783 	g_return_if_fail (E_IS_ALERT (alert));
 784 
 785 	if (alert->priv->message_type == message_type)
 786 		return;
 787 
 788 	alert->priv->message_type = message_type;
 789 
 790 	g_object_notify (G_OBJECT (alert), "message-type");
 791 }
 792 
 793 const gchar *
 794 e_alert_get_primary_text (EAlert *alert)
 795 {
 796 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
 797 
 798 	if (alert->priv->primary_text != NULL)
 799 		goto exit;
 800 
 801 	if (alert->priv->definition == NULL)
 802 		goto exit;
 803 
 804 	if (alert->priv->definition->primary_text == NULL)
 805 		goto exit;
 806 
 807 	if (alert->priv->args == NULL)
 808 		goto exit;
 809 
 810 	alert->priv->primary_text = alert_format_string (
 811 		alert->priv->definition->primary_text,
 812 		alert->priv->args);
 813 
 814 exit:
 815 	return alert->priv->primary_text;
 816 }
 817 
 818 void
 819 e_alert_set_primary_text (EAlert *alert,
 820                             const gchar *primary_text)
 821 {
 822 	g_return_if_fail (E_IS_ALERT (alert));
 823 
 824 	if (g_strcmp0 (alert->priv->primary_text, primary_text) == 0)
 825 		return;
 826 
 827 	g_free (alert->priv->primary_text);
 828 	alert->priv->primary_text = g_strdup (primary_text);
 829 
 830 	g_object_notify (G_OBJECT (alert), "primary-text");
 831 }
 832 
 833 const gchar *
 834 e_alert_get_secondary_text (EAlert *alert)
 835 {
 836 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
 837 
 838 	if (alert->priv->secondary_text != NULL)
 839 		goto exit;
 840 
 841 	if (alert->priv->definition == NULL)
 842 		goto exit;
 843 
 844 	if (alert->priv->definition->secondary_text == NULL)
 845 		goto exit;
 846 
 847 	if (alert->priv->args == NULL)
 848 		goto exit;
 849 
 850 	alert->priv->secondary_text = alert_format_string (
 851 		alert->priv->definition->secondary_text,
 852 		alert->priv->args);
 853 
 854 exit:
 855 	return alert->priv->secondary_text;
 856 }
 857 
 858 void
 859 e_alert_set_secondary_text (EAlert *alert,
 860                             const gchar *secondary_text)
 861 {
 862 	g_return_if_fail (E_IS_ALERT (alert));
 863 
 864 	if (g_strcmp0 (alert->priv->secondary_text, secondary_text) == 0)
 865 		return;
 866 
 867 	g_free (alert->priv->secondary_text);
 868 	alert->priv->secondary_text = g_strdup (secondary_text);
 869 
 870 	g_object_notify (G_OBJECT (alert), "secondary-text");
 871 }
 872 
 873 const gchar *
 874 e_alert_get_stock_id (EAlert *alert)
 875 {
 876 	const gchar *stock_id;
 877 
 878 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
 879 
 880 	switch (e_alert_get_message_type (alert)) {
 881 		case GTK_MESSAGE_INFO:
 882 			stock_id = GTK_STOCK_DIALOG_INFO;
 883 			break;
 884 		case GTK_MESSAGE_WARNING:
 885 			stock_id = GTK_STOCK_DIALOG_WARNING;
 886 			break;
 887 		case GTK_MESSAGE_QUESTION:
 888 			stock_id = GTK_STOCK_DIALOG_QUESTION;
 889 			break;
 890 		case GTK_MESSAGE_ERROR:
 891 			stock_id = GTK_STOCK_DIALOG_ERROR;
 892 			break;
 893 		default:
 894 			stock_id = GTK_STOCK_MISSING_IMAGE;
 895 			g_warn_if_reached ();
 896 			break;
 897 	}
 898 
 899 	return stock_id;
 900 }
 901 
 902 void
 903 e_alert_add_action (EAlert *alert,
 904                     GtkAction *action,
 905                     gint response_id)
 906 {
 907 	g_return_if_fail (E_IS_ALERT (alert));
 908 	g_return_if_fail (GTK_ACTION (action));
 909 
 910 	g_object_set_data (
 911 		G_OBJECT (action), "e-alert-response-id",
 912 		GINT_TO_POINTER (response_id));
 913 
 914 	g_signal_connect_swapped (
 915 		action, "activate",
 916 		G_CALLBACK (alert_action_activate), alert);
 917 
 918 	g_queue_push_tail (&alert->priv->actions, g_object_ref (action));
 919 }
 920 
 921 GList *
 922 e_alert_peek_actions (EAlert *alert)
 923 {
 924 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
 925 
 926 	return g_queue_peek_head_link (&alert->priv->actions);
 927 }
 928 
 929 GtkWidget *
 930 e_alert_create_image (EAlert *alert,
 931                       GtkIconSize size)
 932 {
 933 	const gchar *stock_id;
 934 
 935 	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
 936 
 937 	stock_id = e_alert_get_stock_id (alert);
 938 
 939 	return gtk_image_new_from_stock (stock_id, size);
 940 }
 941 
 942 void
 943 e_alert_response (EAlert *alert,
 944                   gint response_id)
 945 {
 946 	g_return_if_fail (E_IS_ALERT (alert));
 947 
 948 	g_signal_emit (alert, signals[RESPONSE], 0, response_id);
 949 }
 950 
 951 /**
 952  * e_alert_start_timer:
 953  * @alert: an #EAlert
 954  * @seconds: seconds until timeout occurs
 955  *
 956  * Starts an internal timer for @alert.  When the timer expires, @alert
 957  * will emit the default response.  There is only one timer per #EAlert,
 958  * so calling this function repeatedly on the same #EAlert will restart
 959  * its timer each time.  If @seconds is zero, the timer is cancelled and
 960  * no response will be emitted.
 961  **/
 962 void
 963 e_alert_start_timer (EAlert *alert,
 964                      guint seconds)
 965 {
 966 	g_return_if_fail (E_IS_ALERT (alert));
 967 
 968 	if (alert->priv->timeout_id > 0) {
 969 		g_source_remove (alert->priv->timeout_id);
 970 		alert->priv->timeout_id = 0;
 971 	}
 972 
 973 	if (seconds > 0)
 974 		alert->priv->timeout_id = g_timeout_add_seconds (
 975 			seconds, (GSourceFunc) alert_timeout_cb, alert);
 976 }
 977 
 978 void
 979 e_alert_submit (EAlertSink *alert_sink,
 980                 const gchar *tag,
 981                 ...)
 982 {
 983 	va_list va;
 984 
 985 	va_start (va, tag);
 986 	e_alert_submit_valist (alert_sink, tag, va);
 987 	va_end (va);
 988 }
 989 
 990 void
 991 e_alert_submit_valist (EAlertSink *alert_sink,
 992                        const gchar *tag,
 993                        va_list va)
 994 {
 995 	EAlert *alert;
 996 
 997 	g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
 998 	g_return_if_fail (tag != NULL);
 999 
1000 	alert = e_alert_new_valist (tag, va);
1001 	e_alert_sink_submit_alert (alert_sink, alert);
1002 	g_object_unref (alert);
1003 }