[evolution/account-mgmt: 5/55] Add ESourceConfig widget.



commit a6815f0f5e1d1946cfe0ead44e37f0ffe6fbb06c
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             |  397 ++++++++++
 calendar/gui/e-cal-source-config.h             |   73 ++
 widgets/misc/Makefile.am                       |   27 +-
 widgets/misc/e-interval-chooser.c              |  214 +++++
 widgets/misc/e-interval-chooser.h              |   68 ++
 widgets/misc/e-source-config-backend.c         |  140 ++++
 widgets/misc/e-source-config-backend.h         |   89 +++
 widgets/misc/e-source-config-dialog.c          |  244 ++++++
 widgets/misc/e-source-config-dialog.h          |   66 ++
 widgets/misc/e-source-config.c                 | 1004 ++++++++++++++++++++++++
 widgets/misc/e-source-config.h                 |  100 +++
 widgets/misc/e-source-notebook.c               |  359 +++++++++
 widgets/misc/e-source-notebook.h               |   73 ++
 widgets/misc/test-source-config.c              |   54 ++
 18 files changed, 3233 insertions(+), 1 deletions(-)
---
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index 19c9c4f..a580be0 100644
--- a/addressbook/gui/widgets/Makefile.am
+++ b/addressbook/gui/widgets/Makefile.am
@@ -55,6 +55,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..8b9ed62
--- /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 <libedataserver/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_label (label);
+	priv->default_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	label = _("Autocomplete with this address book");
+	widget = gtk_check_button_new_with_label (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 const gchar *
+book_source_config_get_backend_extension_name (ESourceConfig *config)
+{
+	return E_SOURCE_EXTENSION_ADDRESS_BOOK;
+}
+
+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);
+
+	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->get_backend_extension_name =
+		book_source_config_get_backend_extension_name;
+	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_label (
+		_("Copy 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 7599180..3e27988 100644
--- a/calendar/gui/Makefile.am
+++ b/calendar/gui/Makefile.am
@@ -16,6 +16,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			\
@@ -107,6 +108,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..4710fa5
--- /dev/null
+++ b/calendar/gui/e-cal-source-config.c
@@ -0,0 +1,397 @@
+/*
+ * 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 <libedataserver/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 {
+	ECalClientSourceType 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_CLIENT_SOURCE_TYPE_EVENTS)
+		return e_source_registry_get_default_calendar (registry);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS)
+		return e_source_registry_get_default_memo_list (registry);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS)
+		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_CLIENT_SOURCE_TYPE_EVENTS)
+		e_source_registry_set_default_calendar (registry, source);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS)
+		e_source_registry_set_default_memo_list (registry, source);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS)
+		e_source_registry_set_default_task_list (registry, source);
+}
+
+static void
+cal_source_config_set_source_type (ECalSourceConfig *config,
+                                   ECalClientSourceType 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_CLIENT_SOURCE_TYPE_EVENTS:
+			label = _("Mark as default calendar");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+			label = _("Mark as default task list");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+			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_label (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, _("Color:"), priv->color_button);
+
+	e_source_config_insert_widget (
+		config, NULL, NULL, priv->default_button);
+}
+
+static const gchar *
+cal_source_config_get_backend_extension_name (ESourceConfig *config)
+{
+	ECalSourceConfig *cal_config;
+	const gchar *extension_name;
+
+	cal_config = E_CAL_SOURCE_CONFIG (config);
+
+	switch (e_cal_source_config_get_source_type (cal_config)) {
+		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+			extension_name = E_SOURCE_EXTENSION_CALENDAR;
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+			break;
+		default:
+			g_return_val_if_reached (NULL);
+	}
+
+	return extension_name;
+}
+
+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);
+
+	extension_name = e_source_config_get_backend_extension_name (config);
+	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->get_backend_extension_name =
+		cal_source_config_get_backend_extension_name;
+	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_CLIENT_SOURCE_TYPE,
+			E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
+			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,
+                         ECalClientSourceType 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);
+}
+
+ECalClientSourceType
+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_CLIENT_SOURCE_TYPE_EVENTS:
+			label = _("Copy calendar contents locally "
+				  "for offline operation");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+			label = _("Copy task list contents locally "
+				  "for offline operation");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+			label = _("Copy memo list contents locally "
+				  "for offline operation");
+			break;
+		default:
+			g_return_if_reached ();
+	}
+
+	widget = gtk_check_button_new_with_label (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..924958e
--- /dev/null
+++ b/calendar/gui/e-cal-source-config.h
@@ -0,0 +1,73 @@
+/*
+ * 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-client.h>
+#include <misc/e-source-config.h>
+#include <libedataserver/e-source-extension.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,
+						 ECalClientSourceType source_type);
+ECalClientSourceType
+		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 ab7efa0..411dcb3 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -37,6 +37,7 @@ widgetsinclude_HEADERS =			\
 	e-focus-tracker.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			\
@@ -60,6 +61,10 @@ 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-source-notebook.h			\
 	e-url-entry.h				\
 	e-web-view.h				\
 	e-web-view-gtkhtml.h			\
@@ -119,6 +124,7 @@ libemiscwidgets_la_SOURCES =			\
 	e-focus-tracker.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 +148,10 @@ 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-source-notebook.c			\
 	e-url-entry.c				\
 	e-web-view.c				\
 	e-web-view-gtkhtml.c			\
@@ -172,7 +182,8 @@ libemiscwidgets_la_LIBADD =					\
 noinst_PROGRAMS = 			\
 	test-calendar			\
 	test-dateedit			\
-	test-preferences-window	
+	test-preferences-window		\
+	test-source-config
 
 test_widgets_misc_CPPFLAGS=						\
 	$(AM_CPPFLAGS)							\
@@ -226,6 +237,20 @@ test_preferences_window_LDADD = 		\
 	$(EVOLUTION_DATA_SERVER_LIBS)		\
 	$(GNOME_PLATFORM_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	\
+	$(EVOLUTION_DATA_SERVER_LIBS)		\
+	$(GNOME_PLATFORM_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..5a2b4e0
--- /dev/null
+++ b/widgets/misc/e-interval-chooser.c
@@ -0,0 +1,214 @@
+/*
+ * 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>
+
+#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_MINUTES
+};
+
+G_DEFINE_TYPE (
+	EIntervalChooser,
+	e_interval_chooser,
+	GTK_TYPE_BOX)
+
+static void
+interval_chooser_notify_interval (GObject *object)
+{
+	g_object_notify (object, "interval-minutes");
+}
+
+static void
+interval_chooser_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_INTERVAL_MINUTES:
+			e_interval_chooser_set_interval_minutes (
+				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_MINUTES:
+			g_value_set_uint (
+				value,
+				e_interval_chooser_get_interval_minutes (
+				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_MINUTES,
+		g_param_spec_uint (
+			"interval-minutes",
+			"Interval in Minutes",
+			"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_minutes (EIntervalChooser *chooser)
+{
+	EDurationType units;
+	gdouble interval_minutes;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_REFRESH (chooser), 0);
+
+	units = gtk_combo_box_get_active (chooser->priv->combo_box);
+
+	interval_minutes = gtk_spin_button_get_value (
+		chooser->priv->spin_button);
+
+	switch (units) {
+		case E_DURATION_HOURS:
+			interval_minutes *= MINUTES_PER_HOUR;
+			break;
+		case E_DURATION_DAYS:
+			interval_minutes *= MINUTES_PER_DAY;
+			break;
+		default:
+			break;
+	}
+
+	return (guint) interval_minutes;
+}
+
+void
+e_interval_chooser_set_interval_minutes (EIntervalChooser *chooser,
+                                         guint interval_minutes)
+{
+	EDurationType units;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG_REFRESH (chooser));
+
+	if (interval_minutes == 0) {
+		units = E_DURATION_MINUTES;
+	} else if (interval_minutes % MINUTES_PER_DAY == 0) {
+		interval_minutes /= MINUTES_PER_DAY;
+		units = E_DURATION_DAYS;
+	} else if (interval_minutes % MINUTES_PER_HOUR == 0) {
+		interval_minutes /= 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_minutes);
+
+	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..351cbbe
--- /dev/null
+++ b/widgets/misc/e-interval-chooser.h
@@ -0,0 +1,68 @@
+/*
+ * 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_minutes
+						(EIntervalChooser *refresh);
+void		e_interval_chooser_set_interval_minutes
+						(EIntervalChooser *refresh,
+						 guint interval_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..e6802f9
--- /dev/null
+++ b/widgets/misc/e-source-config-backend.c
@@ -0,0 +1,140 @@
+/*
+ * 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 gboolean
+source_config_backend_allow_creation (ESourceConfigBackend *backend)
+{
+	return TRUE;
+}
+
+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->allow_creation = source_config_backend_allow_creation;
+	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);
+}
+
+gboolean
+e_source_config_backend_allow_creation (ESourceConfigBackend *backend)
+{
+	ESourceConfigBackendClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend), FALSE);
+
+	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+	g_return_val_if_fail (class->allow_creation != NULL, FALSE);
+
+	return class->allow_creation (backend);
+}
+
+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..df0e23d
--- /dev/null
+++ b/widgets/misc/e-source-config-backend.h
@@ -0,0 +1,89 @@
+/*
+ * 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 <libebackend/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 *parent_uid;
+	const gchar *backend_name;
+
+	gboolean	(*allow_creation)	(ESourceConfigBackend *backend);
+	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);
+gboolean	e_source_config_backend_allow_creation
+					(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..f898c9d
--- /dev/null
+++ b/widgets/misc/e-source-config-dialog.c
@@ -0,0 +1,244 @@
+/*
+ * 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)
+{
+	dialog->priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (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..03a665b
--- /dev/null
+++ b/widgets/misc/e-source-config.c
@@ -0,0 +1,1004 @@
+/*
+ * 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 <libebackend/e-extensible.h>
+#include <libedataserver/e-source-backend.h>
+#include <libedataserver/e-source-refresh.h>
+#include <libedataserver/e-source-security.h>
+
+#include <e-util/e-marshal.h>
+#include <misc/e-interval-chooser.h>
+
+#include "e-source-config-backend.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,
+	INIT_CANDIDATE,
+	RESIZE_WINDOW,
+	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 gint
+source_config_compare_backends (ESourceConfigBackend *backend_a,
+                                ESourceConfigBackend *backend_b,
+                                ESourceConfig *config)
+{
+	ESourceConfigBackendClass *class_a;
+	ESourceConfigBackendClass *class_b;
+	ESourceRegistry *registry;
+	ESource *source_a;
+	ESource *source_b;
+	const gchar *parent_uid_a;
+	const gchar *parent_uid_b;
+	const gchar *backend_name_a;
+	const gchar *backend_name_b;
+
+	registry = e_source_config_get_registry (config);
+
+	class_a = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend_a);
+	class_b = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend_b);
+
+	parent_uid_a = class_a->parent_uid;
+	parent_uid_b = class_b->parent_uid;
+
+	backend_name_a = class_a->backend_name;
+	backend_name_b = class_b->backend_name;
+
+	if (g_strcmp0 (backend_name_a, backend_name_b) == 0)
+		return 0;
+
+	/* "On This Computer" always comes first. */
+
+	if (g_strcmp0 (backend_name_a, "local") == 0)
+		return -1;
+
+	if (g_strcmp0 (backend_name_b, "local") == 0)
+		return 1;
+
+	source_a = e_source_registry_lookup_by_uid (registry, parent_uid_a);
+	source_b = e_source_registry_lookup_by_uid (registry, parent_uid_b);
+
+	g_return_val_if_fail (E_IS_SOURCE (source_a), 1);
+	g_return_val_if_fail (E_IS_SOURCE (source_b), -1);
+
+	return e_source_compare_by_display_name (source_a, source_b);
+}
+
+static void
+source_config_add_candidate (ESourceConfig *config,
+                             ESourceConfigBackend *backend,
+                             EDBusObject *dbus_object)
+{
+	Candidate *candidate;
+	GtkBox *backend_box;
+	GtkLabel *type_label;
+	GtkComboBoxText *type_combo;
+	ESourceConfigBackendClass *class;
+	ESourceRegistry *registry;
+	ESourceBackend *extension;
+	ESource *parent;
+	const gchar *display_name;
+	const gchar *extension_name;
+
+	backend_box = GTK_BOX (config->priv->backend_box);
+	type_label = GTK_LABEL (config->priv->type_label);
+	type_combo = GTK_COMBO_BOX_TEXT (config->priv->type_combo);
+
+	registry = e_source_config_get_registry (config);
+	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+	parent = e_source_registry_lookup_by_uid (registry, class->parent_uid);
+
+	if (!E_IS_SOURCE (parent))
+		return;
+
+	/* Some backends don't allow new sources to be created.
+	 * The "contacts" calendar backend is one such example. */
+	if (dbus_object == NULL) {
+		if (!e_source_config_backend_allow_creation (backend))
+			return;
+	}
+
+	candidate = g_slice_new (Candidate);
+	candidate->backend = g_object_ref (backend);
+
+	/* Skip passing a GError here.  If dbus_object is NULL, this should
+	 * never fail.  If dbus_object is non-NULL, then its data should have
+	 * been produced by a GKeyFile on the server-side, so the chances of
+	 * it failing to load this time are slim. */
+	candidate->scratch_source = e_source_new (dbus_object, 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);
+
+	e_source_set_parent (candidate->scratch_source, class->parent_uid);
+
+	extension_name =
+		e_source_config_get_backend_extension_name (config);
+	extension = e_source_get_extension (
+		candidate->scratch_source, extension_name);
+	e_source_backend_set_backend_name (extension, class->backend_name);
+
+	g_ptr_array_add (config->priv->candidates, candidate);
+
+	display_name = e_source_get_display_name (parent);
+	gtk_combo_box_text_append_text (type_combo, display_name);
+	gtk_label_set_text (type_label, 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);
+
+	/* Bind the standard widgets to the new scratch source. */
+	g_signal_emit (
+		config, signals[INIT_CANDIDATE], 0,
+		candidate->scratch_source);
+
+	/* Insert any backend-specific widgets. */
+	e_source_config_backend_insert_widgets (
+		candidate->backend, candidate->scratch_source);
+
+	g_signal_connect_swapped (
+		candidate->scratch_source, "changed",
+		G_CALLBACK (e_source_config_check_complete), config);
+
+	/* Trigger the "changed" handler we just connected to set the
+	 * initial "complete" state based on the widgets we just added. */
+	e_source_changed (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)
+{
+	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);
+	}
+
+	e_source_config_resize_window (config);
+}
+
+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) {
+		ESourceBackend *extension;
+		ESourceConfigBackend *backend;
+		EDBusObject *dbus_object;
+		const gchar *backend_name;
+		const gchar *extension_name;
+
+		extension_name =
+			e_source_config_get_backend_extension_name (config);
+		extension = e_source_get_extension (
+			original_source, extension_name);
+		backend_name = e_source_backend_get_backend_name (extension);
+		g_return_if_fail (backend_name != NULL);
+
+		backend = g_hash_table_lookup (
+			config->priv->backends, backend_name);
+		g_return_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend));
+
+		dbus_object = e_source_get_dbus_object (original_source);
+
+		source_config_add_candidate (config, backend, dbus_object);
+
+	} else {
+		GList *list, *link;
+
+		list = g_list_sort_with_data (
+			g_hash_table_get_values (config->priv->backends),
+			(GCompareDataFunc) source_config_compare_backends,
+			config);
+
+		for (link = list; link != NULL; link = g_list_next (link)) {
+			ESourceConfigBackend *backend;
+
+			backend = E_SOURCE_CONFIG_BACKEND (link->data);
+			source_config_add_candidate (config, backend, NULL);
+		}
+
+		g_list_free (list);
+	}
+}
+
+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)
+{
+	ESourceRegistry *registry;
+	GError *error = NULL;
+
+	registry = e_source_config_get_registry (config);
+
+	/* FIXME Need better error handling.  Maybe implement
+	 *       EAlertSink?  Also, this blocks! */
+	e_source_registry_commit_source_sync (
+		registry, scratch_source, NULL, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+source_config_resize_window (ESourceConfig *config)
+{
+	GtkWidget *toplevel;
+
+	/* Expand or shrink our parent window vertically to accommodate
+	 * 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. */
+
+	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 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;
+	class->resize_window = source_config_resize_window;
+
+	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);
+
+	signals[INIT_CANDIDATE] = g_signal_new (
+		"init-candidate",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, init_candidate),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+
+	signals[RESIZE_WINDOW] = g_signal_new (
+		"resize-window",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, resize_window),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+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 (caption);
+	gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+	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;
+}
+
+const gchar *
+e_source_config_get_backend_extension_name (ESourceConfig *config)
+{
+	ESourceConfigClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	class = E_SOURCE_CONFIG_GET_CLASS (config);
+	g_return_val_if_fail (class->get_backend_extension_name != NULL, NULL);
+
+	return class->get_backend_extension_name (config);
+}
+
+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;
+}
+
+void
+e_source_config_resize_window (ESourceConfig *config)
+{
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+
+	g_signal_emit (config, signals[RESIZE_WINDOW], 0);
+}
+
+void
+e_source_config_add_refresh_interval (ESourceConfig *config,
+                                      ESource *scratch_source)
+{
+	GtkWidget *widget;
+	GtkWidget *container;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_REFRESH;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	widget = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
+	e_source_config_insert_widget (config, scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_hbox_new (FALSE, 6);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	/* Translators: This is the first of a sequence of widgets:
+	 * "Refresh every [NUMERIC_ENTRY] [TIME_UNITS_COMBO]" */
+	widget = gtk_label_new (_("Refresh every"));
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = e_interval_chooser_new ();
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "interval-minutes",
+		widget, "interval-minutes",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+void
+e_source_config_add_secure_connection (ESourceConfig *config,
+                                       ESource *scratch_source)
+{
+	GtkWidget *widget;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+	const gchar *label;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_SECURITY;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	label = _("Use a secure connection");
+	widget = gtk_check_button_new_with_label (label);
+	e_source_config_insert_widget (config, scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "secure",
+		widget, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
diff --git a/widgets/misc/e-source-config.h b/widgets/misc/e-source-config.h
new file mode 100644
index 0000000..728ab6b
--- /dev/null
+++ b/widgets/misc/e-source-config.h
@@ -0,0 +1,100 @@
+/*
+ * 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-extension.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 */
+	const gchar *	(*get_backend_extension_name)
+						(ESourceConfig *config);
+
+	/* Signals */
+	void		(*init_candidate)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	gboolean	(*check_complete)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	void		(*commit_changes)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	void		(*resize_window)	(ESourceConfig *config);
+};
+
+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);
+const gchar *	e_source_config_get_backend_extension_name
+						(ESourceConfig *config);
+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);
+void		e_source_config_resize_window	(ESourceConfig *config);
+
+/* Convenience functions for common settings. */
+void		e_source_config_add_refresh_interval
+						(ESourceConfig *config,
+						 ESource *scratch_source);
+void		e_source_config_add_secure_connection
+						(ESourceConfig *config,
+						 ESource *scratch_source);
+
+#endif /* E_SOURCE_CONFIG_H */
diff --git a/widgets/misc/e-source-notebook.c b/widgets/misc/e-source-notebook.c
new file mode 100644
index 0000000..08d4490
--- /dev/null
+++ b/widgets/misc/e-source-notebook.c
@@ -0,0 +1,359 @@
+/*
+ * e-source-notebook.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-notebook.h"
+
+#define E_SOURCE_NOTEBOOK_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_NOTEBOOK, ESourceNotebookPrivate))
+
+#define CHILD_SOURCE_KEY_FORMAT  "__e_source_notebook_%p_child_source__"
+
+struct _ESourceNotebookPrivate {
+	ESource *active_source;
+	gchar *child_source_key;
+};
+
+enum {
+	PROP_0,
+	PROP_ACTIVE_SOURCE
+};
+
+enum {
+	PROP_CHILD_0,
+	PROP_CHILD_SOURCE
+};
+
+G_DEFINE_TYPE (
+	ESourceNotebook,
+	e_source_notebook,
+	GTK_TYPE_NOTEBOOK)
+
+static void
+source_notebook_set_child_source (ESourceNotebook *notebook,
+                                  GtkWidget *child,
+                                  ESource *source)
+{
+	const gchar *key;
+
+	key = notebook->priv->child_source_key;
+
+	if (E_IS_SOURCE (source))
+		g_object_set_data_full (
+			G_OBJECT (child), key,
+			g_object_ref (source),
+			(GDestroyNotify) g_object_unref);
+	else
+		g_object_set_data (G_OBJECT (child), key, NULL);
+}
+
+static ESource *
+source_notebook_get_child_source (ESourceNotebook *notebook,
+                                  GtkWidget *child)
+{
+	const gchar *key;
+
+	key = notebook->priv->child_source_key;
+
+	return g_object_get_data (G_OBJECT (child), key);
+}
+
+static gboolean
+source_notebook_page_num_to_source (GBinding *binding,
+                                    const GValue *source_value,
+                                    GValue *target_value,
+                                    gpointer user_data)
+{
+	GtkNotebook *notebook;
+	GtkWidget *child;
+	ESource *source;
+	gint page_num;
+
+	/* The binding's source and target are the same instance. */
+	notebook = GTK_NOTEBOOK (g_binding_get_source (binding));
+
+	page_num = g_value_get_int (source_value);
+	child = gtk_notebook_get_nth_page (notebook, page_num);
+
+	if (child != NULL)
+		source = source_notebook_get_child_source (
+			E_SOURCE_NOTEBOOK (notebook), child);
+	else
+		source = NULL;
+
+	g_value_set_object (target_value, source);
+
+	return TRUE;
+}
+
+static gboolean
+source_notebook_source_to_page_num (GBinding *binding,
+                                    const GValue *source_value,
+                                    GValue *target_value,
+                                    gpointer user_data)
+{
+	GtkNotebook *notebook;
+	ESource *source;
+	gint n_pages, ii;
+
+	/* The binding's source and target are the same instance. */
+	notebook = GTK_NOTEBOOK (g_binding_get_source (binding));
+
+	source = g_value_get_object (source_value);
+	n_pages = gtk_notebook_get_n_pages (notebook);
+
+	for (ii = 0; ii < n_pages; ii++) {
+		GtkWidget *child;
+		ESource *candidate;
+
+		child = gtk_notebook_get_nth_page (notebook, ii);
+		candidate = source_notebook_get_child_source (
+			E_SOURCE_NOTEBOOK (notebook), child);
+
+		if (e_source_equal (source, candidate)) {
+			g_value_set_int (target_value, ii);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+source_notebook_set_property (GObject *object,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ACTIVE_SOURCE:
+			e_source_notebook_set_active_source (
+				E_SOURCE_NOTEBOOK (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_notebook_get_property (GObject *object,
+                              guint property_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ACTIVE_SOURCE:
+			g_value_set_object (
+				value,
+				e_source_notebook_get_active_source (
+				E_SOURCE_NOTEBOOK (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_notebook_dispose (GObject *object)
+{
+	ESourceNotebookPrivate *priv;
+
+	priv = E_SOURCE_NOTEBOOK_GET_PRIVATE (object);
+
+	if (priv->active_source != NULL) {
+		g_object_unref (priv->active_source);
+		priv->active_source = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_notebook_parent_class)->dispose (object);
+}
+
+static void
+source_notebook_finalize (GObject *object)
+{
+	ESourceNotebookPrivate *priv;
+
+	priv = E_SOURCE_NOTEBOOK_GET_PRIVATE (object);
+
+	g_free (priv->child_source_key);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_notebook_parent_class)->finalize (object);
+}
+
+static void
+source_notebook_constructed (GObject *object)
+{
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_source_notebook_parent_class)->constructed (object);
+
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (object), FALSE);
+
+	/* Current page is still -1 so skip G_BINDING_SYNC_CREATE. */
+	g_object_bind_property_full (
+		object, "page",
+		object, "active-source",
+		G_BINDING_BIDIRECTIONAL,
+		source_notebook_page_num_to_source,
+		source_notebook_source_to_page_num,
+		NULL, (GDestroyNotify) NULL);
+}
+
+static void
+source_notebook_set_child_property (GtkContainer *container,
+                                    GtkWidget *child,
+                                    guint property_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CHILD_SOURCE:
+			source_notebook_set_child_source (
+				E_SOURCE_NOTEBOOK (container),
+				child, g_value_get_object (value));
+			return;
+	}
+
+	GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (
+		container, property_id, pspec);
+}
+
+static void
+source_notebook_get_child_property (GtkContainer *container,
+                                    GtkWidget *child,
+                                    guint property_id,
+                                    GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CHILD_SOURCE:
+			g_value_set_object (
+				value,
+				source_notebook_get_child_source (
+				E_SOURCE_NOTEBOOK (container), child));
+			return;
+	}
+
+	GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (
+		container, property_id, pspec);
+}
+
+static void
+e_source_notebook_class_init (ESourceNotebookClass *class)
+{
+	GObjectClass *object_class;
+	GtkContainerClass *container_class;
+
+	g_type_class_add_private (class, sizeof (ESourceNotebookPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_notebook_set_property;
+	object_class->get_property = source_notebook_get_property;
+	object_class->dispose = source_notebook_dispose;
+	object_class->finalize = source_notebook_finalize;
+	object_class->constructed = source_notebook_constructed;
+
+	container_class = GTK_CONTAINER_CLASS (class);
+	container_class->set_child_property = source_notebook_set_child_property;
+	container_class->get_child_property = source_notebook_get_child_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ACTIVE_SOURCE,
+		g_param_spec_object (
+			"active-source",
+			"Active Source",
+			"The data source for the current page",
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	/* Child property for notebook pages. */
+	gtk_container_class_install_child_property (
+		container_class,
+		PROP_CHILD_SOURCE,
+		g_param_spec_object (
+			"source",
+			"Source",
+			"The data source for this page",
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_source_notebook_init (ESourceNotebook *notebook)
+{
+	gchar *key;
+
+	notebook->priv = E_SOURCE_NOTEBOOK_GET_PRIVATE (notebook);
+
+	key = g_strdup_printf (CHILD_SOURCE_KEY_FORMAT, notebook);
+	notebook->priv->child_source_key = key;
+}
+
+GtkWidget *
+e_source_notebook_new (void)
+{
+	return g_object_new (E_TYPE_SOURCE_NOTEBOOK, NULL);
+}
+
+gint
+e_source_notebook_add_page (ESourceNotebook *notebook,
+                            ESource *source,
+                            GtkWidget *child)
+{
+	g_return_val_if_fail (E_IS_SOURCE_NOTEBOOK (notebook), -1);
+	g_return_val_if_fail (E_IS_SOURCE (source), -1);
+	g_return_val_if_fail (GTK_IS_WIDGET (child), -1);
+
+	gtk_widget_show (child);
+	source_notebook_set_child_source (notebook, child, source);
+
+	return gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL);
+}
+
+ESource *
+e_source_notebook_get_active_source (ESourceNotebook *notebook)
+{
+	g_return_val_if_fail (E_IS_SOURCE_NOTEBOOK (notebook), NULL);
+
+	return notebook->priv->active_source;
+}
+
+void
+e_source_notebook_set_active_source (ESourceNotebook *notebook,
+                                     ESource *source)
+{
+	g_return_if_fail (E_IS_SOURCE_NOTEBOOK (notebook));
+
+	if (source != NULL) {
+		g_return_if_fail (E_IS_SOURCE (source));
+		g_object_ref (source);
+	}
+
+	if (notebook->priv->active_source != NULL)
+		g_object_unref (notebook->priv->active_source);
+
+	notebook->priv->active_source = source;
+
+	g_object_notify (G_OBJECT (notebook), "active-source");
+}
+
diff --git a/widgets/misc/e-source-notebook.h b/widgets/misc/e-source-notebook.h
new file mode 100644
index 0000000..eab3bc4
--- /dev/null
+++ b/widgets/misc/e-source-notebook.h
@@ -0,0 +1,73 @@
+/*
+ * e-source-notebook.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_NOTEBOOK_H
+#define E_SOURCE_NOTEBOOK_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/e-source.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_NOTEBOOK \
+	(e_source_notebook_get_type ())
+#define E_SOURCE_NOTEBOOK(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_NOTEBOOK, ESourceNotebook))
+#define E_SOURCE_NOTEBOOK_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_NOTEBOOK, ESourceNotebookClass))
+#define E_IS_SOURCE_NOTEBOOK(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_NOTEBOOK))
+#define E_IS_SOURCE_NOTEBOOK_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_NOTEBOOK))
+#define E_SOURCE_NOTEBOOK_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_NOTEBOOK))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceNotebook ESourceNotebook;
+typedef struct _ESourceNotebookClass ESourceNotebookClass;
+typedef struct _ESourceNotebookPrivate ESourceNotebookPrivate;
+
+struct _ESourceNotebook {
+	GtkNotebook parent;
+	ESourceNotebookPrivate *priv;
+};
+
+struct _ESourceNotebookClass {
+	GtkNotebookClass parent_class;
+};
+
+GType		e_source_notebook_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_source_notebook_new		(void);
+gint		e_source_notebook_add_page	(ESourceNotebook *notebook,
+						 ESource *source,
+						 GtkWidget *child);
+ESource *	e_source_notebook_get_active_source
+						(ESourceNotebook *notebook);
+void		e_source_notebook_set_active_source
+						(ESourceNotebook *notebook,
+						 ESource *source);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_NOTEBOOK_H */
+
diff --git a/widgets/misc/test-source-config.c b/widgets/misc/test-source-config.c
new file mode 100644
index 0000000..da3301c
--- /dev/null
+++ b/widgets/misc/test-source-config.c
@@ -0,0 +1,54 @@
+#include <stdlib.h>
+#include <gtk/gtk.h>
+
+#include <libedataserver/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);
+
+	registry = e_source_registry_new_sync (NULL, &error);
+
+	if (error != NULL) {
+		g_printerr ("%s\n", error->message);
+		exit (EXIT_FAILURE);
+	}
+
+	if (argc > 1) {
+		source = e_source_registry_lookup_by_uid (registry, argv[1]);
+		if (source == NULL) {
+			g_printerr ("No such UID: %s\n", argv[1]);
+			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]