[evolution/account-mgmt: 2/25] Add ESourceConfig widget.



commit 93c9c8aa13e576eeb0d7ac434f8520562e70a4c6
Author: Matthew Barnes <mbarnes redhat com>
Date:   Fri Dec 17 17:45:54 2010 -0500

    Add ESourceConfig widget.
    
    Base class for building address book and calendar configuration dialogs.

 addressbook/gui/widgets/Makefile.am            |    2 +
 addressbook/gui/widgets/e-book-source-config.c |  254 ++++++++
 addressbook/gui/widgets/e-book-source-config.h |   67 ++
 calendar/gui/Makefile.am                       |    3 +
 calendar/gui/e-cal-source-config.c             |  383 +++++++++++
 calendar/gui/e-cal-source-config.h             |   71 ++
 widgets/misc/Makefile.am                       |   29 +-
 widgets/misc/e-interval-chooser.c              |  213 +++++++
 widgets/misc/e-interval-chooser.h              |   66 ++
 widgets/misc/e-source-config-backend.c         |  120 ++++
 widgets/misc/e-source-config-backend.h         |   84 +++
 widgets/misc/e-source-config-dialog.c          |  248 ++++++++
 widgets/misc/e-source-config-dialog.h          |   66 ++
 widgets/misc/e-source-config.c                 |  808 ++++++++++++++++++++++++
 widgets/misc/e-source-config.h                 |   85 +++
 widgets/misc/test-source-config.c              |   60 ++
 16 files changed, 2557 insertions(+), 2 deletions(-)
---
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index 511876a..bbccbf4 100644
--- a/addressbook/gui/widgets/Makefile.am
+++ b/addressbook/gui/widgets/Makefile.am
@@ -49,6 +49,8 @@ libeabwidgets_la_SOURCES =			\
 	e-addressbook-selector.h		\
 	e-addressbook-view.c			\
 	e-addressbook-view.h			\
+	e-book-source-config.c			\
+	e-book-source-config.h			\
 	gal-view-minicard.c			\
 	gal-view-minicard.h			\
 	gal-view-factory-minicard.c		\
diff --git a/addressbook/gui/widgets/e-book-source-config.c b/addressbook/gui/widgets/e-book-source-config.c
new file mode 100644
index 0000000..9838c57
--- /dev/null
+++ b/addressbook/gui/widgets/e-book-source-config.c
@@ -0,0 +1,254 @@
+/*
+ * e-book-source-config.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-book-source-config.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libebook/e-source-address-book.h>
+#include <libedataserver/e-source-autocomplete.h>
+#include <libedataserver/e-source-offline.h>
+
+#define E_BOOK_SOURCE_CONFIG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfigPrivate))
+
+struct _EBookSourceConfigPrivate {
+	GtkWidget *default_button;
+	GtkWidget *autocomplete_button;
+};
+
+G_DEFINE_TYPE (
+	EBookSourceConfig,
+	e_book_source_config,
+	E_TYPE_SOURCE_CONFIG)
+
+static ESource *
+book_source_config_get_default (ESourceConfig *config)
+{
+	ESourceRegistry *registry;
+
+	registry = e_source_config_get_registry (config);
+
+	return e_source_registry_get_default_address_book (registry);
+}
+
+static void
+book_source_config_set_default (ESourceConfig *config,
+                                ESource *source)
+{
+	ESourceRegistry *registry;
+
+	registry = e_source_config_get_registry (config);
+
+	e_source_registry_set_default_address_book (registry, source);
+}
+
+static void
+book_source_config_dispose (GObject *object)
+{
+	EBookSourceConfigPrivate *priv;
+
+	priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	if (priv->default_button != NULL) {
+		g_object_unref (priv->default_button);
+		priv->default_button = NULL;
+	}
+
+	if (priv->autocomplete_button != NULL) {
+		g_object_unref (priv->autocomplete_button);
+		priv->autocomplete_button = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_book_source_config_parent_class)->dispose (object);
+}
+
+static void
+book_source_config_constructed (GObject *object)
+{
+	EBookSourceConfigPrivate *priv;
+	ESource *default_source;
+	ESource *original_source;
+	ESourceConfig *config;
+	GObjectClass *class;
+	GtkWidget *widget;
+	const gchar *label;
+
+	/* Chain up to parent's constructed() method. */
+	class = G_OBJECT_CLASS (e_book_source_config_parent_class);
+	class->constructed (object);
+
+	config = E_SOURCE_CONFIG (object);
+	priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	label = _("Mark as _default address book");
+	widget = gtk_check_button_new_with_mnemonic (label);
+	priv->default_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	label = _("A_utocomplete with this address book");
+	widget = gtk_check_button_new_with_mnemonic (label);
+	priv->autocomplete_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	default_source = book_source_config_get_default (config);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source != NULL) {
+		gboolean active;
+
+		active = e_source_equal (original_source, default_source);
+		g_object_set (priv->default_button, "active", active, NULL);
+	}
+
+	e_source_config_insert_widget (
+		config, NULL, NULL, priv->default_button);
+
+	e_source_config_insert_widget (
+		config, NULL, NULL, priv->autocomplete_button);
+}
+
+static void
+book_source_config_init_candidate (ESourceConfig *config,
+                                   ESource *scratch_source)
+{
+	EBookSourceConfigPrivate *priv;
+	ESourceConfigClass *class;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	/* Chain up to parent's init_candidate() method. */
+	class = E_SOURCE_CONFIG_CLASS (e_book_source_config_parent_class);
+	class->init_candidate (config, scratch_source);
+
+	priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (config);
+
+	/* XXX Address book sources don't actually use any of the keys in
+	 *     the [Address Book] key file group, but it's the presence of
+	 *     the group itself that identifies the source as an address
+	 *     book, so make sure the address book extension is present in
+	 *     the scratch source. */
+	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	g_object_bind_property (
+		extension, "include-me",
+		priv->autocomplete_button, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+static void
+book_source_config_commit_changes (ESourceConfig *config,
+                                   ESource *scratch_source)
+{
+	EBookSourceConfigPrivate *priv;
+	ESourceConfigClass *class;
+	ESource *default_source;
+	GtkToggleButton *toggle_button;
+
+	priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (config);
+	toggle_button = GTK_TOGGLE_BUTTON (priv->default_button);
+
+	/* Chain up to parent's commit_changes() method. */
+	class = E_SOURCE_CONFIG_CLASS (e_book_source_config_parent_class);
+	class->commit_changes (config, scratch_source);
+
+	default_source = book_source_config_get_default (config);
+
+	/* The default setting is a little tricky to get right.  If
+	 * the toggle button is active, this ESource is now the default.
+	 * That much is simple.  But if the toggle button is NOT active,
+	 * then we have to inspect the old default.  If this ESource WAS
+	 * the default, reset the default to 'system'.  If this ESource
+	 * WAS NOT the old default, leave it alone. */
+	if (gtk_toggle_button_get_active (toggle_button))
+		book_source_config_set_default (config, scratch_source);
+	else if (e_source_equal (scratch_source, default_source))
+		book_source_config_set_default (config, NULL);
+}
+
+static void
+e_book_source_config_class_init (EBookSourceConfigClass *class)
+{
+	GObjectClass *object_class;
+	ESourceConfigClass *source_config_class;
+
+	g_type_class_add_private (class, sizeof (EBookSourceConfigPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = book_source_config_dispose;
+	object_class->constructed = book_source_config_constructed;
+
+	source_config_class = E_SOURCE_CONFIG_CLASS (class);
+	source_config_class->init_candidate = book_source_config_init_candidate;
+	source_config_class->commit_changes = book_source_config_commit_changes;
+}
+
+static void
+e_book_source_config_init (EBookSourceConfig *config)
+{
+	config->priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (config);
+}
+
+GtkWidget *
+e_book_source_config_new (ESourceRegistry *registry,
+                          ESource *original_source)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (original_source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (original_source), NULL);
+
+	return g_object_new (
+		E_TYPE_BOOK_SOURCE_CONFIG, "registry", registry,
+		"original-source", original_source, NULL);
+}
+
+void
+e_book_source_config_add_offline_toggle (EBookSourceConfig *config,
+                                         ESource *scratch_source)
+{
+	GtkWidget *widget;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_BOOK_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_OFFLINE;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	widget = gtk_check_button_new_with_mnemonic (
+		_("Co_py book content locally for offline operation"));
+	e_source_config_insert_widget (
+		E_SOURCE_CONFIG (config), scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "stay-synchronized",
+		widget, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
diff --git a/addressbook/gui/widgets/e-book-source-config.h b/addressbook/gui/widgets/e-book-source-config.h
new file mode 100644
index 0000000..18e0755
--- /dev/null
+++ b/addressbook/gui/widgets/e-book-source-config.h
@@ -0,0 +1,67 @@
+/*
+ * e-book-source-config.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_BOOK_SOURCE_CONFIG_H
+#define E_BOOK_SOURCE_CONFIG_H
+
+#include <misc/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_SOURCE_CONFIG \
+	(e_book_source_config_get_type ())
+#define E_BOOK_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfig))
+#define E_BOOK_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfigClass))
+#define E_IS_BOOK_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG))
+#define E_IS_BOOK_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_BOOK_SOURCE_CONFIG))
+#define E_BOOK_SOURCE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookSourceConfig EBookSourceConfig;
+typedef struct _EBookSourceConfigClass EBookSourceConfigClass;
+typedef struct _EBookSourceConfigPrivate EBookSourceConfigPrivate;
+
+struct _EBookSourceConfig {
+	ESourceConfig parent;
+	EBookSourceConfigPrivate *priv;
+};
+
+struct _EBookSourceConfigClass {
+	ESourceConfigClass parent_class;
+};
+
+GType		e_book_source_config_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_book_source_config_new	(ESourceRegistry *registry,
+						 ESource *original_source);
+void		e_book_source_config_add_offline_toggle
+						(EBookSourceConfig *config,
+						 ESource *scratch_source);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SOURCE_CONFIG_H */
diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am
index a74d4a1..eac1aca 100644
--- a/calendar/gui/Makefile.am
+++ b/calendar/gui/Makefile.am
@@ -17,6 +17,7 @@ ecalendarinclude_HEADERS =			\
 	e-cal-list-view.h			\
 	e-cal-model-calendar.h			\
 	e-cal-model.h				\
+	e-cal-source-config.h			\
 	e-calendar-selector.h			\
 	e-calendar-view.h			\
 	e-cell-date-edit-text.h			\
@@ -113,6 +114,8 @@ libevolution_calendar_la_SOURCES = \
 	e-cal-model-memos.h			\
 	e-cal-model-tasks.c			\
 	e-cal-model-tasks.h			\
+	e-cal-source-config.c			\
+	e-cal-source-config.h			\
 	e-calendar-selector.c			\
 	e-calendar-selector.h			\
 	e-calendar-view.c			\
diff --git a/calendar/gui/e-cal-source-config.c b/calendar/gui/e-cal-source-config.c
new file mode 100644
index 0000000..14edf19
--- /dev/null
+++ b/calendar/gui/e-cal-source-config.c
@@ -0,0 +1,383 @@
+/*
+ * e-cal-source-config.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-cal-source-config.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libecal/e-source-calendar.h>
+#include <libedataserver/e-source-offline.h>
+
+#include <e-util/e-util.h>
+
+#define E_CAL_SOURCE_CONFIG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfigPrivate))
+
+struct _ECalSourceConfigPrivate {
+	ECalSourceType source_type;
+	GtkWidget *color_button;
+	GtkWidget *default_button;
+};
+
+enum {
+	PROP_0,
+	PROP_SOURCE_TYPE
+};
+
+G_DEFINE_TYPE (
+	ECalSourceConfig,
+	e_cal_source_config,
+	E_TYPE_SOURCE_CONFIG)
+
+static ESource *
+cal_source_config_get_default (ESourceConfig *config)
+{
+	ECalSourceConfigPrivate *priv;
+	ESourceRegistry *registry;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+	registry = e_source_config_get_registry (config);
+
+	if (priv->source_type == E_CAL_SOURCE_TYPE_EVENT)
+		return e_source_registry_get_default_calendar (registry);
+	else if (priv->source_type == E_CAL_SOURCE_TYPE_JOURNAL)
+		return e_source_registry_get_default_memo_list (registry);
+	else if (priv->source_type == E_CAL_SOURCE_TYPE_TODO)
+		return e_source_registry_get_default_task_list (registry);
+
+	g_return_val_if_reached (NULL);
+}
+
+static void
+cal_source_config_set_default (ESourceConfig *config,
+                               ESource *source)
+{
+	ECalSourceConfigPrivate *priv;
+	ESourceRegistry *registry;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+	registry = e_source_config_get_registry (config);
+
+	if (priv->source_type == E_CAL_SOURCE_TYPE_EVENT)
+		e_source_registry_set_default_calendar (registry, source);
+	else if (priv->source_type == E_CAL_SOURCE_TYPE_JOURNAL)
+		e_source_registry_set_default_memo_list (registry, source);
+	else if (priv->source_type == E_CAL_SOURCE_TYPE_TODO)
+		e_source_registry_set_default_task_list (registry, source);
+}
+
+static void
+cal_source_config_set_source_type (ECalSourceConfig *config,
+                                   ECalSourceType source_type)
+{
+	config->priv->source_type = source_type;
+}
+
+static void
+cal_source_config_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SOURCE_TYPE:
+			cal_source_config_set_source_type (
+				E_CAL_SOURCE_CONFIG (object),
+				g_value_get_enum (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_source_config_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SOURCE_TYPE:
+			g_value_set_enum (
+				value,
+				e_cal_source_config_get_source_type (
+				E_CAL_SOURCE_CONFIG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_source_config_dispose (GObject *object)
+{
+	ECalSourceConfigPrivate *priv;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	if (priv->color_button != NULL) {
+		g_object_unref (priv->color_button);
+		priv->color_button = NULL;
+	}
+
+	if (priv->default_button != NULL) {
+		g_object_unref (priv->default_button);
+		priv->default_button = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_cal_source_config_parent_class)->dispose (object);
+}
+
+static void
+cal_source_config_constructed (GObject *object)
+{
+	ECalSourceConfigPrivate *priv;
+	ESource *default_source;
+	ESource *original_source;
+	ESourceConfig *config;
+	GObjectClass *class;
+	GtkWidget *widget;
+	const gchar *label;
+
+	/* Chain up to parent's constructed() method. */
+	class = G_OBJECT_CLASS (e_cal_source_config_parent_class);
+	class->constructed (object);
+
+	config = E_SOURCE_CONFIG (object);
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	widget = gtk_color_button_new ();
+	priv->color_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	switch (priv->source_type) {
+		case E_CAL_SOURCE_TYPE_EVENT:
+			label = _("Mark as _default calendar");
+			break;
+		case E_CAL_SOURCE_TYPE_TODO:
+			label = _("Mark as _default task list");
+			break;
+		case E_CAL_SOURCE_TYPE_JOURNAL:
+			label = _("Mark as _default memo list");
+			break;
+		default:
+			/* No need to translate this string. */
+			label = "Invalid ECalSourceType value";
+			g_warn_if_reached ();
+	}
+
+	widget = gtk_check_button_new_with_mnemonic (label);
+	priv->default_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	default_source = cal_source_config_get_default (config);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source != NULL) {
+		gboolean active;
+
+		active = e_source_equal (original_source, default_source);
+		g_object_set (priv->default_button, "active", active, NULL);
+	}
+
+	e_source_config_insert_widget (
+		config, NULL, _("Colo_r:"), priv->color_button);
+
+	e_source_config_insert_widget (
+		config, NULL, NULL, priv->default_button);
+}
+
+static void
+cal_source_config_init_candidate (ESourceConfig *config,
+                                  ESource *scratch_source)
+{
+	ECalSourceConfigPrivate *priv;
+	ESourceConfigClass *class;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	/* Chain up to parent's init_candidate() method. */
+	class = E_SOURCE_CONFIG_CLASS (e_cal_source_config_parent_class);
+	class->init_candidate (config, scratch_source);
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+
+	switch (priv->source_type) {
+		case E_CAL_SOURCE_TYPE_EVENT:
+			extension_name = E_SOURCE_EXTENSION_CALENDAR;
+			break;
+		case E_CAL_SOURCE_TYPE_TODO:
+			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+			break;
+		case E_CAL_SOURCE_TYPE_JOURNAL:
+			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+			break;
+		default:
+			g_return_if_reached ();
+	}
+
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	g_object_bind_property_full (
+		extension, "color",
+		priv->color_button, "color",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE,
+		e_binding_transform_string_to_color,
+		e_binding_transform_color_to_string,
+		NULL, (GDestroyNotify) NULL);
+}
+
+static void
+cal_source_config_commit_changes (ESourceConfig *config,
+                                  ESource *scratch_source)
+{
+	ECalSourceConfigPrivate *priv;
+	GtkToggleButton *toggle_button;
+	ESourceConfigClass *class;
+	ESource *default_source;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+	toggle_button = GTK_TOGGLE_BUTTON (priv->default_button);
+
+	/* Chain up to parent's commit_changes() method. */
+	class = E_SOURCE_CONFIG_CLASS (e_cal_source_config_parent_class);
+	class->commit_changes (config, scratch_source);
+
+	default_source = cal_source_config_get_default (config);
+
+	/* The default setting is a little tricky to get right.  If
+	 * the toggle button is active, this ESource is now the default.
+	 * That much is simple.  But if the toggle button is NOT active,
+	 * then we have to inspect the old default.  If this ESource WAS
+	 * the default, reset the default to 'system'.  If this ESource
+	 * WAS NOT the old default, leave it alone. */
+	if (gtk_toggle_button_get_active (toggle_button))
+		cal_source_config_set_default (config, scratch_source);
+	else if (e_source_equal (scratch_source, default_source))
+		cal_source_config_set_default (config, NULL);
+}
+
+static void
+e_cal_source_config_class_init (ECalSourceConfigClass *class)
+{
+	GObjectClass *object_class;
+	ESourceConfigClass *source_config_class;
+
+	g_type_class_add_private (class, sizeof (ECalSourceConfigPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = cal_source_config_set_property;
+	object_class->get_property = cal_source_config_get_property;
+	object_class->dispose = cal_source_config_dispose;
+	object_class->constructed = cal_source_config_constructed;
+
+	source_config_class = E_SOURCE_CONFIG_CLASS (class);
+	source_config_class->init_candidate = cal_source_config_init_candidate;
+	source_config_class->commit_changes = cal_source_config_commit_changes;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE_TYPE,
+		g_param_spec_enum (
+			"source-type",
+			"Source Type",
+			"The iCalendar object type",
+			E_TYPE_CAL_SOURCE_TYPE,
+			E_CAL_SOURCE_TYPE_EVENT,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_cal_source_config_init (ECalSourceConfig *config)
+{
+	config->priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+}
+
+GtkWidget *
+e_cal_source_config_new (ESourceRegistry *registry,
+                         ESource *original_source,
+                         ECalSourceType source_type)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (original_source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (original_source), NULL);
+
+	return g_object_new (
+		E_TYPE_CAL_SOURCE_CONFIG, "registry", registry,
+		"original-source", original_source, "source-type",
+		source_type, NULL);
+}
+
+ECalSourceType
+e_cal_source_config_get_source_type (ECalSourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_CAL_SOURCE_CONFIG (config), 0);
+
+	return config->priv->source_type;
+}
+
+void
+e_cal_source_config_add_offline_toggle (ECalSourceConfig *config,
+                                        ESource *scratch_source)
+{
+	GtkWidget *widget;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+	const gchar *label;
+
+	g_return_if_fail (E_IS_CAL_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_OFFLINE;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	switch (e_cal_source_config_get_source_type (config)) {
+		case E_CAL_SOURCE_TYPE_EVENT:
+			label = _("Co_py calendar contents locally "
+				  "for offline operation");
+			break;
+		case E_CAL_SOURCE_TYPE_TODO:
+			label = _("Co_py task list contents locally "
+				  "for offline operation");
+			break;
+		case E_CAL_SOURCE_TYPE_JOURNAL:
+			label = _("Co_py memo list contents locally "
+				  "for offline operation");
+			break;
+		default:
+			g_return_if_reached ();
+	}
+
+	widget = gtk_check_button_new_with_mnemonic (label);
+	e_source_config_insert_widget (
+		E_SOURCE_CONFIG (config), scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "stay-synchronized",
+		widget, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
diff --git a/calendar/gui/e-cal-source-config.h b/calendar/gui/e-cal-source-config.h
new file mode 100644
index 0000000..a390a16
--- /dev/null
+++ b/calendar/gui/e-cal-source-config.h
@@ -0,0 +1,71 @@
+/*
+ * e-cal-source-config.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_CAL_SOURCE_CONFIG_H
+#define E_CAL_SOURCE_CONFIG_H
+
+#include <libecal/e-cal.h>
+#include <misc/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_SOURCE_CONFIG \
+	(e_cal_source_config_get_type ())
+#define E_CAL_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfig))
+#define E_CAL_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfigClass))
+#define E_IS_CAL_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG))
+#define E_IS_CAL_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CAL_SOURCE_CONFIG))
+#define E_CAL_SOURCE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalSourceConfig ECalSourceConfig;
+typedef struct _ECalSourceConfigClass ECalSourceConfigClass;
+typedef struct _ECalSourceConfigPrivate ECalSourceConfigPrivate;
+
+struct _ECalSourceConfig {
+	ESourceConfig parent;
+	ECalSourceConfigPrivate *priv;
+};
+
+struct _ECalSourceConfigClass {
+	ESourceConfigClass parent_class;
+};
+
+GType		e_cal_source_config_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_cal_source_config_new		(ESourceRegistry *registry,
+						 ESource *original_source,
+						 ECalSourceType source_type);
+ECalSourceType	e_cal_source_config_get_source_type
+						(ECalSourceConfig *config);
+void		e_cal_source_config_add_offline_toggle
+						(ECalSourceConfig *config,
+						 ESource *scratch_source);
+
+G_END_DECLS
+
+#endif /* E_CAL_SOURCE_CONFIG_H */
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am
index 7e9ce9c..57c7101 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -39,6 +39,7 @@ widgetsinclude_HEADERS =			\
 	e-hsv-utils.h				\
 	e-image-chooser.h			\
 	e-import-assistant.h			\
+	e-interval-chooser.h			\
 	e-map.h					\
 	e-menu-tool-action.h			\
 	e-menu-tool-button.h			\
@@ -63,6 +64,9 @@ widgetsinclude_HEADERS =			\
 	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		\
 	e-url-entry.h				\
 	e-web-view.h				\
 	e-web-view-preview.h			\
@@ -118,6 +122,7 @@ libemiscwidgets_la_SOURCES =			\
 	e-hsv-utils.c				\
 	e-image-chooser.c			\
 	e-import-assistant.c			\
+	e-interval-chooser.c			\
 	e-map.c					\
 	e-menu-tool-action.c			\
 	e-menu-tool-button.c			\
@@ -142,6 +147,9 @@ libemiscwidgets_la_SOURCES =			\
 	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		\
 	e-url-entry.c				\
 	e-web-view.c				\
 	e-web-view-preview.c			\
@@ -165,16 +173,18 @@ libemiscwidgets_la_LIBADD =					\
 noinst_PROGRAMS = 			\
 	test-calendar			\
 	test-dateedit			\
-	test-preferences-window	
+	test-preferences-window		\
+	test-source-config
 
 test_widgets_misc_CPPFLAGS=						\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
 	-I$(top_srcdir)/filter						\
 	-I$(top_srcdir)/widgets						\
-	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"				\
+	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"			\
 	-DG_LOG_DOMAIN=__FILE__						\
 	$(EVOLUTION_MAIL_CFLAGS)					\
+	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
 	$(GNOME_PLATFORM_CFLAGS)
 
 # test-calendar
@@ -219,6 +229,21 @@ test_preferences_window_LDADD = 		\
 	$(GNOME_PLATFORM_LIBS)			\
 	$(E_WIDGETS_LIBS)
 
+# test-source-config
+
+test_source_config_CPPFLAGS = $(test_widgets_misc_CPPFLAGS)
+
+test_source_config_SOURCES =			\
+	test-source-config.c
+
+test_source_config_LDADD = 			\
+	libemiscwidgets.la			\
+	$(top_builddir)/e-util/libeutil.la	\
+	$(top_builddir)/filter/libfilter.la	\
+	$(GNOME_PLATFORM_LIBS)			\
+	$(EVOLUTION_DATA_SERVER_LIBS)		\
+	$(E_WIDGETS_LIBS)
+
 EXTRA_DIST = $(ui_DATA)
 
 -include $(top_srcdir)/git.mk
diff --git a/widgets/misc/e-interval-chooser.c b/widgets/misc/e-interval-chooser.c
new file mode 100644
index 0000000..28cc9c9
--- /dev/null
+++ b/widgets/misc/e-interval-chooser.c
@@ -0,0 +1,213 @@
+/*
+ * e-interval-chooser.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-interval-chooser.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <e-util/e-util.h>
+
+/* backward-compatibility cruft */
+#include "e-util/gtk-compat.h"
+
+#define E_INTERVAL_CHOOSER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_INTERVAL_CHOOSER, EIntervalChooserPrivate))
+
+#define MINUTES_PER_HOUR	(60)
+#define MINUTES_PER_DAY		(MINUTES_PER_HOUR * 24)
+
+struct _EIntervalChooserPrivate {
+	GtkComboBox *combo_box;		/* not referenced */
+	GtkSpinButton *spin_button;	/* not referenced */
+};
+
+enum {
+	PROP_0,
+	PROP_INTERVAL
+};
+
+G_DEFINE_TYPE (
+	EIntervalChooser,
+	e_interval_chooser,
+	GTK_TYPE_BOX)
+
+static void
+interval_chooser_notify_interval (GObject *object)
+{
+	g_object_notify (object, "interval");
+}
+
+static void
+interval_chooser_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_INTERVAL:
+			e_interval_chooser_set_interval (
+				E_INTERVAL_CHOOSER (object),
+				g_value_get_uint (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+interval_chooser_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_INTERVAL:
+			g_value_set_uint (
+				value,
+				e_interval_chooser_get_interval (
+				E_INTERVAL_CHOOSER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_interval_chooser_class_init (EIntervalChooserClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EIntervalChooserPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = interval_chooser_set_property;
+	object_class->get_property = interval_chooser_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_INTERVAL,
+		g_param_spec_uint (
+			"interval",
+			"Interval",
+			"Refresh interval in minutes",
+			0, G_MAXUINT, 60,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_interval_chooser_init (EIntervalChooser *chooser)
+{
+	GtkWidget *widget;
+
+	chooser->priv = E_INTERVAL_CHOOSER_GET_PRIVATE (chooser);
+
+	gtk_orientable_set_orientation (
+		GTK_ORIENTABLE (chooser), GTK_ORIENTATION_HORIZONTAL);
+
+	gtk_box_set_spacing (GTK_BOX (chooser), 6);
+
+	widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+	gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (widget), TRUE);
+	gtk_spin_button_set_update_policy (
+		GTK_SPIN_BUTTON (widget), GTK_UPDATE_IF_VALID);
+	gtk_box_pack_start (GTK_BOX (chooser), widget, TRUE, TRUE, 0);
+	chooser->priv->spin_button = GTK_SPIN_BUTTON (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "notify::value",
+		G_CALLBACK (interval_chooser_notify_interval), chooser);
+
+	widget = gtk_combo_box_text_new ();
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (widget), _("minutes"));
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (widget), _("hours"));
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (widget), _("days"));
+	gtk_box_pack_start (GTK_BOX (chooser), widget, FALSE, FALSE, 0);
+	chooser->priv->combo_box = GTK_COMBO_BOX (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "notify::active",
+		G_CALLBACK (interval_chooser_notify_interval), chooser);
+}
+
+GtkWidget *
+e_interval_chooser_new (void)
+{
+	return g_object_new (E_TYPE_INTERVAL_CHOOSER, NULL);
+}
+
+guint
+e_interval_chooser_get_interval (EIntervalChooser *chooser)
+{
+	EDurationType units;
+	gdouble interval;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_REFRESH (chooser), 0);
+
+	units = gtk_combo_box_get_active (chooser->priv->combo_box);
+	interval = gtk_spin_button_get_value (chooser->priv->spin_button);
+
+	switch (units) {
+		case E_DURATION_HOURS:
+			interval *= MINUTES_PER_HOUR;
+			break;
+		case E_DURATION_DAYS:
+			interval *= MINUTES_PER_DAY;
+			break;
+		default:
+			break;
+	}
+
+	return (guint) interval;
+}
+
+void
+e_interval_chooser_set_interval (EIntervalChooser *chooser,
+                                 guint interval)
+{
+	EDurationType units;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG_REFRESH (chooser));
+
+	if (interval == 0) {
+		units = E_DURATION_MINUTES;
+	} else if (interval % MINUTES_PER_DAY == 0) {
+		interval /= MINUTES_PER_DAY;
+		units = E_DURATION_DAYS;
+	} else if (interval % MINUTES_PER_HOUR == 0) {
+		interval /= MINUTES_PER_HOUR;
+		units = E_DURATION_HOURS;
+	} else {
+		units = E_DURATION_MINUTES;
+	}
+
+	g_object_freeze_notify (G_OBJECT (chooser));
+
+	gtk_combo_box_set_active (chooser->priv->combo_box, units);
+	gtk_spin_button_set_value (chooser->priv->spin_button, interval);
+
+	g_object_thaw_notify (G_OBJECT (chooser));
+}
diff --git a/widgets/misc/e-interval-chooser.h b/widgets/misc/e-interval-chooser.h
new file mode 100644
index 0000000..17af581
--- /dev/null
+++ b/widgets/misc/e-interval-chooser.h
@@ -0,0 +1,66 @@
+/*
+ * e-interval-chooser.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_INTERVAL_CHOOSER_H
+#define E_INTERVAL_CHOOSER_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_INTERVAL_CHOOSER \
+	(e_interval_chooser_get_type ())
+#define E_INTERVAL_CHOOSER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_INTERVAL_CHOOSER, EIntervalChooser))
+#define E_INTERVAL_CHOOSER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_INTERVAL_CHOOSER, EIntervalChooserClass))
+#define E_IS_SOURCE_CONFIG_REFRESH(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_INTERVAL_CHOOSER))
+#define E_IS_SOURCE_CONFIG_REFRESH_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_INTERVAL_CHOOSER))
+#define E_INTERVAL_CHOOSER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_INTERVAL_CHOOSER, EIntervalChooserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EIntervalChooser EIntervalChooser;
+typedef struct _EIntervalChooserClass EIntervalChooserClass;
+typedef struct _EIntervalChooserPrivate EIntervalChooserPrivate;
+
+struct _EIntervalChooser {
+	GtkBox parent;
+	EIntervalChooserPrivate *priv;
+};
+
+struct _EIntervalChooserClass {
+	GtkBoxClass parent_class;
+};
+
+GType		e_interval_chooser_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_interval_chooser_new		(void);
+guint		e_interval_chooser_get_interval	(EIntervalChooser *refresh);
+void		e_interval_chooser_set_interval	(EIntervalChooser *refresh,
+						 guint interval_in_minutes);
+
+G_END_DECLS
+
+#endif /* E_INTERVAL_CHOOSER_H */
diff --git a/widgets/misc/e-source-config-backend.c b/widgets/misc/e-source-config-backend.c
new file mode 100644
index 0000000..97bd844
--- /dev/null
+++ b/widgets/misc/e-source-config-backend.c
@@ -0,0 +1,120 @@
+/*
+ * e-source-config-backend.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-source-config-backend.h"
+
+G_DEFINE_TYPE (
+	ESourceConfigBackend,
+	e_source_config_backend,
+	E_TYPE_EXTENSION)
+
+static void
+source_config_backend_insert_widgets (ESourceConfigBackend *backend,
+                                      ESource *scratch_source)
+{
+	/* does nothing */
+}
+
+static gboolean
+source_config_backend_check_complete (ESourceConfigBackend *backend,
+                                      ESource *scratch_source)
+{
+	return TRUE;
+}
+
+static void
+source_config_backend_commit_changes (ESourceConfigBackend *backend,
+                                      ESource *scratch_source)
+{
+	/* does nothing */
+}
+
+static void
+e_source_config_backend_class_init (ESourceConfigBackendClass *class)
+{
+	EExtensionClass *extension_class;
+
+	extension_class = E_EXTENSION_CLASS (class);
+	extension_class->extensible_type = E_TYPE_SOURCE_CONFIG;
+
+	class->insert_widgets = source_config_backend_insert_widgets;
+	class->check_complete = source_config_backend_check_complete;
+	class->commit_changes = source_config_backend_commit_changes;
+}
+
+static void
+e_source_config_backend_init (ESourceConfigBackend *backend)
+{
+}
+
+ESourceConfig *
+e_source_config_backend_get_config (ESourceConfigBackend *backend)
+{
+	EExtensible *extensible;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend), NULL);
+
+	extensible = e_extension_get_extensible (E_EXTENSION (backend));
+
+	return E_SOURCE_CONFIG (extensible);
+}
+
+void
+e_source_config_backend_insert_widgets (ESourceConfigBackend *backend,
+                                        ESource *scratch_source)
+{
+	ESourceConfigBackendClass *class;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+	g_return_if_fail (class->insert_widgets != NULL);
+
+	class->insert_widgets (backend, scratch_source);
+}
+
+gboolean
+e_source_config_backend_check_complete (ESourceConfigBackend *backend,
+                                        ESource *scratch_source)
+{
+	ESourceConfigBackendClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (scratch_source), FALSE);
+
+	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+	g_return_val_if_fail (class->check_complete != NULL, FALSE);
+
+	return class->check_complete (backend, scratch_source);
+}
+
+void
+e_source_config_backend_commit_changes (ESourceConfigBackend *backend,
+                                        ESource *scratch_source)
+{
+	ESourceConfigBackendClass *class;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+	g_return_if_fail (class->commit_changes != NULL);
+
+	class->commit_changes (backend, scratch_source);
+}
diff --git a/widgets/misc/e-source-config-backend.h b/widgets/misc/e-source-config-backend.h
new file mode 100644
index 0000000..c1003cb
--- /dev/null
+++ b/widgets/misc/e-source-config-backend.h
@@ -0,0 +1,84 @@
+/*
+ * e-source-config-backend.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_SOURCE_CONFIG_BACKEND_H
+#define E_SOURCE_CONFIG_BACKEND_H
+
+#include <e-util/e-extension.h>
+#include <misc/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_CONFIG_BACKEND \
+	(e_source_config_backend_get_type ())
+#define E_SOURCE_CONFIG_BACKEND(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_CONFIG_BACKEND, ESourceConfigBackend))
+#define E_SOURCE_CONFIG_BACKEND_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_CONFIG_BACKEND, ESourceConfigBackendClass))
+#define E_IS_SOURCE_CONFIG_BACKEND(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_CONFIG_BACKEND))
+#define E_IS_SOURCE_CONFIG_BACKEND_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_CONFIG_BACKEND))
+#define E_SOURCE_CONFIG_BACKEND_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_CONFIG_BACKEND, ESourceConfigBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceConfigBackend ESourceConfigBackend;
+typedef struct _ESourceConfigBackendClass ESourceConfigBackendClass;
+typedef struct _ESourceConfigBackendPrivate ESourceConfigBackendPrivate;
+
+struct _ESourceConfigBackend {
+	EExtension parent;
+	ESourceConfigBackendPrivate *priv;
+};
+
+struct _ESourceConfigBackendClass {
+	EExtensionClass parent_class;
+
+	const gchar *backend_name;
+
+	void		(*insert_widgets)	(ESourceConfigBackend *backend,
+						 ESource *scratch_source);
+	gboolean	(*check_complete)	(ESourceConfigBackend *backend,
+						 ESource *scratch_source);
+	void		(*commit_changes)	(ESourceConfigBackend *backend,
+						 ESource *scratch_source);
+};
+
+GType		e_source_config_backend_get_type
+					(void) G_GNUC_CONST;
+ESourceConfig *	e_source_config_backend_get_config
+					(ESourceConfigBackend *backend);
+void		e_source_config_backend_insert_widgets
+					(ESourceConfigBackend *backend,
+					 ESource *scratch_source);
+gboolean	e_source_config_backend_check_complete
+					(ESourceConfigBackend *backend,
+					 ESource *scratch_source);
+void		e_source_config_backend_commit_changes
+					(ESourceConfigBackend *backend,
+					 ESource *scratch_source);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_CONFIG_BACKEND_H */
diff --git a/widgets/misc/e-source-config-dialog.c b/widgets/misc/e-source-config-dialog.c
new file mode 100644
index 0000000..96e91c9
--- /dev/null
+++ b/widgets/misc/e-source-config-dialog.c
@@ -0,0 +1,248 @@
+/*
+ * e-source-config-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-source-config-dialog.h"
+
+#define E_SOURCE_CONFIG_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialogPrivate))
+
+struct _ESourceConfigDialogPrivate {
+	ESourceConfig *config;
+	ESourceRegistry *registry;
+};
+
+enum {
+	PROP_0,
+	PROP_CONFIG
+};
+
+G_DEFINE_TYPE (
+	ESourceConfigDialog,
+	e_source_config_dialog,
+	GTK_TYPE_DIALOG)
+
+static void
+source_config_dialog_source_removed_cb (ESourceRegistry *registry,
+                                        ESource *removed_source,
+                                        ESourceConfigDialog *dialog)
+{
+	ESourceConfig *config;
+	ESource *original_source;
+
+	/* If the ESource being edited is removed, cancel the dialog. */
+
+	config = e_source_config_dialog_get_config (dialog);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source == NULL)
+		return;
+
+	if (!e_source_equal (original_source, removed_source))
+		return;
+
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+}
+
+static void
+source_config_dialog_set_config (ESourceConfigDialog *dialog,
+                                 ESourceConfig *config)
+{
+	ESourceRegistry *registry;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (dialog->priv->config == NULL);
+
+	dialog->priv->config = g_object_ref (config);
+
+	registry = e_source_config_get_registry (config);
+	dialog->priv->registry = g_object_ref (registry);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (source_config_dialog_source_removed_cb), dialog);
+}
+
+static void
+source_config_dialog_set_property (GObject *object,
+                                   guint property_id,
+                                   const GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CONFIG:
+			source_config_dialog_set_config (
+				E_SOURCE_CONFIG_DIALOG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_dialog_get_property (GObject *object,
+                                   guint property_id,
+                                   GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CONFIG:
+			g_value_set_object (
+				value,
+				e_source_config_dialog_get_config (
+				E_SOURCE_CONFIG_DIALOG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_dialog_dispose (GObject *object)
+{
+	ESourceConfigDialogPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (object);
+
+	if (priv->config != NULL) {
+		g_object_unref (priv->config);
+		priv->config = NULL;
+	}
+
+	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;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_config_dialog_parent_class)->dispose (object);
+}
+
+static void
+source_config_dialog_constructed (GObject *object)
+{
+	ESourceConfigDialogPrivate *priv;
+	GtkWidget *content_area;
+	GtkWidget *config;
+	GtkWidget *widget;
+
+	priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (object);
+
+	config = GTK_WIDGET (priv->config);
+
+	widget = gtk_dialog_get_widget_for_response (
+		GTK_DIALOG (object), GTK_RESPONSE_OK);
+
+	gtk_container_set_border_width (GTK_CONTAINER (object), 5);
+	gtk_container_set_border_width (GTK_CONTAINER (config), 5);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (object));
+	gtk_box_pack_start (GTK_BOX (content_area), config, TRUE, TRUE, 0);
+	gtk_widget_show (config);
+
+	/* Don't use G_BINDING_SYNC_CREATE here.  The ESourceConfig widget
+	 * is not ready to run check_complete() until after it's realized. */
+	g_object_bind_property (
+		config, "complete",
+		widget, "sensitive",
+		G_BINDING_DEFAULT);
+}
+
+static void
+source_config_dialog_response (GtkDialog *dialog,
+                               gint response_id)
+{
+	ESourceConfigDialogPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (dialog);
+
+	if (response_id == GTK_RESPONSE_OK)
+		e_source_config_commit_changes (priv->config);
+}
+
+static void
+e_source_config_dialog_class_init (ESourceConfigDialogClass *class)
+{
+	GObjectClass *object_class;
+	GtkDialogClass *dialog_class;
+
+	g_type_class_add_private (class, sizeof (ESourceConfigDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_config_dialog_set_property;
+	object_class->get_property = source_config_dialog_get_property;
+	object_class->dispose = source_config_dialog_dispose;
+	object_class->constructed = source_config_dialog_constructed;
+
+	dialog_class = GTK_DIALOG_CLASS (class);
+	dialog_class->response = source_config_dialog_response;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CONFIG,
+		g_param_spec_object (
+			"config",
+			"Config",
+			"The ESourceConfig instance",
+			E_TYPE_SOURCE_CONFIG,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_source_config_dialog_init (ESourceConfigDialog *dialog)
+{
+	GtkWidget *content_area;
+
+	dialog->priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (dialog);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+
+	gtk_dialog_set_default_response (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+GtkWidget *
+e_source_config_dialog_new (ESourceConfig *config,
+                            GtkWindow *parent)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_CONFIG_DIALOG,
+		"config", config, "transient-for", parent, NULL);
+}
+
+ESourceConfig *
+e_source_config_dialog_get_config (ESourceConfigDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_DIALOG (dialog), NULL);
+
+	return dialog->priv->config;
+}
diff --git a/widgets/misc/e-source-config-dialog.h b/widgets/misc/e-source-config-dialog.h
new file mode 100644
index 0000000..eaa602b
--- /dev/null
+++ b/widgets/misc/e-source-config-dialog.h
@@ -0,0 +1,66 @@
+/*
+ * e-source-config-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_SOURCE_CONFIG_DIALOG_H
+#define E_SOURCE_CONFIG_DIALOG_H
+
+#include <misc/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_CONFIG_DIALOG \
+	(e_source_config_dialog_get_type ())
+#define E_SOURCE_CONFIG_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialog))
+#define E_SOURCE_CONFIG_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialogClass))
+#define E_IS_SOURCE_CONFIG_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG))
+#define E_IS_SOURCE_CONFIG_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_CONFIG_DIALOG))
+#define E_SOURCE_CONFIG_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceConfigDialog ESourceConfigDialog;
+typedef struct _ESourceConfigDialogClass ESourceConfigDialogClass;
+typedef struct _ESourceConfigDialogPrivate ESourceConfigDialogPrivate;
+
+struct _ESourceConfigDialog {
+	GtkDialog parent;
+	ESourceConfigDialogPrivate *priv;
+};
+
+struct _ESourceConfigDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_source_config_dialog_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_source_config_dialog_new	(ESourceConfig *config,
+						 GtkWindow *parent);
+ESourceConfig *	e_source_config_dialog_get_config
+						(ESourceConfigDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_CONFIG_DIALOG_H */
diff --git a/widgets/misc/e-source-config.c b/widgets/misc/e-source-config.c
new file mode 100644
index 0000000..0284c96
--- /dev/null
+++ b/widgets/misc/e-source-config.c
@@ -0,0 +1,808 @@
+/*
+ * e-source-config.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-source-config.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-source-extension.h>
+
+#include <e-util/e-extensible.h>
+#include <e-util/e-marshal.h>
+
+#include "e-source-config-backend.h"
+
+/* backward-compatibility cruft */
+#include "e-util/gtk-compat.h"
+
+#define E_SOURCE_CONFIG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfigPrivate))
+
+typedef struct _Candidate Candidate;
+
+struct _ESourceConfigPrivate {
+	ESource *original_source;
+	ESourceRegistry *registry;
+
+	GHashTable *backends;
+	GPtrArray *candidates;
+
+	GtkWidget *type_label;
+	GtkWidget *type_combo;
+	GtkWidget *name_entry;
+	GtkWidget *backend_box;
+	GtkSizeGroup *size_group;
+
+	gboolean complete;
+};
+
+struct _Candidate {
+	GtkWidget *page;
+	ESource *scratch_source;
+	ESourceConfigBackend *backend;
+};
+
+enum {
+	PROP_0,
+	PROP_COMPLETE,
+	PROP_ORIGINAL_SOURCE,
+	PROP_REGISTRY
+};
+
+enum {
+	CHECK_COMPLETE,
+	COMMIT_CHANGES,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+	ESourceConfig,
+	e_source_config,
+	GTK_TYPE_BOX,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+static void
+source_config_init_backends (ESourceConfig *config)
+{
+	GList *list, *iter;
+
+	config->priv->backends = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (config));
+
+	list = e_extensible_list_extensions (
+		E_EXTENSIBLE (config), E_TYPE_SOURCE_CONFIG_BACKEND);
+
+	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
+		ESourceConfigBackend *backend;
+		ESourceConfigBackendClass *class;
+
+		backend = E_SOURCE_CONFIG_BACKEND (iter->data);
+		class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+
+		if (class->backend_name != NULL)
+			g_hash_table_insert (
+				config->priv->backends,
+				g_strdup (class->backend_name),
+				g_object_ref (backend));
+	}
+
+	g_list_free (list);
+}
+
+static void
+source_config_add_candidate (ESourceConfig *config,
+                             ESource *parent_source,
+                             GFile *file)
+{
+	ESourceConfigClass *class;
+	Candidate *candidate;
+	GtkBox *backend_box;
+	GtkComboBoxText *type_combo;
+	ESourceConfigBackend *backend;
+	const gchar *backend_name;
+	const gchar *display_name;
+	const gchar *parent_uid;
+
+	backend_box = GTK_BOX (config->priv->backend_box);
+	type_combo = GTK_COMBO_BOX_TEXT (config->priv->type_combo);
+
+	backend_name = e_source_get_backend_name (parent_source);
+	if (backend_name == NULL)
+		return;
+
+	backend = g_hash_table_lookup (config->priv->backends, backend_name);
+	if (!E_IS_SOURCE_CONFIG_BACKEND (backend))
+		return;
+
+	candidate = g_slice_new (Candidate);
+	candidate->backend = g_object_ref (backend);
+
+	/* Skip passing a GError here.  If file is NULL, this should
+	 * never fail.  If file is non-NULL, it points to a key file
+	 * which has already been successfully loaded so the chances
+	 * of it failing to load this time are slim. */
+	candidate->scratch_source = e_source_new (file, NULL);
+
+	/* Do not show the page here. */
+	candidate->page = g_object_ref_sink (gtk_vbox_new (FALSE, 6));
+	gtk_box_pack_start (backend_box, candidate->page, FALSE, FALSE, 0);
+
+	parent_uid = e_source_get_uid (parent_source);
+	e_source_set_parent (candidate->scratch_source, parent_uid);
+
+	g_ptr_array_add (config->priv->candidates, candidate);
+
+	display_name = e_source_get_display_name (parent_source);
+	gtk_combo_box_text_append_text (type_combo, display_name);
+
+	/* Make sure the combo box has a valid active item before
+	 * adding widgets.  Otherwise we'll get run-time warnings
+	 * as property bindings are set up. */
+	if (gtk_combo_box_get_active (GTK_COMBO_BOX (type_combo)) == -1)
+		gtk_combo_box_set_active (GTK_COMBO_BOX (type_combo), 0);
+
+	g_signal_connect_swapped (
+		candidate->scratch_source, "changed",
+		G_CALLBACK (e_source_config_check_complete), config);
+
+	/* Bind the standard widgets to the new scratch source. */
+	class = E_SOURCE_CONFIG_GET_CLASS (config);
+	class->init_candidate (config, candidate->scratch_source);
+
+	/* Insert any backend-specific widgets. */
+	e_source_config_backend_insert_widgets (
+		candidate->backend, candidate->scratch_source);
+}
+
+static void
+source_config_free_candidate (Candidate *candidate)
+{
+	g_object_unref (candidate->page);
+	g_object_unref (candidate->scratch_source);
+	g_object_unref (candidate->backend);
+
+	g_slice_free (Candidate, candidate);
+}
+
+static Candidate *
+source_config_get_active_candidate (ESourceConfig *config)
+{
+	GtkComboBox *type_combo;
+	gint index;
+
+	type_combo = GTK_COMBO_BOX (config->priv->type_combo);
+	index = gtk_combo_box_get_active (type_combo);
+	g_return_val_if_fail (index >= 0, NULL);
+
+	return g_ptr_array_index (config->priv->candidates, index);
+}
+
+static void
+source_config_type_combo_changed_cb (GtkComboBox *type_combo,
+                                     ESourceConfig *config)
+{
+	GtkWidget *toplevel;
+	Candidate *candidate;
+	GPtrArray *array;
+	gint index;
+
+	array = config->priv->candidates;
+
+	for (index = 0; index < array->len; index++) {
+		candidate = g_ptr_array_index (array, index);
+		gtk_widget_hide (candidate->page);
+	}
+
+	index = gtk_combo_box_get_active (type_combo);
+	if (index == CLAMP (index, 0, array->len)) {
+		candidate = g_ptr_array_index (array, index);
+		gtk_widget_show (candidate->page);
+	}
+
+	/* Expand or shrink the window vertically to accomodate the newly
+	 * selected backend's options.  Some backends have tons of options,
+	 * some have few.  This avoids the case where you select a backend
+	 * with tons of options and then a backend with few options and
+	 * wind up with lots of unused vertical space.
+	 *
+	 * XXX ESourceConfigDialog ought to handle this, but this is
+	 *     good enough for now.  We'll need to add a way for the
+	 *     ESourceConfigDialog to be notified when the selected
+	 *     backend changes. */
+
+	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (config));
+	if (GTK_IS_WINDOW (toplevel)) {
+		GtkWindow *window = GTK_WINDOW (toplevel);
+		GtkAllocation allocation;
+
+		gtk_widget_get_allocation (toplevel, &allocation);
+		gtk_window_resize (window, allocation.width, 1);
+	}
+}
+
+static void
+source_config_set_original_source (ESourceConfig *config,
+                                   ESource *original_source)
+{
+	g_return_if_fail (config->priv->original_source == NULL);
+
+	if (original_source != NULL)
+		g_object_ref (original_source);
+
+	config->priv->original_source = original_source;
+}
+
+static void
+source_config_set_registry (ESourceConfig *config,
+                            ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (config->priv->registry == NULL);
+
+	config->priv->registry = g_object_ref (registry);
+}
+
+static void
+source_config_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ORIGINAL_SOURCE:
+			source_config_set_original_source (
+				E_SOURCE_CONFIG (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_REGISTRY:
+			source_config_set_registry (
+				E_SOURCE_CONFIG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_COMPLETE:
+			g_value_set_boolean (
+				value,
+				e_source_config_check_complete (
+				E_SOURCE_CONFIG (object)));
+			return;
+
+		case PROP_ORIGINAL_SOURCE:
+			g_value_set_object (
+				value,
+				e_source_config_get_original_source (
+				E_SOURCE_CONFIG (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_source_config_get_registry (
+				E_SOURCE_CONFIG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_dispose (GObject *object)
+{
+	ESourceConfigPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	if (priv->original_source != NULL) {
+		g_object_unref (priv->original_source);
+		priv->original_source = NULL;
+	}
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->type_label != NULL) {
+		g_object_unref (priv->type_label);
+		priv->type_label = NULL;
+	}
+
+	if (priv->type_combo != NULL) {
+		g_object_unref (priv->type_combo);
+		priv->type_combo = NULL;
+	}
+
+	if (priv->name_entry != NULL) {
+		g_object_unref (priv->name_entry);
+		priv->name_entry = NULL;
+	}
+
+	if (priv->backend_box != NULL) {
+		g_object_unref (priv->backend_box);
+		priv->backend_box = NULL;
+	}
+
+	if (priv->size_group != NULL) {
+		g_object_unref (priv->size_group);
+		priv->size_group = NULL;
+	}
+
+	g_hash_table_remove_all (priv->backends);
+	g_ptr_array_set_size (priv->candidates, 0);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_config_parent_class)->dispose (object);
+}
+
+static void
+source_config_finalize (GObject *object)
+{
+	ESourceConfigPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->backends);
+	g_ptr_array_free (priv->candidates, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_config_parent_class)->finalize (object);
+}
+
+static void
+source_config_constructed (GObject *object)
+{
+	ESourceConfig *config;
+	ESource *original_source;
+
+	config = E_SOURCE_CONFIG (object);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source != NULL)
+		e_source_config_insert_widget (
+			config, NULL, _("Type:"),
+			config->priv->type_label);
+	else
+		e_source_config_insert_widget (
+			config, NULL, _("Type:"),
+			config->priv->type_combo);
+
+	e_source_config_insert_widget (
+		config, NULL, _("Name:"),
+		config->priv->name_entry);
+
+	source_config_init_backends (config);
+}
+
+static void
+source_config_realize (GtkWidget *widget)
+{
+	ESourceConfig *config;
+	ESource *original_source;
+
+	/* Chain up to parent's realize() method. */
+	GTK_WIDGET_CLASS (e_source_config_parent_class)->realize (widget);
+
+	/* Do this after constructed() so subclasses can fully
+	 * initialize themselves before we add candidates. */
+
+	config = E_SOURCE_CONFIG (widget);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source != NULL) {
+		ESource *parent;
+		GFile *file;
+		GNode *node;
+		GtkLabel *type_label;
+		const gchar *display_name;
+
+		file = e_source_get_file (original_source);
+		node = e_source_get_node (original_source);
+
+		g_return_if_fail (node->parent != NULL);
+		parent = E_SOURCE (node->parent->data);
+
+		type_label = GTK_LABEL (config->priv->type_label);
+		display_name = e_source_get_display_name (parent);
+		gtk_label_set_text (type_label, display_name);
+
+		source_config_add_candidate (config, parent, file);
+
+	} else {
+		ESourceRegistry *registry;
+		GNode *tree, *child;
+
+		registry = e_source_config_get_registry (config);
+		tree = e_source_registry_build_display_tree (registry, NULL);
+		child = g_node_first_child (tree);
+
+		while (child != NULL) {
+			ESource *parent;
+
+			parent = E_SOURCE (child->data);
+			source_config_add_candidate (config, parent, NULL);
+			child = g_node_next_sibling (child);
+		}
+
+		g_node_destroy (tree);
+	}
+}
+
+static void
+source_config_init_candidate (ESourceConfig *config,
+                              ESource *scratch_source)
+{
+	g_object_bind_property (
+		scratch_source, "display-name",
+		config->priv->name_entry, "text",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+static gboolean
+source_config_check_complete (ESourceConfig *config,
+                              ESource *scratch_source)
+{
+	GtkEntry *name_entry;
+	GtkComboBox *type_combo;
+	const gchar *text;
+
+	/* Make sure the Type: combo box has a valid item. */
+	type_combo = GTK_COMBO_BOX (config->priv->type_combo);
+	if (gtk_combo_box_get_active (type_combo) < 0)
+		return FALSE;
+
+	/* Make sure the Name: entry field is not empty. */
+	name_entry = GTK_ENTRY (config->priv->name_entry);
+	text = gtk_entry_get_text (name_entry);
+	if (text == NULL || *text == '\0')
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+source_config_commit_changes (ESourceConfig *config,
+                              ESource *scratch_source)
+{
+	/* FIXME Need error handling.  Maybe implement EAlertSink? */
+	e_source_sync (scratch_source, NULL);
+}
+
+static gboolean
+source_config_check_complete_accumulator (GSignalInvocationHint *ihint,
+                                          GValue *return_accu,
+                                          const GValue *handler_return,
+                                          gpointer unused)
+{
+	gboolean v_boolean;
+
+	/* Abort emission if a handler returns FALSE. */
+	v_boolean = g_value_get_boolean (handler_return);
+	g_value_set_boolean (return_accu, v_boolean);
+
+	return v_boolean;
+}
+
+static void
+e_source_config_class_init (ESourceConfigClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (ESourceConfigPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_config_set_property;
+	object_class->get_property = source_config_get_property;
+	object_class->dispose = source_config_dispose;
+	object_class->finalize = source_config_finalize;
+	object_class->constructed = source_config_constructed;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->realize = source_config_realize;
+
+	class->init_candidate = source_config_init_candidate;
+	class->check_complete = source_config_check_complete;
+	class->commit_changes = source_config_commit_changes;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COMPLETE,
+		g_param_spec_boolean (
+			"complete",
+			"Complete",
+			"Are the required fields complete?",
+			FALSE,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ORIGINAL_SOURCE,
+		g_param_spec_object (
+			"original-source",
+			"Original Source",
+			"The original ESource",
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Registry of ESources",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[CHECK_COMPLETE] = g_signal_new (
+		"check-complete",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, check_complete),
+		source_config_check_complete_accumulator, NULL,
+		e_marshal_BOOLEAN__OBJECT,
+		G_TYPE_BOOLEAN, 1,
+		E_TYPE_SOURCE);
+
+	signals[COMMIT_CHANGES] = g_signal_new (
+		"commit-changes",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, commit_changes),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+}
+
+static void
+e_source_config_init (ESourceConfig *config)
+{
+	GPtrArray *candidates;
+	GtkSizeGroup *size_group;
+	PangoAttribute *attr;
+	PangoAttrList *attr_list;
+	GtkWidget *widget;
+
+	/* The candidates array holds scratch ESources, one for each
+	 * item in the "type" combo box.  Scratch ESources are never
+	 * added to the registry, so backend extensions can make any
+	 * changes they want to them.  Whichever scratch ESource is
+	 * "active" (selected in the "type" combo box) when the user
+	 * clicks OK wins and is written to disk.  The others are
+	 * discarded. */
+	candidates = g_ptr_array_new_with_free_func (
+		(GDestroyNotify) source_config_free_candidate);
+
+	/* The size group is used for caption labels. */
+	size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+	gtk_box_set_spacing (GTK_BOX (config), 6);
+
+	gtk_orientable_set_orientation (
+		GTK_ORIENTABLE (config), GTK_ORIENTATION_VERTICAL);
+
+	config->priv = E_SOURCE_CONFIG_GET_PRIVATE (config);
+	config->priv->candidates = candidates;
+	config->priv->size_group = size_group;
+
+	/* Either the combo box or the label is shown, never both.
+	 * But we create both widgets and keep them both up-to-date
+	 * regardless just because it makes the logic simpler. */
+
+	attr_list = pango_attr_list_new ();
+
+	attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+	pango_attr_list_insert (attr_list, attr);
+
+	widget = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_label_set_attributes (GTK_LABEL (widget), attr_list);
+	config->priv->type_label = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_combo_box_text_new ();
+	config->priv->type_combo = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_entry_new ();
+	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+	config->priv->name_entry = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	/* The backend box holds backend-specific options.  Each backend
+	 * gets a child widget.  Only one child widget is visible at once. */
+	widget = gtk_vbox_new (FALSE, 12);
+	gtk_box_pack_end (GTK_BOX (config), widget, TRUE, TRUE, 0);
+	config->priv->backend_box = g_object_ref (widget);
+	gtk_widget_show (widget);
+
+	pango_attr_list_unref (attr_list);
+
+	g_signal_connect (
+		config->priv->type_combo, "changed",
+		G_CALLBACK (source_config_type_combo_changed_cb), config);
+}
+
+GtkWidget *
+e_source_config_new (ESourceRegistry *registry,
+                     ESource *original_source)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (original_source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (original_source), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_CONFIG, "registry", registry,
+		"original-source", original_source, NULL);
+}
+
+void
+e_source_config_insert_widget (ESourceConfig *config,
+                               ESource *scratch_source,
+                               const gchar *caption,
+                               GtkWidget *widget)
+{
+	GtkWidget *hbox;
+	GtkWidget *vbox;
+	GtkWidget *label;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (GTK_IS_WIDGET (widget));
+
+	if (scratch_source == NULL)
+		vbox = GTK_WIDGET (config);
+	else
+		vbox = e_source_config_get_page (config, scratch_source);
+
+	hbox = gtk_hbox_new (FALSE, 12);
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
+
+	g_object_bind_property (
+		widget, "visible",
+		hbox, "visible",
+		G_BINDING_SYNC_CREATE);
+
+	label = gtk_label_new_with_mnemonic (caption);
+	gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
+	gtk_size_group_add_widget (config->priv->size_group, label);
+	gtk_widget_show (label);
+
+	gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+}
+
+GtkWidget *
+e_source_config_get_page (ESourceConfig *config,
+                          ESource *scratch_source)
+{
+	Candidate *candidate;
+	GtkWidget *page = NULL;
+	GPtrArray *array;
+	gint index;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+	g_return_val_if_fail (E_IS_SOURCE (scratch_source), NULL);
+
+	array = config->priv->candidates;
+
+	for (index = 0; page == NULL && index < array->len; index++) {
+		candidate = g_ptr_array_index (array, index);
+		if (e_source_equal (scratch_source, candidate->scratch_source))
+			page = candidate->page;
+	}
+
+	g_return_val_if_fail (GTK_IS_BOX (page), NULL);
+
+	return page;
+}
+
+gboolean
+e_source_config_check_complete (ESourceConfig *config)
+{
+	Candidate *candidate;
+	gboolean complete;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), FALSE);
+
+	candidate = source_config_get_active_candidate (config);
+	g_return_val_if_fail (candidate != NULL, FALSE);
+
+	g_signal_emit (
+		config, signals[CHECK_COMPLETE], 0,
+		candidate->scratch_source, &complete);
+
+	complete &= e_source_config_backend_check_complete (
+		candidate->backend, candidate->scratch_source);
+
+	/* XXX Emitting "notify::complete" may cause this function
+	 *     to be called repeatedly by signal handlers.  The IF
+	 *     check below should break any recursive cycles.  Not
+	 *     very efficient but I think we can live with it. */
+
+	if (complete != config->priv->complete) {
+		config->priv->complete = complete;
+		g_object_notify (G_OBJECT (config), "complete");
+	}
+
+	return complete;
+}
+
+void
+e_source_config_commit_changes (ESourceConfig *config)
+{
+	Candidate *candidate;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+
+	candidate = source_config_get_active_candidate (config);
+	g_return_if_fail (candidate != NULL);
+
+	e_source_config_backend_commit_changes (
+		candidate->backend, candidate->scratch_source);
+
+	g_signal_emit (
+		config, signals[COMMIT_CHANGES], 0,
+		candidate->scratch_source);
+}
+
+ESource *
+e_source_config_get_original_source (ESourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return config->priv->original_source;
+}
+
+ESourceRegistry *
+e_source_config_get_registry (ESourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return config->priv->registry;
+}
diff --git a/widgets/misc/e-source-config.h b/widgets/misc/e-source-config.h
new file mode 100644
index 0000000..803a6bf
--- /dev/null
+++ b/widgets/misc/e-source-config.h
@@ -0,0 +1,85 @@
+/*
+ * e-source-config.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_SOURCE_CONFIG_H
+#define E_SOURCE_CONFIG_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/e-source-registry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_CONFIG \
+	(e_source_config_get_type ())
+#define E_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfig))
+#define E_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_CONFIG, ESourceConfigClass))
+#define E_IS_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_CONFIG))
+#define E_IS_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_CONFIG))
+#define E_SOURCE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceConfig ESourceConfig;
+typedef struct _ESourceConfigClass ESourceConfigClass;
+typedef struct _ESourceConfigPrivate ESourceConfigPrivate;
+
+struct _ESourceConfig {
+	GtkBox parent;
+	ESourceConfigPrivate *priv;
+};
+
+struct _ESourceConfigClass {
+	GtkBoxClass parent_class;
+
+	/* Methods */
+	void		(*init_candidate)	(ESourceConfig *config,
+						 ESource *scratch_source);
+
+	/* Signals */
+	gboolean	(*check_complete)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	void		(*commit_changes)	(ESourceConfig *config,
+						 ESource *scratch_source);
+};
+
+GType		e_source_config_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_source_config_new		(ESourceRegistry *registry,
+						 ESource *original_source);
+void		e_source_config_insert_widget	(ESourceConfig *config,
+						 ESource *scratch_source,
+						 const gchar *caption,
+						 GtkWidget *widget);
+GtkWidget *	e_source_config_get_page	(ESourceConfig *config,
+						 ESource *scratch_source);
+gboolean	e_source_config_check_complete	(ESourceConfig *config);
+void		e_source_config_commit_changes	(ESourceConfig *config);
+ESource *	e_source_config_get_original_source
+						(ESourceConfig *config);
+ESourceRegistry *
+		e_source_config_get_registry	(ESourceConfig *config);
+
+#endif /* E_SOURCE_CONFIG_H */
diff --git a/widgets/misc/test-source-config.c b/widgets/misc/test-source-config.c
new file mode 100644
index 0000000..5c98d26
--- /dev/null
+++ b/widgets/misc/test-source-config.c
@@ -0,0 +1,60 @@
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <libebook/e-source-address-book.h>
+
+#include "e-source-config-dialog.h"
+
+static void
+dialog_response (GtkDialog *dialog,
+                 gint response_id)
+{
+	gtk_main_quit ();
+}
+
+gint
+main (gint argc, gchar **argv)
+{
+	ESourceRegistry *registry;
+	ESource *source = NULL;
+	GtkWidget *config;
+	GtkWidget *dialog;
+	GError *error = NULL;
+
+	gtk_init (&argc, &argv);
+
+	if (argc > 1) {
+		GFile *file;
+
+		file = g_file_new_for_commandline_arg (argv[1]);
+		source = e_source_new (file, &error);
+		g_object_unref (file);
+
+		if (error != NULL) {
+			g_printerr ("%s\n", error->message);
+			exit (EXIT_FAILURE);
+		}
+	}
+
+	E_TYPE_SOURCE_ADDRESS_BOOK;
+
+	registry = e_source_registry_get_default ();
+
+	if (!e_source_registry_load_sources (registry, &error)) {
+		g_printerr ("%s\n", error->message);
+		exit (EXIT_FAILURE);
+	}
+
+	config = e_source_config_new (registry, source);
+	dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config), NULL);
+
+	g_signal_connect (
+		dialog, "response",
+		G_CALLBACK (dialog_response), NULL);
+
+	gtk_widget_show (config);
+	gtk_widget_show (dialog);
+
+	gtk_main ();
+
+	return 0;
+}



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