diff options
Diffstat (limited to 'e-util/e-source-config.c')
-rw-r--r-- | e-util/e-source-config.c | 1447 |
1 files changed, 1447 insertions, 0 deletions
diff --git a/e-util/e-source-config.c b/e-util/e-source-config.c new file mode 100644 index 0000000000..aacb48dd5c --- /dev/null +++ b/e-util/e-source-config.c @@ -0,0 +1,1447 @@ +/* + * 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/libebackend.h> + +#include "e-interval-chooser.h" +#include "e-marshal.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; + ESource *collection_source; + ESourceRegistry *registry; + + GHashTable *backends; + GPtrArray *candidates; + + GtkWidget *type_label; + GtkWidget *type_combo; + GtkWidget *name_label; + GtkWidget *name_entry; + GtkWidget *backend_box; + GtkSizeGroup *size_group; + + gboolean complete; +}; + +struct _Candidate { + GtkWidget *page; + ESource *scratch_source; + ESourceConfigBackend *backend; + gulong changed_handler_id; +}; + +enum { + PROP_0, + PROP_COLLECTION_SOURCE, + 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_sources (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + ESource *source_a; + ESource *source_b; + ESource *parent_a; + ESource *parent_b; + ESourceConfig *config; + ESourceRegistry *registry; + const gchar *parent_uid_a; + const gchar *parent_uid_b; + gint result; + + source_a = E_SOURCE (a); + source_b = E_SOURCE (b); + config = E_SOURCE_CONFIG (user_data); + + if (e_source_equal (source_a, source_b)) + return 0; + + /* "On This Computer" always comes first. */ + + parent_uid_a = e_source_get_parent (source_a); + parent_uid_b = e_source_get_parent (source_b); + + if (g_strcmp0 (parent_uid_a, "local-stub") == 0) + return -1; + + if (g_strcmp0 (parent_uid_b, "local-stub") == 0) + return 1; + + registry = e_source_config_get_registry (config); + + parent_a = e_source_registry_ref_source (registry, parent_uid_a); + parent_b = e_source_registry_ref_source (registry, parent_uid_b); + + g_return_val_if_fail (parent_a != NULL, 1); + g_return_val_if_fail (parent_b != NULL, -1); + + result = e_source_compare_by_display_name (parent_a, parent_b); + + g_object_unref (parent_a); + g_object_unref (parent_b); + + return result; +} + +static void +source_config_add_candidate (ESourceConfig *config, + ESource *scratch_source, + ESourceConfigBackend *backend) +{ + Candidate *candidate; + GtkBox *backend_box; + GtkLabel *type_label; + GtkComboBoxText *type_combo; + ESource *parent_source; + ESourceRegistry *registry; + const gchar *display_name; + const gchar *parent_uid; + gulong handler_id; + + 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); + parent_uid = e_source_get_parent (scratch_source); + parent_source = e_source_registry_ref_source (registry, parent_uid); + g_return_if_fail (parent_source != NULL); + + candidate = g_slice_new (Candidate); + candidate->backend = g_object_ref (backend); + candidate->scratch_source = g_object_ref (scratch_source); + + /* 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); + + 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); + + handler_id = g_signal_connect_swapped ( + candidate->scratch_source, "changed", + G_CALLBACK (e_source_config_check_complete), config); + + candidate->changed_handler_id = handler_id; + + /* 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_signal_handler_disconnect ( + candidate->scratch_source, + candidate->changed_handler_id); + + 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); + e_source_config_check_complete (config); +} + +static gboolean +source_config_init_for_adding_source_foreach (gpointer key, + gpointer value, + gpointer user_data) +{ + ESource *scratch_source; + ESourceBackend *extension; + ESourceConfig *config; + ESourceConfigBackend *backend; + ESourceConfigBackendClass *class; + const gchar *extension_name; + + scratch_source = E_SOURCE (key); + backend = E_SOURCE_CONFIG_BACKEND (value); + config = E_SOURCE_CONFIG (user_data); + + /* This may not be the correct backend name for the child of a + * collection. For example, the "yahoo" collection backend uses + * the "caldav" calender backend for calendar children. But the + * ESourceCollectionBackend can override our setting if needed. */ + class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend); + extension_name = e_source_config_get_backend_extension_name (config); + extension = e_source_get_extension (scratch_source, extension_name); + e_source_backend_set_backend_name (extension, class->backend_name); + + source_config_add_candidate (config, scratch_source, backend); + + return FALSE; /* don't stop traversal */ +} + +static void +source_config_init_for_adding_source (ESourceConfig *config) +{ + GList *list, *link; + ESourceRegistry *registry; + GTree *scratch_source_tree; + + /* Candidates are drawn from two sources: + * + * ESourceConfigBackend classes that specify a fixed parent UID, + * meaning there exists one only possible parent source for any + * scratch source created by the backend. The fixed parent UID + * should be a built-in "stub" placeholder ("local-stub", etc). + * + * -and- + * + * Collection sources. We let ESourceConfig subclasses gather + * eligible collection sources to serve as parents for scratch + * sources. A scratch source is matched to a backend based on + * the collection's backend name. The "calendar-enabled" and + * "contacts-enabled" settings also factor into eligibility. + */ + + /* Use a GTree instead of a GHashTable so inserted scratch + * sources automatically sort themselves by their parent's + * display name. */ + scratch_source_tree = g_tree_new_full ( + source_config_compare_sources, config, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) g_object_unref); + + registry = e_source_config_get_registry (config); + + /* First pick out the backends with a fixed parent UID. */ + + list = g_hash_table_get_values (config->priv->backends); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESourceConfigBackend *backend; + ESourceConfigBackendClass *class; + ESource *scratch_source; + ESource *parent_source; + gboolean parent_is_disabled; + + backend = E_SOURCE_CONFIG_BACKEND (link->data); + class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend); + + if (class->parent_uid == NULL) + continue; + + /* Verify the fixed parent UID is valid. */ + parent_source = e_source_registry_ref_source ( + registry, class->parent_uid); + if (parent_source == NULL) { + g_warning ( + "%s: %sClass specifies " + "an invalid parent_uid '%s'", + G_STRFUNC, + G_OBJECT_TYPE_NAME (backend), + class->parent_uid); + continue; + } + parent_is_disabled = !e_source_get_enabled (parent_source); + g_object_unref (parent_source); + + /* It's unusual for a fixed parent source to be disabled. + * A user would have to go out of his way to do this, but + * we should honor it regardless. */ + if (parent_is_disabled) + continue; + + /* Some backends don't allow new sources to be created. + * The "contacts" calendar backend is one such example. */ + if (!e_source_config_backend_allow_creation (backend)) + continue; + + scratch_source = e_source_new (NULL, NULL, NULL); + g_return_if_fail (scratch_source != NULL); + + e_source_set_parent (scratch_source, class->parent_uid); + + g_tree_insert ( + scratch_source_tree, + g_object_ref (scratch_source), + g_object_ref (backend)); + + g_object_unref (scratch_source); + } + + g_list_free (list); + + /* Next gather eligible collection sources to serve as parents. */ + + list = e_source_config_list_eligible_collections (config); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *parent_source; + ESource *scratch_source; + ESourceBackend *extension; + ESourceConfigBackend *backend = NULL; + const gchar *backend_name; + const gchar *parent_uid; + + parent_source = E_SOURCE (link->data); + parent_uid = e_source_get_uid (parent_source); + + extension = e_source_get_extension ( + parent_source, E_SOURCE_EXTENSION_COLLECTION); + backend_name = e_source_backend_get_backend_name (extension); + + if (backend_name != NULL) + backend = g_hash_table_lookup ( + config->priv->backends, backend_name); + + if (backend == NULL) + continue; + + /* Some backends disallow creating certain types of + * resources. For example, the Exchange Web Services + * backend disallows creating new memo lists. */ + if (!e_source_config_backend_allow_creation (backend)) + continue; + + scratch_source = e_source_new (NULL, NULL, NULL); + g_return_if_fail (scratch_source != NULL); + + e_source_set_parent (scratch_source, parent_uid); + + g_tree_insert ( + scratch_source_tree, + g_object_ref (scratch_source), + g_object_ref (backend)); + + g_object_unref (scratch_source); + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + /* XXX GTree doesn't get as much love as GHashTable. + * It's missing an equivalent to GHashTableIter. */ + g_tree_foreach ( + scratch_source_tree, + source_config_init_for_adding_source_foreach, config); + + g_tree_unref (scratch_source_tree); +} + +static void +source_config_init_for_editing_source (ESourceConfig *config) +{ + ESource *original_source; + ESource *scratch_source; + ESourceBackend *extension; + ESourceConfigBackend *backend; + GDBusObject *dbus_object; + const gchar *backend_name; + const gchar *extension_name; + + original_source = e_source_config_get_original_source (config); + g_return_if_fail (original_source != NULL); + + 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 (backend != NULL); + + dbus_object = e_source_ref_dbus_object (original_source); + g_return_if_fail (dbus_object != NULL); + + scratch_source = e_source_new (dbus_object, NULL, NULL); + g_return_if_fail (scratch_source != NULL); + + source_config_add_candidate (config, scratch_source, backend); + + g_object_unref (scratch_source); + g_object_unref (dbus_object); +} + +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_COLLECTION_SOURCE: + g_value_set_object ( + value, + e_source_config_get_collection_source ( + E_SOURCE_CONFIG (object))); + return; + + 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->collection_source != NULL) { + g_object_unref (priv->collection_source); + priv->collection_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_label != NULL) { + g_object_unref (priv->name_label); + priv->name_label = 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; + ESourceRegistry *registry; + ESource *original_source; + ESource *collection_source = NULL; + + config = E_SOURCE_CONFIG (object); + registry = e_source_config_get_registry (config); + original_source = e_source_config_get_original_source (config); + + /* If we have an original source, see if it's part + * of a collection and note the collection source. */ + if (original_source != NULL) { + const gchar *extension_name; + + extension_name = E_SOURCE_EXTENSION_COLLECTION; + collection_source = e_source_registry_find_extension ( + registry, original_source, extension_name); + config->priv->collection_source = collection_source; + } + + 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); + + /* If the original source is part of a collection then we assume + * the display name is server-assigned and not user-assigned, at + * least not assigned through Evolution. */ + if (collection_source != NULL) + e_source_config_insert_widget ( + config, NULL, _("Name:"), + config->priv->name_label); + else + 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) + source_config_init_for_adding_source (config); + else + source_config_init_for_editing_source (config); +} + +static GList * +source_config_list_eligible_collections (ESourceConfig *config) +{ + ESourceRegistry *registry; + GQueue trash = G_QUEUE_INIT; + GList *list, *link; + const gchar *extension_name; + + extension_name = E_SOURCE_EXTENSION_COLLECTION; + registry = e_source_config_get_registry (config); + + list = e_source_registry_list_sources (registry, extension_name); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + gboolean elligible; + + elligible = + e_source_get_enabled (source) && + e_source_get_remote_creatable (source); + + if (!elligible) + g_queue_push_tail (&trash, link); + } + + /* Remove ineligible collections from the list. */ + while ((link = g_queue_pop_head (&trash)) != NULL) { + g_object_unref (link->data); + list = g_list_delete_link (list, link); + } + + return list; +} + +static void +source_config_init_candidate (ESourceConfig *config, + ESource *scratch_source) +{ + g_object_bind_property ( + scratch_source, "display-name", + config->priv->name_label, "label", + G_BINDING_SYNC_CREATE); + + 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->list_eligible_collections = + source_config_list_eligible_collections; + 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_COLLECTION_SOURCE, + g_param_spec_object ( + "collection-source", + "Collection Source", + "The collection ESource to which " + "the ESource being edited belongs", + E_TYPE_SOURCE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + 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; + + attr_list = pango_attr_list_new (); + + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + pango_attr_list_insert (attr_list, attr); + + /* Either the source type combo box or the label is shown, + * never both. But we create both widgets and keep them + * both up-to-date because it makes the logic simpler. */ + + 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); + + /* Similarly for the display name. Either the text entry + * or the label is shown, depending on whether the source + * is a collection member (new sources never are). */ + + 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->name_label = 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); +} + +GList * +e_source_config_list_eligible_collections (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->list_eligible_collections != NULL, NULL); + + return class->list_eligible_collections (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; +} + +ESource * +e_source_config_get_collection_source (ESourceConfig *config) +{ + g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL); + + return config->priv->collection_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); +} + +static gboolean +secure_to_port_cb (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GObject *authentication_extension; + guint16 port; + + authentication_extension = g_binding_get_target (binding); + g_object_get (authentication_extension, "port", &port, NULL); + + if (port == 80 || port == 443 || port == 0) + port = g_value_get_boolean (source_value) ? 443 : 80; + + g_value_set_uint (target_value, port); + + return TRUE; +} + +void +e_source_config_add_secure_connection_for_webdav (ESourceConfig *config, + ESource *scratch_source) +{ + GtkWidget *widget1; + GtkWidget *widget2; + ESourceExtension *extension; + ESourceAuthentication *authentication_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_AUTHENTICATION; + authentication_extension = e_source_get_extension (scratch_source, extension_name); + + g_object_bind_property_full ( + extension, "secure", + authentication_extension, "port", + G_BINDING_DEFAULT, + secure_to_port_cb, + NULL, NULL, NULL); + + 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 ()); +} + |