[evolution] EAlert: Allow arbitrary actions to be added.



commit 51ebf20237270a785af0aa0e614db42275a05c62
Author: Matthew Barnes <mbarnes redhat com>
Date:   Fri Oct 15 14:51:13 2010 -0400

    EAlert: Allow arbitrary actions to be added.
    
    You can now amend the predefined actions in an EAlert by calling
    e_alert_add_action().  Useful for adding actions from an existing
    GtkUIManager.
    
    Call e_alert_peek_actions() to obtain a combined list of predefined
    and custom actions.  These will typically serve as "related" actions
    for GtkButtons (cf. gtk_activatable_set_related_action()).
    
    Also, both EShellWindow and EShellView now implement EAlertSink.  Use
    EShellWindow for application-wide alerts, EShellView for view-specific
    alerts.

 doc/reference/shell/eshell-sections.txt            |    1 +
 doc/reference/shell/tmpl/e-shell-window.sgml       |    5 +
 e-util/e-alert-dialog.c                            |  100 ++++++------
 e-util/e-alert-sink.c                              |   36 +++--
 e-util/e-alert.c                                   |  160 +++++++++++++++++---
 e-util/e-alert.h                                   |   14 +--
 modules/offline-alert/Makefile.am                  |   26 +++
 .../evolution-offline-alert.error.xml              |   13 ++
 shell/e-shell-content.c                            |   34 +++--
 shell/e-shell-content.h                            |    3 +-
 shell/e-shell-window-actions.c                     |    2 -
 shell/e-shell-window-private.c                     |    7 +-
 shell/e-shell-window-private.h                     |    8 +
 shell/e-shell-window.c                             |  110 ++++++++++++--
 shell/e-shell-window.h                             |    1 +
 widgets/misc/e-alert-bar.c                         |  112 +++++++++------
 16 files changed, 458 insertions(+), 174 deletions(-)
---
diff --git a/doc/reference/shell/eshell-sections.txt b/doc/reference/shell/eshell-sections.txt
index 518a231..2c0e82d 100644
--- a/doc/reference/shell/eshell-sections.txt
+++ b/doc/reference/shell/eshell-sections.txt
@@ -321,6 +321,7 @@ e_shell_window_get_shell
 e_shell_window_get_shell_view
 e_shell_window_peek_shell_view
 e_shell_window_get_shell_view_action
+e_shell_window_get_alert_bar
 e_shell_window_get_focus_tracker
 e_shell_window_get_ui_manager
 e_shell_window_get_action
diff --git a/doc/reference/shell/tmpl/e-shell-window.sgml b/doc/reference/shell/tmpl/e-shell-window.sgml
index 817e43f..e70971d 100644
--- a/doc/reference/shell/tmpl/e-shell-window.sgml
+++ b/doc/reference/shell/tmpl/e-shell-window.sgml
@@ -39,6 +39,11 @@ EShellWindow
 
 </para>
 
+<!-- ##### ARG EShellWindow:alert-bar ##### -->
+<para>
+
+</para>
+
 <!-- ##### ARG EShellWindow:focus-tracker ##### -->
 <para>
 
diff --git a/e-util/e-alert-dialog.c b/e-util/e-alert-dialog.c
index 4d6fcd8..888c912 100644
--- a/e-util/e-alert-dialog.c
+++ b/e-util/e-alert-dialog.c
@@ -22,10 +22,13 @@
  */
 
 #include "e-alert-dialog.h"
+
 #include "e-util.h"
+#include "e-alert-action.h"
 
-#define E_ALERT_DIALOG_GET_PRIVATE(o) \
-		(G_TYPE_INSTANCE_GET_PRIVATE ((o), E_TYPE_ALERT_DIALOG, EAlertDialogPrivate))
+#define E_ALERT_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_ALERT_DIALOG, EAlertDialogPrivate))
 
 struct _EAlertDialogPrivate {
 	GtkWindow *parent;
@@ -94,6 +97,9 @@ alert_dialog_dispose (GObject *object)
 	priv = E_ALERT_DIALOG_GET_PRIVATE (object);
 
 	if (priv->alert) {
+		g_signal_handlers_disconnect_matched (
+			priv->alert, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
 		g_object_unref (priv->alert);
 		priv->alert = NULL;
 	}
@@ -105,61 +111,67 @@ alert_dialog_dispose (GObject *object)
 static void
 alert_dialog_constructed (GObject *object)
 {
-	EAlertDialog *self = (EAlertDialog*) object;
 	EAlert *alert;
-	EAlertButton *b;
+	EAlertDialog *dialog;
 	GtkWidget *action_area;
 	GtkWidget *content_area;
 	GtkWidget *container;
 	GtkWidget *widget;
 	PangoAttribute *attr;
 	PangoAttrList *list;
+	GList *actions;
 	const gchar *primary, *secondary;
 
-	g_return_if_fail (self != NULL);
-
-	alert = e_alert_dialog_get_alert (E_ALERT_DIALOG (self));
+	dialog = E_ALERT_DIALOG (object);
+	alert = e_alert_dialog_get_alert (dialog);
 
-	gtk_window_set_title (GTK_WINDOW (self), " ");
+	gtk_window_set_title (GTK_WINDOW (dialog), " ");
 
-	action_area = gtk_dialog_get_action_area (GTK_DIALOG (self));
-	content_area = gtk_dialog_get_content_area (GTK_DIALOG (self));
+	action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
 
 #if !GTK_CHECK_VERSION(2,90,7)
-	g_object_set (self, "has-separator", FALSE, NULL);
+	g_object_set (dialog, "has-separator", FALSE, NULL);
 #endif
 
-	gtk_widget_ensure_style (GTK_WIDGET (self));
+	gtk_widget_ensure_style (GTK_WIDGET (dialog));
 	gtk_container_set_border_width (GTK_CONTAINER (action_area), 12);
 	gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
 
-	gtk_window_set_destroy_with_parent (GTK_WINDOW (self), TRUE);
-
-	b = e_alert_peek_buttons (alert);
-	if (b == NULL) {
-		gtk_dialog_add_button (
-			GTK_DIALOG (self), GTK_STOCK_OK, GTK_RESPONSE_OK);
-	} else {
-		for (; b; b=b->next) {
-			if (b->stock) {
-				if (b->label != NULL)
-					gtk_dialog_add_button (
-						GTK_DIALOG (self),
-						b->label, b->response);
-				else
-					gtk_dialog_add_button (
-						GTK_DIALOG (self),
-						b->stock, b->response);
-			} else
-				gtk_dialog_add_button (
-					GTK_DIALOG (self),
-					b->label, b->response);
-		}
+	gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+
+	/* Forward EAlert::response signals to GtkDialog::response. */
+	g_signal_connect_swapped (
+		alert, "response",
+		G_CALLBACK (gtk_dialog_response), dialog);
+
+	/* Add buttons from actions. */
+	actions = e_alert_peek_actions (alert);
+	while (actions != NULL) {
+		GtkWidget *button;
+
+		/* These actions are already wired to trigger an
+		 * EAlert::response signal when activated, which
+		 * will in turn call to gtk_dialog_response(),
+		 * so we can add buttons directly to the action
+		 * area without knowing their response IDs. */
+
+		button = gtk_button_new ();
+
+		gtk_activatable_set_related_action (
+			GTK_ACTIVATABLE (button),
+			GTK_ACTION (actions->data));
+
+		gtk_box_pack_end (
+			GTK_BOX (action_area),
+			button, FALSE, TRUE, 0);
+
+		actions = g_list_next (actions);
 	}
 
 	if (e_alert_get_default_response (alert))
 		gtk_dialog_set_default_response (
-			GTK_DIALOG (self),
+			GTK_DIALOG (dialog),
 			e_alert_get_default_response (alert));
 
 	widget = gtk_hbox_new (FALSE, 12);
@@ -210,20 +222,9 @@ alert_dialog_constructed (GObject *object)
 }
 
 static void
-alert_dialog_response (GtkDialog *dialog,
-                       gint response_id)
-{
-	EAlert *alert;
-
-	alert = e_alert_dialog_get_alert (E_ALERT_DIALOG (dialog));
-	e_alert_response (alert, response_id);
-}
-
-static void
 e_alert_dialog_class_init (EAlertDialogClass *class)
 {
 	GObjectClass *object_class;
-	GtkDialogClass *dialog_class;
 
 	g_type_class_add_private (class, sizeof (EAlertDialogPrivate));
 
@@ -233,9 +234,6 @@ e_alert_dialog_class_init (EAlertDialogClass *class)
 	object_class->dispose = alert_dialog_dispose;
 	object_class->constructed = alert_dialog_constructed;
 
-	dialog_class = GTK_DIALOG_CLASS (class);
-	dialog_class->response = alert_dialog_response;
-
 	g_object_class_install_property (
 		object_class,
 		PROP_ALERT,
@@ -250,9 +248,9 @@ e_alert_dialog_class_init (EAlertDialogClass *class)
 }
 
 static void
-e_alert_dialog_init (EAlertDialog *self)
+e_alert_dialog_init (EAlertDialog *dialog)
 {
-	self->priv = E_ALERT_DIALOG_GET_PRIVATE (self);
+	dialog->priv = E_ALERT_DIALOG_GET_PRIVATE (dialog);
 }
 
 GtkWidget *
diff --git a/e-util/e-alert-sink.c b/e-util/e-alert-sink.c
index de676ea..f26f114 100644
--- a/e-util/e-alert-sink.c
+++ b/e-util/e-alert-sink.c
@@ -35,16 +35,13 @@ G_DEFINE_INTERFACE (
 	GTK_TYPE_WIDGET)
 
 static void
-alert_sink_submit_alert (EAlertSink *alert_sink,
-                         EAlert *alert)
+alert_sink_fallback (GtkWidget *widget,
+                     EAlert *alert)
 {
 	GtkWidget *dialog;
 	gpointer parent;
 
-	/* This is just a lame fallback handler.  Implementors
-	 * are strongly encouraged to override this method. */
-
-	parent = gtk_widget_get_toplevel (GTK_WIDGET (alert_sink));
+	parent = gtk_widget_get_toplevel (widget);
 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
 
 	dialog = e_alert_dialog_new (parent, alert);
@@ -53,6 +50,15 @@ alert_sink_submit_alert (EAlertSink *alert_sink,
 }
 
 static void
+alert_sink_submit_alert (EAlertSink *alert_sink,
+                         EAlert *alert)
+{
+	/* This is just a lame fallback handler.  Implementors
+	 * are strongly encouraged to override this method. */
+	alert_sink_fallback (GTK_WIDGET (alert_sink), alert);
+}
+
+static void
 e_alert_sink_default_init (EAlertSinkInterface *interface)
 {
 	interface->submit_alert = alert_sink_submit_alert;
@@ -67,7 +73,7 @@ e_alert_sink_default_init (EAlertSinkInterface *interface)
  * well-defined behavior.  It's up to the widget implementing the #EAlertSink
  * interface to decide what to do with them.
  *
- * Either @widget or one of its parents must implement #EAlertSink.
+ * Either @widget or one of its ancestors must implement #EAlertSink.
  *
  * The default behavior is to display the @alert in a dialog.
  **/
@@ -75,18 +81,20 @@ void
 e_alert_sink_submit_alert (GtkWidget *widget,
                            EAlert *alert)
 {
-	EAlertSinkInterface *interface;
+	GtkWidget *ancestor;
 
 	g_return_if_fail (GTK_IS_WIDGET (widget));
 	g_return_if_fail (E_IS_ALERT (alert));
 
-	while (widget != NULL && !E_IS_ALERT_SINK (widget))
-		widget = gtk_widget_get_parent (widget);
+	ancestor = gtk_widget_get_ancestor (widget, E_TYPE_ALERT_SINK);
 
-	g_return_if_fail (E_IS_ALERT_SINK (widget));
+	if (E_IS_ALERT_SINK (ancestor)) {
+		EAlertSinkInterface *interface;
 
-	interface = E_ALERT_SINK_GET_INTERFACE (widget);
-	g_return_if_fail (interface->submit_alert != NULL);
+		interface = E_ALERT_SINK_GET_INTERFACE (ancestor);
+		g_return_if_fail (interface->submit_alert != NULL);
 
-	interface->submit_alert (E_ALERT_SINK (widget), alert);
+		interface->submit_alert (E_ALERT_SINK (ancestor), alert);
+	} else
+		alert_sink_fallback (widget, alert);
 }
diff --git a/e-util/e-alert.c b/e-util/e-alert.c
index 1846e6d..96e9078 100644
--- a/e-util/e-alert.c
+++ b/e-util/e-alert.c
@@ -38,6 +38,7 @@
 #include "e-util.h"
 #include "e-util-private.h"
 #include "e-alert.h"
+#include "e-alert-action.h"
 #include "e-alert-sink.h"
 
 #define d(x)
@@ -46,6 +47,8 @@
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_ALERT, EAlertPrivate))
 
+typedef struct _EAlertButton EAlertButton;
+
 struct _e_alert {
 	const gchar *id;
 	GtkMessageType message_type;
@@ -61,12 +64,19 @@ struct _e_alert_table {
 	GHashTable *alerts;
 };
 
+struct _EAlertButton {
+	EAlertButton *next;
+	const gchar *stock_id;
+	const gchar *label;
+	gint response_id;
+};
+
 static GHashTable *alert_table;
 
 /* ********************************************************************** */
 
 static EAlertButton default_ok_button = {
-	NULL, "gtk-ok", NULL, GTK_RESPONSE_OK
+	NULL, GTK_STOCK_OK, NULL, GTK_RESPONSE_OK
 };
 
 static struct _e_alert default_alerts[] = {
@@ -86,6 +96,11 @@ struct _EAlertPrivate {
 	struct _e_alert *definition;
 	GtkMessageType message_type;
 	gint default_response;
+
+	/* It may occur to one that we could use a GtkActionGroup here,
+	 * but we need to preserve the button order and GtkActionGroup
+	 * uses a hash table, which does not preserve order. */
+	GQueue actions;
 };
 
 enum {
@@ -233,37 +248,37 @@ e_alert_load (const gchar *path)
 						xmlFree (tmp);
 					}
 				} else if (!strcmp((gchar *)scan->name, "button")) {
-					EAlertButton *b;
+					EAlertButton *button;
 					gchar *label = NULL;
-					gchar *stock = NULL;
+					gchar *stock_id = NULL;
 
-					b = g_malloc0 (sizeof (*b));
+					button = g_new0 (EAlertButton, 1);
 					tmp = (gchar *)xmlGetProp(scan, (const guchar *)"stock");
 					if (tmp) {
-						stock = g_strdup (tmp);
-						b->stock = stock;
+						stock_id = g_strdup (tmp);
+						button->stock_id = stock_id;
 						xmlFree (tmp);
 					}
 					tmp = (gchar *)xmlGetProp(scan, (const guchar *)"label");
 					if (tmp) {
 						label = g_strdup (dgettext (table->translation_domain, tmp));
-						b->label = label;
+						button->label = label;
 						xmlFree (tmp);
 					}
 					tmp = (gchar *)xmlGetProp(scan, (const guchar *)"response");
 					if (tmp) {
-						b->response = map_response (tmp);
+						button->response_id = map_response (tmp);
 						xmlFree (tmp);
 					}
 
-					if (stock == NULL && label == NULL) {
+					if (stock_id == NULL && label == NULL) {
 						g_warning("Error file '%s': missing button details in error '%s'", path, e->id);
-						g_free (stock);
+						g_free (stock_id);
 						g_free (label);
-						g_free (b);
+						g_free (button);
 					} else {
-						lastbutton->next = b;
-						lastbutton = b;
+						lastbutton->next = button;
+						lastbutton = button;
 					}
 				}
 			}
@@ -322,6 +337,18 @@ e_alert_load_tables (void)
 	g_free (base);
 }
 
+static void
+alert_action_activate (EAlert *alert,
+                       GtkAction *action)
+{
+	GObject *object;
+	gpointer data;
+
+	object = G_OBJECT (action);
+	data = g_object_get_data (object, "e-alert-response-id");
+	e_alert_response (alert, GPOINTER_TO_INT (data));
+}
+
 static gchar *
 alert_format_string (const gchar *format,
                      GPtrArray *args)
@@ -378,8 +405,6 @@ alert_set_tag (EAlert *alert,
 	g_warn_if_fail (definition);
 
 	alert->priv->definition = definition;
-	e_alert_set_message_type (alert, definition->message_type);
-	e_alert_set_default_response (alert, definition->default_response);
 }
 
 static void
@@ -463,6 +488,20 @@ alert_get_property (GObject *object,
 }
 
 static void
+alert_dispose (GObject *object)
+{
+	EAlertPrivate *priv;
+
+	priv = E_ALERT_GET_PRIVATE (object);
+
+	while (!g_queue_is_empty (&priv->actions))
+		g_object_unref (g_queue_pop_head (&priv->actions));
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_alert_parent_class)->dispose (object);
+}
+
+static void
 alert_finalize (GObject *object)
 {
 	EAlertPrivate *priv;
@@ -480,6 +519,49 @@ alert_finalize (GObject *object)
 }
 
 static void
+alert_constructed (GObject *object)
+{
+	EAlert *alert;
+	EAlertButton *button;
+	struct _e_alert *definition;
+	gint ii = 0;
+
+	alert = E_ALERT (object);
+	definition = alert->priv->definition;
+
+	e_alert_set_message_type (alert, definition->message_type);
+	e_alert_set_default_response (alert, definition->default_response);
+
+	/* Build actions out of the button definitions. */
+	button = definition->buttons;
+	while (button != NULL) {
+		GtkAction *action;
+		gchar *action_name;
+
+		action_name = g_strdup_printf ("alert-response-%d", ii++);
+
+		if (button->stock_id != NULL) {
+			action = gtk_action_new (
+				action_name, NULL, NULL, button->stock_id);
+			e_alert_add_action (
+				alert, action, button->response_id);
+			g_object_unref (action);
+
+		} else if (button->label != NULL) {
+			action = gtk_action_new (
+				action_name, button->label, NULL, NULL);
+			e_alert_add_action (
+				alert, action, button->response_id);
+			g_object_unref (action);
+		}
+
+		g_free (action_name);
+
+		button = button->next;
+	}
+}
+
+static void
 e_alert_class_init (EAlertClass *class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (class);
@@ -488,7 +570,9 @@ e_alert_class_init (EAlertClass *class)
 
 	object_class->set_property = alert_set_property;
 	object_class->get_property = alert_get_property;
+	object_class->dispose = alert_dispose;
 	object_class->finalize = alert_finalize;
+	object_class->constructed = alert_constructed;
 
 	g_object_class_install_property (
 		object_class,
@@ -562,9 +646,11 @@ e_alert_class_init (EAlertClass *class)
 }
 
 static void
-e_alert_init (EAlert *self)
+e_alert_init (EAlert *alert)
 {
-	self->priv = E_ALERT_GET_PRIVATE (self);
+	alert->priv = E_ALERT_GET_PRIVATE (alert);
+
+	g_queue_init (&alert->priv->actions);
 }
 
 /**
@@ -760,11 +846,43 @@ e_alert_get_stock_id (EAlert *alert)
 	return stock_id;
 }
 
-EAlertButton *
-e_alert_peek_buttons (EAlert *alert)
+void
+e_alert_add_action (EAlert *alert,
+                    GtkAction *action,
+                    gint response_id)
 {
-	g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL);
-	return alert->priv->definition->buttons;
+	g_return_if_fail (E_IS_ALERT (alert));
+	g_return_if_fail (GTK_ACTION (action));
+
+	g_object_set_data (
+		G_OBJECT (action), "e-alert-response-id",
+		GINT_TO_POINTER (response_id));
+
+	g_signal_connect_swapped (
+		action, "activate",
+		G_CALLBACK (alert_action_activate), alert);
+
+	g_queue_push_tail (&alert->priv->actions, g_object_ref (action));
+}
+
+GList *
+e_alert_peek_actions (EAlert *alert)
+{
+	g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+	/* Make sure we have at least one action.  Do this on-demand
+	 * in case the XML definition did not specify any actions but
+	 * other actions were added via e_alert_add_action(). */
+	if (g_queue_is_empty (&alert->priv->actions)) {
+		GtkAction *action;
+
+		action = gtk_action_new (
+			"alert-response-0", _("_Dismiss"), NULL, NULL);
+		e_alert_add_action (alert, action, GTK_RESPONSE_CLOSE);
+		g_object_unref (action);
+	}
+
+	return g_queue_peek_head_link (&alert->priv->actions);
 }
 
 GtkWidget *
diff --git a/e-util/e-alert.h b/e-util/e-alert.h
index e0ad92c..f9f0fc8 100644
--- a/e-util/e-alert.h
+++ b/e-util/e-alert.h
@@ -61,15 +61,6 @@ typedef struct _EAlert EAlert;
 typedef struct _EAlertClass EAlertClass;
 typedef struct _EAlertPrivate EAlertPrivate;
 
-typedef struct _EAlertButton EAlertButton;
-
-struct _EAlertButton {
-	EAlertButton *next;
-	const gchar *stock;
-	const gchar *label;
-	gint response;
-};
-
 struct _EAlert {
 	GObject parent;
 	EAlertPrivate *priv;
@@ -103,7 +94,10 @@ const gchar *	e_alert_get_secondary_text	(EAlert *alert);
 void		e_alert_set_secondary_text	(EAlert *alert,
 						 const gchar *secondary_text);
 const gchar *	e_alert_get_stock_id		(EAlert *alert);
-EAlertButton *	e_alert_peek_buttons		(EAlert *alert);
+void		e_alert_add_action		(EAlert *alert,
+						 GtkAction *action,
+						 gint response_id);
+GList *		e_alert_peek_actions		(EAlert *alert);
 GtkWidget *	e_alert_create_image		(EAlert *alert,
 						 GtkIconSize size);
 void		e_alert_response		(EAlert *alert,
diff --git a/modules/offline-alert/Makefile.am b/modules/offline-alert/Makefile.am
new file mode 100644
index 0000000..cdad615
--- /dev/null
+++ b/modules/offline-alert/Makefile.am
@@ -0,0 +1,26 @@
+module_LTLIBRARIES = libevolution-module-offline-alert.la
+
+libevolution_module_offline_alert_la_CPPFLAGS =			\
+	$(AM_CPPFLAGS)						\
+	-I$(top_srcdir)						\
+	-DG_LOG_DOMAIN=\"evolution-offline-alert\"		\
+	$(GNOME_PLATFORM_CFLAGS)
+
+libevolution_module_offline_alert_la_SOURCES =			\
+	evolution-offline-alert.c
+
+libevolution_module_offline_alert_la_LIBADD =			\
+	$(top_builddir)/e-util/libeutil.la			\
+	$(top_builddir)/shell/libeshell.la			\
+	$(GNOME_PLATFORM_LIBS)
+
+libevolution_module_offline_alert_la_LDFLAGS =			\
+	-module -avoid-version $(NO_UNDEFINED)
+
+error_DATA = evolution-offline-alert.error
+errordir = $(privdatadir)/errors
+ EVO_PLUGIN_RULE@
+
+EXTRA_DIST = evolution-offline-alert.error.xml
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/offline-alert/evolution-offline-alert.error.xml b/modules/offline-alert/evolution-offline-alert.error.xml
new file mode 100644
index 0000000..65a1ec7
--- /dev/null
+++ b/modules/offline-alert/evolution-offline-alert.error.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<error-list domain="offline-alert">
+  <error id="offline" type="info">
+    <_primary>Evolution is currently offline.</_primary>
+    <_secondary>Click 'Work Online' to return to online mode.</_secondary>
+    <button _label="_Dismiss" response="GTK_RESPONSE_OK"/>
+  </error>
+  <error id="no-network" type="info">
+    <_primary>Evolution is currently offline due to a network outage.</_primary>
+    <_secondary>Evolution will return to online mode once a network connection is established.</secondary>
+    <button _label="_Dismiss" response="GTK_RESPONSE_OK"/>
+  </error>
+</error-list>
diff --git a/shell/e-shell-content.c b/shell/e-shell-content.c
index 2648783..afd69ff 100644
--- a/shell/e-shell-content.c
+++ b/shell/e-shell-content.c
@@ -36,6 +36,7 @@
 #include "e-util/e-alert-dialog.h"
 #include "filter/e-rule-editor.h"
 #include "widgets/misc/e-action-combo-box.h"
+#include "widgets/misc/e-alert-bar.h"
 #include "widgets/misc/e-hinted-entry.h"
 
 #include "e-shell-backend.h"
@@ -244,29 +245,35 @@ shell_content_size_allocate (GtkWidget *widget,
 	GtkAllocation child_allocation;
 	GtkRequisition child_requisition;
 	GtkWidget *child;
+	gint remaining_height;
 
 	priv = E_SHELL_CONTENT_GET_PRIVATE (widget);
 
+	remaining_height = allocation->height;
 	gtk_widget_set_allocation (widget, allocation);
 
 	child_allocation.x = allocation->x;
+	child_allocation.y = allocation->y;
 	child_allocation.width = allocation->width;
 
-	/* Alert bar gets to be as tall as it wants. */
+	child_requisition.height = 0;
+
+	/* Alert bar gets to be as tall as it wants (if visible). */
 
 	child = priv->alert_bar;
-	child_allocation.y = allocation->y;
+	child_allocation.y += child_requisition.height;
 
 	if (gtk_widget_get_visible (child))
 		gtk_widget_size_request (child, &child_requisition);
 	else
 		child_requisition.height = 0;
 
+	remaining_height -= child_requisition.height;
 	child_allocation.height = child_requisition.height;
 
 	gtk_widget_size_allocate (child, &child_allocation);
 
-	/* So does the search bar (if we have one). */
+	/* Search bar gets to be as tall as it wants (if we have one). */
 
 	child = priv->searchbar;
 	child_allocation.y += child_requisition.height;
@@ -276,6 +283,7 @@ shell_content_size_allocate (GtkWidget *widget,
 	else
 		child_requisition.height = 0;
 
+	remaining_height -= child_requisition.height;
 	child_allocation.height = child_requisition.height;
 
 	if (child != NULL)
@@ -284,8 +292,7 @@ shell_content_size_allocate (GtkWidget *widget,
 	/* The GtkBin child gets whatever vertical space is left. */
 
 	child_allocation.y += child_requisition.height;
-	child_allocation.height =
-		allocation->height - child_requisition.height;
+	child_allocation.height = remaining_height;
 
 	child = gtk_bin_get_child (GTK_BIN (widget));
 	if (child != NULL)
@@ -346,9 +353,8 @@ shell_content_submit_alert (EAlertSink *alert_sink,
 	EShellView *shell_view;
 	EShellWindow *shell_window;
 	EShellContent *shell_content;
-	EAlertBar *alert_bar;
+	GtkWidget *alert_bar;
 	GtkWidget *dialog;
-	GtkWindow *parent;
 
 	shell_content = E_SHELL_CONTENT (alert_sink);
 	shell_view = e_shell_content_get_shell_view (shell_content);
@@ -359,12 +365,13 @@ shell_content_submit_alert (EAlertSink *alert_sink,
 		case GTK_MESSAGE_INFO:
 		case GTK_MESSAGE_WARNING:
 		case GTK_MESSAGE_ERROR:
-			e_alert_bar_add_alert (alert_bar, alert);
+			e_alert_bar_add_alert (
+				E_ALERT_BAR (alert_bar), alert);
 			break;
 
 		default:
-			parent = GTK_WINDOW (shell_window);
-			dialog = e_alert_dialog_new (parent, alert);
+			dialog = e_alert_dialog_new (
+				GTK_WINDOW (shell_window), alert);
 			gtk_dialog_run (GTK_DIALOG (dialog));
 			gtk_widget_destroy (dialog);
 			break;
@@ -395,7 +402,8 @@ e_shell_content_class_init (EShellContentClass *class)
 	container_class->remove = shell_content_remove;
 	container_class->forall = shell_content_forall;
 
-	/* EShellContent:alert-bar
+	/**
+	 * EShellContent:alert-bar
 	 *
 	 * Displays informational and error messages.
 	 **/
@@ -543,12 +551,12 @@ e_shell_content_focus_search_results (EShellContent *shell_content)
  *
  * Returns: the #EAlertBar for @shell_content
  **/
-EAlertBar *
+GtkWidget *
 e_shell_content_get_alert_bar (EShellContent *shell_content)
 {
 	g_return_val_if_fail (E_IS_SHELL_CONTENT (shell_content), NULL);
 
-	return E_ALERT_BAR (shell_content->priv->alert_bar);
+	return shell_content->priv->alert_bar;
 }
 
 /**
diff --git a/shell/e-shell-content.h b/shell/e-shell-content.h
index 099c841..58c20e3 100644
--- a/shell/e-shell-content.h
+++ b/shell/e-shell-content.h
@@ -22,7 +22,6 @@
 #ifndef E_SHELL_CONTENT_H
 #define E_SHELL_CONTENT_H
 
-#include <misc/e-alert-bar.h>
 #include <shell/e-shell-common.h>
 
 /* Standard GObject macros */
@@ -79,7 +78,7 @@ void		e_shell_content_set_searchbar	(EShellContent *shell_content,
 guint32		e_shell_content_check_state	(EShellContent *shell_content);
 void		e_shell_content_focus_search_results
 						(EShellContent *shell_content);
-EAlertBar *	e_shell_content_get_alert_bar	(EShellContent *shell_content);
+GtkWidget *	e_shell_content_get_alert_bar	(EShellContent *shell_content);
 struct _EShellView *
 		e_shell_content_get_shell_view	(EShellContent *shell_content);
 const gchar *	e_shell_content_get_view_id	(EShellContent *shell_content);
diff --git a/shell/e-shell-window-actions.c b/shell/e-shell-window-actions.c
index 740adcc..a77bd35 100644
--- a/shell/e-shell-window-actions.c
+++ b/shell/e-shell-window-actions.c
@@ -22,9 +22,7 @@
 #include "e-shell-window-private.h"
 #include "e-preferences-window.h"
 
-#include <e-util/e-util-private.h>
 #include <e-util/e-dialog-utils.h>
-#include <e-util/e-alert-dialog.h>
 #include <e-util/e-print.h>
 #include <gal-define-views-dialog.h>
 
diff --git a/shell/e-shell-window-private.c b/shell/e-shell-window-private.c
index 5c4e121..dee6450 100644
--- a/shell/e-shell-window-private.c
+++ b/shell/e-shell-window-private.c
@@ -289,10 +289,6 @@ e_shell_window_private_constructed (EShellWindow *shell_window)
 
 	e_shell_window_actions_init (shell_window);
 
-	/* Do this after intializing actions because it
-	 * triggers shell_window_update_close_action_cb(). */
-	e_shell_watch_window (shell, window);
-
 	accel_group = gtk_ui_manager_get_accel_group (ui_manager);
 	gtk_window_add_accel_group (GTK_WINDOW (shell_window), accel_group);
 
@@ -481,6 +477,8 @@ e_shell_window_private_constructed (EShellWindow *shell_window)
 	id = "org.gnome.evolution.shell";
 	e_plugin_ui_register_manager (ui_manager, id, shell_window);
 	e_plugin_ui_enable_manager (ui_manager, id);
+
+	e_shell_watch_window (shell, window);
 }
 
 void
@@ -514,6 +512,7 @@ e_shell_window_private_dispose (EShellWindow *shell_window)
 
 	g_hash_table_remove_all (priv->loaded_views);
 
+	DISPOSE (priv->alert_bar);
 	DISPOSE (priv->content_pane);
 	DISPOSE (priv->content_notebook);
 	DISPOSE (priv->sidebar_notebook);
diff --git a/shell/e-shell-window-private.h b/shell/e-shell-window-private.h
index 960fc74..79f15ac 100644
--- a/shell/e-shell-window-private.h
+++ b/shell/e-shell-window-private.h
@@ -27,9 +27,16 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
+#include <gconf/gconf-client.h>
+
 #include <e-util/e-util.h>
+#include <e-util/e-util-private.h>
+#include <e-util/e-alert-dialog.h>
+#include <e-util/e-alert-sink.h>
+#include <e-util/e-extensible.h>
 #include <e-util/e-plugin-ui.h>
 #include <e-util/gconf-bridge.h>
+#include <widgets/misc/e-alert-bar.h>
 #include <widgets/misc/e-import-assistant.h>
 #include <widgets/misc/e-menu-tool-button.h>
 #include <widgets/misc/e-online-button.h>
@@ -87,6 +94,7 @@ struct _EShellWindowPrivate {
 
 	/*** Widgetry ***/
 
+	GtkWidget *alert_bar;
 	GtkWidget *content_pane;
 	GtkWidget *content_notebook;
 	GtkWidget *sidebar_notebook;
diff --git a/shell/e-shell-window.c b/shell/e-shell-window.c
index 7704fb5..4f4e5c1 100644
--- a/shell/e-shell-window.c
+++ b/shell/e-shell-window.c
@@ -27,15 +27,10 @@
 
 #include "e-shell-window-private.h"
 
-#include <gconf/gconf-client.h>
-
-#include <e-util/e-extensible.h>
-#include <e-util/e-plugin-ui.h>
-#include <e-util/e-util-private.h>
-
 enum {
 	PROP_0,
 	PROP_ACTIVE_VIEW,
+	PROP_ALERT_BAR,
 	PROP_FOCUS_TRACKER,
 	PROP_GEOMETRY,
 	PROP_SAFE_MODE,
@@ -54,11 +49,17 @@ enum {
 
 static gulong signals[LAST_SIGNAL];
 
+/* Forward Declarations */
+static void	e_shell_window_alert_sink_init
+					(EAlertSinkInterface *interface);
+
 G_DEFINE_TYPE_WITH_CODE (
 	EShellWindow,
 	e_shell_window,
 	GTK_TYPE_WINDOW,
 	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK, e_shell_window_alert_sink_init)
+	G_IMPLEMENT_INTERFACE (
 		E_TYPE_EXTENSIBLE, NULL))
 
 static void
@@ -254,6 +255,12 @@ shell_window_get_property (GObject *object,
 				E_SHELL_WINDOW (object)));
 			return;
 
+		case PROP_ALERT_BAR:
+			g_value_set_object (
+				value, e_shell_window_get_alert_bar (
+				E_SHELL_WINDOW (object)));
+			return;
+
 		case PROP_FOCUS_TRACKER:
 			g_value_set_object (
 				value, e_shell_window_get_focus_tracker (
@@ -492,19 +499,29 @@ shell_window_construct_sidebar (EShellWindow *shell_window)
 static GtkWidget *
 shell_window_construct_content (EShellWindow *shell_window)
 {
-	GtkWidget *notebook;
+	GtkWidget *box;
+	GtkWidget *widget;
 
-	notebook = gtk_notebook_new ();
-	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
-	gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
-	shell_window->priv->content_notebook = g_object_ref_sink (notebook);
-	gtk_widget_show (notebook);
+	box = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (box);
+
+	widget = e_alert_bar_new ();
+	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+	shell_window->priv->alert_bar = g_object_ref (widget);
+	/* EAlertBar controls its own visibility. */
+
+	widget = gtk_notebook_new ();
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+	gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0);
+	shell_window->priv->content_notebook = g_object_ref (widget);
+	gtk_widget_show (widget);
 
 	g_signal_connect (
 		shell_window, "notify::active-view",
-		G_CALLBACK (shell_window_set_notebook_page), notebook);
+		G_CALLBACK (shell_window_set_notebook_page), widget);
 
-	return notebook;
+	return box;
 }
 
 static GtkWidget *
@@ -676,6 +693,34 @@ shell_window_realize (GtkWidget *widget)
 }
 
 static void
+shell_window_submit_alert (EAlertSink *alert_sink,
+                           EAlert *alert)
+{
+	EShellWindow *shell_window;
+	GtkWidget *alert_bar;
+	GtkWidget *dialog;
+
+	shell_window = E_SHELL_WINDOW (alert_sink);
+	alert_bar = e_shell_window_get_alert_bar (shell_window);
+
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+		case GTK_MESSAGE_WARNING:
+		case GTK_MESSAGE_ERROR:
+			e_alert_bar_add_alert (
+				E_ALERT_BAR (alert_bar), alert);
+			break;
+
+		default:
+			dialog = e_alert_dialog_new (
+				GTK_WINDOW (shell_window), alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			break;
+	}
+}
+
+static void
 e_shell_window_class_init (EShellWindowClass *class)
 {
 	GObjectClass *object_class;
@@ -716,6 +761,21 @@ e_shell_window_class_init (EShellWindowClass *class)
 			G_PARAM_READWRITE));
 
 	/**
+	 * EShellWindow:alert-bar
+	 *
+	 * Displays informational and error messages.
+	 **/
+	g_object_class_install_property (
+		object_class,
+		PROP_ALERT_BAR,
+		g_param_spec_object (
+			"alert-bar",
+			"Alert Bar",
+			"Displays informational and error messages",
+			E_TYPE_ALERT_BAR,
+			G_PARAM_READABLE));
+
+	/**
 	 * EShellWindow:focus-tracker
 	 *
 	 * The shell window's #EFocusTracker.
@@ -876,6 +936,12 @@ e_shell_window_class_init (EShellWindowClass *class)
 }
 
 static void
+e_shell_window_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = shell_window_submit_alert;
+}
+
+static void
 e_shell_window_init (EShellWindow *shell_window)
 {
 	shell_window->priv = E_SHELL_WINDOW_GET_PRIVATE (shell_window);
@@ -1036,6 +1102,22 @@ e_shell_window_get_shell_view_action (EShellWindow *shell_window,
 }
 
 /**
+ * e_shell_window_get_alert_bar:
+ * @shell_window: an #EShellWindow
+ *
+ * Returns the #EAlertBar used to display informational and error messages.
+ *
+ * Returns: the #EAlertBar for @shell_window
+ **/
+GtkWidget *
+e_shell_window_get_alert_bar (EShellWindow *shell_window)
+{
+	g_return_val_if_fail (E_IS_SHELL_WINDOW (shell_window), NULL);
+
+	return shell_window->priv->alert_bar;
+}
+
+/**
  * e_shell_window_get_focus_tracker:
  * @shell_window: an #EShellWindow
  *
diff --git a/shell/e-shell-window.h b/shell/e-shell-window.h
index 8e29093..25e039a 100644
--- a/shell/e-shell-window.h
+++ b/shell/e-shell-window.h
@@ -96,6 +96,7 @@ struct _EShellView *
 GtkAction *	e_shell_window_get_shell_view_action
 						(EShellWindow *shell_window,
 						 const gchar *view_name);
+GtkWidget *	e_shell_window_get_alert_bar	(EShellWindow *shell_window);
 EFocusTracker *	e_shell_window_get_focus_tracker
 						(EShellWindow *shell_window);
 GtkUIManager *	e_shell_window_get_ui_manager	(EShellWindow *shell_window);
diff --git a/widgets/misc/e-alert-bar.c b/widgets/misc/e-alert-bar.c
index b050978..6e81bec 100644
--- a/widgets/misc/e-alert-bar.c
+++ b/widgets/misc/e-alert-bar.c
@@ -21,11 +21,14 @@
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
+#include "e-util/e-alert-action.h"
+
 #define E_ALERT_BAR_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_ALERT_BAR, EAlertBarPrivate))
 
-#define ICON_SIZE	GTK_ICON_SIZE_DIALOG
+/* GTK_ICON_SIZE_DIALOG is a tad too big. */
+#define ICON_SIZE	GTK_ICON_SIZE_DND
 
 struct _EAlertBarPrivate {
 	GQueue alerts;
@@ -46,8 +49,8 @@ alert_bar_show_alert (EAlertBar *alert_bar)
 	GtkLabel *label;
 	GtkInfoBar *info_bar;
 	GtkWidget *action_area;
-	EAlertButton *buttons;
 	EAlert *alert;
+	GList *actions;
 	GList *children;
 	GtkMessageType message_type;
 	const gchar *stock_id;
@@ -69,22 +72,27 @@ alert_bar_show_alert (EAlertBar *alert_bar)
 	}
 
 	/* Add new buttons. */
-	buttons = e_alert_peek_buttons (alert);
-	if (buttons == NULL) {
-		gtk_info_bar_add_button (
-			info_bar, _("_Dismiss"), GTK_RESPONSE_CLOSE);
-	} else while (buttons != NULL) {
-		const gchar *button_text;
-
-		if (buttons->stock != NULL)
-			button_text = buttons->stock;
-		else
-			button_text = buttons->label;
-
-		gtk_info_bar_add_button (
-			info_bar, button_text, buttons->response);
-
-		buttons = buttons->next;
+	actions = e_alert_peek_actions (alert);
+	while (actions != NULL) {
+		GtkWidget *button;
+
+		/* These actions are already wired to trigger an
+		 * EAlert::response signal when activated, which
+		 * will in turn call gtk_info_bar_response(), so
+		 * we can add buttons directly to the action
+		 * area without knowning their response IDs. */
+
+		button = gtk_button_new ();
+
+		gtk_activatable_set_related_action (
+			GTK_ACTIVATABLE (button),
+			GTK_ACTION (actions->data));
+
+		gtk_box_pack_end (
+			GTK_BOX (action_area),
+			button, FALSE, FALSE, 0);
+
+		actions = g_list_next (actions);
 	}
 
 	response_id = e_alert_get_default_response (alert);
@@ -109,51 +117,65 @@ alert_bar_show_alert (EAlertBar *alert_bar)
 }
 
 static void
-alert_bar_dispose (GObject *object)
+alert_bar_response_cb (EAlert *alert,
+                       gint response_id,
+                       EAlertBar *alert_bar)
 {
-	EAlertBarPrivate *priv;
-
-	priv = E_ALERT_BAR_GET_PRIVATE (object);
-
-	while (!g_queue_is_empty (&priv->alerts))
-		g_object_unref (g_queue_pop_head (&priv->alerts));
+	GQueue *queue;
+	EAlert *head;
+	GList *link;
+	gboolean was_head;
+
+	queue = &alert_bar->priv->alerts;
+	head = g_queue_peek_head (queue);
+	was_head = (alert == head);
+
+	g_signal_handlers_disconnect_by_func (
+		alert, alert_bar_response_cb, alert_bar);
+
+	/* XXX Would be easier if g_queue_remove() returned a boolean. */
+	link = g_queue_find (queue, alert);
+	if (link != NULL) {
+		g_queue_delete_link (queue, link);
+		g_object_unref (alert);
+	}
 
-	/* Chain up to parent's dispose() method. */
-	G_OBJECT_CLASS (e_alert_bar_parent_class)->dispose (object);
+	if (g_queue_is_empty (queue))
+		gtk_widget_hide (GTK_WIDGET (alert_bar));
+	else if (was_head) {
+		GtkInfoBar *info_bar = GTK_INFO_BAR (alert_bar);
+		gtk_info_bar_response (info_bar, response_id);
+		alert_bar_show_alert (alert_bar);
+	}
 }
 
 static void
-alert_bar_response (GtkInfoBar *info_bar,
-                    gint response_id)
+alert_bar_dispose (GObject *object)
 {
-	EAlertBar *alert_bar;
-	EAlert *alert;
+	EAlertBarPrivate *priv;
 
-	alert_bar = E_ALERT_BAR (info_bar);
+	priv = E_ALERT_BAR_GET_PRIVATE (object);
 
-	alert = g_queue_pop_head (&alert_bar->priv->alerts);
-	e_alert_response (alert, response_id);
-	g_object_unref (alert);
+	while (!g_queue_is_empty (&priv->alerts)) {
+		EAlert *alert = g_queue_pop_head (&priv->alerts);
+		g_signal_handlers_disconnect_by_func (
+			alert, alert_bar_response_cb, object);
+		g_object_unref (alert);
+	}
 
-	if (!g_queue_is_empty (&alert_bar->priv->alerts))
-		alert_bar_show_alert (alert_bar);
-	else
-		gtk_widget_hide (GTK_WIDGET (alert_bar));
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_alert_bar_parent_class)->dispose (object);
 }
 
 static void
 e_alert_bar_class_init (EAlertBarClass *class)
 {
 	GObjectClass *object_class;
-	GtkInfoBarClass *info_bar_class;
 
 	g_type_class_add_private (class, sizeof (EAlertBarPrivate));
 
 	object_class = G_OBJECT_CLASS (class);
 	object_class->dispose = alert_bar_dispose;
-
-	info_bar_class = GTK_INFO_BAR_CLASS (class);
-	info_bar_class->response = alert_bar_response;
 }
 
 static void
@@ -230,6 +252,10 @@ e_alert_bar_add_alert (EAlertBar *alert_bar,
 	g_return_if_fail (E_IS_ALERT_BAR (alert_bar));
 	g_return_if_fail (E_IS_ALERT (alert));
 
+	g_signal_connect (
+		alert, "response",
+		G_CALLBACK (alert_bar_response_cb), alert_bar);
+
 	g_queue_push_head (&alert_bar->priv->alerts, g_object_ref (alert));
 
 	alert_bar_show_alert (alert_bar);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]