diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2010-12-18 06:45:54 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2012-06-03 11:00:38 +0800 |
commit | 2a2933ec5cdc0d7afd9b8da5f9146c5d7f1630df (patch) | |
tree | 6dcf480dbe7a01eaecb1092c7cc4c17b2e733257 | |
parent | d3498e0ccd853b7e91877e82fd6150ac412294cc (diff) | |
download | gsoc2013-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.am | 2 | ||||
-rw-r--r-- | addressbook/gui/widgets/e-book-source-config.c | 254 | ||||
-rw-r--r-- | addressbook/gui/widgets/e-book-source-config.h | 67 | ||||
-rw-r--r-- | calendar/gui/Makefile.am | 3 | ||||
-rw-r--r-- | calendar/gui/e-cal-source-config.c | 397 | ||||
-rw-r--r-- | calendar/gui/e-cal-source-config.h | 73 | ||||
-rw-r--r-- | widgets/misc/Makefile.am | 27 | ||||
-rw-r--r-- | widgets/misc/e-interval-chooser.c | 214 | ||||
-rw-r--r-- | widgets/misc/e-interval-chooser.h | 68 | ||||
-rw-r--r-- | widgets/misc/e-source-config-backend.c | 140 | ||||
-rw-r--r-- | widgets/misc/e-source-config-backend.h | 89 | ||||
-rw-r--r-- | widgets/misc/e-source-config-dialog.c | 313 | ||||
-rw-r--r-- | widgets/misc/e-source-config-dialog.h | 65 | ||||
-rw-r--r-- | widgets/misc/e-source-config.c | 1152 | ||||
-rw-r--r-- | widgets/misc/e-source-config.h | 111 | ||||
-rw-r--r-- | widgets/misc/e-source-notebook.c | 359 | ||||
-rw-r--r-- | widgets/misc/e-source-notebook.h | 73 | ||||
-rw-r--r-- | widgets/misc/test-source-config.c | 56 |
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; +} |