1 /*
2  * e-source-config.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <glib/gi18n-lib.h>
21 
22 #include <libebackend/libebackend.h>
23 
24 #include "e-interval-chooser.h"
25 #include "e-marshal.h"
26 #include "e-misc-utils.h"
27 #include "e-source-config-backend.h"
28 
29 #include "e-source-config.h"
30 
31 #define E_SOURCE_CONFIG_GET_PRIVATE(obj) \
32 	(G_TYPE_INSTANCE_GET_PRIVATE \
33 	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfigPrivate))
34 
35 typedef struct _Candidate Candidate;
36 
37 struct _ESourceConfigPrivate {
38 	ESource *original_source;
39 	ESource *collection_source;
40 	ESourceRegistry *registry;
41 	gchar *preselect_type;
42 
43 	GHashTable *backends;
44 	GPtrArray *candidates;
45 
46 	GtkWidget *type_label;
47 	GtkWidget *type_combo;
48 	GtkWidget *name_label;
49 	GtkWidget *name_entry;
50 	GtkWidget *backend_box;
51 	GtkSizeGroup *size_group;
52 
53 	gboolean complete;
54 };
55 
56 struct _Candidate {
57 	GtkWidget *page;
58 	ESource *scratch_source;
59 	ESourceConfigBackend *backend;
60 	gulong changed_handler_id;
61 };
62 
63 enum {
64 	PROP_0,
65 	PROP_COLLECTION_SOURCE,
66 	PROP_COMPLETE,
67 	PROP_ORIGINAL_SOURCE,
68 	PROP_REGISTRY
69 };
70 
71 enum {
72 	CHECK_COMPLETE,
73 	COMMIT_CHANGES,
74 	INIT_CANDIDATE,
75 	RESIZE_WINDOW,
76 	LAST_SIGNAL
77 };
78 
79 static guint signals[LAST_SIGNAL];
80 
G_DEFINE_TYPE_WITH_CODE(ESourceConfig,e_source_config,GTK_TYPE_BOX,G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL))81 G_DEFINE_TYPE_WITH_CODE (
82 	ESourceConfig,
83 	e_source_config,
84 	GTK_TYPE_BOX,
85 	G_IMPLEMENT_INTERFACE (
86 		E_TYPE_EXTENSIBLE, NULL))
87 
88 static void
89 source_config_init_backends (ESourceConfig *config)
90 {
91 	GList *list, *iter;
92 
93 	config->priv->backends = g_hash_table_new_full (
94 		(GHashFunc) g_str_hash,
95 		(GEqualFunc) g_str_equal,
96 		(GDestroyNotify) g_free,
97 		(GDestroyNotify) g_object_unref);
98 
99 	e_extensible_load_extensions (E_EXTENSIBLE (config));
100 
101 	list = e_extensible_list_extensions (
102 		E_EXTENSIBLE (config), E_TYPE_SOURCE_CONFIG_BACKEND);
103 
104 	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
105 		ESourceConfigBackend *backend;
106 		ESourceConfigBackendClass *class;
107 
108 		backend = E_SOURCE_CONFIG_BACKEND (iter->data);
109 		class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
110 
111 		if (class->backend_name != NULL)
112 			g_hash_table_insert (
113 				config->priv->backends,
114 				g_strdup (class->backend_name),
115 				g_object_ref (backend));
116 	}
117 
118 	g_list_free (list);
119 }
120 
121 static gint
source_config_compare_sources(gconstpointer a,gconstpointer b,gpointer user_data)122 source_config_compare_sources (gconstpointer a,
123                                gconstpointer b,
124                                gpointer user_data)
125 {
126 	ESource *source_a;
127 	ESource *source_b;
128 	ESource *parent_a;
129 	ESource *parent_b;
130 	ESourceConfig *config;
131 	ESourceRegistry *registry;
132 	const gchar *parent_uid_a;
133 	const gchar *parent_uid_b;
134 	gint result;
135 
136 	source_a = E_SOURCE (a);
137 	source_b = E_SOURCE (b);
138 	config = E_SOURCE_CONFIG (user_data);
139 
140 	if (e_source_equal (source_a, source_b))
141 		return 0;
142 
143 	/* "On This Computer" always comes first. */
144 
145 	parent_uid_a = e_source_get_parent (source_a);
146 	parent_uid_b = e_source_get_parent (source_b);
147 
148 	if (g_strcmp0 (parent_uid_a, "local-stub") == 0)
149 		return -1;
150 
151 	if (g_strcmp0 (parent_uid_b, "local-stub") == 0)
152 		return 1;
153 
154 	registry = e_source_config_get_registry (config);
155 
156 	parent_a = e_source_registry_ref_source (registry, parent_uid_a);
157 	parent_b = e_source_registry_ref_source (registry, parent_uid_b);
158 
159 	g_return_val_if_fail (parent_a != NULL, 1);
160 	g_return_val_if_fail (parent_b != NULL, -1);
161 
162 	result = e_source_compare_by_display_name (parent_a, parent_b);
163 
164 	g_object_unref (parent_a);
165 	g_object_unref (parent_b);
166 
167 	return result;
168 }
169 
170 static void
source_config_add_candidate(ESourceConfig * config,ESource * scratch_source,ESourceConfigBackend * backend)171 source_config_add_candidate (ESourceConfig *config,
172                              ESource *scratch_source,
173                              ESourceConfigBackend *backend)
174 {
175 	Candidate *candidate;
176 	GtkBox *backend_box;
177 	GtkLabel *type_label;
178 	GtkComboBoxText *type_combo;
179 	GtkWidget *widget;
180 	ESource *parent_source;
181 	ESourceRegistry *registry;
182 	const gchar *display_name;
183 	const gchar *parent_uid;
184 	gulong handler_id;
185 
186 	backend_box = GTK_BOX (config->priv->backend_box);
187 	type_label = GTK_LABEL (config->priv->type_label);
188 	type_combo = GTK_COMBO_BOX_TEXT (config->priv->type_combo);
189 
190 	registry = e_source_config_get_registry (config);
191 	parent_uid = e_source_get_parent (scratch_source);
192 	parent_source = e_source_registry_ref_source (registry, parent_uid);
193 	g_return_if_fail (parent_source != NULL);
194 
195 	candidate = g_slice_new (Candidate);
196 	candidate->backend = g_object_ref (backend);
197 	candidate->scratch_source = g_object_ref (scratch_source);
198 
199 	/* Do not show the page here. */
200 	widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
201 	gtk_box_pack_start (backend_box, widget, FALSE, FALSE, 0);
202 	candidate->page = g_object_ref_sink (widget);
203 
204 	g_ptr_array_add (config->priv->candidates, candidate);
205 
206 	display_name = e_source_get_display_name (parent_source);
207 	gtk_combo_box_text_append_text (type_combo, display_name);
208 	gtk_label_set_text (type_label, display_name);
209 
210 	/* Make sure the combo box has a valid active item before
211 	 * adding widgets.  Otherwise we'll get run-time warnings
212 	 * as property bindings are set up. */
213 	if (gtk_combo_box_get_active (GTK_COMBO_BOX (type_combo)) == -1)
214 		gtk_combo_box_set_active (GTK_COMBO_BOX (type_combo), 0);
215 
216 	/* Bind the standard widgets to the new scratch source. */
217 	g_signal_emit (
218 		config, signals[INIT_CANDIDATE], 0,
219 		candidate->scratch_source);
220 
221 	/* Insert any backend-specific widgets. */
222 	e_source_config_backend_insert_widgets (
223 		candidate->backend, candidate->scratch_source);
224 
225 	handler_id = g_signal_connect_swapped (
226 		candidate->scratch_source, "changed",
227 		G_CALLBACK (e_source_config_check_complete), config);
228 
229 	candidate->changed_handler_id = handler_id;
230 
231 	/* Trigger the "changed" handler we just connected to set the
232 	 * initial "complete" state based on the widgets we just added. */
233 	e_source_changed (candidate->scratch_source);
234 
235 	g_object_unref (parent_source);
236 }
237 
238 static void
source_config_free_candidate(Candidate * candidate)239 source_config_free_candidate (Candidate *candidate)
240 {
241 	g_signal_handler_disconnect (
242 		candidate->scratch_source,
243 		candidate->changed_handler_id);
244 
245 	g_object_unref (candidate->page);
246 	g_object_unref (candidate->scratch_source);
247 	g_object_unref (candidate->backend);
248 
249 	g_slice_free (Candidate, candidate);
250 }
251 
252 static Candidate *
source_config_get_active_candidate(ESourceConfig * config)253 source_config_get_active_candidate (ESourceConfig *config)
254 {
255 	GtkComboBox *type_combo;
256 	gint index;
257 
258 	type_combo = GTK_COMBO_BOX (config->priv->type_combo);
259 	index = gtk_combo_box_get_active (type_combo);
260 	g_return_val_if_fail (index >= 0, NULL);
261 
262 	return g_ptr_array_index (config->priv->candidates, index);
263 }
264 
265 static void
source_config_type_combo_changed_cb(GtkComboBox * type_combo,ESourceConfig * config)266 source_config_type_combo_changed_cb (GtkComboBox *type_combo,
267                                      ESourceConfig *config)
268 {
269 	Candidate *candidate;
270 	GPtrArray *array;
271 	gint index;
272 
273 	array = config->priv->candidates;
274 
275 	for (index = 0; index < array->len; index++) {
276 		candidate = g_ptr_array_index (array, index);
277 		gtk_widget_hide (candidate->page);
278 	}
279 
280 	index = gtk_combo_box_get_active (type_combo);
281 	if (index == CLAMP (index, 0, array->len)) {
282 		candidate = g_ptr_array_index (array, index);
283 		gtk_widget_show (candidate->page);
284 	}
285 
286 	e_source_config_resize_window (config);
287 	e_source_config_check_complete (config);
288 }
289 
290 static gboolean
source_config_init_for_adding_source_foreach(gpointer key,gpointer value,gpointer user_data)291 source_config_init_for_adding_source_foreach (gpointer key,
292                                               gpointer value,
293                                               gpointer user_data)
294 {
295 	ESource *scratch_source;
296 	ESourceBackend *extension;
297 	ESourceConfig *config;
298 	ESourceConfigBackend *backend;
299 	ESourceConfigBackendClass *class;
300 	const gchar *extension_name;
301 
302 	scratch_source = E_SOURCE (key);
303 	backend = E_SOURCE_CONFIG_BACKEND (value);
304 	config = E_SOURCE_CONFIG (user_data);
305 
306 	/* This may not be the correct backend name for the child of a
307 	 * collection.  For example, the "yahoo" collection backend uses
308 	 * the "caldav" calender backend for calendar children.  But the
309 	 * ESourceCollectionBackend can override our setting if needed. */
310 	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
311 	extension_name = e_source_config_get_backend_extension_name (config);
312 	extension = e_source_get_extension (scratch_source, extension_name);
313 	e_source_backend_set_backend_name (extension, class->backend_name);
314 
315 	source_config_add_candidate (config, scratch_source, backend);
316 
317 	return FALSE;  /* don't stop traversal */
318 }
319 
320 static void
source_config_init_for_adding_source(ESourceConfig * config)321 source_config_init_for_adding_source (ESourceConfig *config)
322 {
323 	GList *list, *link;
324 	ESourceRegistry *registry;
325 	GTree *scratch_source_tree;
326 
327 	/* Candidates are drawn from two sources:
328 	 *
329 	 * ESourceConfigBackend classes that specify a fixed parent UID,
330 	 * meaning there exists one only possible parent source for any
331 	 * scratch source created by the backend.  The fixed parent UID
332 	 * should be a built-in "stub" placeholder ("local-stub", etc).
333 	 *
334 	 * -and-
335 	 *
336 	 * Collection sources.  We let ESourceConfig subclasses gather
337 	 * eligible collection sources to serve as parents for scratch
338 	 * sources.  A scratch source is matched to a backend based on
339 	 * the collection's backend name.  The "calendar-enabled" and
340 	 * "contacts-enabled" settings also factor into eligibility.
341 	 */
342 
343 	/* Use a GTree instead of a GHashTable so inserted scratch
344 	 * sources automatically sort themselves by their parent's
345 	 * display name. */
346 	scratch_source_tree = g_tree_new_full (
347 		source_config_compare_sources, config,
348 		(GDestroyNotify) g_object_unref,
349 		(GDestroyNotify) g_object_unref);
350 
351 	registry = e_source_config_get_registry (config);
352 
353 	/* First pick out the backends with a fixed parent UID. */
354 
355 	list = g_hash_table_get_values (config->priv->backends);
356 
357 	for (link = list; link != NULL; link = g_list_next (link)) {
358 		ESourceConfigBackend *backend;
359 		ESourceConfigBackendClass *class;
360 		ESource *scratch_source;
361 		ESource *parent_source;
362 		gboolean parent_is_disabled;
363 
364 		backend = E_SOURCE_CONFIG_BACKEND (link->data);
365 		class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
366 
367 		if (class->parent_uid == NULL)
368 			continue;
369 
370 		/* Verify the fixed parent UID is valid. */
371 		parent_source = e_source_registry_ref_source (
372 			registry, class->parent_uid);
373 		if (parent_source == NULL) {
374 			g_warning (
375 				"%s: %sClass specifies "
376 				"an invalid parent_uid '%s'",
377 				G_STRFUNC,
378 				G_OBJECT_TYPE_NAME (backend),
379 				class->parent_uid);
380 			continue;
381 		}
382 		parent_is_disabled = !e_source_get_enabled (parent_source);
383 		g_object_unref (parent_source);
384 
385 		/* It's unusual for a fixed parent source to be disabled.
386 		 * A user would have to go out of his way to do this, but
387 		 * we should honor it regardless. */
388 		if (parent_is_disabled)
389 			continue;
390 
391 		/* Some backends don't allow new sources to be created.
392 		 * The "contacts" calendar backend is one such example. */
393 		if (!e_source_config_backend_allow_creation (backend))
394 			continue;
395 
396 		scratch_source = e_source_new (NULL, NULL, NULL);
397 		g_return_if_fail (scratch_source != NULL);
398 
399 		e_source_set_parent (scratch_source, class->parent_uid);
400 
401 		g_tree_insert (
402 			scratch_source_tree,
403 			g_object_ref (scratch_source),
404 			g_object_ref (backend));
405 
406 		g_object_unref (scratch_source);
407 	}
408 
409 	g_list_free (list);
410 
411 	/* Next gather eligible collection sources to serve as parents. */
412 
413 	list = e_source_config_list_eligible_collections (config);
414 
415 	for (link = list; link != NULL; link = g_list_next (link)) {
416 		ESource *parent_source;
417 		ESource *scratch_source;
418 		ESourceBackend *extension;
419 		ESourceConfigBackend *backend = NULL;
420 		const gchar *backend_name;
421 		const gchar *parent_uid;
422 
423 		parent_source = E_SOURCE (link->data);
424 		parent_uid = e_source_get_uid (parent_source);
425 
426 		extension = e_source_get_extension (
427 			parent_source, E_SOURCE_EXTENSION_COLLECTION);
428 		backend_name = e_source_backend_get_backend_name (extension);
429 
430 		if (backend_name != NULL)
431 			backend = g_hash_table_lookup (
432 				config->priv->backends, backend_name);
433 
434 		if (backend == NULL)
435 			continue;
436 
437 		/* Some backends disallow creating certain types of
438 		 * resources.  For example, the Exchange Web Services
439 		 * backend disallows creating new memo lists. */
440 		if (!e_source_config_backend_allow_creation (backend))
441 			continue;
442 
443 		scratch_source = e_source_new (NULL, NULL, NULL);
444 		g_return_if_fail (scratch_source != NULL);
445 
446 		e_source_set_parent (scratch_source, parent_uid);
447 
448 		g_tree_insert (
449 			scratch_source_tree,
450 			g_object_ref (scratch_source),
451 			g_object_ref (backend));
452 
453 		g_object_unref (scratch_source);
454 	}
455 
456 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
457 
458 	/* XXX GTree doesn't get as much love as GHashTable.
459 	 *     It's missing an equivalent to GHashTableIter. */
460 	g_tree_foreach (
461 		scratch_source_tree,
462 		source_config_init_for_adding_source_foreach, config);
463 
464 	g_tree_unref (scratch_source_tree);
465 
466 	if (config->priv->preselect_type) {
467 		Candidate *candidate;
468 		GPtrArray *array;
469 		gint index;
470 
471 		array = config->priv->candidates;
472 
473 		for (index = 0; index < array->len; index++) {
474 			ESourceConfigBackendClass *backend_class;
475 
476 			candidate = g_ptr_array_index (array, index);
477 			backend_class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (candidate->backend);
478 
479 			if (backend_class && (
480 			    g_strcmp0 (config->priv->preselect_type, backend_class->backend_name) == 0 ||
481 			    g_strcmp0 (config->priv->preselect_type, backend_class->parent_uid) == 0)) {
482 				gtk_combo_box_set_active (GTK_COMBO_BOX (config->priv->type_combo), index);
483 				break;
484 			}
485 		}
486 	}
487 }
488 
489 static void
source_config_init_for_editing_source(ESourceConfig * config)490 source_config_init_for_editing_source (ESourceConfig *config)
491 {
492 	ESource *original_source;
493 	ESource *scratch_source;
494 	ESourceBackend *extension;
495 	ESourceConfigBackend *backend;
496 	GDBusObject *dbus_object;
497 	const gchar *backend_name;
498 	const gchar *extension_name;
499 
500 	original_source = e_source_config_get_original_source (config);
501 	g_return_if_fail (original_source != NULL);
502 
503 	extension_name = e_source_config_get_backend_extension_name (config);
504 	extension = e_source_get_extension (original_source, extension_name);
505 	backend_name = e_source_backend_get_backend_name (extension);
506 	g_return_if_fail (backend_name != NULL);
507 
508 	/* Special-case Google books and calendars to use 'google' editor, instead
509 	   of the 'carddav'/'caldav' editor, even they use 'carddav'/'caldav' backend. */
510 	if ((g_ascii_strcasecmp (backend_name, "caldav") == 0 ||
511 	     g_ascii_strcasecmp (backend_name, "carddav") == 0) &&
512 	    g_strcmp0 (e_source_get_parent (original_source), "google-stub") == 0)
513 		backend_name = "google";
514 
515 	backend = g_hash_table_lookup (config->priv->backends, backend_name);
516 	g_return_if_fail (backend != NULL);
517 
518 	dbus_object = e_source_ref_dbus_object (original_source);
519 	g_return_if_fail (dbus_object != NULL);
520 
521 	scratch_source = e_source_new (dbus_object, NULL, NULL);
522 	g_return_if_fail (scratch_source != NULL);
523 
524 	source_config_add_candidate (config, scratch_source, backend);
525 
526 	g_object_unref (scratch_source);
527 	g_object_unref (dbus_object);
528 }
529 
530 static void
source_config_set_original_source(ESourceConfig * config,ESource * original_source)531 source_config_set_original_source (ESourceConfig *config,
532                                    ESource *original_source)
533 {
534 	g_return_if_fail (config->priv->original_source == NULL);
535 
536 	if (original_source != NULL)
537 		g_object_ref (original_source);
538 
539 	config->priv->original_source = original_source;
540 }
541 
542 static void
source_config_set_registry(ESourceConfig * config,ESourceRegistry * registry)543 source_config_set_registry (ESourceConfig *config,
544                             ESourceRegistry *registry)
545 {
546 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
547 	g_return_if_fail (config->priv->registry == NULL);
548 
549 	config->priv->registry = g_object_ref (registry);
550 }
551 
552 static void
source_config_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)553 source_config_set_property (GObject *object,
554                             guint property_id,
555                             const GValue *value,
556                             GParamSpec *pspec)
557 {
558 	switch (property_id) {
559 		case PROP_ORIGINAL_SOURCE:
560 			source_config_set_original_source (
561 				E_SOURCE_CONFIG (object),
562 				g_value_get_object (value));
563 			return;
564 
565 		case PROP_REGISTRY:
566 			source_config_set_registry (
567 				E_SOURCE_CONFIG (object),
568 				g_value_get_object (value));
569 			return;
570 	}
571 
572 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
573 }
574 
575 static void
source_config_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)576 source_config_get_property (GObject *object,
577                             guint property_id,
578                             GValue *value,
579                             GParamSpec *pspec)
580 {
581 	switch (property_id) {
582 		case PROP_COLLECTION_SOURCE:
583 			g_value_set_object (
584 				value,
585 				e_source_config_get_collection_source (
586 				E_SOURCE_CONFIG (object)));
587 			return;
588 
589 		case PROP_COMPLETE:
590 			g_value_set_boolean (
591 				value,
592 				e_source_config_check_complete (
593 				E_SOURCE_CONFIG (object)));
594 			return;
595 
596 		case PROP_ORIGINAL_SOURCE:
597 			g_value_set_object (
598 				value,
599 				e_source_config_get_original_source (
600 				E_SOURCE_CONFIG (object)));
601 			return;
602 
603 		case PROP_REGISTRY:
604 			g_value_set_object (
605 				value,
606 				e_source_config_get_registry (
607 				E_SOURCE_CONFIG (object)));
608 			return;
609 	}
610 
611 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
612 }
613 
614 static void
source_config_dispose(GObject * object)615 source_config_dispose (GObject *object)
616 {
617 	ESourceConfigPrivate *priv;
618 
619 	priv = E_SOURCE_CONFIG_GET_PRIVATE (object);
620 	g_clear_object (&priv->original_source);
621 	g_clear_object (&priv->collection_source);
622 	g_clear_object (&priv->registry);
623 	g_clear_object (&priv->type_label);
624 	g_clear_object (&priv->type_combo);
625 	g_clear_object (&priv->name_label);
626 	g_clear_object (&priv->name_entry);
627 	g_clear_object (&priv->backend_box);
628 	g_clear_object (&priv->size_group);
629 
630 	g_hash_table_remove_all (priv->backends);
631 	g_ptr_array_set_size (priv->candidates, 0);
632 
633 	g_clear_pointer (&priv->preselect_type, g_free);
634 
635 	/* Chain up to parent's dispose() method. */
636 	G_OBJECT_CLASS (e_source_config_parent_class)->dispose (object);
637 }
638 
639 static void
source_config_finalize(GObject * object)640 source_config_finalize (GObject *object)
641 {
642 	ESourceConfigPrivate *priv;
643 
644 	priv = E_SOURCE_CONFIG_GET_PRIVATE (object);
645 
646 	g_hash_table_destroy (priv->backends);
647 	g_ptr_array_free (priv->candidates, TRUE);
648 
649 	/* Chain up to parent's finalize() method. */
650 	G_OBJECT_CLASS (e_source_config_parent_class)->finalize (object);
651 }
652 
653 static void
source_config_constructed(GObject * object)654 source_config_constructed (GObject *object)
655 {
656 	ESourceConfig *config;
657 	ESourceRegistry *registry;
658 	ESource *original_source;
659 	ESource *collection_source = NULL;
660 	gboolean is_webdav_collection = FALSE, allow_sources_rename = FALSE;
661 
662 	/* Chain up to parent's method. */
663 	G_OBJECT_CLASS (e_source_config_parent_class)->constructed (object);
664 
665 	config = E_SOURCE_CONFIG (object);
666 	registry = e_source_config_get_registry (config);
667 	original_source = e_source_config_get_original_source (config);
668 
669 	/* If we have an original source, see if it's part
670 	 * of a collection and note the collection source. */
671 	if (original_source != NULL) {
672 		const gchar *extension_name;
673 
674 		extension_name = E_SOURCE_EXTENSION_COLLECTION;
675 		collection_source = e_source_registry_find_extension (
676 			registry, original_source, extension_name);
677 		config->priv->collection_source = collection_source;
678 
679 		if (collection_source) {
680 			ESourceCollection *collection_extension;
681 
682 			collection_extension = e_source_get_extension (collection_source, extension_name);
683 			is_webdav_collection = g_strcmp0 (e_source_backend_get_backend_name (E_SOURCE_BACKEND (collection_extension)), "webdav") == 0;
684 			allow_sources_rename = e_source_collection_get_allow_sources_rename (collection_extension);
685 		}
686 	}
687 
688 	if (original_source != NULL)
689 		e_source_config_insert_widget (
690 			config, NULL, _("Type:"),
691 			config->priv->type_label);
692 	else
693 		e_source_config_insert_widget (
694 			config, NULL, _("Type:"),
695 			config->priv->type_combo);
696 
697 	/* If the original source is part of a collection then we assume
698 	 * the display name is server-assigned and not user-assigned, at
699 	 * least not assigned through Evolution.
700 	 * Let the WebDAV collection change the display name, because it
701 	 * can synchronize it when the user didn't change it. */
702 	if (collection_source != NULL && !is_webdav_collection && !allow_sources_rename)
703 		e_source_config_insert_widget (
704 			config, NULL, _("Name:"),
705 			config->priv->name_label);
706 	else
707 		e_source_config_insert_widget (
708 			config, NULL, _("Name:"),
709 			config->priv->name_entry);
710 
711 	source_config_init_backends (config);
712 }
713 
714 static void
source_config_realize(GtkWidget * widget)715 source_config_realize (GtkWidget *widget)
716 {
717 	ESourceConfig *config;
718 	ESource *original_source;
719 
720 	/* Chain up to parent's realize() method. */
721 	GTK_WIDGET_CLASS (e_source_config_parent_class)->realize (widget);
722 
723 	/* Do this after constructed() so subclasses can fully
724 	 * initialize themselves before we add candidates. */
725 
726 	config = E_SOURCE_CONFIG (widget);
727 	original_source = e_source_config_get_original_source (config);
728 
729 	if (original_source == NULL)
730 		source_config_init_for_adding_source (config);
731 	else
732 		source_config_init_for_editing_source (config);
733 
734 	/* Connect this signal AFTER we're done adding candidates
735 	 * so we don't trigger check_complete() before candidates
736 	 * have a chance to insert widgets. */
737 	g_signal_connect (
738 		config->priv->type_combo, "changed",
739 		G_CALLBACK (source_config_type_combo_changed_cb), config);
740 
741 	/* Trigger the callback to make sure the right page
742 	 * is selected, window is an appropriate size, etc. */
743 	g_signal_emit_by_name (config->priv->type_combo, "changed");
744 }
745 
746 static GList *
source_config_list_eligible_collections(ESourceConfig * config)747 source_config_list_eligible_collections (ESourceConfig *config)
748 {
749 	ESourceRegistry *registry;
750 	GQueue trash = G_QUEUE_INIT;
751 	GList *list, *link;
752 	const gchar *extension_name;
753 
754 	extension_name = E_SOURCE_EXTENSION_COLLECTION;
755 	registry = e_source_config_get_registry (config);
756 
757 	list = e_source_registry_list_sources (registry, extension_name);
758 
759 	for (link = list; link != NULL; link = g_list_next (link)) {
760 		ESource *source = E_SOURCE (link->data);
761 		gboolean elligible;
762 
763 		elligible =
764 			e_source_get_enabled (source) &&
765 			e_source_get_remote_creatable (source);
766 
767 		if (!elligible)
768 			g_queue_push_tail (&trash, link);
769 	}
770 
771 	/* Remove ineligible collections from the list. */
772 	while ((link = g_queue_pop_head (&trash)) != NULL) {
773 		g_object_unref (link->data);
774 		list = g_list_delete_link (list, link);
775 	}
776 
777 	return list;
778 }
779 
780 static void
source_config_init_candidate(ESourceConfig * config,ESource * scratch_source)781 source_config_init_candidate (ESourceConfig *config,
782                               ESource *scratch_source)
783 {
784 	e_binding_bind_object_text_property (
785 		scratch_source, "display-name",
786 		config->priv->name_label, "label",
787 		G_BINDING_SYNC_CREATE);
788 
789 	e_binding_bind_object_text_property (
790 		scratch_source, "display-name",
791 		config->priv->name_entry, "text",
792 		G_BINDING_BIDIRECTIONAL |
793 		G_BINDING_SYNC_CREATE);
794 }
795 
796 static gboolean
source_config_check_complete(ESourceConfig * config,ESource * scratch_source)797 source_config_check_complete (ESourceConfig *config,
798                               ESource *scratch_source)
799 {
800 	GtkEntry *name_entry;
801 	GtkComboBox *type_combo;
802 	const gchar *text;
803 	gboolean correct;
804 
805 	/* Make sure the Type: combo box has a valid item. */
806 	type_combo = GTK_COMBO_BOX (config->priv->type_combo);
807 	if (gtk_combo_box_get_active (type_combo) < 0)
808 		return FALSE;
809 
810 	/* Make sure the Name: entry field is not empty. */
811 	name_entry = GTK_ENTRY (config->priv->name_entry);
812 	text = gtk_entry_get_text (name_entry);
813 	correct = text != NULL && *text != '\0';
814 
815 	e_util_set_entry_issue_hint (config->priv->name_entry, correct ? NULL : _("Name cannot be empty"));
816 
817 	return correct;
818 }
819 
820 static void
source_config_commit_changes(ESourceConfig * config,ESource * scratch_source)821 source_config_commit_changes (ESourceConfig *config,
822                               ESource *scratch_source)
823 {
824 	/* Placeholder so subclasses can safely chain up. */
825 }
826 
827 static void
source_config_resize_window(ESourceConfig * config)828 source_config_resize_window (ESourceConfig *config)
829 {
830 	GtkWidget *toplevel;
831 
832 	/* Expand or shrink our parent window vertically to accommodate
833 	 * the newly selected backend's options.  Some backends have tons
834 	 * of options, some have few.  This avoids the case where you
835 	 * select a backend with tons of options and then a backend with
836 	 * few options and wind up with lots of unused vertical space. */
837 
838 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (config));
839 
840 	if (GTK_IS_WINDOW (toplevel)) {
841 		GtkWindow *window = GTK_WINDOW (toplevel);
842 		GtkAllocation allocation;
843 
844 		gtk_widget_get_allocation (toplevel, &allocation);
845 		gtk_window_resize (window, allocation.width, 1);
846 	}
847 }
848 
849 static gboolean
source_config_check_complete_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer unused)850 source_config_check_complete_accumulator (GSignalInvocationHint *ihint,
851                                           GValue *return_accu,
852                                           const GValue *handler_return,
853                                           gpointer unused)
854 {
855 	gboolean v_boolean;
856 
857 	/* Abort emission if a handler returns FALSE. */
858 	v_boolean = g_value_get_boolean (handler_return);
859 	g_value_set_boolean (return_accu, v_boolean);
860 
861 	return v_boolean;
862 }
863 
864 static void
e_source_config_class_init(ESourceConfigClass * class)865 e_source_config_class_init (ESourceConfigClass *class)
866 {
867 	GObjectClass *object_class;
868 	GtkWidgetClass *widget_class;
869 
870 	g_type_class_add_private (class, sizeof (ESourceConfigPrivate));
871 
872 	object_class = G_OBJECT_CLASS (class);
873 	object_class->set_property = source_config_set_property;
874 	object_class->get_property = source_config_get_property;
875 	object_class->dispose = source_config_dispose;
876 	object_class->finalize = source_config_finalize;
877 	object_class->constructed = source_config_constructed;
878 
879 	widget_class = GTK_WIDGET_CLASS (class);
880 	widget_class->realize = source_config_realize;
881 
882 	class->list_eligible_collections =
883 		source_config_list_eligible_collections;
884 	class->init_candidate = source_config_init_candidate;
885 	class->check_complete = source_config_check_complete;
886 	class->commit_changes = source_config_commit_changes;
887 	class->resize_window = source_config_resize_window;
888 
889 	g_object_class_install_property (
890 		object_class,
891 		PROP_COLLECTION_SOURCE,
892 		g_param_spec_object (
893 			"collection-source",
894 			"Collection Source",
895 			"The collection ESource to which "
896 			"the ESource being edited belongs",
897 			E_TYPE_SOURCE,
898 			G_PARAM_READABLE |
899 			G_PARAM_STATIC_STRINGS));
900 
901 	g_object_class_install_property (
902 		object_class,
903 		PROP_COMPLETE,
904 		g_param_spec_boolean (
905 			"complete",
906 			"Complete",
907 			"Are the required fields complete?",
908 			FALSE,
909 			G_PARAM_READABLE |
910 			G_PARAM_STATIC_STRINGS));
911 
912 	g_object_class_install_property (
913 		object_class,
914 		PROP_ORIGINAL_SOURCE,
915 		g_param_spec_object (
916 			"original-source",
917 			"Original Source",
918 			"The original ESource",
919 			E_TYPE_SOURCE,
920 			G_PARAM_READWRITE |
921 			G_PARAM_CONSTRUCT_ONLY |
922 			G_PARAM_STATIC_STRINGS));
923 
924 	g_object_class_install_property (
925 		object_class,
926 		PROP_REGISTRY,
927 		g_param_spec_object (
928 			"registry",
929 			"Registry",
930 			"Registry of ESources",
931 			E_TYPE_SOURCE_REGISTRY,
932 			G_PARAM_READWRITE |
933 			G_PARAM_CONSTRUCT_ONLY |
934 			G_PARAM_STATIC_STRINGS));
935 
936 	signals[CHECK_COMPLETE] = g_signal_new (
937 		"check-complete",
938 		G_TYPE_FROM_CLASS (class),
939 		G_SIGNAL_RUN_LAST,
940 		G_STRUCT_OFFSET (ESourceConfigClass, check_complete),
941 		source_config_check_complete_accumulator, NULL,
942 		e_marshal_BOOLEAN__OBJECT,
943 		G_TYPE_BOOLEAN, 1,
944 		E_TYPE_SOURCE);
945 
946 	signals[COMMIT_CHANGES] = g_signal_new (
947 		"commit-changes",
948 		G_TYPE_FROM_CLASS (class),
949 		G_SIGNAL_RUN_LAST,
950 		G_STRUCT_OFFSET (ESourceConfigClass, commit_changes),
951 		NULL, NULL,
952 		g_cclosure_marshal_VOID__OBJECT,
953 		G_TYPE_NONE, 1,
954 		E_TYPE_SOURCE);
955 
956 	signals[INIT_CANDIDATE] = g_signal_new (
957 		"init-candidate",
958 		G_TYPE_FROM_CLASS (class),
959 		G_SIGNAL_RUN_LAST,
960 		G_STRUCT_OFFSET (ESourceConfigClass, init_candidate),
961 		NULL, NULL,
962 		g_cclosure_marshal_VOID__OBJECT,
963 		G_TYPE_NONE, 1,
964 		E_TYPE_SOURCE);
965 
966 	signals[RESIZE_WINDOW] = g_signal_new (
967 		"resize-window",
968 		G_TYPE_FROM_CLASS (class),
969 		G_SIGNAL_RUN_LAST,
970 		G_STRUCT_OFFSET (ESourceConfigClass, resize_window),
971 		NULL, NULL,
972 		g_cclosure_marshal_VOID__VOID,
973 		G_TYPE_NONE, 0);
974 }
975 
976 static void
e_source_config_init(ESourceConfig * config)977 e_source_config_init (ESourceConfig *config)
978 {
979 	GPtrArray *candidates;
980 	GtkSizeGroup *size_group;
981 	PangoAttribute *attr;
982 	PangoAttrList *attr_list;
983 	GtkWidget *widget;
984 
985 	/* The candidates array holds scratch ESources, one for each
986 	 * item in the "type" combo box.  Scratch ESources are never
987 	 * added to the registry, so backend extensions can make any
988 	 * changes they want to them.  Whichever scratch ESource is
989 	 * "active" (selected in the "type" combo box) when the user
990 	 * clicks OK wins and is written to disk.  The others are
991 	 * discarded. */
992 	candidates = g_ptr_array_new_with_free_func (
993 		(GDestroyNotify) source_config_free_candidate);
994 
995 	/* The size group is used for caption labels. */
996 	size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
997 
998 	gtk_box_set_spacing (GTK_BOX (config), 6);
999 
1000 	gtk_orientable_set_orientation (
1001 		GTK_ORIENTABLE (config), GTK_ORIENTATION_VERTICAL);
1002 
1003 	config->priv = E_SOURCE_CONFIG_GET_PRIVATE (config);
1004 	config->priv->candidates = candidates;
1005 	config->priv->size_group = size_group;
1006 
1007 	attr_list = pango_attr_list_new ();
1008 
1009 	attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
1010 	pango_attr_list_insert (attr_list, attr);
1011 
1012 	/* Either the source type combo box or the label is shown,
1013 	 * never both.  But we create both widgets and keep them
1014 	 * both up-to-date because it makes the logic simpler. */
1015 
1016 	widget = gtk_label_new (NULL);
1017 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
1018 	gtk_label_set_attributes (GTK_LABEL (widget), attr_list);
1019 	config->priv->type_label = g_object_ref_sink (widget);
1020 	gtk_widget_show (widget);
1021 
1022 	widget = gtk_combo_box_text_new ();
1023 	config->priv->type_combo = g_object_ref_sink (widget);
1024 	gtk_widget_show (widget);
1025 
1026 	/* Similarly for the display name.  Either the text entry
1027 	 * or the label is shown, depending on whether the source
1028 	 * is a collection member (new sources never are). */
1029 
1030 	widget = gtk_label_new (NULL);
1031 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
1032 	gtk_label_set_attributes (GTK_LABEL (widget), attr_list);
1033 	config->priv->name_label = g_object_ref_sink (widget);
1034 	gtk_widget_show (widget);
1035 
1036 	widget = gtk_entry_new ();
1037 	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
1038 	config->priv->name_entry = g_object_ref_sink (widget);
1039 	gtk_widget_show (widget);
1040 
1041 	/* The backend box holds backend-specific options.  Each backend
1042 	 * gets a child widget.  Only one child widget is visible at once. */
1043 	widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
1044 	gtk_box_pack_end (GTK_BOX (config), widget, TRUE, TRUE, 0);
1045 	config->priv->backend_box = g_object_ref (widget);
1046 	gtk_widget_show (widget);
1047 
1048 	pango_attr_list_unref (attr_list);
1049 }
1050 
1051 GtkWidget *
e_source_config_new(ESourceRegistry * registry,ESource * original_source)1052 e_source_config_new (ESourceRegistry *registry,
1053                      ESource *original_source)
1054 {
1055 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
1056 
1057 	if (original_source != NULL)
1058 		g_return_val_if_fail (E_IS_SOURCE (original_source), NULL);
1059 
1060 	return g_object_new (
1061 		E_TYPE_SOURCE_CONFIG, "registry", registry,
1062 		"original-source", original_source, NULL);
1063 }
1064 
1065 void
e_source_config_set_preselect_type(ESourceConfig * config,const gchar * source_uid)1066 e_source_config_set_preselect_type (ESourceConfig *config,
1067 				    const gchar *source_uid)
1068 {
1069 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1070 
1071 	if (config->priv->preselect_type != source_uid) {
1072 		g_free (config->priv->preselect_type);
1073 		config->priv->preselect_type = g_strdup (source_uid);
1074 	}
1075 }
1076 
1077 const gchar *
e_source_config_get_preselect_type(ESourceConfig * config)1078 e_source_config_get_preselect_type (ESourceConfig *config)
1079 {
1080 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1081 
1082 	return config->priv->preselect_type;
1083 }
1084 
1085 void
e_source_config_insert_widget(ESourceConfig * config,ESource * scratch_source,const gchar * caption,GtkWidget * widget)1086 e_source_config_insert_widget (ESourceConfig *config,
1087                                ESource *scratch_source,
1088                                const gchar *caption,
1089                                GtkWidget *widget)
1090 {
1091 	GtkWidget *hbox;
1092 	GtkWidget *vbox;
1093 	GtkWidget *label;
1094 
1095 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1096 	g_return_if_fail (GTK_IS_WIDGET (widget));
1097 
1098 	if (scratch_source == NULL)
1099 		vbox = GTK_WIDGET (config);
1100 	else
1101 		vbox = e_source_config_get_page (config, scratch_source);
1102 
1103 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
1104 	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
1105 
1106 	e_binding_bind_property (
1107 		widget, "visible",
1108 		hbox, "visible",
1109 		G_BINDING_SYNC_CREATE);
1110 
1111 	label = gtk_label_new (caption);
1112 	gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
1113 	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
1114 	gtk_size_group_add_widget (config->priv->size_group, label);
1115 	gtk_widget_show (label);
1116 
1117 	gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
1118 }
1119 
1120 GtkWidget *
e_source_config_get_page(ESourceConfig * config,ESource * scratch_source)1121 e_source_config_get_page (ESourceConfig *config,
1122                           ESource *scratch_source)
1123 {
1124 	Candidate *candidate;
1125 	GtkWidget *page = NULL;
1126 	GPtrArray *array;
1127 	gint index;
1128 
1129 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1130 	g_return_val_if_fail (E_IS_SOURCE (scratch_source), NULL);
1131 
1132 	array = config->priv->candidates;
1133 
1134 	for (index = 0; page == NULL && index < array->len; index++) {
1135 		candidate = g_ptr_array_index (array, index);
1136 		if (e_source_equal (scratch_source, candidate->scratch_source))
1137 			page = candidate->page;
1138 	}
1139 
1140 	g_return_val_if_fail (GTK_IS_BOX (page), NULL);
1141 
1142 	return page;
1143 }
1144 
1145 void
e_source_config_select_page(ESourceConfig * config,ESource * scratch_source)1146 e_source_config_select_page (ESourceConfig *config,
1147 			     ESource *scratch_source)
1148 {
1149 	Candidate *candidate;
1150 	GPtrArray *array;
1151 	gint index;
1152 
1153 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1154 	g_return_if_fail (E_IS_SOURCE (scratch_source));
1155 
1156 	array = config->priv->candidates;
1157 
1158 	for (index = 0; index < array->len; index++) {
1159 		candidate = g_ptr_array_index (array, index);
1160 		if (e_source_equal (scratch_source, candidate->scratch_source)) {
1161 			gtk_combo_box_set_active (GTK_COMBO_BOX (config->priv->type_combo), index);
1162 			return;
1163 		}
1164 	}
1165 
1166 	g_warn_if_reached ();
1167 }
1168 
1169 const gchar *
e_source_config_get_backend_extension_name(ESourceConfig * config)1170 e_source_config_get_backend_extension_name (ESourceConfig *config)
1171 {
1172 	ESourceConfigClass *class;
1173 
1174 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1175 
1176 	class = E_SOURCE_CONFIG_GET_CLASS (config);
1177 	g_return_val_if_fail (class != NULL, NULL);
1178 	g_return_val_if_fail (class->get_backend_extension_name != NULL, NULL);
1179 
1180 	return class->get_backend_extension_name (config);
1181 }
1182 
1183 GList *
e_source_config_list_eligible_collections(ESourceConfig * config)1184 e_source_config_list_eligible_collections (ESourceConfig *config)
1185 {
1186 	ESourceConfigClass *class;
1187 
1188 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1189 
1190 	class = E_SOURCE_CONFIG_GET_CLASS (config);
1191 	g_return_val_if_fail (class != NULL, NULL);
1192 	g_return_val_if_fail (class->list_eligible_collections != NULL, NULL);
1193 
1194 	return class->list_eligible_collections (config);
1195 }
1196 
1197 gboolean
e_source_config_check_complete(ESourceConfig * config)1198 e_source_config_check_complete (ESourceConfig *config)
1199 {
1200 	Candidate *candidate;
1201 	gboolean complete;
1202 
1203 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), FALSE);
1204 
1205 	candidate = source_config_get_active_candidate (config);
1206 	g_return_val_if_fail (candidate != NULL, FALSE);
1207 
1208 	g_signal_emit (
1209 		config, signals[CHECK_COMPLETE], 0,
1210 		candidate->scratch_source, &complete);
1211 
1212 	complete &= e_source_config_backend_check_complete (
1213 		candidate->backend, candidate->scratch_source);
1214 
1215 	/* XXX Emitting "notify::complete" may cause this function
1216 	 *     to be called repeatedly by signal handlers.  The IF
1217 	 *     check below should break any recursive cycles.  Not
1218 	 *     very efficient but I think we can live with it. */
1219 
1220 	if (complete != config->priv->complete) {
1221 		config->priv->complete = complete;
1222 		g_object_notify (G_OBJECT (config), "complete");
1223 	}
1224 
1225 	return complete;
1226 }
1227 
1228 ESource *
e_source_config_get_original_source(ESourceConfig * config)1229 e_source_config_get_original_source (ESourceConfig *config)
1230 {
1231 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1232 
1233 	return config->priv->original_source;
1234 }
1235 
1236 ESource *
e_source_config_get_collection_source(ESourceConfig * config)1237 e_source_config_get_collection_source (ESourceConfig *config)
1238 {
1239 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1240 
1241 	return config->priv->collection_source;
1242 }
1243 
1244 GSList * /* (transfer full) (element-type #ESource) */
e_source_config_list_candidates(ESourceConfig * config)1245 e_source_config_list_candidates (ESourceConfig *config)
1246 {
1247 	GSList *candidates = NULL;
1248 	GPtrArray *array;
1249 	gint index;
1250 
1251 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1252 
1253 	array = config->priv->candidates;
1254 
1255 	for (index = 0; index < array->len; index++) {
1256 		Candidate *candidate;
1257 
1258 		candidate = g_ptr_array_index (array, index);
1259 		candidates = g_slist_prepend (candidates, g_object_ref (candidate->scratch_source));
1260 	}
1261 
1262 	return g_slist_reverse (candidates);
1263 }
1264 
1265 ESourceRegistry *
e_source_config_get_registry(ESourceConfig * config)1266 e_source_config_get_registry (ESourceConfig *config)
1267 {
1268 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1269 
1270 	return config->priv->registry;
1271 }
1272 
1273 void
e_source_config_resize_window(ESourceConfig * config)1274 e_source_config_resize_window (ESourceConfig *config)
1275 {
1276 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1277 
1278 	g_signal_emit (config, signals[RESIZE_WINDOW], 0);
1279 }
1280 
1281 /* Helper for e_source_config_commit() */
1282 static void
source_config_commit_cb(GObject * object,GAsyncResult * result,gpointer user_data)1283 source_config_commit_cb (GObject *object,
1284                          GAsyncResult *result,
1285                          gpointer user_data)
1286 {
1287 	GSimpleAsyncResult *simple;
1288 	GError *error = NULL;
1289 
1290 	simple = G_SIMPLE_ASYNC_RESULT (user_data);
1291 
1292 	e_source_registry_commit_source_finish (
1293 		E_SOURCE_REGISTRY (object), result, &error);
1294 
1295 	if (error != NULL)
1296 		g_simple_async_result_take_error (simple, error);
1297 
1298 	g_simple_async_result_complete (simple);
1299 	g_object_unref (simple);
1300 }
1301 
1302 void
e_source_config_commit(ESourceConfig * config,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1303 e_source_config_commit (ESourceConfig *config,
1304                         GCancellable *cancellable,
1305                         GAsyncReadyCallback callback,
1306                         gpointer user_data)
1307 {
1308 	GSimpleAsyncResult *simple;
1309 	ESourceRegistry *registry;
1310 	Candidate *candidate;
1311 
1312 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1313 
1314 	registry = e_source_config_get_registry (config);
1315 
1316 	candidate = source_config_get_active_candidate (config);
1317 	g_return_if_fail (candidate != NULL);
1318 
1319 	e_source_config_backend_commit_changes (
1320 		candidate->backend, candidate->scratch_source);
1321 
1322 	g_signal_emit (
1323 		config, signals[COMMIT_CHANGES], 0,
1324 		candidate->scratch_source);
1325 
1326 	simple = g_simple_async_result_new (
1327 		G_OBJECT (config), callback,
1328 		user_data, e_source_config_commit);
1329 
1330 	e_source_registry_commit_source (
1331 		registry, candidate->scratch_source,
1332 		cancellable, source_config_commit_cb, simple);
1333 }
1334 
1335 gboolean
e_source_config_commit_finish(ESourceConfig * config,GAsyncResult * result,GError ** error)1336 e_source_config_commit_finish (ESourceConfig *config,
1337                                GAsyncResult *result,
1338                                GError **error)
1339 {
1340 	GSimpleAsyncResult *simple;
1341 
1342 	g_return_val_if_fail (
1343 		g_simple_async_result_is_valid (
1344 		result, G_OBJECT (config),
1345 		e_source_config_commit), FALSE);
1346 
1347 	simple = G_SIMPLE_ASYNC_RESULT (result);
1348 
1349 	/* Assume success unless a GError is set. */
1350 	return !g_simple_async_result_propagate_error (simple, error);
1351 }
1352 
1353 void
e_source_config_add_refresh_interval(ESourceConfig * config,ESource * scratch_source)1354 e_source_config_add_refresh_interval (ESourceConfig *config,
1355                                       ESource *scratch_source)
1356 {
1357 	GtkWidget *widget;
1358 	GtkWidget *container;
1359 	ESourceExtension *extension;
1360 	const gchar *extension_name;
1361 
1362 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1363 	g_return_if_fail (E_IS_SOURCE (scratch_source));
1364 
1365 	extension_name = E_SOURCE_EXTENSION_REFRESH;
1366 	extension = e_source_get_extension (scratch_source, extension_name);
1367 
1368 	widget = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
1369 	e_source_config_insert_widget (config, scratch_source, NULL, widget);
1370 	gtk_widget_show (widget);
1371 
1372 	container = widget;
1373 
1374 	widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1375 	gtk_container_add (GTK_CONTAINER (container), widget);
1376 	gtk_widget_show (widget);
1377 
1378 	container = widget;
1379 
1380 	/* Translators: This is the first of a sequence of widgets:
1381 	 * "Refresh every [NUMERIC_ENTRY] [TIME_UNITS_COMBO]" */
1382 	widget = gtk_label_new (_("Refresh every"));
1383 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1384 	gtk_widget_show (widget);
1385 
1386 	widget = e_interval_chooser_new ();
1387 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
1388 	gtk_widget_show (widget);
1389 
1390 	e_binding_bind_property (
1391 		extension, "interval-minutes",
1392 		widget, "interval-minutes",
1393 		G_BINDING_BIDIRECTIONAL |
1394 		G_BINDING_SYNC_CREATE);
1395 }
1396 
1397 void
e_source_config_add_secure_connection(ESourceConfig * config,ESource * scratch_source)1398 e_source_config_add_secure_connection (ESourceConfig *config,
1399                                        ESource *scratch_source)
1400 {
1401 	GtkWidget *widget;
1402 	ESourceExtension *extension;
1403 	const gchar *extension_name;
1404 	const gchar *label;
1405 
1406 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1407 	g_return_if_fail (E_IS_SOURCE (scratch_source));
1408 
1409 	extension_name = E_SOURCE_EXTENSION_SECURITY;
1410 	extension = e_source_get_extension (scratch_source, extension_name);
1411 
1412 	label = _("Use a secure connection");
1413 	widget = gtk_check_button_new_with_label (label);
1414 	e_source_config_insert_widget (config, scratch_source, NULL, widget);
1415 	gtk_widget_show (widget);
1416 
1417 	e_binding_bind_property (
1418 		extension, "secure",
1419 		widget, "active",
1420 		G_BINDING_BIDIRECTIONAL |
1421 		G_BINDING_SYNC_CREATE);
1422 }
1423 
1424 static gboolean
secure_to_port_cb(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer user_data)1425 secure_to_port_cb (GBinding *binding,
1426                    const GValue *source_value,
1427                    GValue *target_value,
1428                    gpointer user_data)
1429 {
1430 	GObject *authentication_extension;
1431 	guint16 port;
1432 
1433 	authentication_extension = g_binding_get_target (binding);
1434 
1435 	port = e_source_authentication_get_port (
1436 		E_SOURCE_AUTHENTICATION (authentication_extension));
1437 
1438 	if (port == 80 || port == 443 || port == 0)
1439 		port = g_value_get_boolean (source_value) ? 443 : 80;
1440 
1441 	g_value_set_uint (target_value, port);
1442 
1443 	return TRUE;
1444 }
1445 
1446 static gboolean
webdav_source_ssl_trust_to_sensitive_cb(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer user_data)1447 webdav_source_ssl_trust_to_sensitive_cb (GBinding *binding,
1448                                          const GValue *source_value,
1449                                          GValue *target_value,
1450                                          gpointer user_data)
1451 {
1452 	const gchar *ssl_trust = g_value_get_string (source_value);
1453 
1454 	g_value_set_boolean (target_value, ssl_trust && *ssl_trust);
1455 
1456 	return TRUE;
1457 }
1458 
1459 static void
webdav_unset_ssl_trust_clicked_cb(GtkWidget * button,ESourceWebdav * extension)1460 webdav_unset_ssl_trust_clicked_cb (GtkWidget *button,
1461                                    ESourceWebdav *extension)
1462 {
1463 	e_source_webdav_set_ssl_trust (extension, NULL);
1464 }
1465 
1466 void
e_source_config_add_secure_connection_for_webdav(ESourceConfig * config,ESource * scratch_source)1467 e_source_config_add_secure_connection_for_webdav (ESourceConfig *config,
1468                                                   ESource *scratch_source)
1469 {
1470 	GtkWidget *widget;
1471 	ESourceExtension *extension;
1472 	ESourceAuthentication *authentication_extension;
1473 	ESource *collection_source;
1474 	const gchar *extension_name;
1475 	const gchar *label;
1476 
1477 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
1478 	g_return_if_fail (E_IS_SOURCE (scratch_source));
1479 
1480 	collection_source = e_source_config_get_collection_source (config);
1481 
1482 	if (!collection_source) {
1483 		extension_name = E_SOURCE_EXTENSION_SECURITY;
1484 		extension = e_source_get_extension (scratch_source, extension_name);
1485 
1486 		label = _("Use a secure connection");
1487 		widget = gtk_check_button_new_with_label (label);
1488 		e_source_config_insert_widget (config, scratch_source, NULL, widget);
1489 		gtk_widget_show (widget);
1490 
1491 		e_binding_bind_property (
1492 			extension, "secure",
1493 			widget, "active",
1494 			G_BINDING_BIDIRECTIONAL |
1495 			G_BINDING_SYNC_CREATE);
1496 
1497 		extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
1498 		authentication_extension =
1499 			e_source_get_extension (scratch_source, extension_name);
1500 
1501 		e_binding_bind_property_full (
1502 			extension, "secure",
1503 			authentication_extension, "port",
1504 			G_BINDING_DEFAULT,
1505 			secure_to_port_cb,
1506 			NULL, NULL, NULL);
1507 	}
1508 
1509 	extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
1510 	extension = e_source_get_extension (scratch_source, extension_name);
1511 
1512 	widget = gtk_button_new_with_mnemonic (
1513 		_("Unset _trust for SSL/TLS certificate"));
1514 	gtk_widget_set_margin_left (widget, collection_source ? 0 : 24);
1515 	e_source_config_insert_widget (config, scratch_source, NULL, widget);
1516 	gtk_widget_show (widget);
1517 
1518 	e_binding_bind_property_full (
1519 		extension, "ssl-trust",
1520 		widget, "sensitive",
1521 		G_BINDING_SYNC_CREATE,
1522 		webdav_source_ssl_trust_to_sensitive_cb,
1523 		NULL, NULL, NULL);
1524 
1525 	g_signal_connect (
1526 		widget, "clicked",
1527 		G_CALLBACK (webdav_unset_ssl_trust_clicked_cb), extension);
1528 }
1529 
1530 GtkWidget *
e_source_config_add_user_entry(ESourceConfig * config,ESource * scratch_source)1531 e_source_config_add_user_entry (ESourceConfig *config,
1532                                 ESource *scratch_source)
1533 {
1534 	GtkWidget *widget;
1535 	ESource *original_source;
1536 	ESourceExtension *extension;
1537 	const gchar *extension_name;
1538 
1539 	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
1540 	g_return_val_if_fail (E_IS_SOURCE (scratch_source), NULL);
1541 
1542 	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
1543 	extension = e_source_get_extension (scratch_source, extension_name);
1544 
1545 	original_source = e_source_config_get_original_source (config);
1546 
1547 	widget = gtk_entry_new ();
1548 	e_source_config_insert_widget (
1549 		config, scratch_source, _("User:"), widget);
1550 	gtk_widget_show (widget);
1551 
1552 	e_binding_bind_object_text_property (
1553 		extension, "user",
1554 		widget, "text",
1555 		G_BINDING_BIDIRECTIONAL |
1556 		G_BINDING_SYNC_CREATE);
1557 
1558 	/* If this is a new data source, initialize the
1559 	 * GtkEntry to the user name of the current user. */
1560 	if (original_source == NULL)
1561 		gtk_entry_set_text (GTK_ENTRY (widget), g_get_user_name ());
1562 
1563 	return widget;
1564 }
1565 
1566