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