aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2010-12-18 06:45:54 +0800
committerMatthew Barnes <mbarnes@redhat.com>2012-06-03 11:00:38 +0800
commit2a2933ec5cdc0d7afd9b8da5f9146c5d7f1630df (patch)
tree6dcf480dbe7a01eaecb1092c7cc4c17b2e733257
parentd3498e0ccd853b7e91877e82fd6150ac412294cc (diff)
downloadgsoc2013-evolution-2a2933ec5cdc0d7afd9b8da5f9146c5d7f1630df.tar.gz
gsoc2013-evolution-2a2933ec5cdc0d7afd9b8da5f9146c5d7f1630df.tar.zst
gsoc2013-evolution-2a2933ec5cdc0d7afd9b8da5f9146c5d7f1630df.zip
Add ESourceConfig widget.
Base class for building address book and calendar configuration dialogs.
-rw-r--r--addressbook/gui/widgets/Makefile.am2
-rw-r--r--addressbook/gui/widgets/e-book-source-config.c254
-rw-r--r--addressbook/gui/widgets/e-book-source-config.h67
-rw-r--r--calendar/gui/Makefile.am3
-rw-r--r--calendar/gui/e-cal-source-config.c397
-rw-r--r--calendar/gui/e-cal-source-config.h73
-rw-r--r--widgets/misc/Makefile.am27
-rw-r--r--widgets/misc/e-interval-chooser.c214
-rw-r--r--widgets/misc/e-interval-chooser.h68
-rw-r--r--widgets/misc/e-source-config-backend.c140
-rw-r--r--widgets/misc/e-source-config-backend.h89
-rw-r--r--widgets/misc/e-source-config-dialog.c313
-rw-r--r--widgets/misc/e-source-config-dialog.h65
-rw-r--r--widgets/misc/e-source-config.c1152
-rw-r--r--widgets/misc/e-source-config.h111
-rw-r--r--widgets/misc/e-source-notebook.c359
-rw-r--r--widgets/misc/e-source-notebook.h73
-rw-r--r--widgets/misc/test-source-config.c56
18 files changed, 3462 insertions, 1 deletions
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index 19c9c4f72b..a580be000b 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 0000000000..8b9ed62b30
--- /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 0000000000..18e075511e
--- /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 75991807df..3e27988f69 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 0000000000..4710fa576e
--- /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 0000000000..924958e00c
--- /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 5768c72bc7..c09a78f70c 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-spell-entry.h \
e-url-entry.h \
e-web-view.h \
@@ -120,6 +125,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 \
@@ -143,6 +149,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-spell-entry.c \
e-url-entry.c \
e-web-view.c \
@@ -174,7 +184,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) \
@@ -228,6 +239,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 0000000000..5a2b4e04ad
--- /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 0000000000..351cbbe336
--- /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 0000000000..e6802f99ae
--- /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 0000000000..df0e23d84e
--- /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 0000000000..1174d38d23
--- /dev/null
+++ b/widgets/misc/e-source-config-dialog.c
@@ -0,0 +1,313 @@
+/*
+ * 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_commit_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ESourceConfig *config;
+ ESourceConfigDialog *dialog;
+ GdkWindow *gdk_window;
+ GError *error = NULL;
+
+ config = E_SOURCE_CONFIG (object);
+ dialog = E_SOURCE_CONFIG_DIALOG (user_data);
+
+ /* Set the cursor back to normal. */
+ gdk_window = gtk_widget_get_window (GTK_WIDGET (dialog));
+ gdk_window_set_cursor (gdk_window, NULL);
+
+ /* Allow user interaction with window content. */
+ gtk_widget_set_sensitive (GTK_WIDGET (dialog), TRUE);
+
+ e_source_config_commit_finish (config, result, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_object_unref (dialog);
+ g_error_free (error);
+
+ /* FIXME ESourceConfigDialog should implement EAlertSink. */
+ } else if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_object_unref (dialog);
+ g_error_free (error);
+
+ } else {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ }
+}
+
+static void
+source_config_dialog_commit (ESourceConfigDialog *dialog)
+{
+ GdkCursor *gdk_cursor;
+ GdkWindow *gdk_window;
+ ESourceConfig *config;
+
+ config = e_source_config_dialog_get_config (dialog);
+
+ /* Make the cursor appear busy. */
+ gdk_cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window = gtk_widget_get_window (GTK_WIDGET (dialog));
+ gdk_window_set_cursor (gdk_window, gdk_cursor);
+ g_object_unref (gdk_cursor);
+
+ /* Prevent user interaction with window content. */
+ gtk_widget_set_sensitive (GTK_WIDGET (dialog), FALSE);
+
+ /* XXX This operation is not cancellable. */
+ e_source_config_commit (
+ config, NULL,
+ source_config_dialog_commit_cb,
+ g_object_ref (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)
+{
+ /* Do not chain up. GtkDialog does not implement this method. */
+
+ switch (response_id) {
+ case GTK_RESPONSE_OK:
+ source_config_dialog_commit (
+ E_SOURCE_CONFIG_DIALOG (dialog));
+ break;
+ case GTK_RESPONSE_CANCEL:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ default:
+ break;
+ }
+}
+
+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)
+{
+ g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+ return g_object_new (
+ E_TYPE_SOURCE_CONFIG_DIALOG,
+ "config", config, 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 0000000000..b4e49efc63
--- /dev/null
+++ b/widgets/misc/e-source-config-dialog.h
@@ -0,0 +1,65 @@
+/*
+ * 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);
+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 0000000000..69a238e535
--- /dev/null
+++ b/widgets/misc/e-source-config.c
@@ -0,0 +1,1152 @@
+/*
+ * 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-authentication.h>
+#include <libedataserver/e-source-backend.h>
+#include <libedataserver/e-source-refresh.h>
+#include <libedataserver/e-source-security.h>
+#include <libedataserver/e-source-webdav.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;
+ gint result;
+
+ 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_ref_source (registry, parent_uid_a);
+ source_b = e_source_registry_ref_source (registry, parent_uid_b);
+
+ g_return_val_if_fail (source_a != NULL, 1);
+ g_return_val_if_fail (source_b != NULL, -1);
+
+ result = e_source_compare_by_display_name (source_a, source_b);
+
+ g_object_unref (source_a);
+ g_object_unref (source_b);
+
+ return result;
+}
+
+static void
+source_config_add_candidate (ESourceConfig *config,
+ ESourceConfigBackend *backend)
+{
+ Candidate *candidate;
+ GtkBox *backend_box;
+ GtkLabel *type_label;
+ GtkComboBoxText *type_combo;
+ ESourceConfigBackendClass *class;
+ ESourceRegistry *registry;
+ ESourceBackend *extension;
+ ESource *original_source;
+ ESource *parent_source;
+ GDBusObject *dbus_object;
+ const gchar *display_name;
+ const gchar *extension_name;
+ const gchar *parent_uid;
+
+ 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);
+
+ original_source = e_source_config_get_original_source (config);
+
+ if (original_source != NULL)
+ parent_uid = e_source_get_parent (original_source);
+ else
+ parent_uid = class->parent_uid;
+
+ /* Make sure the parent source exists. This will either
+ * be a collection source or a built-in "stub" source. */
+ parent_source = e_source_registry_ref_source (registry, parent_uid);
+ if (parent_source == NULL)
+ return;
+
+ /* Some backends don't allow new sources to be created.
+ * The "contacts" calendar backend is one such example. */
+ if (original_source == 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. */
+ if (original_source != NULL)
+ dbus_object = e_source_ref_dbus_object (original_source);
+ else
+ dbus_object = NULL;
+ candidate->scratch_source = e_source_new (dbus_object, NULL, NULL);
+ if (dbus_object != NULL)
+ g_object_unref (dbus_object);
+
+ /* 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, 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_source);
+ 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);
+
+ g_object_unref (parent_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;
+ 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));
+
+ source_config_add_candidate (config, backend);
+
+ } 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);
+ }
+
+ 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)
+{
+ /* Placeholder so subclasses can safely chain up. */
+}
+
+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;
+}
+
+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);
+}
+
+/* Helper for e_source_config_commit() */
+static void
+source_config_commit_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ GError *error = NULL;
+
+ simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+ e_source_registry_commit_source_finish (
+ E_SOURCE_REGISTRY (object), result, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+void
+e_source_config_commit (ESourceConfig *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ ESourceRegistry *registry;
+ Candidate *candidate;
+
+ g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+
+ registry = e_source_config_get_registry (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);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (config), callback,
+ user_data, e_source_config_commit);
+
+ e_source_registry_commit_source (
+ registry, candidate->scratch_source,
+ cancellable, source_config_commit_cb, simple);
+}
+
+gboolean
+e_source_config_commit_finish (ESourceConfig *config,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (config),
+ e_source_config_commit), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+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);
+}
+
+void
+e_source_config_add_secure_connection_for_webdav (ESourceConfig *config,
+ ESource *scratch_source)
+{
+ GtkWidget *widget1;
+ GtkWidget *widget2;
+ 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");
+ widget1 = gtk_check_button_new_with_label (label);
+ e_source_config_insert_widget (config, scratch_source, NULL, widget1);
+ gtk_widget_show (widget1);
+
+ g_object_bind_property (
+ extension, "secure",
+ widget1, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
+ extension = e_source_get_extension (scratch_source, extension_name);
+
+ label = _("Ignore invalid SSL certificate");
+ widget2 = gtk_check_button_new_with_label (label);
+ gtk_widget_set_margin_left (widget2, 24);
+ e_source_config_insert_widget (config, scratch_source, NULL, widget2);
+ gtk_widget_show (widget2);
+
+ g_object_bind_property (
+ widget1, "active",
+ widget2, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ extension, "ignore-invalid-cert",
+ widget2, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+}
+
+void
+e_source_config_add_user_entry (ESourceConfig *config,
+ ESource *scratch_source)
+{
+ GtkWidget *widget;
+ ESource *original_source;
+ 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_AUTHENTICATION;
+ extension = e_source_get_extension (scratch_source, extension_name);
+
+ original_source = e_source_config_get_original_source (config);
+
+ widget = gtk_entry_new ();
+ e_source_config_insert_widget (
+ config, scratch_source, _("User"), widget);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ extension, "user",
+ widget, "text",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ /* If this is a new data source, initialize the
+ * GtkEntry to the user name of the current user. */
+ if (original_source == NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), g_get_user_name ());
+}
+
diff --git a/widgets/misc/e-source-config.h b/widgets/misc/e-source-config.h
new file mode 100644
index 0000000000..35ad70ff33
--- /dev/null
+++ b/widgets/misc/e-source-config.h
@@ -0,0 +1,111 @@
+/*
+ * 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);
+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);
+void e_source_config_commit (ESourceConfig *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_source_config_commit_finish (ESourceConfig *config,
+ GAsyncResult *result,
+ GError **error);
+
+/* 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);
+void e_source_config_add_secure_connection_for_webdav
+ (ESourceConfig *config,
+ ESource *scratch_source);
+void e_source_config_add_user_entry (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 0000000000..08d4490839
--- /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 0000000000..eab3bc4dc3
--- /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 0000000000..2fd69bf136
--- /dev/null
+++ b/widgets/misc/test-source-config.c
@@ -0,0 +1,56 @@
+#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_ref_source (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));
+
+ g_signal_connect (
+ dialog, "response",
+ G_CALLBACK (dialog_response), NULL);
+
+ gtk_widget_show (config);
+ gtk_widget_show (dialog);
+
+ g_object_unref (source);
+
+ gtk_main ();
+
+ return 0;
+}