/* * 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 * */ #include "e-source-config.h" #include #include #include #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_box_new (GTK_ORIENTATION_VERTICAL, 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); /* Connect this signal AFTER we're done adding candidates * so we don't trigger check_complete() before candidates * have a chance to insert widgets. */ g_signal_connect ( config->priv->type_combo, "changed", G_CALLBACK (source_config_type_combo_changed_cb), config); /* Trigger the callback to make sure the right page * is selected, window is an appropriate size, etc. */ g_signal_emit_by_name (config->priv->type_combo, "changed"); } 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_box_new (GTK_ORIENTATION_VERTICAL, 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); } 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_box_new (GTK_ORIENTATION_HORIZONTAL, 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_box_new (GTK_ORIENTATION_HORIZONTAL, 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); port = e_source_authentication_get_port ( E_SOURCE_AUTHENTICATION (authentication_extension)); 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; } static gboolean webdav_source_ssl_trust_to_sensitive_cb (GBinding *binding, const GValue *source_value, GValue *target_value, gpointer user_data) { const gchar *ssl_trust = g_value_get_string (source_value); g_value_set_boolean (target_value, ssl_trust && *ssl_trust); return TRUE; } static void webdav_unset_ssl_trust_clicked_cb (GtkWidget *button, ESourceWebdav *extension) { e_source_webdav_set_ssl_trust (extension, NULL); } 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); widget2 = gtk_button_new_with_mnemonic (_("Unset _trust for SSL certificate")); 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_full ( extension, "ssl-trust", widget2, "sensitive", G_BINDING_SYNC_CREATE, webdav_source_ssl_trust_to_sensitive_cb, NULL, NULL, NULL); g_signal_connect (widget2, "clicked", G_CALLBACK (webdav_unset_ssl_trust_clicked_cb), extension); } 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 ()); }