[evolution/account-mgmt: 9/55] Adapt widgets/misc to the new ESource API.



commit 042aa06f63bd0c716ba5a51a0698b3933cc01c14
Author: Matthew Barnes <mbarnes redhat com>
Date:   Fri Apr 1 20:43:32 2011 -0400

    Adapt widgets/misc to the new ESource API.

 po/POTFILES.in                                |    1 +
 widgets/misc/Makefile.am                      |   67 ++-
 widgets/misc/e-alarm-selector.c               |   97 +++
 widgets/misc/e-auth-combo-box.c               |    6 +
 widgets/misc/e-autocomplete-selector.c        |   99 +++
 widgets/misc/e-mail-account-manager.c         |  384 -----------
 widgets/misc/e-mail-account-manager.h         |   77 ---
 widgets/misc/e-mail-account-tree-view.c       |  575 ----------------
 widgets/misc/e-mail-account-tree-view.h       |   83 ---
 widgets/misc/e-mail-identity-combo-box.c      |  165 +++--
 widgets/misc/e-mail-identity-combo-box.h      |    5 -
 widgets/misc/e-mail-signature-combo-box.c     |  617 +++++++++++++++++
 widgets/misc/e-mail-signature-combo-box.h     |   91 +++
 widgets/misc/e-mail-signature-editor.c        |  909 +++++++++++++++++++++++++
 widgets/misc/e-mail-signature-editor.h        |   82 +++
 widgets/misc/e-mail-signature-manager.c       |  705 +++++++++++++++++++
 widgets/misc/e-mail-signature-manager.h       |   88 +++
 widgets/misc/e-mail-signature-preview.c       |  360 ++++++++++
 widgets/misc/e-mail-signature-preview.h       |   79 +++
 widgets/misc/e-mail-signature-script-dialog.c |  732 ++++++++++++++++++++
 widgets/misc/e-mail-signature-script-dialog.h |   90 +++
 widgets/misc/e-mail-signature-tree-view.c     |  397 +++++++++++
 widgets/misc/e-mail-signature-tree-view.h     |   76 ++
 widgets/misc/e-signature-combo-box.c          |  346 ----------
 widgets/misc/e-signature-combo-box.h          |   78 ---
 widgets/misc/e-signature-editor.c             |  587 ----------------
 widgets/misc/e-signature-editor.h             |   73 --
 widgets/misc/e-signature-manager.c            |  758 ---------------------
 widgets/misc/e-signature-manager.h            |  100 ---
 widgets/misc/e-signature-preview.c            |  308 ---------
 widgets/misc/e-signature-preview.h            |   81 ---
 widgets/misc/e-signature-script-dialog.c      |  440 ------------
 widgets/misc/e-signature-script-dialog.h      |   76 --
 widgets/misc/e-signature-tree-view.c          |  431 ------------
 widgets/misc/e-signature-tree-view.h          |   78 ---
 widgets/misc/test-mail-accounts.c             |   61 --
 widgets/misc/test-mail-signatures.c           |  199 ++++++
 widgets/misc/test-source-config.c             |    3 +-
 widgets/misc/widgets.error.xml                |   28 +
 39 files changed, 4789 insertions(+), 4643 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b58380a..2f42762 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -472,6 +472,7 @@ widgets/misc/e-spell-entry.c
 widgets/misc/e-url-entry.c
 widgets/misc/e-web-view.c
 widgets/misc/e-web-view-gtkhtml.c
+widgets/misc/widgets.error.xml
 widgets/table/e-cell-combo.c
 widgets/table/e-cell-date.c
 widgets/table/e-cell-date-edit.c
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am
index bb3c662..d592686 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -37,9 +37,13 @@ widgetsinclude_HEADERS =			\
 	e-image-chooser.h			\
 	e-import-assistant.h			\
 	e-interval-chooser.h			\
-	e-mail-account-manager.h		\
-	e-mail-account-tree-view.h		\
 	e-mail-identity-combo-box.h		\
+	e-mail-signature-combo-box.h		\
+	e-mail-signature-editor.h		\
+	e-mail-signature-manager.h		\
+	e-mail-signature-preview.h		\
+	e-mail-signature-script-dialog.h	\
+	e-mail-signature-tree-view.h		\
 	e-map.h					\
 	e-menu-tool-action.h			\
 	e-menu-tool-button.h			\
@@ -57,12 +61,6 @@ widgetsinclude_HEADERS =			\
 	e-selection-model-array.h		\
 	e-selection-model-simple.h		\
 	e-send-options.h			\
-	e-signature-combo-box.h			\
-	e-signature-editor.h			\
-	e-signature-manager.h			\
-	e-signature-preview.h			\
-	e-signature-script-dialog.h		\
-	e-signature-tree-view.h			\
 	e-source-config.h			\
 	e-source-config-backend.h		\
 	e-source-config-dialog.h		\
@@ -127,9 +125,13 @@ libemiscwidgets_la_SOURCES =			\
 	e-image-chooser.c			\
 	e-import-assistant.c			\
 	e-interval-chooser.c			\
-	e-mail-account-manager.c		\
-	e-mail-account-tree-view.c		\
 	e-mail-identity-combo-box.c		\
+	e-mail-signature-combo-box.c		\
+	e-mail-signature-editor.c		\
+	e-mail-signature-manager.c		\
+	e-mail-signature-preview.c		\
+	e-mail-signature-script-dialog.c	\
+	e-mail-signature-tree-view.c		\
 	e-map.c					\
 	e-menu-tool-action.c			\
 	e-menu-tool-button.c			\
@@ -147,12 +149,6 @@ libemiscwidgets_la_SOURCES =			\
 	e-selection-model-array.c		\
 	e-selection-model-simple.c		\
 	e-send-options.c			\
-	e-signature-combo-box.c			\
-	e-signature-editor.c			\
-	e-signature-manager.c			\
-	e-signature-preview.c			\
-	e-signature-script-dialog.c		\
-	e-signature-tree-view.c			\
 	e-source-config.c			\
 	e-source-config-backend.c		\
 	e-source-config-dialog.c		\
@@ -185,9 +181,15 @@ libemiscwidgets_la_LIBADD =					\
 	$(CLUTTER_LIBS)						\
 	$(GTKHTML_LIBS)
 
+error_DATA = widgets.error
+errordir = $(privdatadir)/errors
+# provides error rules too
+ EVO_PLUGIN_RULE@
+
 noinst_PROGRAMS = 			\
 	test-calendar			\
 	test-dateedit			\
+	test-mail-signatures		\
 	test-preferences-window		\
 	test-source-config
 
@@ -205,8 +207,7 @@ test_widgets_misc_CPPFLAGS=						\
 
 test_calendar_CPPFLAGS = $(test_widgets_misc_CPPFLAGS)
 
-test_calendar_SOURCES = 	\
-	test-calendar.c
+test_calendar_SOURCES = test-calendar.c
 
 test_calendar_LDADD = 				\
 	libemiscwidgets.la			\
@@ -219,8 +220,7 @@ test_calendar_LDADD = 				\
 
 test_dateedit_CPPFLAGS = $(test_widgets_misc_CPPFLAGS)
 
-test_dateedit_SOURCES = 	\
-	test-dateedit.c
+test_dateedit_SOURCES = test-dateedit.c
 
 test_dateedit_LDADD = 				\
 	libemiscwidgets.la			\
@@ -229,12 +229,28 @@ test_dateedit_LDADD = 				\
 	$(EVOLUTION_DATA_SERVER_LIBS)		\
 	$(GNOME_PLATFORM_LIBS)
 
+# test-mail-signatures
+
+test_mail_signatures_CPPFLAGS =			\
+	$(test_widgets_misc_CPPFLAGS)		\
+	$(GTKHTML_CFLAGS)
+
+test_mail_signatures_SOURCES = test-mail-signatures.c
+
+test_mail_signatures_LDADD =			\
+	libemiscwidgets.la			\
+	$(top_builddir)/e-util/libeutil.la	\
+	$(top_builddir)/filter/libfilter.la	\
+	$(top_builddir)/libevolution-utils/libevolution-utils.la \
+	$(EVOLUTION_DATA_SERVER_LIBS)		\
+	$(GNOME_PLATFORM_LIBS)			\
+	$(GTKHTML_LIBS)
+
 # test-preferences-window
 
 test_preferences_window_CPPFLAGS = $(test_widgets_misc_CPPFLAGS)
 
-test_preferences_window_SOURCES =		\
-	test-preferences-window.c
+test_preferences_window_SOURCES = test-preferences-window.c
 
 test_preferences_window_LDADD = 		\
 	libemiscwidgets.la			\
@@ -247,8 +263,7 @@ test_preferences_window_LDADD = 		\
 
 test_source_config_CPPFLAGS = $(test_widgets_misc_CPPFLAGS)
 
-test_source_config_SOURCES =			\
-	test-source-config.c
+test_source_config_SOURCES = test-source-config.c
 
 test_source_config_LDADD = 			\
 	libemiscwidgets.la			\
@@ -257,6 +272,8 @@ test_source_config_LDADD = 			\
 	$(EVOLUTION_DATA_SERVER_LIBS)		\
 	$(GNOME_PLATFORM_LIBS)
 
-EXTRA_DIST = $(ui_DATA)
+BUILT_SOURCES = $(error_DATA)
+
+EXTRA_DIST = $(ui_DATA) widgets.error.xml
 
 -include $(top_srcdir)/git.mk
diff --git a/widgets/misc/e-alarm-selector.c b/widgets/misc/e-alarm-selector.c
new file mode 100644
index 0000000..3a61663
--- /dev/null
+++ b/widgets/misc/e-alarm-selector.c
@@ -0,0 +1,97 @@
+/*
+ * e-alarm-selector.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-alarm-selector.h"
+
+#include <libedataserver/e-source-alarms.h>
+#include <libedataserver/e-source-calendar.h>
+
+G_DEFINE_TYPE (
+	EAlarmSelector,
+	e_alarm_selector,
+	E_TYPE_SOURCE_SELECTOR)
+
+static gboolean
+alarm_selector_get_source_selected (ESourceSelector *selector,
+                                    ESource *source)
+{
+	ESourceAlarms *extension;
+	const gchar *extension_name;
+
+	/* Make sure this source is a calendar. */
+	extension_name = e_source_selector_get_extension_name (selector);
+	if (!e_source_has_extension (source, extension_name))
+		return FALSE;
+
+	extension_name = E_SOURCE_EXTENSION_ALARMS;
+	extension = e_source_get_extension (source, extension_name);
+	g_return_val_if_fail (E_IS_SOURCE_ALARMS (extension), FALSE);
+
+	return e_source_alarms_get_include_me (extension);
+}
+
+static void
+alarm_selector_set_source_selected (ESourceSelector *selector,
+                                    ESource *source,
+                                    gboolean selected)
+{
+	ESourceAlarms *extension;
+	const gchar *extension_name;
+
+	/* Make sure this source is a calendar. */
+	extension_name = e_source_selector_get_extension_name (selector);
+	if (!e_source_has_extension (source, extension_name))
+		return;
+
+	extension_name = E_SOURCE_EXTENSION_ALARMS;
+	extension = e_source_get_extension (source, extension_name);
+	g_return_if_fail (E_IS_SOURCE_ALARMS (extension));
+
+	if (selected != e_source_alarms_get_include_me (extension)) {
+		e_source_alarms_set_include_me (extension, selected);
+		e_source_selector_queue_write (selector, source);
+	}
+}
+
+static void
+e_alarm_selector_class_init (EAlarmSelectorClass *class)
+{
+	ESourceSelectorClass *source_selector_class;
+
+	source_selector_class = E_SOURCE_SELECTOR_CLASS (class);
+	source_selector_class->get_source_selected =
+					alarm_selector_get_source_selected;
+	source_selector_class->set_source_selected =
+					alarm_selector_set_source_selected;
+}
+
+static void
+e_alarm_selector_init (EAlarmSelector *selector)
+{
+}
+
+GtkWidget *
+e_alarm_selector_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_ALARM_SELECTOR,
+		"extension-name", E_SOURCE_EXTENSION_CALENDAR,
+		"registry", registry, NULL);
+}
diff --git a/widgets/misc/e-auth-combo-box.c b/widgets/misc/e-auth-combo-box.c
index 182e171..bd3d8c7 100644
--- a/widgets/misc/e-auth-combo-box.c
+++ b/widgets/misc/e-auth-combo-box.c
@@ -185,6 +185,12 @@ e_auth_combo_box_init (EAuthComboBox *combo_box)
 	combo_box->priv = E_AUTH_COMBO_BOX_GET_PRIVATE (combo_box);
 }
 
+GtkWidget *
+e_auth_combo_box_new (void)
+{
+	return g_object_new (E_TYPE_AUTH_COMBO_BOX, NULL);
+}
+
 CamelProvider *
 e_auth_combo_box_get_provider (EAuthComboBox *combo_box)
 {
diff --git a/widgets/misc/e-autocomplete-selector.c b/widgets/misc/e-autocomplete-selector.c
new file mode 100644
index 0000000..38caa83
--- /dev/null
+++ b/widgets/misc/e-autocomplete-selector.c
@@ -0,0 +1,99 @@
+/*
+ * e-autocomplete-selector.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-autocomplete-selector.h"
+
+#include <libedataserver/e-source-address-book.h>
+#include <libedataserver/e-source-autocomplete.h>
+
+G_DEFINE_TYPE (
+	EAutocompleteSelector,
+	e_autocomplete_selector,
+	E_TYPE_SOURCE_SELECTOR)
+
+static gboolean
+autocomplete_selector_get_source_selected (ESourceSelector *selector,
+                                           ESource *source)
+{
+	ESourceAutocomplete *extension;
+	const gchar *extension_name;
+
+	/* Make sure this source is an address book. */
+	extension_name = e_source_selector_get_extension_name (selector);
+	if (!e_source_has_extension (source, extension_name))
+		return FALSE;
+
+	extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+	extension = e_source_get_extension (source, extension_name);
+	g_return_val_if_fail (E_IS_SOURCE_AUTOCOMPLETE (extension), FALSE);
+
+	return e_source_autocomplete_get_include_me (extension);
+}
+
+static void
+autocomplete_selector_set_source_selected (ESourceSelector *selector,
+                                           ESource *source,
+                                           gboolean selected)
+{
+	ESourceAutocomplete *extension;
+	const gchar *extension_name;
+
+	/* Make sure this source is an address book. */
+	extension_name = e_source_selector_get_extension_name (selector);
+	if (!e_source_has_extension (source, extension_name))
+		return;
+
+	extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+	extension = e_source_get_extension (source, extension_name);
+	g_return_if_fail (E_IS_SOURCE_AUTOCOMPLETE (extension));
+
+	if (selected != e_source_autocomplete_get_include_me (extension)) {
+		e_source_autocomplete_set_include_me (extension, selected);
+		e_source_selector_queue_write (selector, source);
+	}
+}
+
+static void
+e_autocomplete_selector_class_init (EAutocompleteSelectorClass *class)
+{
+	ESourceSelectorClass *source_selector_class;
+
+	source_selector_class = E_SOURCE_SELECTOR_CLASS (class);
+	source_selector_class->get_source_selected =
+				autocomplete_selector_get_source_selected;
+	source_selector_class->set_source_selected =
+				autocomplete_selector_set_source_selected;
+}
+
+static void
+e_autocomplete_selector_init (EAutocompleteSelector *selector)
+{
+	e_source_selector_set_show_colors (
+		E_SOURCE_SELECTOR (selector), FALSE);
+}
+
+GtkWidget *
+e_autocomplete_selector_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_AUTOCOMPLETE_SELECTOR,
+		"extension-name", E_SOURCE_EXTENSION_ADDRESS_BOOK,
+		"registry", registry, NULL);
+}
diff --git a/widgets/misc/e-mail-identity-combo-box.c b/widgets/misc/e-mail-identity-combo-box.c
index 4b83015..e43f41d 100644
--- a/widgets/misc/e-mail-identity-combo-box.c
+++ b/widgets/misc/e-mail-identity-combo-box.c
@@ -79,32 +79,20 @@ mail_identity_combo_box_registry_changed (ESourceRegistry *registry,
 		combo_box);
 }
 
-static ESource *
-mail_identity_combo_box_get_default (EMailIdentityComboBox *combo_box)
+static void
+mail_identity_combo_box_activate_default (EMailIdentityComboBox *combo_box)
 {
 	ESource *source;
 	ESourceRegistry *registry;
-	ESourceMailAccount *mail_account;
-	const gchar *extension_name;
-	const gchar *uid;
 
-	extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
 	registry = e_mail_identity_combo_box_get_registry (combo_box);
-	source = e_source_registry_get_default_mail_account (registry);
-
-	if (source == NULL)
-		return NULL;
-
-	if (!e_source_has_extension (source, extension_name))
-		return NULL;
-
-	mail_account = e_source_get_extension (source, extension_name);
-	uid = e_source_mail_account_get_identity (mail_account);
-
-	if (uid == NULL)
-		return NULL;
+	source = e_source_registry_ref_default_mail_identity (registry);
 
-	return e_source_registry_lookup_by_uid (registry, uid);
+	if (source != NULL) {
+		const gchar *uid = e_source_get_uid (source);
+		gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
+		g_object_unref (source);
+	}
 }
 
 static void
@@ -225,7 +213,8 @@ e_mail_identity_combo_box_class_init (EMailIdentityComboBoxClass *class)
 {
 	GObjectClass *object_class;
 
-	g_type_class_add_private (class, sizeof (EMailIdentityComboBoxPrivate));
+	g_type_class_add_private (
+		class, sizeof (EMailIdentityComboBoxPrivate));
 
 	object_class = G_OBJECT_CLASS (class);
 	object_class->set_property = mail_identity_combo_box_set_property;
@@ -267,10 +256,12 @@ e_mail_identity_combo_box_refresh (EMailIdentityComboBox *combo_box)
 {
 	ESourceRegistry *registry;
 	GtkTreeModel *tree_model;
+	GtkComboBox *gtk_combo_box;
 	ESource *source;
 	GList *list, *link;
+	GHashTable *address_table;
 	const gchar *extension_name;
-	gchar *saved_uid = NULL;
+	const gchar *saved_uid;
 
 	g_return_if_fail (E_IS_MAIL_IDENTITY_COMBO_BOX (combo_box));
 
@@ -279,55 +270,112 @@ e_mail_identity_combo_box_refresh (EMailIdentityComboBox *combo_box)
 		combo_box->priv->refresh_idle_id = 0;
 	}
 
-	registry = e_mail_identity_combo_box_get_registry (combo_box);
-	tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+	gtk_combo_box = GTK_COMBO_BOX (combo_box);
+	tree_model = gtk_combo_box_get_model (gtk_combo_box);
 
-	source = e_mail_identity_combo_box_get_active_source (combo_box);
-	if (source != NULL)
-		saved_uid = g_strdup (e_source_get_uid (source));
+	/* This is an interned string, which means it's safe
+	 * to use even after clearing the combo box model. */
+	saved_uid = gtk_combo_box_get_active_id (gtk_combo_box);
 
 	gtk_list_store_clear (GTK_LIST_STORE (tree_model));
 
 	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+	registry = e_mail_identity_combo_box_get_registry (combo_box);
 	list = e_source_registry_list_sources (registry, extension_name);
 
+	/* Build a hash table of GQueues by email address so we can
+	 * spot duplicate email addresses.  Then if the GQueue for a
+	 * given email address has multiple elements, we use a more
+	 * verbose description in the combo box. */
+
+	address_table = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_queue_free);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESourceMailIdentity *extension;
+		GQueue *queue;
+		const gchar *address;
+
+		source = E_SOURCE (link->data);
+		extension = e_source_get_extension (source, extension_name);
+		address = e_source_mail_identity_get_address (extension);
+
+		if (address == NULL)
+			continue;
+
+		queue = g_hash_table_lookup (address_table, address);
+		if (queue == NULL) {
+			queue = g_queue_new ();
+			g_hash_table_insert (
+				address_table,
+				g_strdup (address), queue);
+		}
+
+		g_queue_push_tail (queue, source);
+	}
+
 	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESourceMailIdentity *extension;
 		GtkTreeIter iter;
+		GQueue *queue;
+		GString *string;
+		const gchar *address;
 		const gchar *display_name;
+		const gchar *name;
 		const gchar *uid;
 
 		source = E_SOURCE (link->data);
+		if (!e_source_get_enabled (source))
+			continue;
+
+		extension = e_source_get_extension (source, extension_name);
+		name = e_source_mail_identity_get_name (extension);
+		address = e_source_mail_identity_get_address (extension);
+
+		if (name == NULL || address == NULL)
+			continue;
+
+		queue = g_hash_table_lookup (address_table, address);
+
 		display_name = e_source_get_display_name (source);
 		uid = e_source_get_uid (source);
 
+		string = g_string_sized_new (512);
+		g_string_append_printf (string, "%s <%s>", name, address);
+
+		/* Show the account name for duplicate email addresses. */
+		if (queue != NULL && g_queue_get_length (queue) > 1)
+			g_string_append_printf (string, " (%s)", display_name);
+
 		gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
 
 		gtk_list_store_set (
 			GTK_LIST_STORE (tree_model), &iter,
-			COLUMN_DISPLAY_NAME, display_name,
+			COLUMN_DISPLAY_NAME, string->str,
 			COLUMN_UID, uid, -1);
+
+		g_string_free (string, TRUE);
 	}
 
-	g_list_free (list);
+	g_hash_table_destroy (address_table);
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
 
 	/* Try and restore the previous selected source, or else pick
 	 * the default identity of the default mail account.  If even
 	 * that fails, just pick the first item. */
 
-	source = NULL;
+	if (saved_uid != NULL)
+		gtk_combo_box_set_active_id (gtk_combo_box, saved_uid);
 
-	if (saved_uid != NULL) {
-		source = e_source_registry_lookup_by_uid (registry, saved_uid);
-		g_free (saved_uid);
-	}
-
-	if (source == NULL)
-		source = mail_identity_combo_box_get_default (combo_box);
+	if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL)
+		mail_identity_combo_box_activate_default (combo_box);
 
-	if (source != NULL)
-		e_mail_identity_combo_box_set_active_source (combo_box, source);
-	else
-		gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+	if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL)
+		gtk_combo_box_set_active (gtk_combo_box, 0);
 }
 
 ESourceRegistry *
@@ -337,38 +385,3 @@ e_mail_identity_combo_box_get_registry (EMailIdentityComboBox *combo_box)
 
 	return combo_box->priv->registry;
 }
-
-ESource *
-e_mail_identity_combo_box_get_active_source (EMailIdentityComboBox *combo_box)
-{
-	ESourceRegistry *registry;
-	ESource *source = NULL;
-	const gchar *uid;
-
-	g_return_val_if_fail (E_IS_MAIL_IDENTITY_COMBO_BOX (combo_box), NULL);
-
-	registry = e_mail_identity_combo_box_get_registry (combo_box);
-	uid = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
-
-	if (uid != NULL)
-		source = e_source_registry_lookup_by_uid (registry, uid);
-
-	return source;
-}
-
-void
-e_mail_identity_combo_box_set_active_source (EMailIdentityComboBox *combo_box,
-                                             ESource *active_source)
-{
-	const gchar *uid;
-
-	g_return_if_fail (E_IS_MAIL_IDENTITY_COMBO_BOX (combo_box));
-	g_return_if_fail (E_IS_SOURCE (active_source));
-
-	/* It is a programming error to pass an ESource that has no
-	 * "Mail Identity" extension. */
-	g_return_if_fail (SOURCE_IS_MAIL_IDENTITY (active_source));
-
-	uid = e_source_get_uid (active_source);
-	gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
-}
diff --git a/widgets/misc/e-mail-identity-combo-box.h b/widgets/misc/e-mail-identity-combo-box.h
index b40957d..a94e0b2 100644
--- a/widgets/misc/e-mail-identity-combo-box.h
+++ b/widgets/misc/e-mail-identity-combo-box.h
@@ -65,11 +65,6 @@ void		e_mail_identity_combo_box_refresh
 ESourceRegistry *
 		e_mail_identity_combo_box_get_registry
 					(EMailIdentityComboBox *combo_box);
-ESource *	e_mail_identity_combo_box_get_active_source
-					(EMailIdentityComboBox *combo_box);
-void		e_mail_identity_combo_box_set_active_source
-					(EMailIdentityComboBox *combo_box,
-					 ESource *active_source);
 
 G_END_DECLS
 
diff --git a/widgets/misc/e-mail-signature-combo-box.c b/widgets/misc/e-mail-signature-combo-box.c
new file mode 100644
index 0000000..b2c749a
--- /dev/null
+++ b/widgets/misc/e-mail-signature-combo-box.c
@@ -0,0 +1,617 @@
+/*
+ * e-mail-signature-combo-box.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-combo-box.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-source-mail-identity.h>
+#include <libedataserver/e-source-mail-signature.h>
+
+#define E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBoxPrivate))
+
+#define SOURCE_IS_MAIL_SIGNATURE(source) \
+	(e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_SIGNATURE))
+
+struct _EMailSignatureComboBoxPrivate {
+	ESourceRegistry *registry;
+	guint refresh_idle_id;
+	gchar *identity_uid;
+};
+
+enum {
+	PROP_0,
+	PROP_IDENTITY_UID,
+	PROP_REGISTRY
+};
+
+enum {
+	COLUMN_DISPLAY_NAME,
+	COLUMN_UID
+};
+
+G_DEFINE_TYPE (
+	EMailSignatureComboBox,
+	e_mail_signature_combo_box,
+	GTK_TYPE_COMBO_BOX)
+
+static gboolean
+mail_signature_combo_box_refresh_idle_cb (EMailSignatureComboBox *combo_box)
+{
+	/* The refresh function will clear the idle ID. */
+	e_mail_signature_combo_box_refresh (combo_box);
+
+	return FALSE;
+}
+
+static void
+mail_signature_combo_box_registry_changed (ESourceRegistry *registry,
+                                           ESource *source,
+                                           EMailSignatureComboBox *combo_box)
+{
+	/* If the ESource in question has a "Mail Signature" extension,
+	 * schedule a refresh of the tree model.  Otherwise ignore it.
+	 * We use an idle callback to limit how frequently we refresh
+	 * the tree model, in case the registry is emitting lots of
+	 * signals at once. */
+
+	if (!SOURCE_IS_MAIL_SIGNATURE (source))
+		return;
+
+	if (combo_box->priv->refresh_idle_id > 0)
+		return;
+
+	combo_box->priv->refresh_idle_id = gdk_threads_add_idle (
+		(GSourceFunc) mail_signature_combo_box_refresh_idle_cb,
+		combo_box);
+}
+
+static void
+mail_signature_combo_box_set_registry (EMailSignatureComboBox *combo_box,
+                                       ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (combo_box->priv->registry == NULL);
+
+	combo_box->priv->registry = g_object_ref (registry);
+
+	g_signal_connect (
+		registry, "source-added",
+		G_CALLBACK (mail_signature_combo_box_registry_changed),
+		combo_box);
+
+	g_signal_connect (
+		registry, "source-changed",
+		G_CALLBACK (mail_signature_combo_box_registry_changed),
+		combo_box);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (mail_signature_combo_box_registry_changed),
+		combo_box);
+}
+
+static void
+mail_signature_combo_box_set_property (GObject *object,
+                                       guint property_id,
+                                       const GValue *value,
+                                       GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_IDENTITY_UID:
+			e_mail_signature_combo_box_set_identity_uid (
+				E_MAIL_SIGNATURE_COMBO_BOX (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_REGISTRY:
+			mail_signature_combo_box_set_registry (
+				E_MAIL_SIGNATURE_COMBO_BOX (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_combo_box_get_property (GObject *object,
+                                       guint property_id,
+                                       GValue *value,
+                                       GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_IDENTITY_UID:
+			g_value_set_string (
+				value,
+				e_mail_signature_combo_box_get_identity_uid (
+				E_MAIL_SIGNATURE_COMBO_BOX (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_combo_box_get_registry (
+				E_MAIL_SIGNATURE_COMBO_BOX (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_combo_box_dispose (GObject *object)
+{
+	EMailSignatureComboBoxPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->registry, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->refresh_idle_id > 0) {
+		g_source_remove (priv->refresh_idle_id);
+		priv->refresh_idle_id = 0;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_combo_box_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_combo_box_finalize (GObject *object)
+{
+	EMailSignatureComboBoxPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE (object);
+
+	g_free (priv->identity_uid);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_mail_signature_combo_box_parent_class)->
+		finalize (object);
+}
+
+static void
+mail_signature_combo_box_constructed (GObject *object)
+{
+	GtkListStore *list_store;
+	GtkComboBox *combo_box;
+	GtkCellLayout *cell_layout;
+	GtkCellRenderer *cell_renderer;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_mail_signature_combo_box_parent_class)->
+		constructed (object);
+
+	combo_box = GTK_COMBO_BOX (object);
+	cell_layout = GTK_CELL_LAYOUT (object);
+
+	list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+	gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (list_store));
+	gtk_combo_box_set_id_column (combo_box, COLUMN_UID);
+	g_object_unref (list_store);
+
+	cell_renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (cell_layout, cell_renderer, TRUE);
+	gtk_cell_layout_add_attribute (
+		cell_layout, cell_renderer, "text", COLUMN_DISPLAY_NAME);
+
+	e_mail_signature_combo_box_refresh (
+		E_MAIL_SIGNATURE_COMBO_BOX (object));
+}
+
+static void
+e_mail_signature_combo_box_class_init (EMailSignatureComboBoxClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (
+		class, sizeof (EMailSignatureComboBoxPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_combo_box_set_property;
+	object_class->get_property = mail_signature_combo_box_get_property;
+	object_class->dispose = mail_signature_combo_box_dispose;
+	object_class->finalize = mail_signature_combo_box_finalize;
+	object_class->constructed = mail_signature_combo_box_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_IDENTITY_UID,
+		g_param_spec_string (
+			"identity-uid",
+			"Identity UID",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_mail_signature_combo_box_init (EMailSignatureComboBox *combo_box)
+{
+	combo_box->priv = E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE (combo_box);
+}
+
+GtkWidget *
+e_mail_signature_combo_box_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_COMBO_BOX,
+		"registry", registry, NULL);
+}
+
+void
+e_mail_signature_combo_box_refresh (EMailSignatureComboBox *combo_box)
+{
+	ESourceRegistry *registry;
+	GtkComboBox *gtk_combo_box;
+	GtkTreeModel *tree_model;
+	GtkTreeIter iter;
+	ESource *source;
+	GList *list, *link;
+	const gchar *extension_name;
+	const gchar *saved_uid;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box));
+
+	if (combo_box->priv->refresh_idle_id > 0) {
+		g_source_remove (combo_box->priv->refresh_idle_id);
+		combo_box->priv->refresh_idle_id = 0;
+	}
+
+	gtk_combo_box = GTK_COMBO_BOX (combo_box);
+	tree_model = gtk_combo_box_get_model (gtk_combo_box);
+
+	/* This is an interned string, which means it's safe
+	 * to use even after clearing the combo box model. */
+	saved_uid = gtk_combo_box_get_active_id (gtk_combo_box);
+
+	gtk_list_store_clear (GTK_LIST_STORE (tree_model));
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	registry = e_mail_signature_combo_box_get_registry (combo_box);
+	list = e_source_registry_list_sources (registry, extension_name);
+
+	/* The "None" option always comes first. */
+
+	gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (tree_model), &iter,
+		COLUMN_DISPLAY_NAME, _("None"),
+		COLUMN_UID, "none", -1);
+
+	/* The "autogenerated" UID has special meaning. */
+
+	gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (tree_model), &iter,
+		COLUMN_DISPLAY_NAME, _("Autogenerated"),
+		COLUMN_UID, E_MAIL_SIGNATURE_AUTOGENERATED_UID, -1);
+
+	/* Followed by the other mail signatures, alphabetized. */
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		GtkTreeIter iter;
+		const gchar *display_name;
+		const gchar *uid;
+
+		source = E_SOURCE (link->data);
+		display_name = e_source_get_display_name (source);
+		uid = e_source_get_uid (source);
+
+		gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+		gtk_list_store_set (
+			GTK_LIST_STORE (tree_model), &iter,
+			COLUMN_DISPLAY_NAME, display_name,
+			COLUMN_UID, uid, -1);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	/* Try and restore the previous selected source, or else "None". */
+
+	if (saved_uid != NULL)
+		gtk_combo_box_set_active_id (gtk_combo_box, saved_uid);
+
+	if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL)
+		gtk_combo_box_set_active (gtk_combo_box, 0);
+}
+
+ESourceRegistry *
+e_mail_signature_combo_box_get_registry (EMailSignatureComboBox *combo_box)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box), NULL);
+
+	return combo_box->priv->registry;
+}
+
+const gchar *
+e_mail_signature_combo_box_get_identity_uid (EMailSignatureComboBox *combo_box)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box), NULL);
+
+	return combo_box->priv->identity_uid;
+}
+
+void
+e_mail_signature_combo_box_set_identity_uid (EMailSignatureComboBox *combo_box,
+                                             const gchar *identity_uid)
+{
+	const gchar *active_id;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box));
+
+	g_free (combo_box->priv->identity_uid);
+	combo_box->priv->identity_uid = g_strdup (identity_uid);
+
+	g_object_notify (G_OBJECT (combo_box), "identity-uid");
+
+	/* If "Autogenerated" is selected, emit a "changed" signal as
+	 * a hint to whomever is listening to reload the signature. */
+	active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
+	if (g_strcmp0 (active_id, E_MAIL_SIGNATURE_AUTOGENERATED_UID) == 0)
+		g_signal_emit_by_name (combo_box, "changed");
+}
+
+/**************** e_mail_signature_combo_box_load_selected() *****************/
+
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+	gchar *contents;
+	gsize length;
+	gboolean is_html;
+};
+
+static void
+load_context_free (LoadContext *context)
+{
+	g_free (context->contents);
+	g_slice_free (LoadContext, context);
+}
+
+static void
+mail_signature_combo_box_autogenerate (EMailSignatureComboBox *combo_box,
+                                       LoadContext *context)
+{
+	ESourceMailIdentity *extension;
+	ESourceRegistry *registry;
+	ESource *source;
+	GString *buffer;
+	const gchar *extension_name;
+	const gchar *identity_uid;
+	const gchar *text;
+	gchar *escaped;
+
+	identity_uid = e_mail_signature_combo_box_get_identity_uid (combo_box);
+
+	/* If we have no mail identity UID, handle it as though
+	 * "None" were selected.  No need to report an error. */
+	if (identity_uid == NULL)
+		return;
+
+	registry = e_mail_signature_combo_box_get_registry (combo_box);
+	source = e_source_registry_ref_source (registry, identity_uid);
+
+	/* If the mail identity lookup fails, handle it as though
+	 * "None" were selected.  No need to report an error. */
+	if (source == NULL)
+		return;
+
+	/* If the source is not actually a mail identity, handle it as
+	 * though "None" were selected.  No need to report an error. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+	if (!e_source_has_extension (source, extension_name)) {
+		g_object_unref (source);
+		return;
+	}
+
+	extension = e_source_get_extension (source, extension_name);
+
+	/* The autogenerated signature format is:
+	 *
+	 *   <NAME> <ADDRESS>
+	 *   <ORGANIZATION>
+	 *
+	 * The <ADDRESS> is a mailto link and
+	 * the <ORGANIZATION> line is optional.
+	 */
+
+	buffer = g_string_sized_new (512);
+
+	text = e_source_mail_identity_get_name (extension);
+	escaped = (text != NULL) ? g_markup_escape_text (text, -1) : NULL;
+	if (escaped != NULL && *escaped != '\0')
+		g_string_append (buffer, escaped);
+	g_free (escaped);
+
+	text = e_source_mail_identity_get_address (extension);
+	escaped = (text != NULL) ? g_markup_escape_text (text, -1) : NULL;
+	if (escaped != NULL && *escaped != '\0')
+		g_string_append_printf (
+			buffer, " &lt;<a href=\"mailto:%s\";>%s</a>&gt;",
+			escaped, escaped);
+	g_free (escaped);
+
+	text = e_source_mail_identity_get_organization (extension);
+	escaped = (text != NULL) ? g_markup_escape_text (text, -1) : NULL;
+	if (escaped != NULL && *escaped != '\0')
+		g_string_append_printf (buffer, "<br>%s", escaped);
+	g_free (escaped);
+
+	context->length = buffer->len;
+	context->contents = g_string_free (buffer, FALSE);
+	context->is_html = TRUE;
+
+	g_object_unref (source);
+}
+
+static void
+mail_signature_combo_box_load_cb (ESource *source,
+                                  GAsyncResult *result,
+                                  GSimpleAsyncResult *simple)
+{
+	ESourceMailSignature *extension;
+	LoadContext *context;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	GError *error = NULL;
+
+	context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	e_source_mail_signature_load_finish (
+		source, result, &context->contents, &context->length, &error);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+		g_error_free (error);
+		return;
+	}
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	mime_type = e_source_mail_signature_get_mime_type (extension);
+	context->is_html = (g_strcmp0 (mime_type, "text/html") == 0);
+
+	g_simple_async_result_complete (simple);
+
+	g_object_unref (simple);
+}
+
+void
+e_mail_signature_combo_box_load_selected (EMailSignatureComboBox *combo_box,
+                                          gint io_priority,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	ESourceRegistry *registry;
+	LoadContext *context;
+	ESource *source;
+	const gchar *active_id;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box));
+
+	context = g_slice_new0 (LoadContext);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (combo_box), callback, user_data,
+		e_mail_signature_combo_box_load_selected);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, context, (GDestroyNotify) load_context_free);
+
+	active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
+
+	if (active_id == NULL) {
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	if (g_strcmp0 (active_id, E_MAIL_SIGNATURE_AUTOGENERATED_UID) == 0) {
+		mail_signature_combo_box_autogenerate (combo_box, context);
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	registry = e_mail_signature_combo_box_get_registry (combo_box);
+	source = e_source_registry_ref_source (registry, active_id);
+
+	/* If for some reason the ESource lookup fails, handle it as
+	 * though "None" were selected.  No need to report an error. */
+	if (source == NULL) {
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	e_source_mail_signature_load (
+		source, io_priority, cancellable, (GAsyncReadyCallback)
+		mail_signature_combo_box_load_cb, simple);
+
+	g_object_unref (source);
+}
+
+gboolean
+e_mail_signature_combo_box_load_selected_finish (EMailSignatureComboBox *combo_box,
+                                                 GAsyncResult *result,
+                                                 gchar **contents,
+                                                 gsize *length,
+                                                 gboolean *is_html,
+                                                 GError **error)
+{
+	GSimpleAsyncResult *simple;
+	LoadContext *context;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (combo_box),
+		e_mail_signature_combo_box_load_selected), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+
+	if (contents != NULL) {
+		*contents = context->contents;
+		context->contents = NULL;
+	}
+
+	if (length != NULL)
+		*length = context->length;
+
+	if (is_html != NULL)
+		*is_html = context->is_html;
+
+	return TRUE;
+}
diff --git a/widgets/misc/e-mail-signature-combo-box.h b/widgets/misc/e-mail-signature-combo-box.h
new file mode 100644
index 0000000..49abc50
--- /dev/null
+++ b/widgets/misc/e-mail-signature-combo-box.h
@@ -0,0 +1,91 @@
+/*
+ * e-mail-signature-combo-box.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SIGNATURE_COMBO_BOX_H
+#define E_MAIL_SIGNATURE_COMBO_BOX_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/e-source-registry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_COMBO_BOX \
+	(e_mail_signature_combo_box_get_type ())
+#define E_MAIL_SIGNATURE_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBox))
+#define E_MAIL_SIGNATURE_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBoxClass))
+#define E_IS_MAIL_SIGNATURE_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX))
+#define E_IS_MAIL_SIGNATURE_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_COMBO_BOX))
+#define E_MAIL_SIGNATURE_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBoxClass))
+
+#define E_MAIL_SIGNATURE_AUTOGENERATED_UID "autogenerated"
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureComboBox EMailSignatureComboBox;
+typedef struct _EMailSignatureComboBoxClass EMailSignatureComboBoxClass;
+typedef struct _EMailSignatureComboBoxPrivate EMailSignatureComboBoxPrivate;
+
+struct _EMailSignatureComboBox {
+	GtkComboBox parent;
+	EMailSignatureComboBoxPrivate *priv;
+};
+
+struct _EMailSignatureComboBoxClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_mail_signature_combo_box_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_combo_box_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_combo_box_refresh
+					(EMailSignatureComboBox *combo_box);
+ESourceRegistry *
+		e_mail_signature_combo_box_get_registry
+					(EMailSignatureComboBox *combo_box);
+const gchar *	e_mail_signature_combo_box_get_identity_uid
+					(EMailSignatureComboBox *combo_box);
+void		e_mail_signature_combo_box_set_identity_uid
+					(EMailSignatureComboBox *combo_box,
+					 const gchar *identity_uid);
+void		e_mail_signature_combo_box_load_selected
+					(EMailSignatureComboBox *combo_box,
+					 gint io_priority,
+					 GCancellable *cancellable,
+					 GAsyncReadyCallback callback,
+					 gpointer user_data);
+gboolean	e_mail_signature_combo_box_load_selected_finish
+					(EMailSignatureComboBox *combo_box,
+					 GAsyncResult *result,
+					 gchar **contents,
+					 gsize *length,
+					 gboolean *is_html,
+					 GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_COMBO_BOX_H */
diff --git a/widgets/misc/e-mail-signature-editor.c b/widgets/misc/e-mail-signature-editor.c
new file mode 100644
index 0000000..1f9f1a3
--- /dev/null
+++ b/widgets/misc/e-mail-signature-editor.c
@@ -0,0 +1,909 @@
+/*
+ * e-mail-signature-editor.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-editor.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-source-mail-signature.h>
+
+#include <libevolution-utils/e-alert-dialog.h>
+#include <libevolution-utils/e-alert-sink.h>
+#include <misc/e-alert-bar.h>
+#include <misc/e-web-view-gtkhtml.h>
+
+#define E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditorPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EMailSignatureEditorPrivate {
+	GtkActionGroup *action_group;
+	EFocusTracker *focus_tracker;
+	GCancellable *cancellable;
+	ESourceRegistry *registry;
+	ESource *source;
+	gchar *original_name;
+
+	GtkWidget *entry;		/* not referenced */
+	GtkWidget *alert_bar;		/* not referenced */
+};
+
+struct _AsyncContext {
+	GCancellable *cancellable;
+	gchar *contents;
+	gsize length;
+};
+
+enum {
+	PROP_0,
+	PROP_FOCUS_TRACKER,
+	PROP_REGISTRY,
+	PROP_SOURCE
+};
+
+static const gchar *ui =
+"<ui>\n"
+"  <menubar name='main-menu'>\n"
+"    <placeholder name='pre-edit-menu'>\n"
+"      <menu action='file-menu'>\n"
+"        <menuitem action='save-and-close'/>\n"
+"        <separator/>"
+"        <menuitem action='close'/>\n"
+"      </menu>\n"
+"    </placeholder>\n"
+"  </menubar>\n"
+"  <toolbar name='main-toolbar'>\n"
+"    <placeholder name='pre-main-toolbar'>\n"
+"      <toolitem action='save-and-close'/>\n"
+"    </placeholder>\n"
+"  </toolbar>\n"
+"</ui>";
+
+/* Forward Declarations */
+static void	e_mail_signature_editor_alert_sink_init
+					(EAlertSinkInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EMailSignatureEditor,
+	e_mail_signature_editor,
+	GTKHTML_TYPE_EDITOR,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK,
+		e_mail_signature_editor_alert_sink_init))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->cancellable != NULL)
+		g_object_unref (async_context->cancellable);
+
+	g_free (async_context->contents);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static void
+mail_signature_editor_loaded_cb (GObject *object,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+	ESource *source;
+	EMailSignatureEditor *editor;
+	ESourceMailSignature *extension;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	gchar *contents = NULL;
+	gboolean is_html;
+	GError *error = NULL;
+
+	source = E_SOURCE (object);
+	editor = E_MAIL_SIGNATURE_EDITOR (user_data);
+
+	e_source_mail_signature_load_finish (
+		source, result, &contents, NULL, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (contents == NULL);
+		g_object_unref (editor);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (contents == NULL);
+		e_alert_submit (
+			E_ALERT_SINK (editor),
+			"widgets:no-load-signature",
+			error->message, NULL);
+		g_object_unref (editor);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (contents != NULL);
+
+	/* The load operation should have set the MIME type. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	mime_type = e_source_mail_signature_get_mime_type (extension);
+	is_html = (g_strcmp0 (mime_type, "text/html") == 0);
+
+	gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (editor), is_html);
+
+	if (is_html)
+		gtkhtml_editor_insert_html (
+			GTKHTML_EDITOR (editor), contents);
+	else
+		gtkhtml_editor_insert_text (
+			GTKHTML_EDITOR (editor), contents);
+
+	g_free (contents);
+
+	g_object_unref (editor);
+}
+
+static gboolean
+mail_signature_editor_delete_event_cb (EMailSignatureEditor *editor,
+                                       GdkEvent *event)
+{
+	GtkActionGroup *action_group;
+	GtkAction *action;
+
+	action_group = editor->priv->action_group;
+	action = gtk_action_group_get_action (action_group, "close");
+	gtk_action_activate (action);
+
+	return TRUE;
+}
+
+static void
+action_close_cb (GtkAction *action,
+                 EMailSignatureEditor *editor)
+{
+	gboolean something_changed = FALSE;
+	const gchar *original_name;
+	const gchar *signature_name;
+
+	original_name = editor->priv->original_name;
+	signature_name = gtk_entry_get_text (GTK_ENTRY (editor->priv->entry));
+
+	something_changed |= gtkhtml_editor_has_undo (GTKHTML_EDITOR (editor));
+	something_changed |= (strcmp (signature_name, original_name) != 0);
+
+	if (something_changed) {
+		gint response;
+
+		response = e_alert_run_dialog_for_args (
+			GTK_WINDOW (editor),
+			"widgets:ask-signature-changed", NULL);
+		if (response == GTK_RESPONSE_YES) {
+			GtkActionGroup *action_group;
+
+			action_group = editor->priv->action_group;
+			action = gtk_action_group_get_action (
+				action_group, "save-and-close");
+			gtk_action_activate (action);
+			return;
+		} else if (response == GTK_RESPONSE_CANCEL)
+			return;
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (editor));
+}
+
+static void
+action_save_and_close_cb (GtkAction *action,
+                          EMailSignatureEditor *editor)
+{
+	GtkEntry *entry;
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	ESource *source;
+	gchar *display_name;
+	GError *error = NULL;
+
+	entry = GTK_ENTRY (editor->priv->entry);
+	source = e_mail_signature_editor_get_source (editor);
+
+	display_name = g_strstrip (g_strdup (gtk_entry_get_text (entry)));
+
+	/* Make sure the signature name is not blank. */
+	if (*display_name == '\0') {
+		e_alert_submit (
+			E_ALERT_SINK (editor),
+			"widgets:blank-signature", NULL);
+		gtk_widget_grab_focus (GTK_WIDGET (entry));
+		g_free (display_name);
+		return;
+	}
+
+	e_source_set_display_name (source, display_name);
+
+	g_free (display_name);
+
+	/* Cancel any ongoing load or save operations. */
+	if (editor->priv->cancellable != NULL) {
+		g_cancellable_cancel (editor->priv->cancellable);
+		g_object_unref (editor->priv->cancellable);
+	}
+
+	editor->priv->cancellable = g_cancellable_new ();
+
+	closure = e_async_closure_new ();
+
+	e_mail_signature_editor_commit (
+		editor, editor->priv->cancellable,
+		e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	e_mail_signature_editor_commit_finish (editor, result, &error);
+
+	e_async_closure_free (closure);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_error_free (error);
+
+	} else if (error != NULL) {
+		e_alert_submit (
+			E_ALERT_SINK (editor),
+			"widgets:no-save-signature",
+			error->message, NULL);
+		g_error_free (error);
+
+	/* Only destroy the editor if the save was successful. */
+	} else {
+		gtk_widget_destroy (GTK_WIDGET (editor));
+	}
+}
+
+static GtkActionEntry entries[] = {
+
+	{ "close",
+	  GTK_STOCK_CLOSE,
+	  N_("_Close"),
+	  "<Control>w",
+	  NULL,
+	  G_CALLBACK (action_close_cb) },
+
+	{ "save-and-close",
+	  GTK_STOCK_SAVE,
+	  N_("_Save and Close"),
+	  "<Control>Return",
+	  NULL,
+	  G_CALLBACK (action_save_and_close_cb) },
+
+	{ "file-menu",
+	  NULL,
+	  N_("_File"),
+	  NULL,
+	  NULL,
+	  NULL }
+};
+
+static void
+mail_signature_editor_set_registry (EMailSignatureEditor *editor,
+                                    ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (editor->priv->registry == NULL);
+
+	editor->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_editor_set_source (EMailSignatureEditor *editor,
+                                  ESource *source)
+{
+	GDBusObject *dbus_object = NULL;
+	const gchar *extension_name;
+	GError *error = NULL;
+
+	g_return_if_fail (source == NULL || E_IS_SOURCE (source));
+	g_return_if_fail (editor->priv->source == NULL);
+
+	if (source != NULL)
+		dbus_object = e_source_ref_dbus_object (source);
+
+	/* Clone the source so we can make changes to it freely. */
+	editor->priv->source = e_source_new (dbus_object, NULL, &error);
+
+	if (dbus_object != NULL)
+		g_object_unref (dbus_object);
+
+	/* This should rarely fail.  If the file was loaded successfully
+	 * once then it should load successfully here as well, unless an
+	 * I/O error occurs. */
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	/* Make sure the source has a mail signature extension. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	e_source_get_extension (editor->priv->source, extension_name);
+}
+
+static void
+mail_signature_editor_set_property (GObject *object,
+                                    guint property_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			mail_signature_editor_set_registry (
+				E_MAIL_SIGNATURE_EDITOR (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SOURCE:
+			mail_signature_editor_set_source (
+				E_MAIL_SIGNATURE_EDITOR (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_editor_get_property (GObject *object,
+                                    guint property_id,
+                                    GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_FOCUS_TRACKER:
+			g_value_set_object (
+				value,
+				e_mail_signature_editor_get_focus_tracker (
+				E_MAIL_SIGNATURE_EDITOR (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_editor_get_registry (
+				E_MAIL_SIGNATURE_EDITOR (object)));
+			return;
+
+		case PROP_SOURCE:
+			g_value_set_object (
+				value,
+				e_mail_signature_editor_get_source (
+				E_MAIL_SIGNATURE_EDITOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_editor_dispose (GObject *object)
+{
+	EMailSignatureEditorPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (object);
+
+	if (priv->action_group != NULL) {
+		g_object_unref (priv->action_group);
+		priv->action_group = NULL;
+	}
+
+	if (priv->focus_tracker != NULL) {
+		g_object_unref (priv->focus_tracker);
+		priv->focus_tracker = NULL;
+	}
+
+	if (priv->cancellable != NULL) {
+		g_cancellable_cancel (priv->cancellable);
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->source != NULL) {
+		g_object_unref (priv->source);
+		priv->source = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_editor_finalize (GObject *object)
+{
+	EMailSignatureEditorPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (object);
+
+	g_free (priv->original_name);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
+		finalize (object);
+}
+
+static void
+mail_signature_editor_constructed (GObject *object)
+{
+	EMailSignatureEditor *editor;
+	GtkActionGroup *action_group;
+	EFocusTracker *focus_tracker;
+	GtkhtmlEditor *gtkhtml_editor;
+	GtkUIManager *ui_manager;
+	GDBusObject *dbus_object;
+	ESource *source;
+	GtkAction *action;
+	GtkWidget *container;
+	GtkWidget *widget;
+	const gchar *display_name;
+	GError *error = NULL;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
+		constructed (object);
+
+	editor = E_MAIL_SIGNATURE_EDITOR (object);
+
+	gtkhtml_editor = GTKHTML_EDITOR (editor);
+	ui_manager = gtkhtml_editor_get_ui_manager (gtkhtml_editor);
+
+	/* Because we are loading from a hard-coded string, there is
+	 * no chance of I/O errors.  Failure here implies a malformed
+	 * UI definition.  Full stop. */
+	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+	if (error != NULL)
+		g_error ("%s", error->message);
+
+	action_group = gtk_action_group_new ("signature");
+	gtk_action_group_set_translation_domain (
+		action_group, GETTEXT_PACKAGE);
+	gtk_action_group_add_actions (
+		action_group, entries,
+		G_N_ELEMENTS (entries), editor);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	editor->priv->action_group = g_object_ref (action_group);
+
+	/* Hide page properties because it is not inherited in the mail. */
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "properties-page");
+	gtk_action_set_visible (action, FALSE);
+
+	action = gtkhtml_editor_get_action (
+		gtkhtml_editor, "context-properties-page");
+	gtk_action_set_visible (action, FALSE);
+
+	gtk_ui_manager_ensure_update (ui_manager);
+
+	gtk_window_set_title (GTK_WINDOW (editor), _("Edit Signature"));
+
+	/* Construct the signature name entry. */
+
+	container = gtkhtml_editor->vbox;
+
+	widget = gtk_hbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	/* Position 2 should be between the main and style toolbars. */
+	gtk_box_reorder_child (GTK_BOX (container), widget, 2);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_entry_new ();
+	gtk_box_pack_end (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	editor->priv->entry = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	widget = gtk_label_new_with_mnemonic (_("_Signature Name:"));
+	gtk_label_set_mnemonic_widget (GTK_LABEL (widget), editor->priv->entry);
+	gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		editor, "delete-event",
+		G_CALLBACK (mail_signature_editor_delete_event_cb), NULL);
+
+	/* Construct the alert bar for errors. */
+
+	container = gtkhtml_editor->vbox;
+
+	widget = e_alert_bar_new ();
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	/* Position 5 should be between the style toolbar and editing area. */
+	gtk_box_reorder_child (GTK_BOX (container), widget, 5);
+	editor->priv->alert_bar = widget;  /* not referenced */
+	/* EAlertBar controls its own visibility. */
+
+	/* Configure an EFocusTracker to manage selection actions.
+	 *
+	 * XXX GtkhtmlEditor does not manage its own selection actions,
+	 *     which is technically a bug but works in our favor here
+	 *     because it won't cause any conflicts with EFocusTracker. */
+
+	focus_tracker = e_focus_tracker_new (GTK_WINDOW (editor));
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "cut");
+	e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "copy");
+	e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "paste");
+	e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "select-all");
+	e_focus_tracker_set_select_all_action (focus_tracker, action);
+
+	editor->priv->focus_tracker = focus_tracker;
+
+	source = e_mail_signature_editor_get_source (editor);
+
+	display_name = e_source_get_display_name (source);
+	if (display_name == NULL || *display_name == '\0')
+		display_name = _("Unnamed");
+
+	/* Set the entry text before we grab focus. */
+	g_free (editor->priv->original_name);
+	editor->priv->original_name = g_strdup (display_name);
+	gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), display_name);
+
+	/* Set the focus appropriately.  If this is a new signature, draw
+	 * the user's attention to the signature name entry.  Otherwise go
+	 * straight to the editing area. */
+	if (source == NULL)
+		gtk_widget_grab_focus (editor->priv->entry);
+	else {
+		GtkHTML *html;
+
+		html = gtkhtml_editor_get_html (gtkhtml_editor);
+		gtk_widget_grab_focus (GTK_WIDGET (html));
+	}
+
+	/* Load file content only for an existing signature.
+	 * (A new signature will not yet have a GDBusObject.) */
+	dbus_object = e_source_ref_dbus_object (source);
+	if (dbus_object != NULL) {
+		GCancellable *cancellable;
+
+		cancellable = g_cancellable_new ();
+
+		e_source_mail_signature_load (
+			source,
+			G_PRIORITY_DEFAULT,
+			cancellable,
+			mail_signature_editor_loaded_cb,
+			g_object_ref (editor));
+
+		g_warn_if_fail (editor->priv->cancellable == NULL);
+		editor->priv->cancellable = cancellable;
+
+		g_object_unref (dbus_object);
+	}
+}
+
+static void
+mail_signature_editor_cut_clipboard (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_copy_clipboard (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_paste_clipboard (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_select_all (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_submit_alert (EAlertSink *alert_sink,
+                                    EAlert *alert)
+{
+	EMailSignatureEditorPrivate *priv;
+	EAlertBar *alert_bar;
+	GtkWidget *dialog;
+	GtkWindow *parent;
+
+	priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (alert_sink);
+
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+		case GTK_MESSAGE_WARNING:
+		case GTK_MESSAGE_ERROR:
+			alert_bar = E_ALERT_BAR (priv->alert_bar);
+			e_alert_bar_add_alert (alert_bar, alert);
+			break;
+
+		default:
+			parent = GTK_WINDOW (alert_sink);
+			dialog = e_alert_dialog_new (parent, alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			break;
+	}
+}
+
+static void
+e_mail_signature_editor_class_init (EMailSignatureEditorClass *class)
+{
+	GObjectClass *object_class;
+	GtkhtmlEditorClass *editor_class;
+
+	g_type_class_add_private (class, sizeof (EMailSignatureEditorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_editor_set_property;
+	object_class->get_property = mail_signature_editor_get_property;
+	object_class->dispose = mail_signature_editor_dispose;
+	object_class->finalize = mail_signature_editor_finalize;
+	object_class->constructed = mail_signature_editor_constructed;
+
+	editor_class = GTKHTML_EDITOR_CLASS (class);
+	editor_class->cut_clipboard = mail_signature_editor_cut_clipboard;
+	editor_class->copy_clipboard = mail_signature_editor_copy_clipboard;
+	editor_class->paste_clipboard = mail_signature_editor_paste_clipboard;
+	editor_class->select_all = mail_signature_editor_select_all;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FOCUS_TRACKER,
+		g_param_spec_object (
+			"focus-tracker",
+			NULL,
+			NULL,
+			E_TYPE_FOCUS_TRACKER,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE,
+		g_param_spec_object (
+			"source",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_mail_signature_editor_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = mail_signature_editor_submit_alert;
+}
+
+static void
+e_mail_signature_editor_init (EMailSignatureEditor *editor)
+{
+	editor->priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (editor);
+}
+
+GtkWidget *
+e_mail_signature_editor_new (ESourceRegistry *registry,
+                             ESource *source)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_EDITOR,
+		"html", e_web_view_gtkhtml_new (),
+		"registry", registry,
+		"source", source, NULL);
+}
+
+EFocusTracker *
+e_mail_signature_editor_get_focus_tracker (EMailSignatureEditor *editor)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+	return editor->priv->focus_tracker;
+}
+
+ESourceRegistry *
+e_mail_signature_editor_get_registry (EMailSignatureEditor *editor)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+	return editor->priv->registry;
+}
+
+ESource *
+e_mail_signature_editor_get_source (EMailSignatureEditor *editor)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+	return editor->priv->source;
+}
+
+/********************** e_mail_signature_editor_commit() *********************/
+
+static void
+mail_signature_editor_replace_cb (GObject *object,
+                                  GAsyncResult *result,
+                                  gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+	e_source_mail_signature_replace_finish (
+		E_SOURCE (object), result, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+
+	g_object_unref (simple);
+}
+
+static void
+mail_signature_editor_commit_cb (GObject *object,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	ESource *source = NULL;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	e_source_registry_commit_source_finish (
+		E_SOURCE_REGISTRY (object), result, &source, &error);
+
+	if (error != NULL) {
+		g_warn_if_fail (source == NULL);
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	e_source_mail_signature_replace (
+		source,
+		async_context->contents,
+		async_context->length,
+		G_PRIORITY_DEFAULT,
+		async_context->cancellable,
+		mail_signature_editor_replace_cb,
+		simple);
+
+	g_object_unref (source);
+}
+
+void
+e_mail_signature_editor_commit (EMailSignatureEditor *editor,
+                                GCancellable *cancellable,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	ESourceMailSignature *extension;
+	ESourceRegistry *registry;
+	ESource *source;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	gchar *contents;
+	gboolean is_html;
+	gsize length;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor));
+
+	registry = e_mail_signature_editor_get_registry (editor);
+	source = e_mail_signature_editor_get_source (editor);
+	is_html = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (editor));
+
+	if (is_html) {
+		mime_type = "text/html";
+		contents = gtkhtml_editor_get_text_html (
+			GTKHTML_EDITOR (editor), &length);
+	} else {
+		mime_type = "text/plain";
+		contents = gtkhtml_editor_get_text_plain (
+			GTKHTML_EDITOR (editor), &length);
+	}
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	e_source_mail_signature_set_mime_type (extension, mime_type);
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->contents = contents;  /* takes ownership */
+	async_context->length = length;
+
+	if (G_IS_CANCELLABLE (cancellable))
+		async_context->cancellable = g_object_ref (cancellable);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (editor), callback, user_data,
+		e_mail_signature_editor_commit);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	e_source_registry_commit_source (
+		registry, source,
+		async_context->cancellable,
+		mail_signature_editor_commit_cb,
+		simple);
+}
+
+gboolean
+e_mail_signature_editor_commit_finish (EMailSignatureEditor *editor,
+                                       GAsyncResult *result,
+                                       GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (editor),
+		e_mail_signature_editor_commit), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
diff --git a/widgets/misc/e-mail-signature-editor.h b/widgets/misc/e-mail-signature-editor.h
new file mode 100644
index 0000000..5527100
--- /dev/null
+++ b/widgets/misc/e-mail-signature-editor.h
@@ -0,0 +1,82 @@
+/*
+ * e-mail-signature-editor.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SIGNATURE_EDITOR_H
+#define E_MAIL_SIGNATURE_EDITOR_H
+
+#include <gtkhtml-editor.h>
+#include <misc/e-focus-tracker.h>
+#include <libedataserver/e-source-registry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_EDITOR \
+	(e_mail_signature_editor_get_type ())
+#define E_MAIL_SIGNATURE_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditor))
+#define E_MAIL_SIGNATURE_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditorClass))
+#define E_IS_MAIL_SIGNATURE_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR))
+#define E_IS_MAIL_SIGNATURE_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_EDITOR))
+#define E_MAIL_SIGNATURE_EDITOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureEditor EMailSignatureEditor;
+typedef struct _EMailSignatureEditorClass EMailSignatureEditorClass;
+typedef struct _EMailSignatureEditorPrivate EMailSignatureEditorPrivate;
+
+struct _EMailSignatureEditor {
+	GtkhtmlEditor parent;
+	EMailSignatureEditorPrivate *priv;
+};
+
+struct _EMailSignatureEditorClass {
+	GtkhtmlEditorClass parent_class;
+};
+
+GType		e_mail_signature_editor_get_type
+						(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_editor_new	(ESourceRegistry *registry,
+						 ESource *source);
+EFocusTracker *	e_mail_signature_editor_get_focus_tracker
+						(EMailSignatureEditor *editor);
+ESourceRegistry *
+		e_mail_signature_editor_get_registry
+						(EMailSignatureEditor *editor);
+ESource *	e_mail_signature_editor_get_source
+						(EMailSignatureEditor *editor);
+void		e_mail_signature_editor_commit	(EMailSignatureEditor *editor,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_mail_signature_editor_commit_finish
+						(EMailSignatureEditor *editor,
+						 GAsyncResult *result,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_EDITOR_H */
diff --git a/widgets/misc/e-mail-signature-manager.c b/widgets/misc/e-mail-signature-manager.c
new file mode 100644
index 0000000..d00787b
--- /dev/null
+++ b/widgets/misc/e-mail-signature-manager.c
@@ -0,0 +1,705 @@
+/*
+ * e-mail-signature-manager.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-manager.h"
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-source-mail-signature.h>
+
+#include "e-mail-signature-preview.h"
+#include "e-mail-signature-tree-view.h"
+#include "e-mail-signature-script-dialog.h"
+
+#define E_MAIL_SIGNATURE_MANAGER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManagerPrivate))
+
+#define PREVIEW_HEIGHT 200
+
+struct _EMailSignatureManagerPrivate {
+	ESourceRegistry *registry;
+
+	GtkWidget *tree_view;		/* not referenced */
+	GtkWidget *add_button;		/* not referenced */
+	GtkWidget *add_script_button;	/* not referenced */
+	GtkWidget *edit_button;		/* not referenced */
+	GtkWidget *remove_button;	/* not referenced */
+	GtkWidget *preview;		/* not referenced */
+
+	gboolean prefer_html;
+};
+
+enum {
+	PROP_0,
+	PROP_PREFER_HTML,
+	PROP_REGISTRY
+};
+
+enum {
+	ADD_SIGNATURE,
+	ADD_SIGNATURE_SCRIPT,
+	EDITOR_CREATED,
+	EDIT_SIGNATURE,
+	REMOVE_SIGNATURE,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+	EMailSignatureManager,
+	e_mail_signature_manager,
+	GTK_TYPE_PANED)
+
+static void
+mail_signature_manager_emit_editor_created (EMailSignatureManager *manager,
+                                            GtkWidget *editor)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor));
+
+	g_signal_emit (manager, signals[EDITOR_CREATED], 0, editor);
+}
+
+static gboolean
+mail_signature_manager_key_press_event_cb (EMailSignatureManager *manager,
+                                           GdkEventKey *event)
+{
+	if (event->keyval == GDK_KEY_Delete) {
+		e_mail_signature_manager_remove_signature (manager);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+mail_signature_manager_run_script_dialog (EMailSignatureManager *manager,
+                                          ESource *source,
+                                          const gchar *title)
+{
+	ESourceRegistry *registry;
+	GtkWidget *dialog;
+	gpointer parent;
+
+	registry = e_mail_signature_manager_get_registry (manager);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	dialog = e_mail_signature_script_dialog_new (registry, parent, source);
+	gtk_window_set_title (GTK_WINDOW (dialog), title);
+
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+		EAsyncClosure *closure;
+		GAsyncResult *result;
+		GError *error = NULL;
+
+		closure = e_async_closure_new ();
+
+		/* FIXME Make this cancellable. */
+		e_mail_signature_script_dialog_commit (
+			E_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL,
+			e_async_closure_callback, closure);
+
+		result = e_async_closure_wait (closure);
+
+		e_mail_signature_script_dialog_commit_finish (
+			E_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog),
+			result, &error);
+
+		e_async_closure_free (closure);
+
+		/* FIXME Make this into an EAlert. */
+		if (error != NULL) {
+			g_warning ("%s: %s", G_STRFUNC, error->message);
+			g_error_free (error);
+		}
+	}
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+mail_signature_manager_selection_changed_cb (EMailSignatureManager *manager,
+                                             GtkTreeSelection *selection)
+{
+	EMailSignaturePreview *preview;
+	EMailSignatureTreeView *tree_view;
+	ESource *source;
+	GtkWidget *edit_button;
+	GtkWidget *remove_button;
+	gboolean sensitive;
+	const gchar *uid = NULL;
+
+	edit_button = manager->priv->edit_button;
+	remove_button = manager->priv->remove_button;
+
+	tree_view = E_MAIL_SIGNATURE_TREE_VIEW (manager->priv->tree_view);
+	source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+
+	if (source != NULL)
+		uid = e_source_get_uid (source);
+
+	preview = E_MAIL_SIGNATURE_PREVIEW (manager->priv->preview);
+	e_mail_signature_preview_set_source_uid (preview, uid);
+
+	sensitive = (source != NULL);
+	gtk_widget_set_sensitive (edit_button, sensitive);
+	gtk_widget_set_sensitive (remove_button, sensitive);
+
+	if (source != NULL)
+		g_object_unref (source);
+}
+
+static void
+mail_signature_manager_set_registry (EMailSignatureManager *manager,
+                                     ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (manager->priv->registry == NULL);
+
+	manager->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_manager_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_PREFER_HTML:
+			e_mail_signature_manager_set_prefer_html (
+				E_MAIL_SIGNATURE_MANAGER (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_REGISTRY:
+			mail_signature_manager_set_registry (
+				E_MAIL_SIGNATURE_MANAGER (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_manager_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_PREFER_HTML:
+			g_value_set_boolean (
+				value,
+				e_mail_signature_manager_get_prefer_html (
+				E_MAIL_SIGNATURE_MANAGER (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_manager_get_registry (
+				E_MAIL_SIGNATURE_MANAGER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_manager_dispose (GObject *object)
+{
+	EMailSignatureManagerPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_MANAGER_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_manager_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_manager_constructed (GObject *object)
+{
+	EMailSignatureManager *manager;
+	GtkTreeSelection *selection;
+	ESourceRegistry *registry;
+	GSettings *settings;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GtkWidget *hbox;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_mail_signature_manager_parent_class)->
+		constructed (object);
+
+	manager = E_MAIL_SIGNATURE_MANAGER (object);
+	registry = e_mail_signature_manager_get_registry (manager);
+
+	gtk_orientable_set_orientation (
+		GTK_ORIENTABLE (manager), GTK_ORIENTATION_VERTICAL);
+
+	container = GTK_WIDGET (manager);
+
+	widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+	gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 12, 0, 0);
+	gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_hbox_new (FALSE, 6);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	container = hbox = widget;
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_mail_signature_tree_view_new (registry);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	manager->priv->tree_view = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "key-press-event",
+		G_CALLBACK (mail_signature_manager_key_press_event_cb),
+		manager);
+
+	g_signal_connect_swapped (
+		widget, "row-activated",
+		G_CALLBACK (e_mail_signature_manager_edit_signature),
+		manager);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+	g_signal_connect_swapped (
+		selection, "changed",
+		G_CALLBACK (mail_signature_manager_selection_changed_cb),
+		manager);
+
+	container = hbox;
+
+	widget = gtk_vbutton_box_new ();
+	gtk_button_box_set_layout (
+		GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_START);
+	gtk_box_set_spacing (GTK_BOX (widget), 6);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_ADD);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	manager->priv->add_button = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "clicked",
+		G_CALLBACK (e_mail_signature_manager_add_signature),
+		manager);
+
+	widget = gtk_button_new_with_mnemonic (_("Add _Script"));
+	gtk_button_set_image (
+		GTK_BUTTON (widget), gtk_image_new_from_stock (
+		GTK_STOCK_EXECUTE, GTK_ICON_SIZE_BUTTON));
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	manager->priv->add_script_button = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	settings = g_settings_new ("org.gnome.desktop.lockdown");
+
+	g_settings_bind (
+		settings, "disable-command-line",
+		widget, "visible",
+		G_SETTINGS_BIND_GET |
+		G_SETTINGS_BIND_INVERT_BOOLEAN);
+
+	g_object_unref (settings);
+
+	g_signal_connect_swapped (
+		widget, "clicked",
+		G_CALLBACK (e_mail_signature_manager_add_signature_script),
+		manager);
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_EDIT);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	manager->priv->edit_button = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "clicked",
+		G_CALLBACK (e_mail_signature_manager_edit_signature),
+		manager);
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	manager->priv->remove_button = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "clicked",
+		G_CALLBACK (e_mail_signature_manager_remove_signature),
+		manager);
+
+	container = GTK_WIDGET (manager);
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_mail_signature_preview_new (registry);
+	gtk_widget_set_size_request (widget, -1, PREVIEW_HEIGHT);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	manager->priv->preview = widget;  /* not referenced */
+	gtk_widget_show (widget);
+}
+
+static void
+mail_signature_manager_add_signature (EMailSignatureManager *manager)
+{
+	ESourceRegistry *registry;
+	GtkWidget *editor;
+
+	registry = e_mail_signature_manager_get_registry (manager);
+
+	editor = e_mail_signature_editor_new (registry, NULL);
+	gtkhtml_editor_set_html_mode (
+		GTKHTML_EDITOR (editor), manager->priv->prefer_html);
+	mail_signature_manager_emit_editor_created (manager, editor);
+
+	gtk_widget_grab_focus (manager->priv->tree_view);
+}
+
+static void
+mail_signature_manager_add_signature_script (EMailSignatureManager *manager)
+{
+	const gchar *title;
+
+	title = _("Add Signature Script");
+	mail_signature_manager_run_script_dialog (manager, NULL, title);
+
+	gtk_widget_grab_focus (manager->priv->tree_view);
+}
+
+static void
+mail_signature_manager_editor_created (EMailSignatureManager *manager,
+                                       EMailSignatureEditor *editor)
+{
+	GtkWindowPosition position;
+	gpointer parent;
+
+	position = GTK_WIN_POS_CENTER_ON_PARENT;
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	gtk_window_set_transient_for (GTK_WINDOW (editor), parent);
+	gtk_window_set_position (GTK_WINDOW (editor), position);
+	gtk_widget_show (GTK_WIDGET (editor));
+}
+
+static void
+mail_signature_manager_edit_signature (EMailSignatureManager *manager)
+{
+	EMailSignatureTreeView *tree_view;
+	ESourceMailSignature *extension;
+	ESourceRegistry *registry;
+	GtkWidget *editor;
+	ESource *source;
+	GFileInfo *file_info;
+	GFile *file;
+	const gchar *attribute;
+	const gchar *extension_name;
+	const gchar *title;
+	GError *error = NULL;
+
+	registry = e_mail_signature_manager_get_registry (manager);
+	tree_view = E_MAIL_SIGNATURE_TREE_VIEW (manager->priv->tree_view);
+	source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+	g_return_if_fail (source != NULL);
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	file = e_source_mail_signature_get_file (extension);
+
+	attribute = G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE;
+
+	/* XXX This blocks but it should just be a local file. */
+	file_info = g_file_query_info (
+		file, attribute, G_FILE_QUERY_INFO_NONE, NULL, &error);
+
+	/* FIXME Make this into an EAlert. */
+	if (error != NULL) {
+		g_warn_if_fail (file_info == NULL);
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_object_unref (source);
+		g_error_free (error);
+		return;
+	}
+
+	if (g_file_info_get_attribute_boolean (file_info, attribute))
+		goto script;
+
+	editor = e_mail_signature_editor_new (registry, source);
+	mail_signature_manager_emit_editor_created (manager, editor);
+
+	goto exit;
+
+script:
+	title = _("Edit Signature Script");
+	mail_signature_manager_run_script_dialog (manager, source, title);
+
+exit:
+	gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+	g_object_unref (file_info);
+	g_object_unref (source);
+}
+
+static void
+mail_signature_manager_remove_signature (EMailSignatureManager *manager)
+{
+	EMailSignatureTreeView *tree_view;
+	ESourceMailSignature *extension;
+	ESource *source;
+	GFile *file;
+	const gchar *extension_name;
+	GError *error = NULL;
+
+	tree_view = E_MAIL_SIGNATURE_TREE_VIEW (manager->priv->tree_view);
+	source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+
+	if (source == NULL)
+		return;
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+
+	file = e_source_mail_signature_get_file (extension);
+
+	/* XXX This blocks but it should just be a local file. */
+	if (!g_file_delete (file, NULL, &error)) {
+		g_warning ("%s", error->message);
+		g_clear_error (&error);
+	}
+
+	/* Remove the mail signature data source asynchronously.
+	 * XXX No callback function because there's not much we can do
+	 *     if this fails.  We should probably implement EAlertSink. */
+	e_source_remove (source, NULL, NULL, NULL);
+
+	gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+	g_object_unref (source);
+}
+
+static void
+e_mail_signature_manager_class_init (EMailSignatureManagerClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (
+		class, sizeof (EMailSignatureManagerPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_manager_set_property;
+	object_class->get_property = mail_signature_manager_get_property;
+	object_class->dispose = mail_signature_manager_dispose;
+	object_class->constructed = mail_signature_manager_constructed;
+
+	class->add_signature = mail_signature_manager_add_signature;
+	class->add_signature_script =
+		mail_signature_manager_add_signature_script;
+	class->editor_created = mail_signature_manager_editor_created;
+	class->edit_signature = mail_signature_manager_edit_signature;
+	class->remove_signature = mail_signature_manager_remove_signature;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PREFER_HTML,
+		g_param_spec_boolean (
+			"prefer-html",
+			"Prefer HTML",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[ADD_SIGNATURE] = g_signal_new (
+		"add-signature",
+		G_OBJECT_CLASS_TYPE (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EMailSignatureManagerClass, add_signature),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[ADD_SIGNATURE_SCRIPT] = g_signal_new (
+		"add-signature-script",
+		G_OBJECT_CLASS_TYPE (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (
+			EMailSignatureManagerClass, add_signature_script),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[EDITOR_CREATED] = g_signal_new (
+		"editor-created",
+		G_OBJECT_CLASS_TYPE (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EMailSignatureManagerClass, editor_created),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_MAIL_SIGNATURE_EDITOR);
+
+	signals[EDIT_SIGNATURE] = g_signal_new (
+		"edit-signature",
+		G_OBJECT_CLASS_TYPE (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EMailSignatureManagerClass, edit_signature),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[REMOVE_SIGNATURE] = g_signal_new (
+		"remove-signature",
+		G_OBJECT_CLASS_TYPE (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EMailSignatureManagerClass, remove_signature),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_mail_signature_manager_init (EMailSignatureManager *manager)
+{
+	manager->priv = E_MAIL_SIGNATURE_MANAGER_GET_PRIVATE (manager);
+}
+
+GtkWidget *
+e_mail_signature_manager_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_MANAGER,
+		"registry", registry, NULL);
+}
+
+void
+e_mail_signature_manager_add_signature (EMailSignatureManager *manager)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+	g_signal_emit (manager, signals[ADD_SIGNATURE], 0);
+}
+
+void
+e_mail_signature_manager_add_signature_script (EMailSignatureManager *manager)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+	g_signal_emit (manager, signals[ADD_SIGNATURE_SCRIPT], 0);
+}
+
+void
+e_mail_signature_manager_edit_signature (EMailSignatureManager *manager)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+	g_signal_emit (manager, signals[EDIT_SIGNATURE], 0);
+}
+
+void
+e_mail_signature_manager_remove_signature (EMailSignatureManager *manager)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+	g_signal_emit (manager, signals[REMOVE_SIGNATURE], 0);
+}
+
+gboolean
+e_mail_signature_manager_get_prefer_html (EMailSignatureManager *manager)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager), FALSE);
+
+	return manager->priv->prefer_html;
+}
+
+void
+e_mail_signature_manager_set_prefer_html (EMailSignatureManager *manager,
+                                          gboolean prefer_html)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+	manager->priv->prefer_html = prefer_html;
+
+	g_object_notify (G_OBJECT (manager), "prefer-html");
+}
+
+ESourceRegistry *
+e_mail_signature_manager_get_registry (EMailSignatureManager *manager)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager), NULL);
+
+	return manager->priv->registry;
+}
diff --git a/widgets/misc/e-mail-signature-manager.h b/widgets/misc/e-mail-signature-manager.h
new file mode 100644
index 0000000..a627bd5
--- /dev/null
+++ b/widgets/misc/e-mail-signature-manager.h
@@ -0,0 +1,88 @@
+/*
+ * e-mail-signature-manager.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SIGNATURE_MANAGER_H
+#define E_MAIL_SIGNATURE_MANAGER_H
+
+#include <gtk/gtk.h>
+#include <misc/e-mail-signature-editor.h>
+#include <misc/e-mail-signature-tree-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_MANAGER \
+	(e_mail_signature_manager_get_type ())
+#define E_MAIL_SIGNATURE_MANAGER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManager))
+#define E_MAIL_SIGNATURE_MANAGER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManagerClass))
+#define E_IS_MAIL_SIGNATURE_MANAGER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER))
+#define E_IS_MAIL_SIGNATURE_MANAGER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_MANAGER))
+#define E_MAIL_SIGNATURE_MANAGER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManagerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureManager EMailSignatureManager;
+typedef struct _EMailSignatureManagerClass EMailSignatureManagerClass;
+typedef struct _EMailSignatureManagerPrivate EMailSignatureManagerPrivate;
+
+struct _EMailSignatureManager {
+	GtkPaned parent;
+	EMailSignatureManagerPrivate *priv;
+};
+
+struct _EMailSignatureManagerClass {
+	GtkPanedClass parent_class;
+
+	void	(*add_signature)	(EMailSignatureManager *manager);
+	void	(*add_signature_script)	(EMailSignatureManager *manager);
+	void	(*editor_created)	(EMailSignatureManager *manager,
+					 EMailSignatureEditor *editor);
+	void	(*edit_signature)	(EMailSignatureManager *manager);
+	void	(*remove_signature)	(EMailSignatureManager *manager);
+};
+
+GType		e_mail_signature_manager_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_manager_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_manager_add_signature
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_add_signature_script
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_edit_signature
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_remove_signature
+					(EMailSignatureManager *manager);
+gboolean	e_mail_signature_manager_get_prefer_html
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_set_prefer_html
+					(EMailSignatureManager *manager,
+					 gboolean prefer_html);
+ESourceRegistry *
+		e_mail_signature_manager_get_registry
+					(EMailSignatureManager *manager);
+
+#endif /* E_MAIL_SIGNATURE_MANAGER_H */
diff --git a/widgets/misc/e-mail-signature-preview.c b/widgets/misc/e-mail-signature-preview.c
new file mode 100644
index 0000000..02d9328
--- /dev/null
+++ b/widgets/misc/e-mail-signature-preview.c
@@ -0,0 +1,360 @@
+/*
+ * e-mail-signature-preview.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-preview.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib/gstdio.h>
+
+#include <libedataserver/e-source-mail-signature.h>
+
+#include <libevolution-utils/e-alert-sink.h>
+
+#define E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreviewPrivate))
+
+#define SOURCE_IS_MAIL_SIGNATURE(source) \
+	(e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_SIGNATURE))
+
+struct _EMailSignaturePreviewPrivate {
+	ESourceRegistry *registry;
+	GCancellable *cancellable;
+	gchar *source_uid;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY,
+	PROP_SOURCE_UID
+};
+
+enum {
+	REFRESH,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+	EMailSignaturePreview,
+	e_mail_signature_preview,
+	E_TYPE_WEB_VIEW)
+
+static void
+mail_signature_preview_load_cb (ESource *source,
+                                GAsyncResult *result,
+                                EMailSignaturePreview *preview)
+{
+	ESourceMailSignature *extension;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	gchar *contents = NULL;
+	GError *error = NULL;
+
+	e_source_mail_signature_load_finish (
+		source, result, &contents, NULL, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (contents == NULL);
+		g_object_unref (preview);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (contents == NULL);
+		e_alert_submit (
+			E_ALERT_SINK (preview),
+			"widgets:no-load-signature",
+			error->message, NULL);
+		g_object_unref (preview);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (contents != NULL);
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	mime_type = e_source_mail_signature_get_mime_type (extension);
+
+	if (g_strcmp0 (mime_type, "text/html") == 0)
+		e_web_view_load_string (E_WEB_VIEW (preview), contents);
+	else {
+		gchar *string;
+
+		string = g_markup_printf_escaped ("<pre>%s</pre>", contents);
+		e_web_view_load_string (E_WEB_VIEW (preview), string);
+		g_free (string);
+	}
+
+	g_free (contents);
+
+	g_object_unref (preview);
+}
+
+static void
+mail_signature_preview_set_registry (EMailSignaturePreview *preview,
+                                     ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (preview->priv->registry == NULL);
+
+	preview->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_preview_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			mail_signature_preview_set_registry (
+				E_MAIL_SIGNATURE_PREVIEW (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SOURCE_UID:
+			e_mail_signature_preview_set_source_uid (
+				E_MAIL_SIGNATURE_PREVIEW (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_preview_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_preview_get_registry (
+				E_MAIL_SIGNATURE_PREVIEW (object)));
+			return;
+
+		case PROP_SOURCE_UID:
+			g_value_set_string (
+				value,
+				e_mail_signature_preview_get_source_uid (
+				E_MAIL_SIGNATURE_PREVIEW (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_preview_dispose (GObject *object)
+{
+	EMailSignaturePreviewPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->cancellable != NULL) {
+		g_cancellable_cancel (priv->cancellable);
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_preview_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_preview_finalize (GObject *object)
+{
+	EMailSignaturePreviewPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE (object);
+
+	g_free (priv->source_uid);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_mail_signature_preview_parent_class)->
+		finalize (object);
+}
+
+static void
+mail_signature_preview_refresh (EMailSignaturePreview *preview)
+{
+	ESourceRegistry *registry;
+	ESource *source;
+	const gchar *extension_name;
+	const gchar *source_uid;
+
+	/* Cancel any unfinished refreshes. */
+	if (preview->priv->cancellable != NULL) {
+		g_cancellable_cancel (preview->priv->cancellable);
+		g_object_unref (preview->priv->cancellable);
+		preview->priv->cancellable = NULL;
+	}
+
+	source_uid = e_mail_signature_preview_get_source_uid (preview);
+
+	if (source_uid == NULL)
+		goto fail;
+
+	registry = e_mail_signature_preview_get_registry (preview);
+	source = e_source_registry_ref_source (registry, source_uid);
+
+	if (source == NULL)
+		goto fail;
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	if (!e_source_has_extension (source, extension_name)) {
+		g_object_unref (source);
+		goto fail;
+	}
+
+	preview->priv->cancellable = g_cancellable_new ();
+
+	e_source_mail_signature_load (
+		source, G_PRIORITY_DEFAULT,
+		preview->priv->cancellable, (GAsyncReadyCallback)
+		mail_signature_preview_load_cb, g_object_ref (preview));
+
+	g_object_unref (source);
+
+	return;
+
+fail:
+	e_web_view_clear (E_WEB_VIEW (preview));
+}
+
+static void
+e_mail_signature_preview_class_init (EMailSignaturePreviewClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EMailSignaturePreviewPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_preview_set_property;
+	object_class->get_property = mail_signature_preview_get_property;
+	object_class->dispose = mail_signature_preview_dispose;
+	object_class->finalize = mail_signature_preview_finalize;
+
+	class->refresh = mail_signature_preview_refresh;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE_UID,
+		g_param_spec_string (
+			"source-uid",
+			"Source UID",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[REFRESH] = g_signal_new (
+		"refresh",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EMailSignaturePreviewClass, refresh),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_mail_signature_preview_init (EMailSignaturePreview *preview)
+{
+	preview->priv = E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE (preview);
+}
+
+GtkWidget *
+e_mail_signature_preview_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_PREVIEW,
+		"registry", registry, NULL);
+}
+
+void
+e_mail_signature_preview_refresh (EMailSignaturePreview *preview)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview));
+
+	g_signal_emit (preview, signals[REFRESH], 0);
+}
+
+ESourceRegistry *
+e_mail_signature_preview_get_registry (EMailSignaturePreview *preview)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview), NULL);
+
+	return preview->priv->registry;
+}
+
+const gchar *
+e_mail_signature_preview_get_source_uid (EMailSignaturePreview *preview)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview), NULL);
+
+	return preview->priv->source_uid;
+}
+
+void
+e_mail_signature_preview_set_source_uid (EMailSignaturePreview *preview,
+                                         const gchar *source_uid)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview));
+
+	/* Avoid repeatedly loading the same signature file. */
+	if (g_strcmp0 (source_uid, preview->priv->source_uid) == 0)
+		return;
+
+	g_free (preview->priv->source_uid);
+	preview->priv->source_uid = g_strdup (source_uid);
+
+	g_object_notify (G_OBJECT (preview), "source-uid");
+
+	e_mail_signature_preview_refresh (preview);
+}
diff --git a/widgets/misc/e-mail-signature-preview.h b/widgets/misc/e-mail-signature-preview.h
new file mode 100644
index 0000000..205ff62
--- /dev/null
+++ b/widgets/misc/e-mail-signature-preview.h
@@ -0,0 +1,79 @@
+/*
+ * e-mail-signature-preview.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SIGNATURE_PREVIEW_H
+#define E_MAIL_SIGNATURE_PREVIEW_H
+
+#include <misc/e-web-view.h>
+#include <libedataserver/e-source-registry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_PREVIEW \
+	(e_mail_signature_preview_get_type ())
+#define E_MAIL_SIGNATURE_PREVIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreview))
+#define E_MAIL_SIGNATURE_PREVIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreviewClass))
+#define E_IS_MAIL_SIGNATURE_PREVIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW))
+#define E_IS_MAIL_SIGNATURE_PREVIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_PREVIEW))
+#define E_MAIL_SIGNATURE_PREVIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreview))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignaturePreview EMailSignaturePreview;
+typedef struct _EMailSignaturePreviewClass EMailSignaturePreviewClass;
+typedef struct _EMailSignaturePreviewPrivate EMailSignaturePreviewPrivate;
+
+struct _EMailSignaturePreview {
+	EWebView parent;
+	EMailSignaturePreviewPrivate *priv;
+};
+
+struct _EMailSignaturePreviewClass {
+	EWebViewClass parent_class;
+
+	/* Signals */
+	void		(*refresh)	(EMailSignaturePreview *preview);
+};
+
+GType		e_mail_signature_preview_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_preview_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_preview_refresh
+					(EMailSignaturePreview *preview);
+ESourceRegistry *
+		e_mail_signature_preview_get_registry
+					(EMailSignaturePreview *preview);
+const gchar *	e_mail_signature_preview_get_source_uid
+					(EMailSignaturePreview *preview);
+void		e_mail_signature_preview_set_source_uid
+					(EMailSignaturePreview *preview,
+					 const gchar *source_uid);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_PREVIEW_H */
diff --git a/widgets/misc/e-mail-signature-script-dialog.c b/widgets/misc/e-mail-signature-script-dialog.c
new file mode 100644
index 0000000..74d9bca
--- /dev/null
+++ b/widgets/misc/e-mail-signature-script-dialog.c
@@ -0,0 +1,732 @@
+/*
+ * e-mail-signature-script-dialog.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-script-dialog.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-source-mail-signature.h>
+
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialogPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EMailSignatureScriptDialogPrivate {
+	ESourceRegistry *registry;
+	ESource *source;
+
+	GtkWidget *entry;		/* not referenced */
+	GtkWidget *file_chooser;	/* not referenced */
+	GtkWidget *alert;		/* not referenced */
+
+	gchar *symlink_target;
+};
+
+struct _AsyncContext {
+	GCancellable *cancellable;
+	gchar *symlink_target;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY,
+	PROP_SOURCE,
+	PROP_SYMLINK_TARGET
+};
+
+G_DEFINE_TYPE (
+	EMailSignatureScriptDialog,
+	e_mail_signature_script_dialog,
+	GTK_TYPE_DIALOG)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->cancellable != NULL)
+		g_object_unref (async_context->cancellable);
+
+	g_free (async_context->symlink_target);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static gboolean
+mail_signature_script_dialog_filter_cb (const GtkFileFilterInfo *filter_info)
+{
+	return g_file_test (filter_info->filename, G_FILE_TEST_IS_EXECUTABLE);
+}
+
+static void
+mail_signature_script_dialog_update_status (EMailSignatureScriptDialog *dialog)
+{
+	ESource *source;
+	const gchar *display_name;
+	const gchar *symlink_target;
+	gboolean show_alert;
+	gboolean sensitive;
+
+	source = e_mail_signature_script_dialog_get_source (dialog);
+
+	display_name = e_source_get_display_name (source);
+	sensitive = (display_name != NULL && *display_name != '\0');
+
+	symlink_target =
+		e_mail_signature_script_dialog_get_symlink_target (dialog);
+
+	if (symlink_target != NULL) {
+		gboolean executable;
+
+		executable = g_file_test (
+			symlink_target, G_FILE_TEST_IS_EXECUTABLE);
+
+		show_alert = !executable;
+		sensitive &= executable;
+	} else {
+		sensitive = FALSE;
+		show_alert = FALSE;
+	}
+
+	if (show_alert)
+		gtk_widget_show (dialog->priv->alert);
+	else
+		gtk_widget_hide (dialog->priv->alert);
+
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK, sensitive);
+}
+
+static void
+mail_signature_script_dialog_file_set_cb (GtkFileChooserButton *button,
+                                          EMailSignatureScriptDialog *dialog)
+{
+	ESource *source;
+	ESourceMailSignature *extension;
+	GtkFileChooser *file_chooser;
+	const gchar *extension_name;
+	gchar *filename;
+
+	file_chooser = GTK_FILE_CHOOSER (button);
+	filename = gtk_file_chooser_get_filename (file_chooser);
+
+	g_free (dialog->priv->symlink_target);
+	dialog->priv->symlink_target = filename;  /* takes ownership */
+
+	/* Invalidate the saved MIME type. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	source = e_mail_signature_script_dialog_get_source (dialog);
+	extension = e_source_get_extension (source, extension_name);
+	e_source_mail_signature_set_mime_type (extension, NULL);
+
+	g_object_notify (G_OBJECT (dialog), "symlink-target");
+
+	mail_signature_script_dialog_update_status (dialog);
+}
+
+static void
+mail_signature_script_dialog_query_cb (GFile *file,
+                                       GAsyncResult *result,
+                                       EMailSignatureScriptDialog *dialog)
+{
+	GFileInfo *file_info;
+	const gchar *symlink_target;
+	GError *error = NULL;
+
+	file_info = g_file_query_info_finish (file, result, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (file_info == NULL);
+		g_object_unref (dialog);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (file_info == NULL);
+		g_warning ("%s", error->message);
+		g_object_unref (dialog);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (G_IS_FILE_INFO (file_info));
+
+	symlink_target = g_file_info_get_symlink_target (file_info);
+
+	e_mail_signature_script_dialog_set_symlink_target (
+		dialog, symlink_target);
+
+	g_object_unref (file_info);
+	g_object_unref (dialog);
+}
+
+static void
+mail_signature_script_dialog_set_registry (EMailSignatureScriptDialog *dialog,
+                                           ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (dialog->priv->registry == NULL);
+
+	dialog->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_script_dialog_set_source (EMailSignatureScriptDialog *dialog,
+                                         ESource *source)
+{
+	GDBusObject *dbus_object = NULL;
+	const gchar *extension_name;
+	GError *error = NULL;
+
+	g_return_if_fail (source == NULL || E_IS_SOURCE (source));
+	g_return_if_fail (dialog->priv->source == NULL);
+
+	if (source != NULL)
+		dbus_object = e_source_ref_dbus_object (source);
+
+	/* Clone the source so we can make changes to it freely. */
+	dialog->priv->source = e_source_new (dbus_object, NULL, &error);
+
+	/* This should rarely fail.  If the file was loaded successfully
+	 * once then it should load successfully here as well, unless an
+	 * I/O error occurs. */
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	/* Make sure the source has a mail signature extension. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	e_source_get_extension (dialog->priv->source, extension_name);
+
+	/* If we're editing an existing signature, query the symbolic
+	 * link target of the signature file so we can initialize the
+	 * file chooser button.  Note: The asynchronous callback will
+	 * run after the dialog initialization is complete. */
+	if (dbus_object != NULL) {
+		ESourceMailSignature *extension;
+		const gchar *extension_name;
+		GFile *file;
+
+		extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+		extension = e_source_get_extension (source, extension_name);
+		file = e_source_mail_signature_get_file (extension);
+
+		g_file_query_info_async (
+			file, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+			G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
+			NULL, (GAsyncReadyCallback)
+			mail_signature_script_dialog_query_cb,
+			g_object_ref (dialog));
+
+		g_object_unref (dbus_object);
+	}
+}
+
+static void
+mail_signature_script_dialog_set_property (GObject *object,
+                                           guint property_id,
+                                           const GValue *value,
+                                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			mail_signature_script_dialog_set_registry (
+				E_MAIL_SIGNATURE_SCRIPT_DIALOG (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SOURCE:
+			mail_signature_script_dialog_set_source (
+				E_MAIL_SIGNATURE_SCRIPT_DIALOG (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SYMLINK_TARGET:
+			e_mail_signature_script_dialog_set_symlink_target (
+				E_MAIL_SIGNATURE_SCRIPT_DIALOG (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_script_dialog_get_property (GObject *object,
+                                           guint property_id,
+                                           GValue *value,
+                                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_script_dialog_get_registry (
+				E_MAIL_SIGNATURE_SCRIPT_DIALOG (object)));
+			return;
+
+		case PROP_SOURCE:
+			g_value_set_object (
+				value,
+				e_mail_signature_script_dialog_get_source (
+				E_MAIL_SIGNATURE_SCRIPT_DIALOG (object)));
+			return;
+
+		case PROP_SYMLINK_TARGET:
+			g_value_set_string (
+				value,
+				e_mail_signature_script_dialog_get_symlink_target (
+				E_MAIL_SIGNATURE_SCRIPT_DIALOG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_script_dialog_dispose (GObject *object)
+{
+	EMailSignatureScriptDialogPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->source != NULL) {
+		g_object_unref (priv->source);
+		priv->source = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_script_dialog_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_script_dialog_finalize (GObject *object)
+{
+	EMailSignatureScriptDialogPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE (object);
+
+	g_free (priv->symlink_target);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_mail_signature_script_dialog_parent_class)->
+		finalize (object);
+}
+
+static void
+mail_signature_script_dialog_constructed (GObject *object)
+{
+	EMailSignatureScriptDialog *dialog;
+	GtkFileFilter *filter;
+	GtkWidget *container;
+	GtkWidget *widget;
+	ESource *source;
+	const gchar *display_name;
+	gchar *markup;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_mail_signature_script_dialog_parent_class)->
+		constructed (object);
+
+	dialog = E_MAIL_SIGNATURE_SCRIPT_DIALOG (object);
+
+	source = e_mail_signature_script_dialog_get_source (dialog);
+	display_name = e_source_get_display_name (source);
+
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+
+	gtk_dialog_add_button (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+
+	gtk_dialog_add_button (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_SAVE, GTK_RESPONSE_OK);
+
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+	container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+	widget = gtk_table_new (4, 2, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (widget), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (widget), 6);
+	gtk_table_set_row_spacing (GTK_TABLE (widget), 0, 12);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 5);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_image_new_from_stock (
+		GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, 0, 1, 0, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_label_new (_(
+		"The output of this script will be used as your\n"
+		"signature. The name you specify will be used\n"
+		"for display purposes only."));
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (widget), display_name);
+	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+	dialog->priv->entry = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		widget, "text",
+		source, "display-name",
+		G_BINDING_DEFAULT);
+
+	widget = gtk_label_new_with_mnemonic (_("_Name:"));
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (widget), dialog->priv->entry);
+	gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_file_chooser_button_new (
+		NULL, GTK_FILE_CHOOSER_ACTION_OPEN);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		1, 2, 2, 3, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+	dialog->priv->file_chooser = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	/* Restrict file selection to executable files. */
+	filter = gtk_file_filter_new ();
+	gtk_file_filter_add_custom (
+		filter, GTK_FILE_FILTER_FILENAME,
+		(GtkFileFilterFunc) mail_signature_script_dialog_filter_cb,
+		NULL, NULL);
+	gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (widget), filter);
+
+	/* We create symbolic links to script files from the "signatures"
+	 * directory, so restrict the selection to local files only. */
+	gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), TRUE);
+
+	widget = gtk_label_new_with_mnemonic (_("S_cript:"));
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (widget), dialog->priv->file_chooser);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	/* This is just a placeholder. */
+	widget = gtk_label_new (NULL);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, 3, 4, GTK_FILL, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_hbox_new (FALSE, 6);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		1, 2, 3, 4, 0, 0, 0, 0);
+	dialog->priv->alert = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_image_new_from_stock (
+		GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	markup = g_markup_printf_escaped (
+		"<small>%s</small>",
+		_("Script file must be executable."));
+	widget = gtk_label_new (markup);
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+	g_free (markup);
+
+	g_signal_connect (
+		dialog->priv->file_chooser, "file-set",
+		G_CALLBACK (mail_signature_script_dialog_file_set_cb), dialog);
+
+	g_signal_connect_swapped (
+		dialog->priv->entry, "changed",
+		G_CALLBACK (mail_signature_script_dialog_update_status), dialog);
+
+	mail_signature_script_dialog_update_status (dialog);
+}
+
+static void
+e_mail_signature_script_dialog_class_init (EMailSignatureScriptDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (
+		class, sizeof (EMailSignatureScriptDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_script_dialog_set_property;
+	object_class->get_property = mail_signature_script_dialog_get_property;
+	object_class->dispose = mail_signature_script_dialog_dispose;
+	object_class->finalize = mail_signature_script_dialog_finalize;
+	object_class->constructed = mail_signature_script_dialog_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE,
+		g_param_spec_object (
+			"source",
+			"Source",
+			NULL,
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SYMLINK_TARGET,
+		g_param_spec_string (
+			"symlink-target",
+			"Symlink Target",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_mail_signature_script_dialog_init (EMailSignatureScriptDialog *dialog)
+{
+	dialog->priv = E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE (dialog);
+}
+
+GtkWidget *
+e_mail_signature_script_dialog_new (ESourceRegistry *registry,
+                                    GtkWindow *parent,
+                                    ESource *source)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG,
+		"registry", registry,
+		"transient-for", parent,
+		"source", source, NULL);
+}
+
+ESourceRegistry *
+e_mail_signature_script_dialog_get_registry (EMailSignatureScriptDialog *dialog)
+{
+	g_return_val_if_fail (
+		E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL);
+
+	return dialog->priv->registry;
+}
+
+ESource *
+e_mail_signature_script_dialog_get_source (EMailSignatureScriptDialog *dialog)
+{
+	g_return_val_if_fail (
+		E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL);
+
+	return dialog->priv->source;
+}
+
+const gchar *
+e_mail_signature_script_dialog_get_symlink_target (EMailSignatureScriptDialog *dialog)
+{
+	g_return_val_if_fail (
+		E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL);
+
+	return dialog->priv->symlink_target;
+}
+
+void
+e_mail_signature_script_dialog_set_symlink_target (EMailSignatureScriptDialog *dialog,
+                                                   const gchar *symlink_target)
+{
+	GtkFileChooser *file_chooser;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog));
+	g_return_if_fail (symlink_target != NULL);
+
+	g_free (dialog->priv->symlink_target);
+	dialog->priv->symlink_target = g_strdup (symlink_target);
+
+	file_chooser = GTK_FILE_CHOOSER (dialog->priv->file_chooser);
+	gtk_file_chooser_set_filename (file_chooser, symlink_target);
+
+	g_object_notify (G_OBJECT (dialog), "symlink-target");
+
+	mail_signature_script_dialog_update_status (dialog);
+}
+
+/****************** e_mail_signature_script_dialog_commit() ******************/
+
+static void
+mail_signature_script_dialog_symlink_cb (GObject *object,
+                                         GAsyncResult *result,
+                                         gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+	e_source_mail_signature_symlink_finish (
+		E_SOURCE (object), result, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+
+	g_object_unref (simple);
+}
+
+static void
+mail_signature_script_dialog_commit_cb (GObject *object,
+                                        GAsyncResult *result,
+                                        gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	ESource *source = NULL;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	e_source_registry_commit_source_finish (
+		E_SOURCE_REGISTRY (object), result, &source, &error);
+
+	if (error != NULL) {
+		g_warn_if_fail (source == NULL);
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	e_source_mail_signature_symlink (
+		source,
+		async_context->symlink_target,
+		G_PRIORITY_DEFAULT,
+		async_context->cancellable,
+		mail_signature_script_dialog_symlink_cb,
+		simple);
+
+	g_object_unref (source);
+}
+
+void
+e_mail_signature_script_dialog_commit (EMailSignatureScriptDialog *dialog,
+                                       GCancellable *cancellable,
+                                       GAsyncReadyCallback callback,
+                                       gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	ESourceRegistry *registry;
+	ESource *source;
+	const gchar *symlink_target;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog));
+
+	registry = e_mail_signature_script_dialog_get_registry (dialog);
+	source = e_mail_signature_script_dialog_get_source (dialog);
+
+	symlink_target =
+		e_mail_signature_script_dialog_get_symlink_target (dialog);
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->symlink_target = g_strdup (symlink_target);
+
+	if (G_IS_CANCELLABLE (cancellable))
+		async_context->cancellable = g_object_ref (cancellable);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (dialog), callback, user_data,
+		e_mail_signature_script_dialog_commit);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	e_source_registry_commit_source (
+		registry, source,
+		async_context->cancellable,
+		mail_signature_script_dialog_commit_cb,
+		simple);
+}
+
+gboolean
+e_mail_signature_script_dialog_commit_finish (EMailSignatureScriptDialog *dialog,
+                                              GAsyncResult *result,
+                                              GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (dialog),
+		e_mail_signature_script_dialog_commit), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
diff --git a/widgets/misc/e-mail-signature-script-dialog.h b/widgets/misc/e-mail-signature-script-dialog.h
new file mode 100644
index 0000000..0b458ab
--- /dev/null
+++ b/widgets/misc/e-mail-signature-script-dialog.h
@@ -0,0 +1,90 @@
+/*
+ * e-mail-signature-script-dialog.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SIGNATURE_SCRIPT_DIALOG_H
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/e-source-registry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG \
+	(e_mail_signature_script_dialog_get_type ())
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialog))
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialogClass))
+#define E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG))
+#define E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG))
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureScriptDialog EMailSignatureScriptDialog;
+typedef struct _EMailSignatureScriptDialogClass EMailSignatureScriptDialogClass;
+typedef struct _EMailSignatureScriptDialogPrivate EMailSignatureScriptDialogPrivate;
+
+struct _EMailSignatureScriptDialog {
+	GtkDialog parent;
+	EMailSignatureScriptDialogPrivate *priv;
+};
+
+struct _EMailSignatureScriptDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_mail_signature_script_dialog_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_script_dialog_new
+					(ESourceRegistry *registry,
+					 GtkWindow *parent,
+					 ESource *source);
+ESourceRegistry *
+		e_mail_signature_script_dialog_get_registry
+					(EMailSignatureScriptDialog *dialog);
+ESource *	e_mail_signature_script_dialog_get_source
+					(EMailSignatureScriptDialog *dialog);
+const gchar *	e_mail_signature_script_dialog_get_symlink_target
+					(EMailSignatureScriptDialog *dialog);
+void		e_mail_signature_script_dialog_set_symlink_target
+					(EMailSignatureScriptDialog *dialog,
+					 const gchar *symlink_target);
+void		e_mail_signature_script_dialog_commit
+					(EMailSignatureScriptDialog *dialog,
+					 GCancellable *cancellable,
+					 GAsyncReadyCallback callback,
+					 gpointer user_data);
+gboolean	e_mail_signature_script_dialog_commit_finish
+					(EMailSignatureScriptDialog *dialog,
+					 GAsyncResult *result,
+					 GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_SCRIPT_DIALOG_H */
diff --git a/widgets/misc/e-mail-signature-tree-view.c b/widgets/misc/e-mail-signature-tree-view.c
new file mode 100644
index 0000000..3dd6a85
--- /dev/null
+++ b/widgets/misc/e-mail-signature-tree-view.c
@@ -0,0 +1,397 @@
+/*
+ * e-mail-signature-tree-view.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-tree-view.h"
+
+#include <libedataserver/e-source-mail-signature.h>
+
+#define E_MAIL_SIGNATURE_TREE_VIEW_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeViewPrivate))
+
+#define SOURCE_IS_MAIL_SIGNATURE(source) \
+	(e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_SIGNATURE))
+
+struct _EMailSignatureTreeViewPrivate {
+	ESourceRegistry *registry;
+	guint refresh_idle_id;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY
+};
+
+enum {
+	COLUMN_DISPLAY_NAME,
+	COLUMN_UID,
+	NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (
+	EMailSignatureTreeView,
+	e_mail_signature_tree_view,
+	GTK_TYPE_TREE_VIEW)
+
+static gboolean
+mail_signature_tree_view_refresh_idle_cb (EMailSignatureTreeView *tree_view)
+{
+	/* The refresh function will clear the idle ID. */
+	e_mail_signature_tree_view_refresh (tree_view);
+
+	return FALSE;
+}
+
+static void
+mail_signature_tree_view_registry_changed (ESourceRegistry *registry,
+                                           ESource *source,
+                                           EMailSignatureTreeView *tree_view)
+{
+	/* If the ESource in question has a "Mail Signature" extension,
+	 * schedule a refresh of the tree model.  Otherwise ignore it.
+	 * We use an idle callback to limit how frequently we refresh
+	 * the tree model, in case the registry is emitting lots of
+	 * signals at once. */
+
+	if (!SOURCE_IS_MAIL_SIGNATURE (source))
+		return;
+
+	if (tree_view->priv->refresh_idle_id > 0)
+		return;
+
+	tree_view->priv->refresh_idle_id = gdk_threads_add_idle (
+		(GSourceFunc) mail_signature_tree_view_refresh_idle_cb,
+		tree_view);
+}
+
+static void
+mail_signature_tree_view_set_registry (EMailSignatureTreeView *tree_view,
+                                       ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (tree_view->priv->registry == NULL);
+
+	tree_view->priv->registry = g_object_ref (registry);
+
+	g_signal_connect (
+		registry, "source-added",
+		G_CALLBACK (mail_signature_tree_view_registry_changed),
+		tree_view);
+
+	g_signal_connect (
+		registry, "source-changed",
+		G_CALLBACK (mail_signature_tree_view_registry_changed),
+		tree_view);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (mail_signature_tree_view_registry_changed),
+		tree_view);
+}
+
+static void
+mail_signature_tree_view_set_property (GObject *object,
+                                       guint property_id,
+                                       const GValue *value,
+                                       GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			mail_signature_tree_view_set_registry (
+				E_MAIL_SIGNATURE_TREE_VIEW (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_tree_view_get_property (GObject *object,
+                                       guint property_id,
+                                       GValue *value,
+                                       GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_tree_view_get_registry (
+				E_MAIL_SIGNATURE_TREE_VIEW (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_tree_view_dispose (GObject *object)
+{
+	EMailSignatureTreeViewPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_TREE_VIEW_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->registry, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->refresh_idle_id > 0) {
+		g_source_remove (priv->refresh_idle_id);
+		priv->refresh_idle_id = 0;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_tree_view_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_tree_view_constructed (GObject *object)
+{
+	GtkTreeView *tree_view;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *cell_renderer;
+	GtkListStore *list_store;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_mail_signature_tree_view_parent_class)->
+		constructed (object);
+
+	list_store = gtk_list_store_new (
+		NUM_COLUMNS,
+		G_TYPE_STRING,		/* COLUMN_DISPLAY_NAME */
+		G_TYPE_STRING);		/* COLUMN_UID */
+
+	tree_view = GTK_TREE_VIEW (object);
+	gtk_tree_view_set_headers_visible (tree_view, FALSE);
+	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
+
+	g_object_unref (list_store);
+
+	/* Column: Signature Name */
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_column_set_expand (column, TRUE);
+
+	cell_renderer = gtk_cell_renderer_text_new ();
+	g_object_set (cell_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+	gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+
+	gtk_tree_view_column_add_attribute (
+		column, cell_renderer, "text", COLUMN_DISPLAY_NAME);
+
+	gtk_tree_view_append_column (tree_view, column);
+
+	e_mail_signature_tree_view_refresh (
+		E_MAIL_SIGNATURE_TREE_VIEW (object));
+}
+
+static void
+e_mail_signature_tree_view_class_init (EMailSignatureTreeViewClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (
+		class, sizeof (EMailSignatureTreeViewPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_tree_view_set_property;
+	object_class->get_property = mail_signature_tree_view_get_property;
+	object_class->dispose = mail_signature_tree_view_dispose;
+	object_class->constructed = mail_signature_tree_view_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_mail_signature_tree_view_init (EMailSignatureTreeView *tree_view)
+{
+	tree_view->priv = E_MAIL_SIGNATURE_TREE_VIEW_GET_PRIVATE (tree_view);
+}
+
+GtkWidget *
+e_mail_signature_tree_view_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_TREE_VIEW,
+		"registry", registry, NULL);
+}
+
+void
+e_mail_signature_tree_view_refresh (EMailSignatureTreeView *tree_view)
+{
+	ESourceRegistry *registry;
+	GtkTreeModel *tree_model;
+	GtkTreeSelection *selection;
+	ESource *source;
+	GList *list, *link;
+	const gchar *extension_name;
+	gchar *saved_uid = NULL;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view));
+
+	if (tree_view->priv->refresh_idle_id > 0) {
+		g_source_remove (tree_view->priv->refresh_idle_id);
+		tree_view->priv->refresh_idle_id = 0;
+	}
+
+	registry = e_mail_signature_tree_view_get_registry (tree_view);
+	tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+	source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+	if (source != NULL) {
+		saved_uid = e_source_dup_uid (source);
+		g_object_unref (source);
+	}
+
+	gtk_list_store_clear (GTK_LIST_STORE (tree_model));
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	list = e_source_registry_list_sources (registry, extension_name);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		GtkTreeIter iter;
+		const gchar *display_name;
+		const gchar *uid;
+
+		source = E_SOURCE (link->data);
+		display_name = e_source_get_display_name (source);
+		uid = e_source_get_uid (source);
+
+		gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+		gtk_list_store_set (
+			GTK_LIST_STORE (tree_model), &iter,
+			COLUMN_DISPLAY_NAME, display_name,
+			COLUMN_UID, uid, -1);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	/* Try and restore the previous selected source. */
+
+	source = NULL;
+
+	if (saved_uid != NULL) {
+		source = e_source_registry_ref_source (registry, saved_uid);
+		g_free (saved_uid);
+	}
+
+	if (source != NULL) {
+		e_mail_signature_tree_view_set_selected_source (
+			tree_view, source);
+		g_object_unref (source);
+	}
+
+	/* Hint to refresh a signature preview. */
+	g_signal_emit_by_name (selection, "changed");
+}
+
+ESourceRegistry *
+e_mail_signature_tree_view_get_registry (EMailSignatureTreeView *tree_view)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view), NULL);
+
+	return tree_view->priv->registry;
+}
+
+ESource *
+e_mail_signature_tree_view_ref_selected_source (EMailSignatureTreeView *tree_view)
+{
+	ESourceRegistry *registry;
+	GtkTreeSelection *selection;
+	GtkTreeModel *tree_model;
+	GtkTreeIter iter;
+	ESource *source;
+	gchar *uid;
+
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view), NULL);
+
+	registry = e_mail_signature_tree_view_get_registry (tree_view);
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+	if (!gtk_tree_selection_get_selected (selection, &tree_model, &iter))
+		return NULL;
+
+	gtk_tree_model_get (tree_model, &iter, COLUMN_UID, &uid, -1);
+	source = e_source_registry_ref_source (registry, uid);
+	g_free (uid);
+
+	return source;
+}
+
+void
+e_mail_signature_tree_view_set_selected_source (EMailSignatureTreeView *tree_view,
+                                                ESource *source)
+{
+	ESourceRegistry *registry;
+	GtkTreeSelection *selection;
+	GtkTreeModel *tree_model;
+	GtkTreeIter iter;
+	gboolean valid;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	/* It is a programming error to pass an ESource that has no
+	 * "Mail Signature" extension. */
+	g_return_if_fail (SOURCE_IS_MAIL_SIGNATURE (source));
+
+	registry = e_mail_signature_tree_view_get_registry (tree_view);
+	tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+	valid = gtk_tree_model_get_iter_first (tree_model, &iter);
+
+	while (valid) {
+		ESource *candidate;
+		gchar *uid;
+
+		gtk_tree_model_get (tree_model, &iter, COLUMN_UID, &uid, -1);
+		candidate = e_source_registry_ref_source (registry, uid);
+		g_free (uid);
+
+		if (candidate != NULL && e_source_equal (source, candidate)) {
+			gtk_tree_selection_select_iter (selection, &iter);
+			g_object_unref (candidate);
+			break;
+		}
+
+		if (candidate != NULL)
+			g_object_unref (candidate);
+
+		valid = gtk_tree_model_iter_next (tree_model, &iter);
+	}
+}
diff --git a/widgets/misc/e-mail-signature-tree-view.h b/widgets/misc/e-mail-signature-tree-view.h
new file mode 100644
index 0000000..e3129c9
--- /dev/null
+++ b/widgets/misc/e-mail-signature-tree-view.h
@@ -0,0 +1,76 @@
+/*
+ * e-mail-signature-tree-view.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SIGNATURE_TREE_VIEW_H
+#define E_MAIL_SIGNATURE_TREE_VIEW_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/e-source-registry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_TREE_VIEW \
+	(e_mail_signature_tree_view_get_type ())
+#define E_MAIL_SIGNATURE_TREE_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeView))
+#define E_MAIL_SIGNATURE_TREE_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeViewClass))
+#define E_IS_MAIL_SIGNATURE_TREE_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW))
+#define E_IS_MAIL_SIGNATURE_TREE_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_TREE_VIEW))
+#define E_MAIL_SIGNATURE_TREE_VIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureTreeView EMailSignatureTreeView;
+typedef struct _EMailSignatureTreeViewClass EMailSignatureTreeViewClass;
+typedef struct _EMailSignatureTreeViewPrivate EMailSignatureTreeViewPrivate;
+
+struct _EMailSignatureTreeView {
+	GtkTreeView parent;
+	EMailSignatureTreeViewPrivate *priv;
+};
+
+struct _EMailSignatureTreeViewClass {
+	GtkTreeViewClass parent_class;
+};
+
+GType		e_mail_signature_tree_view_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_tree_view_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_tree_view_refresh
+					(EMailSignatureTreeView *tree_view);
+ESourceRegistry *
+		e_mail_signature_tree_view_get_registry
+					(EMailSignatureTreeView *tree_view);
+ESource *	e_mail_signature_tree_view_ref_selected_source
+					(EMailSignatureTreeView *tree_view);
+void		e_mail_signature_tree_view_set_selected_source
+					(EMailSignatureTreeView *tree_view,
+					 ESource *selected_source);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_TREE_VIEW_H */
diff --git a/widgets/misc/test-mail-signatures.c b/widgets/misc/test-mail-signatures.c
new file mode 100644
index 0000000..7650195
--- /dev/null
+++ b/widgets/misc/test-mail-signatures.c
@@ -0,0 +1,199 @@
+/*
+ * test-mail-signatures.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <stdlib.h>
+
+#include <libedataserver/e-source-mail-identity.h>
+
+#include <libevolution-utils/e-alert-sink.h>
+#include <misc/e-mail-identity-combo-box.h>
+#include <misc/e-mail-signature-combo-box.h>
+#include <misc/e-mail-signature-manager.h>
+#include <misc/e-mail-signature-preview.h>
+
+static GCancellable *cancellable = NULL;
+
+static void
+signature_loaded_cb (EMailSignatureComboBox *combo_box,
+                     GAsyncResult *result,
+                     EWebView *web_view)
+{
+	gchar *contents = NULL;
+	gboolean is_html;
+	GError *error = NULL;
+
+	e_mail_signature_combo_box_load_selected_finish (
+		combo_box, result, &contents, NULL, &is_html, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (contents == NULL);
+		g_object_unref (web_view);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (contents == NULL);
+		e_alert_submit (
+			E_ALERT_SINK (web_view),
+			"widgets:no-load-signature",
+			error->message, NULL);
+		g_object_unref (web_view);
+		g_error_free (error);
+		return;
+	}
+
+	if (contents == NULL)
+		e_web_view_clear (web_view);
+	else if (is_html)
+		e_web_view_load_string (web_view, contents);
+	else {
+		gchar *string;
+
+		string = g_markup_printf_escaped ("<pre>%s</pre>", contents);
+		e_web_view_load_string (web_view, string);
+		g_free (string);
+	}
+
+	g_free (contents);
+
+	g_object_unref (web_view);
+}
+
+static void
+signature_combo_changed_cb (EMailSignatureComboBox *combo_box,
+                            EWebView *web_view)
+{
+	if (cancellable != NULL) {
+		g_cancellable_cancel (cancellable);
+		g_object_unref (cancellable);
+	}
+
+	cancellable = g_cancellable_new ();
+
+	e_mail_signature_combo_box_load_selected (
+		combo_box, G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) signature_loaded_cb,
+		g_object_ref (web_view));
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	ESourceRegistry *registry;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GtkWidget *vbox;
+	GtkWidget *identity_combo;
+	GtkWidget *signature_combo;
+	GError *error = NULL;
+
+	gtk_init (&argc, &argv);
+
+	registry = e_source_registry_new_sync (NULL, &error);
+
+	if (error != NULL) {
+		g_printerr ("%s\n", error->message);
+		exit (EXIT_FAILURE);
+	}
+
+	/* Construct the widgets. */
+
+	widget = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title (GTK_WINDOW (widget), "Mail Signatures");
+	gtk_window_set_default_size (GTK_WINDOW (widget), 400, 400);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "delete-event",
+		G_CALLBACK (gtk_main_quit), NULL);
+
+	container = widget;
+
+	widget = gtk_vbox_new (FALSE, 12);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	container = vbox = widget;
+
+	widget = gtk_label_new ("<b>EMailSignatureComboBox</b>");
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_vbox_new (FALSE, 6);
+	gtk_widget_set_margin_left (widget, 12);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_mail_signature_combo_box_new (registry);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	signature_combo = widget;
+	gtk_widget_show (widget);
+
+	widget = e_mail_identity_combo_box_new (registry);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	identity_combo = widget;
+	gtk_widget_show (widget);
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_web_view_new ();
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		signature_combo, "changed",
+		G_CALLBACK (signature_combo_changed_cb), widget);
+
+	container = vbox;
+
+	widget = gtk_label_new ("<b>EMailSignatureManager</b>");
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = e_mail_signature_manager_new (registry);
+	gtk_widget_set_margin_left (widget, 12);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		identity_combo, "active-id",
+		signature_combo, "identity-uid",
+		G_BINDING_SYNC_CREATE);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/widgets/misc/test-source-config.c b/widgets/misc/test-source-config.c
index 2fd69bf..c7b8ac8 100644
--- a/widgets/misc/test-source-config.c
+++ b/widgets/misc/test-source-config.c
@@ -13,7 +13,8 @@ dialog_response (GtkDialog *dialog,
 }
 
 gint
-main (gint argc, gchar **argv)
+main (gint argc,
+      gchar **argv)
 {
 	ESourceRegistry *registry;
 	ESource *source = NULL;
diff --git a/widgets/misc/widgets.error.xml b/widgets/misc/widgets.error.xml
new file mode 100644
index 0000000..efaa41c
--- /dev/null
+++ b/widgets/misc/widgets.error.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<error-list domain="widgets">
+
+  <error id="ask-signature-changed" type="question" default="GTK_RESPONSE_YES">
+    <_primary>Do you wish to save your changes?</_primary>
+    <_secondary xml:space="preserve">This signature has been changed, but has not been saved.</_secondary>
+    <button _label="_Discard changes" response="GTK_RESPONSE_NO"/>
+    <button stock="gtk-cancel" response="GTK_RESPONSE_CANCEL"/>
+    <button stock="gtk-save" response="GTK_RESPONSE_YES"/>
+  </error>
+
+  <error id="blank-signature" type="error">
+    <_primary>Blank Signature</_primary>
+    <_secondary>Please provide an unique name to identify this signature.</_secondary>
+  </error>
+
+  <error id="no-load-signature" type="error">
+    <_primary>Could not load signature.</_primary>
+    <secondary xml:space="preserve">{0}</secondary>
+  </error>
+
+  <error id="no-save-signature" type="error">
+    <_primary>Could not save signature.</_primary>
+    <secondary xml:space="preserve">{0}</secondary>
+  </error>
+
+</error-list>
+



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