Assamese translation updated
[platform/upstream/evolution-data-server.git] / libedataserverui / e-source-selector.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-source-selector.c
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * Author: Ettore Perazzoli <ettore@ximian.com>
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29
30 #include "e-cell-renderer-color.h"
31 #include "e-data-server-ui-marshal.h"
32 #include "e-source-selector.h"
33
34 #define E_SOURCE_SELECTOR_GET_PRIVATE(obj) \
35         (G_TYPE_INSTANCE_GET_PRIVATE \
36         ((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))
37
38 typedef struct _AsyncContext AsyncContext;
39
40 struct _ESourceSelectorPrivate {
41         ESourceRegistry *registry;
42         GHashTable *source_index;
43         gchar *extension_name;
44
45         GtkTreeRowReference *saved_primary_selection;
46
47         /* ESource -> GSource */
48         GHashTable *pending_writes;
49         GMainContext *main_context;
50
51         gboolean toggled_last;
52         gboolean select_new;
53         gboolean show_colors;
54         gboolean show_toggles;
55 };
56
57 struct _AsyncContext {
58         ESourceSelector *selector;
59         ESource *source;
60 };
61
62 enum {
63         PROP_0,
64         PROP_EXTENSION_NAME,
65         PROP_PRIMARY_SELECTION,
66         PROP_REGISTRY,
67         PROP_SHOW_COLORS,
68         PROP_SHOW_TOGGLES
69 };
70
71 enum {
72         SELECTION_CHANGED,
73         PRIMARY_SELECTION_CHANGED,
74         POPUP_EVENT,
75         DATA_DROPPED,
76         NUM_SIGNALS
77 };
78
79 enum {
80         COLUMN_NAME,
81         COLUMN_COLOR,
82         COLUMN_ACTIVE,
83         COLUMN_SHOW_COLOR,
84         COLUMN_SHOW_TOGGLE,
85         COLUMN_WEIGHT,
86         COLUMN_SOURCE,
87         NUM_COLUMNS
88 };
89
90 static guint signals[NUM_SIGNALS];
91
92 G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)
93
94 /* ESafeToggleRenderer does not emit 'toggled' signal
95  * on 'activate' when mouse is not over the toggle. */
96
97 typedef GtkCellRendererToggle ECellRendererSafeToggle;
98 typedef GtkCellRendererToggleClass ECellRendererSafeToggleClass;
99
100 /* Forward Declarations */
101 GType e_cell_renderer_safe_toggle_get_type (void);
102
103 G_DEFINE_TYPE (
104         ECellRendererSafeToggle,
105         e_cell_renderer_safe_toggle,
106         GTK_TYPE_CELL_RENDERER_TOGGLE)
107
108 static gboolean
109 safe_toggle_activate (GtkCellRenderer *cell,
110                       GdkEvent *event,
111                       GtkWidget *widget,
112                       const gchar *path,
113                       const GdkRectangle *background_area,
114                       const GdkRectangle *cell_area,
115                       GtkCellRendererState flags)
116 {
117         gboolean point_in_cell_area = TRUE;
118
119         if (event->type == GDK_BUTTON_PRESS && cell_area != NULL) {
120                 cairo_region_t *region;
121
122                 region = cairo_region_create_rectangle (cell_area);
123                 point_in_cell_area = cairo_region_contains_point (
124                         region, event->button.x, event->button.y);
125                 cairo_region_destroy (region);
126         }
127
128         if (!point_in_cell_area)
129                 return FALSE;
130
131         return GTK_CELL_RENDERER_CLASS (
132                 e_cell_renderer_safe_toggle_parent_class)->activate (
133                 cell, event, widget, path, background_area, cell_area, flags);
134 }
135
136 static void
137 e_cell_renderer_safe_toggle_class_init (ECellRendererSafeToggleClass *class)
138 {
139         GtkCellRendererClass *cell_renderer_class;
140
141         cell_renderer_class = GTK_CELL_RENDERER_CLASS (class);
142         cell_renderer_class->activate = safe_toggle_activate;
143 }
144
145 static void
146 e_cell_renderer_safe_toggle_init (ECellRendererSafeToggle *obj)
147 {
148 }
149
150 static GtkCellRenderer *
151 e_cell_renderer_safe_toggle_new (void)
152 {
153         return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
154 }
155
156 static void
157 clear_saved_primary_selection (ESourceSelector *selector)
158 {
159         gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
160         selector->priv->saved_primary_selection = NULL;
161 }
162
163 static void
164 async_context_free (AsyncContext *async_context)
165 {
166         if (async_context->selector != NULL)
167                 g_object_unref (async_context->selector);
168
169         if (async_context->source != NULL)
170                 g_object_unref (async_context->source);
171
172         g_slice_free (AsyncContext, async_context);
173 }
174
175 static void
176 pending_writes_destroy_source (GSource *source)
177 {
178         g_source_destroy (source);
179         g_source_unref (source);
180 }
181
182 static void
183 source_selector_write_done_cb (GObject *source_object,
184                                GAsyncResult *result,
185                                gpointer user_data)
186 {
187         ESource *source;
188         ESourceSelector *selector;
189         GError *error = NULL;
190
191         source = E_SOURCE (source_object);
192         selector = E_SOURCE_SELECTOR (user_data);
193
194         e_source_write_finish (source, result, &error);
195
196         /* FIXME Display the error in the selector somehow? */
197         if (error != NULL) {
198                 g_warning ("%s: %s", G_STRFUNC, error->message);
199                 g_error_free (error);
200         }
201
202         g_object_unref (selector);
203 }
204
205 static gboolean
206 source_selector_write_idle_cb (gpointer user_data)
207 {
208         AsyncContext *async_context = user_data;
209         GHashTable *pending_writes;
210
211         /* XXX This operation is not cancellable. */
212         e_source_write (
213                 async_context->source, NULL,
214                 source_selector_write_done_cb,
215                 g_object_ref (async_context->selector));
216
217         pending_writes = async_context->selector->priv->pending_writes;
218         g_hash_table_remove (pending_writes, async_context->source);
219
220         return FALSE;
221 }
222
223 static void
224 source_selector_cancel_write (ESourceSelector *selector,
225                               ESource *source)
226 {
227         GHashTable *pending_writes;
228
229         /* Cancel any pending writes for this ESource so as not
230          * to overwrite whatever change we're being notified of. */
231         pending_writes = selector->priv->pending_writes;
232         g_hash_table_remove (pending_writes, source);
233 }
234
235 static void
236 source_selector_update_row (ESourceSelector *selector,
237                             ESource *source)
238 {
239         GHashTable *source_index;
240         ESourceExtension *extension = NULL;
241         GtkTreeRowReference *reference;
242         GtkTreeModel *model;
243         GtkTreePath *path;
244         GtkTreeIter iter;
245         const gchar *extension_name;
246         const gchar *display_name;
247         gboolean selected;
248
249         source_index = selector->priv->source_index;
250         reference = g_hash_table_lookup (source_index, source);
251
252         /* This function runs when ANY ESource in the registry changes.
253          * If the ESource is not in our tree model then return silently. */
254         if (reference == NULL)
255                 return;
256
257         /* If we do have a row reference, it should be valid. */
258         g_return_if_fail (gtk_tree_row_reference_valid (reference));
259
260         model = gtk_tree_row_reference_get_model (reference);
261         path = gtk_tree_row_reference_get_path (reference);
262         gtk_tree_model_get_iter (model, &iter, path);
263         gtk_tree_path_free (path);
264
265         display_name = e_source_get_display_name (source);
266
267         extension_name = e_source_selector_get_extension_name (selector);
268         selected = e_source_selector_source_is_selected (selector, source);
269
270         if (e_source_has_extension (source, extension_name))
271                 extension = e_source_get_extension (source, extension_name);
272
273         if (extension != NULL) {
274                 GdkColor color;
275                 const gchar *color_spec = NULL;
276                 gboolean show_color = FALSE;
277                 gboolean show_toggle;
278
279                 show_color =
280                         E_IS_SOURCE_SELECTABLE (extension) &&
281                         e_source_selector_get_show_colors (selector);
282
283                 if (show_color)
284                         color_spec = e_source_selectable_get_color (
285                                 E_SOURCE_SELECTABLE (extension));
286
287                 if (color_spec != NULL && *color_spec != '\0')
288                         show_color = gdk_color_parse (color_spec, &color);
289
290                 show_toggle = e_source_selector_get_show_toggles (selector);
291
292                 gtk_tree_store_set (
293                         GTK_TREE_STORE (model), &iter,
294                         COLUMN_NAME, display_name,
295                         COLUMN_COLOR, show_color ? &color : NULL,
296                         COLUMN_ACTIVE, selected,
297                         COLUMN_SHOW_COLOR, show_color,
298                         COLUMN_SHOW_TOGGLE, show_toggle,
299                         COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
300                         COLUMN_SOURCE, source,
301                         -1);
302         } else {
303                 gtk_tree_store_set (
304                         GTK_TREE_STORE (model), &iter,
305                         COLUMN_NAME, display_name,
306                         COLUMN_COLOR, NULL,
307                         COLUMN_ACTIVE, FALSE,
308                         COLUMN_SHOW_COLOR, FALSE,
309                         COLUMN_SHOW_TOGGLE, FALSE,
310                         COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
311                         COLUMN_SOURCE, source,
312                         -1);
313         }
314 }
315
316 static gboolean
317 source_selector_traverse (GNode *node,
318                           ESourceSelector *selector)
319 {
320         ESource *source;
321         GHashTable *source_index;
322         GtkTreeRowReference *reference = NULL;
323         GtkTreeModel *model;
324         GtkTreePath *path;
325         GtkTreeIter iter;
326
327         /* Skip the root node. */
328         if (G_NODE_IS_ROOT (node))
329                 return FALSE;
330
331         source_index = selector->priv->source_index;
332
333         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
334
335         if (node->parent != NULL && node->parent->data != NULL)
336                 reference = g_hash_table_lookup (
337                         source_index, node->parent->data);
338
339         if (gtk_tree_row_reference_valid (reference)) {
340                 GtkTreeIter parent;
341
342                 path = gtk_tree_row_reference_get_path (reference);
343                 gtk_tree_model_get_iter (model, &parent, path);
344                 gtk_tree_path_free (path);
345
346                 gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
347         } else
348                 gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
349
350         source = E_SOURCE (node->data);
351
352         path = gtk_tree_model_get_path (model, &iter);
353         reference = gtk_tree_row_reference_new (model, path);
354         g_hash_table_insert (source_index, g_object_ref (source), reference);
355         gtk_tree_path_free (path);
356
357         source_selector_update_row (selector, source);
358
359         return FALSE;
360 }
361
362 static void
363 source_selector_save_expanded (GtkTreeView *tree_view,
364                                GtkTreePath *path,
365                                GQueue *queue)
366 {
367         GtkTreeModel *model;
368         GtkTreeIter iter;
369         ESource *source;
370
371         model = gtk_tree_view_get_model (tree_view);
372         gtk_tree_model_get_iter (model, &iter, path);
373         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
374         g_queue_push_tail (queue, source);
375 }
376
377 static void
378 source_selector_build_model (ESourceSelector *selector)
379 {
380         ESourceRegistry *registry;
381         GQueue queue = G_QUEUE_INIT;
382         GHashTable *source_index;
383         GtkTreeView *tree_view;
384         GtkTreeModel *model;
385         ESource *selected;
386         const gchar *extension_name;
387         GNode *root;
388
389         tree_view = GTK_TREE_VIEW (selector);
390
391         registry = e_source_selector_get_registry (selector);
392         extension_name = e_source_selector_get_extension_name (selector);
393
394         /* Make sure we have what we need to build the model, since
395          * this can get called early in the initialization phase. */
396         if (registry == NULL || extension_name == NULL)
397                 return;
398
399         source_index = selector->priv->source_index;
400         selected = e_source_selector_ref_primary_selection (selector);
401
402         /* Save expanded sources to restore later. */
403         gtk_tree_view_map_expanded_rows (
404                 tree_view, (GtkTreeViewMappingFunc)
405                 source_selector_save_expanded, &queue);
406
407         model = gtk_tree_view_get_model (tree_view);
408         gtk_tree_store_clear (GTK_TREE_STORE (model));
409
410         g_hash_table_remove_all (source_index);
411
412         root = e_source_registry_build_display_tree (registry, extension_name);
413
414         g_node_traverse (
415                 root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
416                 (GNodeTraverseFunc) source_selector_traverse,
417                 selector);
418
419         e_source_registry_free_display_tree (root);
420
421         /* Restore previously expanded sources. */
422         while (!g_queue_is_empty (&queue)) {
423                 GtkTreeRowReference *reference;
424                 ESource *source;
425
426                 source = g_queue_pop_head (&queue);
427                 reference = g_hash_table_lookup (source_index, source);
428
429                 if (gtk_tree_row_reference_valid (reference)) {
430                         GtkTreePath *path;
431
432                         path = gtk_tree_row_reference_get_path (reference);
433                         gtk_tree_view_expand_to_path (tree_view, path);
434                         gtk_tree_path_free (path);
435                 }
436
437                 g_object_unref (source);
438         }
439
440         /* Restore the primary selection. */
441         if (selected != NULL) {
442                 e_source_selector_set_primary_selection (selector, selected);
443                 g_object_unref (selected);
444         }
445
446         /* Make sure we have a primary selection.  If not, pick one. */
447         selected = e_source_selector_ref_primary_selection (selector);
448         if (selected == NULL) {
449                 selected = e_source_registry_ref_default_for_extension_name (
450                         registry, extension_name);
451                 e_source_selector_set_primary_selection (selector, selected);
452         }
453         g_object_unref (selected);
454 }
455
456 static void
457 source_selector_expand_to_source (ESourceSelector *selector,
458                                   ESource *source)
459 {
460         GHashTable *source_index;
461         GtkTreeRowReference *reference;
462         GtkTreePath *path;
463
464         source_index = selector->priv->source_index;
465         reference = g_hash_table_lookup (source_index, source);
466
467         /* If the ESource is not in our tree model then return silently. */
468         if (reference == NULL)
469                 return;
470
471         /* If we do have a row reference, it should be valid. */
472         g_return_if_fail (gtk_tree_row_reference_valid (reference));
473
474         /* Expand the tree view to the path containing the ESource */
475         path = gtk_tree_row_reference_get_path (reference);
476         gtk_tree_view_expand_to_path (GTK_TREE_VIEW (selector), path);
477         gtk_tree_path_free (path);
478 }
479
480 static void
481 source_selector_source_added_cb (ESourceRegistry *registry,
482                                  ESource *source,
483                                  ESourceSelector *selector)
484 {
485         source_selector_build_model (selector);
486
487         source_selector_expand_to_source (selector, source);
488 }
489
490 static void
491 source_selector_source_changed_cb (ESourceRegistry *registry,
492                                    ESource *source,
493                                    ESourceSelector *selector)
494 {
495         source_selector_cancel_write (selector, source);
496
497         source_selector_update_row (selector, source);
498 }
499
500 static void
501 source_selector_source_removed_cb (ESourceRegistry *registry,
502                                    ESource *source,
503                                    ESourceSelector *selector)
504 {
505         source_selector_build_model (selector);
506 }
507
508 static void
509 source_selector_source_enabled_cb (ESourceRegistry *registry,
510                                    ESource *source,
511                                    ESourceSelector *selector)
512 {
513         source_selector_build_model (selector);
514
515         source_selector_expand_to_source (selector, source);
516 }
517
518 static void
519 source_selector_source_disabled_cb (ESourceRegistry *registry,
520                                     ESource *source,
521                                     ESourceSelector *selector)
522 {
523         source_selector_build_model (selector);
524 }
525
526 static gboolean
527 same_source_name_exists (ESourceSelector *selector,
528                          const gchar *display_name)
529 {
530         GHashTable *source_index;
531         GHashTableIter iter;
532         gpointer key;
533
534         source_index = selector->priv->source_index;
535         g_hash_table_iter_init (&iter, source_index);
536
537         while (g_hash_table_iter_next (&iter, &key, NULL)) {
538                 ESource *source = E_SOURCE (key);
539                 const gchar *source_name;
540
541                 source_name = e_source_get_display_name (source);
542                 if (g_strcmp0 (display_name, source_name) == 0)
543                         return TRUE;
544         }
545
546         return FALSE;
547 }
548
549 static gboolean
550 selection_func (GtkTreeSelection *selection,
551                 GtkTreeModel *model,
552                 GtkTreePath *path,
553                 gboolean path_currently_selected,
554                 ESourceSelector *selector)
555 {
556         ESource *source;
557         GtkTreeIter iter;
558         const gchar *extension_name;
559
560         if (selector->priv->toggled_last) {
561                 selector->priv->toggled_last = FALSE;
562                 return FALSE;
563         }
564
565         if (path_currently_selected)
566                 return TRUE;
567
568         if (!gtk_tree_model_get_iter (model, &iter, path))
569                 return FALSE;
570
571         extension_name = e_source_selector_get_extension_name (selector);
572         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
573
574         if (!e_source_has_extension (source, extension_name)) {
575                 g_object_unref (source);
576                 return FALSE;
577         }
578
579         clear_saved_primary_selection (selector);
580
581         g_object_unref (source);
582
583         return TRUE;
584 }
585
586 static void
587 text_cell_edited_cb (ESourceSelector *selector,
588                      const gchar *path_string,
589                      const gchar *new_name)
590 {
591         GtkTreeView *tree_view;
592         GtkTreeModel *model;
593         GtkTreePath *path;
594         GtkTreeIter iter;
595         ESource *source;
596
597         tree_view = GTK_TREE_VIEW (selector);
598         model = gtk_tree_view_get_model (tree_view);
599         path = gtk_tree_path_new_from_string (path_string);
600
601         gtk_tree_model_get_iter (model, &iter, path);
602         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
603         gtk_tree_path_free (path);
604
605         if (new_name == NULL || *new_name == '\0')
606                 return;
607
608         if (same_source_name_exists (selector, new_name))
609                 return;
610
611         e_source_set_display_name (source, new_name);
612
613         e_source_selector_queue_write (selector, source);
614 }
615
616 static void
617 cell_toggled_callback (GtkCellRendererToggle *renderer,
618                        const gchar *path_string,
619                        ESourceSelector *selector)
620 {
621         ESource *source;
622         GtkTreeModel *model;
623         GtkTreePath *path;
624         GtkTreeIter iter;
625
626         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
627         path = gtk_tree_path_new_from_string (path_string);
628
629         if (!gtk_tree_model_get_iter (model, &iter, path)) {
630                 gtk_tree_path_free (path);
631                 return;
632         }
633
634         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
635
636         if (e_source_selector_source_is_selected (selector, source))
637                 e_source_selector_unselect_source (selector, source);
638         else
639                 e_source_selector_select_source (selector, source);
640
641         selector->priv->toggled_last = TRUE;
642
643         gtk_tree_path_free (path);
644
645         g_object_unref (source);
646 }
647
648 static void
649 selection_changed_callback (GtkTreeSelection *selection,
650                             ESourceSelector *selector)
651 {
652         g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
653         g_object_notify (G_OBJECT (selector), "primary-selection");
654 }
655
656 static void
657 source_selector_set_extension_name (ESourceSelector *selector,
658                                     const gchar *extension_name)
659 {
660         g_return_if_fail (extension_name != NULL);
661         g_return_if_fail (selector->priv->extension_name == NULL);
662
663         selector->priv->extension_name = g_strdup (extension_name);
664 }
665
666 static void
667 source_selector_set_registry (ESourceSelector *selector,
668                               ESourceRegistry *registry)
669 {
670         g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
671         g_return_if_fail (selector->priv->registry == NULL);
672
673         selector->priv->registry = g_object_ref (registry);
674 }
675
676 static void
677 source_selector_set_property (GObject *object,
678                               guint property_id,
679                               const GValue *value,
680                               GParamSpec *pspec)
681 {
682         switch (property_id) {
683                 case PROP_EXTENSION_NAME:
684                         source_selector_set_extension_name (
685                                 E_SOURCE_SELECTOR (object),
686                                 g_value_get_string (value));
687                         return;
688
689                 case PROP_PRIMARY_SELECTION:
690                         e_source_selector_set_primary_selection (
691                                 E_SOURCE_SELECTOR (object),
692                                 g_value_get_object (value));
693                         return;
694
695                 case PROP_REGISTRY:
696                         source_selector_set_registry (
697                                 E_SOURCE_SELECTOR (object),
698                                 g_value_get_object (value));
699                         return;
700
701                 case PROP_SHOW_COLORS:
702                         e_source_selector_set_show_colors (
703                                 E_SOURCE_SELECTOR (object),
704                                 g_value_get_boolean (value));
705                         return;
706
707                 case PROP_SHOW_TOGGLES:
708                         e_source_selector_set_show_toggles (
709                                 E_SOURCE_SELECTOR (object),
710                                 g_value_get_boolean (value));
711                         return;
712         }
713
714         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
715 }
716
717 static void
718 source_selector_get_property (GObject *object,
719                               guint property_id,
720                               GValue *value,
721                               GParamSpec *pspec)
722 {
723         switch (property_id) {
724                 case PROP_EXTENSION_NAME:
725                         g_value_set_string (
726                                 value,
727                                 e_source_selector_get_extension_name (
728                                 E_SOURCE_SELECTOR (object)));
729                         return;
730
731                 case PROP_PRIMARY_SELECTION:
732                         g_value_take_object (
733                                 value,
734                                 e_source_selector_ref_primary_selection (
735                                 E_SOURCE_SELECTOR (object)));
736                         return;
737
738                 case PROP_REGISTRY:
739                         g_value_set_object (
740                                 value,
741                                 e_source_selector_get_registry (
742                                 E_SOURCE_SELECTOR (object)));
743                         return;
744
745                 case PROP_SHOW_COLORS:
746                         g_value_set_boolean (
747                                 value,
748                                 e_source_selector_get_show_colors (
749                                 E_SOURCE_SELECTOR (object)));
750                         return;
751
752                 case PROP_SHOW_TOGGLES:
753                         g_value_set_boolean (
754                                 value,
755                                 e_source_selector_get_show_toggles (
756                                 E_SOURCE_SELECTOR (object)));
757                         return;
758         }
759
760         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
761 }
762
763 static void
764 source_selector_dispose (GObject *object)
765 {
766         ESourceSelectorPrivate *priv;
767
768         priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
769
770         if (priv->registry != NULL) {
771                 g_signal_handlers_disconnect_matched (
772                         priv->registry,
773                         G_SIGNAL_MATCH_DATA,
774                         0, 0, NULL, NULL, object);
775                 g_object_unref (priv->registry);
776                 priv->registry = NULL;
777         }
778
779         g_hash_table_remove_all (priv->source_index);
780         g_hash_table_remove_all (priv->pending_writes);
781
782         clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
783
784         /* Chain up to parent's dispose() method. */
785         G_OBJECT_CLASS (e_source_selector_parent_class)->dispose (object);
786 }
787
788 static void
789 source_selector_finalize (GObject *object)
790 {
791         ESourceSelectorPrivate *priv;
792
793         priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
794
795         g_hash_table_destroy (priv->source_index);
796         g_hash_table_destroy (priv->pending_writes);
797
798         g_free (priv->extension_name);
799
800         if (priv->main_context != NULL)
801                 g_main_context_unref (priv->main_context);
802
803         /* Chain up to parent's finalize() method. */
804         G_OBJECT_CLASS (e_source_selector_parent_class)->finalize (object);
805 }
806
807 static void
808 source_selector_constructed (GObject *object)
809 {
810         ESourceRegistry *registry;
811         ESourceSelector *selector;
812
813         selector = E_SOURCE_SELECTOR (object);
814         registry = e_source_selector_get_registry (selector);
815
816         g_signal_connect (
817                 registry, "source-added",
818                 G_CALLBACK (source_selector_source_added_cb), selector);
819
820         g_signal_connect (
821                 registry, "source-changed",
822                 G_CALLBACK (source_selector_source_changed_cb), selector);
823
824         g_signal_connect (
825                 registry, "source-removed",
826                 G_CALLBACK (source_selector_source_removed_cb), selector);
827
828         g_signal_connect (
829                 registry, "source-enabled",
830                 G_CALLBACK (source_selector_source_enabled_cb), selector);
831
832         g_signal_connect (
833                 registry, "source-disabled",
834                 G_CALLBACK (source_selector_source_disabled_cb), selector);
835
836         source_selector_build_model (selector);
837
838         gtk_tree_view_expand_all (GTK_TREE_VIEW (selector));
839 }
840
841 static gboolean
842 source_selector_button_press_event (GtkWidget *widget,
843                                     GdkEventButton *event)
844 {
845         ESourceSelector *selector;
846         GtkWidgetClass *widget_class;
847         GtkTreePath *path;
848         ESource *source = NULL;
849         ESource *primary;
850         gboolean right_click = FALSE;
851         gboolean triple_click = FALSE;
852         gboolean row_exists;
853         gboolean res = FALSE;
854
855         selector = E_SOURCE_SELECTOR (widget);
856
857         selector->priv->toggled_last = FALSE;
858
859         /* Triple-clicking a source selects it exclusively. */
860
861         if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
862                 right_click = TRUE;
863         else if (event->button == 1 && event->type == GDK_3BUTTON_PRESS)
864                 triple_click = TRUE;
865         else
866                 goto chainup;
867
868         row_exists = gtk_tree_view_get_path_at_pos (
869                 GTK_TREE_VIEW (widget), event->x, event->y,
870                 &path, NULL, NULL, NULL);
871
872         /* Get the source/group */
873         if (row_exists) {
874                 GtkTreeModel *model;
875                 GtkTreeIter iter;
876
877                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
878
879                 gtk_tree_model_get_iter (model, &iter, path);
880                 gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
881         }
882
883         if (source == NULL)
884                 goto chainup;
885
886         primary = e_source_selector_ref_primary_selection (selector);
887         if (source != primary)
888                 e_source_selector_set_primary_selection (selector, source);
889         if (primary != NULL)
890                 g_object_unref (primary);
891
892         if (right_click)
893                 g_signal_emit (
894                         widget, signals[POPUP_EVENT], 0, source, event, &res);
895
896         if (triple_click) {
897                 e_source_selector_select_exclusive (selector, source);
898                 res = TRUE;
899         }
900
901         g_object_unref (source);
902
903         return res;
904
905 chainup:
906
907         /* Chain up to parent's button_press_event() method. */
908         widget_class = GTK_WIDGET_CLASS (e_source_selector_parent_class);
909         return widget_class->button_press_event (widget, event);
910 }
911
912 static void
913 source_selector_drag_leave (GtkWidget *widget,
914                             GdkDragContext *context,
915                             guint time_)
916 {
917         GtkTreeView *tree_view;
918         GtkTreeViewDropPosition pos;
919
920         tree_view = GTK_TREE_VIEW (widget);
921         pos = GTK_TREE_VIEW_DROP_BEFORE;
922
923         gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos);
924 }
925
926 static gboolean
927 source_selector_drag_motion (GtkWidget *widget,
928                              GdkDragContext *context,
929                              gint x,
930                              gint y,
931                              guint time_)
932 {
933         ESource *source = NULL;
934         GtkTreeView *tree_view;
935         GtkTreeModel *model;
936         GtkTreePath *path = NULL;
937         GtkTreeIter iter;
938         GtkTreeViewDropPosition pos;
939         GdkDragAction action = 0;
940
941         tree_view = GTK_TREE_VIEW (widget);
942         model = gtk_tree_view_get_model (tree_view);
943
944         if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
945                 goto exit;
946
947         if (!gtk_tree_model_get_iter (model, &iter, path))
948                 goto exit;
949
950         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
951
952         if (!e_source_get_writable (source))
953                 goto exit;
954
955         pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
956         gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
957
958         if (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)
959                 action = GDK_ACTION_MOVE;
960         else
961                 action = gdk_drag_context_get_suggested_action (context);
962
963 exit:
964         if (path != NULL)
965                 gtk_tree_path_free (path);
966
967         if (source != NULL)
968                 g_object_unref (source);
969
970         gdk_drag_status (context, action, time_);
971
972         return TRUE;
973 }
974
975 static gboolean
976 source_selector_drag_drop (GtkWidget *widget,
977                            GdkDragContext *context,
978                            gint x,
979                            gint y,
980                            guint time_)
981 {
982         ESource *source;
983         ESourceSelector *selector;
984         GtkTreeView *tree_view;
985         GtkTreeModel *model;
986         GtkTreePath *path;
987         GtkTreeIter iter;
988         const gchar *extension_name;
989         gboolean drop_zone;
990         gboolean valid;
991
992         tree_view = GTK_TREE_VIEW (widget);
993         model = gtk_tree_view_get_model (tree_view);
994
995         if (!gtk_tree_view_get_path_at_pos (
996                 tree_view, x, y, &path, NULL, NULL, NULL))
997                 return FALSE;
998
999         valid = gtk_tree_model_get_iter (model, &iter, path);
1000         gtk_tree_path_free (path);
1001         g_return_val_if_fail (valid, FALSE);
1002
1003         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1004
1005         selector = E_SOURCE_SELECTOR (widget);
1006         extension_name = e_source_selector_get_extension_name (selector);
1007         drop_zone = e_source_has_extension (source, extension_name);
1008
1009         g_object_unref (source);
1010
1011         return drop_zone;
1012 }
1013
1014 static void
1015 source_selector_drag_data_received (GtkWidget *widget,
1016                                     GdkDragContext *context,
1017                                     gint x,
1018                                     gint y,
1019                                     GtkSelectionData *selection_data,
1020                                     guint info,
1021                                     guint time_)
1022 {
1023         ESource *source = NULL;
1024         GtkTreeView *tree_view;
1025         GtkTreeModel *model;
1026         GtkTreePath *path = NULL;
1027         GtkTreeIter iter;
1028         GdkDragAction action;
1029         gboolean delete;
1030         gboolean success = FALSE;
1031
1032         tree_view = GTK_TREE_VIEW (widget);
1033         model = gtk_tree_view_get_model (tree_view);
1034
1035         action = gdk_drag_context_get_selected_action (context);
1036         delete = (action == GDK_ACTION_MOVE);
1037
1038         if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
1039                 goto exit;
1040
1041         if (!gtk_tree_model_get_iter (model, &iter, path))
1042                 goto exit;
1043
1044         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1045
1046         if (!e_source_get_writable (source))
1047                 goto exit;
1048
1049         g_signal_emit (
1050                 widget, signals[DATA_DROPPED], 0, selection_data,
1051                 source, gdk_drag_context_get_selected_action (context),
1052                 info, &success);
1053
1054 exit:
1055         if (path != NULL)
1056                 gtk_tree_path_free (path);
1057
1058         if (source != NULL)
1059                 g_object_unref (source);
1060
1061         gtk_drag_finish (context, success, delete, time_);
1062 }
1063
1064 static gboolean
1065 source_selector_popup_menu (GtkWidget *widget)
1066 {
1067         ESourceSelector *selector;
1068         ESource *source;
1069         gboolean res = FALSE;
1070
1071         selector = E_SOURCE_SELECTOR (widget);
1072         source = e_source_selector_ref_primary_selection (selector);
1073         g_signal_emit (selector, signals[POPUP_EVENT], 0, source, NULL, &res);
1074
1075         if (source != NULL)
1076                 g_object_unref (source);
1077
1078         return res;
1079 }
1080
1081 static gboolean
1082 source_selector_test_collapse_row (GtkTreeView *tree_view,
1083                                    GtkTreeIter *iter,
1084                                    GtkTreePath *path)
1085 {
1086         ESourceSelectorPrivate *priv;
1087         GtkTreeSelection *selection;
1088         GtkTreeModel *model;
1089         GtkTreeIter child_iter;
1090
1091         priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
1092
1093         /* Clear this because something else has been clicked on now */
1094         priv->toggled_last = FALSE;
1095
1096         if (priv->saved_primary_selection)
1097                 return FALSE;
1098
1099         selection = gtk_tree_view_get_selection (tree_view);
1100
1101         if (!gtk_tree_selection_get_selected (selection, &model, &child_iter))
1102                 return FALSE;
1103
1104         if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
1105                 GtkTreeRowReference *reference;
1106                 GtkTreePath *child_path;
1107
1108                 child_path = gtk_tree_model_get_path (model, &child_iter);
1109                 reference = gtk_tree_row_reference_new (model, child_path);
1110                 priv->saved_primary_selection = reference;
1111                 gtk_tree_path_free (child_path);
1112         }
1113
1114         return FALSE;
1115 }
1116
1117 static void
1118 source_selector_row_expanded (GtkTreeView *tree_view,
1119                               GtkTreeIter *iter,
1120                               GtkTreePath *path)
1121 {
1122         ESourceSelectorPrivate *priv;
1123         GtkTreeModel *model;
1124         GtkTreePath *child_path;
1125         GtkTreeIter child_iter;
1126
1127         priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
1128
1129         if (!priv->saved_primary_selection)
1130                 return;
1131
1132         model = gtk_tree_view_get_model (tree_view);
1133
1134         child_path = gtk_tree_row_reference_get_path (
1135                 priv->saved_primary_selection);
1136         gtk_tree_model_get_iter (model, &child_iter, child_path);
1137
1138         if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
1139                 GtkTreeSelection *selection;
1140
1141                 selection = gtk_tree_view_get_selection (tree_view);
1142                 gtk_tree_selection_select_iter (selection, &child_iter);
1143
1144                 clear_saved_primary_selection (E_SOURCE_SELECTOR (tree_view));
1145         }
1146
1147         gtk_tree_path_free (child_path);
1148 }
1149
1150 static gboolean
1151 source_selector_get_source_selected (ESourceSelector *selector,
1152                                      ESource *source)
1153 {
1154         ESourceSelectable *extension;
1155         const gchar *extension_name;
1156         gboolean selected = TRUE;
1157
1158         extension_name = e_source_selector_get_extension_name (selector);
1159
1160         if (!e_source_has_extension (source, extension_name))
1161                 return FALSE;
1162
1163         extension = e_source_get_extension (source, extension_name);
1164
1165         if (E_IS_SOURCE_SELECTABLE (extension))
1166                 selected = e_source_selectable_get_selected (extension);
1167
1168         return selected;
1169 }
1170
1171 static void
1172 source_selector_set_source_selected (ESourceSelector *selector,
1173                                      ESource *source,
1174                                      gboolean selected)
1175 {
1176         ESourceSelectable *extension;
1177         const gchar *extension_name;
1178
1179         extension_name = e_source_selector_get_extension_name (selector);
1180
1181         if (!e_source_has_extension (source, extension_name))
1182                 return;
1183
1184         extension = e_source_get_extension (source, extension_name);
1185
1186         if (!E_IS_SOURCE_SELECTABLE (extension))
1187                 return;
1188
1189         if (selected != e_source_selectable_get_selected (extension)) {
1190                 e_source_selectable_set_selected (extension, selected);
1191                 e_source_selector_queue_write (selector, source);
1192         }
1193 }
1194
1195 static gboolean
1196 ess_bool_accumulator (GSignalInvocationHint *ihint,
1197                       GValue *out,
1198                       const GValue *in,
1199                       gpointer data)
1200 {
1201         gboolean v_boolean;
1202
1203         v_boolean = g_value_get_boolean (in);
1204         g_value_set_boolean (out, v_boolean);
1205
1206         return !v_boolean;
1207 }
1208
1209 static void
1210 e_source_selector_class_init (ESourceSelectorClass *class)
1211 {
1212         GObjectClass *object_class;
1213         GtkWidgetClass *widget_class;
1214         GtkTreeViewClass *tree_view_class;
1215
1216         g_type_class_add_private (class, sizeof (ESourceSelectorPrivate));
1217
1218         object_class = G_OBJECT_CLASS (class);
1219         object_class->set_property = source_selector_set_property;
1220         object_class->get_property = source_selector_get_property;
1221         object_class->dispose  = source_selector_dispose;
1222         object_class->finalize = source_selector_finalize;
1223         object_class->constructed = source_selector_constructed;
1224
1225         widget_class = GTK_WIDGET_CLASS (class);
1226         widget_class->button_press_event = source_selector_button_press_event;
1227         widget_class->drag_leave = source_selector_drag_leave;
1228         widget_class->drag_motion = source_selector_drag_motion;
1229         widget_class->drag_drop = source_selector_drag_drop;
1230         widget_class->drag_data_received = source_selector_drag_data_received;
1231         widget_class->popup_menu = source_selector_popup_menu;
1232
1233         tree_view_class = GTK_TREE_VIEW_CLASS (class);
1234         tree_view_class->test_collapse_row = source_selector_test_collapse_row;
1235         tree_view_class->row_expanded = source_selector_row_expanded;
1236
1237         class->get_source_selected = source_selector_get_source_selected;
1238         class->set_source_selected = source_selector_set_source_selected;
1239
1240         g_object_class_install_property (
1241                 object_class,
1242                 PROP_EXTENSION_NAME,
1243                 g_param_spec_string (
1244                         "extension-name",
1245                         NULL,
1246                         NULL,
1247                         NULL,
1248                         G_PARAM_READWRITE |
1249                         G_PARAM_CONSTRUCT_ONLY |
1250                         G_PARAM_STATIC_STRINGS));
1251
1252         g_object_class_install_property (
1253                 object_class,
1254                 PROP_PRIMARY_SELECTION,
1255                 g_param_spec_object (
1256                         "primary-selection",
1257                         NULL,
1258                         NULL,
1259                         E_TYPE_SOURCE,
1260                         G_PARAM_READWRITE |
1261                         G_PARAM_STATIC_STRINGS));
1262
1263         g_object_class_install_property (
1264                 object_class,
1265                 PROP_REGISTRY,
1266                 g_param_spec_object (
1267                         "registry",
1268                         NULL,
1269                         NULL,
1270                         E_TYPE_SOURCE_REGISTRY,
1271                         G_PARAM_READWRITE |
1272                         G_PARAM_CONSTRUCT_ONLY |
1273                         G_PARAM_STATIC_STRINGS));
1274
1275         g_object_class_install_property (
1276                 object_class,
1277                 PROP_SHOW_COLORS,
1278                 g_param_spec_boolean (
1279                         "show-colors",
1280                         NULL,
1281                         NULL,
1282                         TRUE,
1283                         G_PARAM_READWRITE |
1284                         G_PARAM_STATIC_STRINGS));
1285
1286         g_object_class_install_property (
1287                 object_class,
1288                 PROP_SHOW_TOGGLES,
1289                 g_param_spec_boolean (
1290                         "show-toggles",
1291                         NULL,
1292                         NULL,
1293                         TRUE,
1294                         G_PARAM_READWRITE |
1295                         G_PARAM_STATIC_STRINGS));
1296
1297         signals[SELECTION_CHANGED] = g_signal_new (
1298                 "selection-changed",
1299                 G_OBJECT_CLASS_TYPE (object_class),
1300                 G_SIGNAL_RUN_LAST,
1301                 G_STRUCT_OFFSET (ESourceSelectorClass, selection_changed),
1302                 NULL, NULL,
1303                 g_cclosure_marshal_VOID__VOID,
1304                 G_TYPE_NONE, 0);
1305
1306         /* XXX Consider this signal deprecated.  Connect
1307          *     to "notify::primary-selection" instead. */
1308         signals[PRIMARY_SELECTION_CHANGED] = g_signal_new (
1309                 "primary-selection-changed",
1310                 G_OBJECT_CLASS_TYPE (object_class),
1311                 G_SIGNAL_RUN_LAST,
1312                 G_STRUCT_OFFSET (ESourceSelectorClass, primary_selection_changed),
1313                 NULL, NULL,
1314                 g_cclosure_marshal_VOID__VOID,
1315                 G_TYPE_NONE, 0);
1316
1317         signals[POPUP_EVENT] = g_signal_new (
1318                 "popup-event",
1319                 G_OBJECT_CLASS_TYPE (object_class),
1320                 G_SIGNAL_RUN_LAST,
1321                 G_STRUCT_OFFSET (ESourceSelectorClass, popup_event),
1322                 ess_bool_accumulator, NULL,
1323                 e_data_server_ui_marshal_BOOLEAN__OBJECT_BOXED,
1324                 G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT,
1325                 GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1326
1327         signals[DATA_DROPPED] = g_signal_new (
1328                 "data-dropped",
1329                 G_OBJECT_CLASS_TYPE (object_class),
1330                 G_SIGNAL_RUN_LAST,
1331                 G_STRUCT_OFFSET (ESourceSelectorClass, data_dropped),
1332                 NULL, NULL,
1333                 e_data_server_ui_marshal_BOOLEAN__BOXED_OBJECT_FLAGS_UINT,
1334                 G_TYPE_BOOLEAN, 4,
1335                 GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
1336                 E_TYPE_SOURCE,
1337                 GDK_TYPE_DRAG_ACTION,
1338                 G_TYPE_UINT);
1339 }
1340
1341 static void
1342 e_source_selector_init (ESourceSelector *selector)
1343 {
1344         GHashTable *pending_writes;
1345         GtkTreeViewColumn *column;
1346         GtkTreeSelection *selection;
1347         GtkCellRenderer *renderer;
1348         GtkTreeStore *tree_store;
1349         GtkTreeView *tree_view;
1350
1351         pending_writes = g_hash_table_new_full (
1352                 (GHashFunc) g_direct_hash,
1353                 (GEqualFunc) g_direct_equal,
1354                 (GDestroyNotify) g_object_unref,
1355                 (GDestroyNotify) pending_writes_destroy_source);
1356
1357         selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);
1358
1359         selector->priv->pending_writes = pending_writes;
1360
1361         selector->priv->main_context = g_main_context_get_thread_default ();
1362         if (selector->priv->main_context != NULL)
1363                 g_main_context_ref (selector->priv->main_context);
1364
1365         tree_view = GTK_TREE_VIEW (selector);
1366
1367         gtk_tree_view_set_search_column (tree_view, COLUMN_SOURCE);
1368         gtk_tree_view_set_enable_search (tree_view, TRUE);
1369
1370         selector->priv->toggled_last = FALSE;
1371         selector->priv->select_new = FALSE;
1372         selector->priv->show_colors = TRUE;
1373         selector->priv->show_toggles = TRUE;
1374
1375         selector->priv->source_index = g_hash_table_new_full (
1376                 (GHashFunc) e_source_hash,
1377                 (GEqualFunc) e_source_equal,
1378                 (GDestroyNotify) g_object_unref,
1379                 (GDestroyNotify) gtk_tree_row_reference_free);
1380
1381         tree_store = gtk_tree_store_new (
1382                 NUM_COLUMNS,
1383                 G_TYPE_STRING,          /* COLUMN_NAME */
1384                 GDK_TYPE_COLOR,         /* COLUMN_COLOR */
1385                 G_TYPE_BOOLEAN,         /* COLUMN_ACTIVE */
1386                 G_TYPE_BOOLEAN,         /* COLUMN_SHOW_COLOR */
1387                 G_TYPE_BOOLEAN,         /* COLUMN_SHOW_TOGGLE */
1388                 G_TYPE_INT,             /* COLUMN_WEIGHT */
1389                 E_TYPE_SOURCE);         /* COLUMN_SOURCE */
1390
1391         gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));
1392
1393         column = gtk_tree_view_column_new ();
1394         gtk_tree_view_append_column (tree_view, column);
1395
1396         renderer = e_cell_renderer_color_new ();
1397         g_object_set (
1398                 G_OBJECT (renderer), "mode",
1399                 GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
1400         gtk_tree_view_column_pack_start (column, renderer, FALSE);
1401         gtk_tree_view_column_add_attribute (
1402                 column, renderer, "color", COLUMN_COLOR);
1403         gtk_tree_view_column_add_attribute (
1404                 column, renderer, "visible", COLUMN_SHOW_COLOR);
1405
1406         renderer = e_cell_renderer_safe_toggle_new ();
1407         gtk_tree_view_column_pack_start (column, renderer, FALSE);
1408         gtk_tree_view_column_add_attribute (
1409                 column, renderer, "active", COLUMN_ACTIVE);
1410         gtk_tree_view_column_add_attribute (
1411                 column, renderer, "visible", COLUMN_SHOW_TOGGLE);
1412         g_signal_connect (
1413                 renderer, "toggled",
1414                 G_CALLBACK (cell_toggled_callback), selector);
1415
1416         renderer = gtk_cell_renderer_text_new ();
1417         g_object_set (
1418                 G_OBJECT (renderer),
1419                 "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1420         g_signal_connect_swapped (
1421                 renderer, "edited",
1422                 G_CALLBACK (text_cell_edited_cb), selector);
1423         gtk_tree_view_column_pack_start (column, renderer, TRUE);
1424         gtk_tree_view_column_set_attributes (
1425                 column, renderer,
1426                 "text", COLUMN_NAME,
1427                 "weight", COLUMN_WEIGHT,
1428                 NULL);
1429
1430         selection = gtk_tree_view_get_selection (tree_view);
1431         gtk_tree_selection_set_select_function (
1432                 selection, (GtkTreeSelectionFunc)
1433                 selection_func, selector, NULL);
1434         g_signal_connect_object (
1435                 selection, "changed",
1436                 G_CALLBACK (selection_changed_callback),
1437                 G_OBJECT (selector), 0);
1438
1439         gtk_tree_view_set_headers_visible (tree_view, FALSE);
1440 }
1441
1442 /**
1443  * e_source_selector_new:
1444  * @registry: an #ESourceRegistry
1445  * @extension_name: the name of an #ESource extension
1446  *
1447  * Displays a list of sources from @registry having an extension named
1448  * @extension_name.  The sources are grouped by backend or groupware
1449  * account, which are described by the parent source.
1450  *
1451  * Returns: a new #ESourceSelector
1452  **/
1453 GtkWidget *
1454 e_source_selector_new (ESourceRegistry *registry,
1455                        const gchar *extension_name)
1456 {
1457         g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
1458         g_return_val_if_fail (extension_name != NULL, NULL);
1459
1460         return g_object_new (
1461                 E_TYPE_SOURCE_SELECTOR, "registry", registry,
1462                 "extension-name", extension_name, NULL);
1463 }
1464
1465 /**
1466  * e_source_selector_get_registry:
1467  * @selector: an #ESourceSelector
1468  *
1469  * Returns the #ESourceRegistry that @selector is getting sources from.
1470  *
1471  * Returns: an #ESourceRegistry
1472  *
1473  * Since: 3.6
1474  **/
1475 ESourceRegistry *
1476 e_source_selector_get_registry (ESourceSelector *selector)
1477 {
1478         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
1479
1480         return selector->priv->registry;
1481 }
1482
1483 /**
1484  * e_source_selector_get_extension_name:
1485  * @selector: an #ESourceSelector
1486  *
1487  * Returns the extension name used to filter which sources are displayed.
1488  *
1489  * Returns: the #ESource extension name
1490  *
1491  * Since: 3.6
1492  **/
1493 const gchar *
1494 e_source_selector_get_extension_name (ESourceSelector *selector)
1495 {
1496         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
1497
1498         return selector->priv->extension_name;
1499 }
1500
1501 /**
1502  * e_source_selector_get_show_colors:
1503  * @selector: an #ESourceSelector
1504  *
1505  * Returns whether colors are shown next to data sources.
1506  *
1507  * Returns: %TRUE if colors are being shown
1508  *
1509  * Since: 3.6
1510  **/
1511 gboolean
1512 e_source_selector_get_show_colors (ESourceSelector *selector)
1513 {
1514         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
1515
1516         return selector->priv->show_colors;
1517 }
1518
1519 /**
1520  * e_source_selector_set_show_colors:
1521  * @selector: an #ESourceSelector
1522  * @show_colors: whether to show colors
1523  *
1524  * Sets whether to show colors next to data sources.
1525  *
1526  * Since: 3.6
1527  **/
1528 void
1529 e_source_selector_set_show_colors (ESourceSelector *selector,
1530                                    gboolean show_colors)
1531 {
1532         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1533
1534         if ((show_colors ? 1 : 0) == (selector->priv->show_colors ? 1 : 0))
1535                 return;
1536
1537         selector->priv->show_colors = show_colors;
1538
1539         g_object_notify (G_OBJECT (selector), "show-colors");
1540
1541         source_selector_build_model (selector);
1542 }
1543
1544 /**
1545  * e_source_selector_get_show_toggles:
1546  * @selector: an #ESourceSelector
1547  *
1548  * Returns whether toggles are shown next to data sources.
1549  *
1550  * Returns: %TRUE if toggles are being shown
1551  *
1552  * Since: 3.6
1553  **/
1554 gboolean
1555 e_source_selector_get_show_toggles (ESourceSelector *selector)
1556 {
1557         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
1558
1559         return selector->priv->show_toggles;
1560 }
1561
1562 /**
1563  * e_source_selector_set_show_toggles:
1564  * @selector: an #ESourceSelector
1565  * @show_toggles: whether to show toggles
1566  *
1567  * Sets whether to show toggles next to data sources.
1568  *
1569  * Since: 3.6
1570  **/
1571 void
1572 e_source_selector_set_show_toggles (ESourceSelector *selector,
1573                                    gboolean show_toggles)
1574 {
1575         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1576
1577         if ((show_toggles ? 1 : 0) == (selector->priv->show_toggles ? 1 : 0))
1578                 return;
1579
1580         selector->priv->show_toggles = show_toggles;
1581
1582         g_object_notify (G_OBJECT (selector), "show-toggles");
1583
1584         source_selector_build_model (selector);
1585 }
1586
1587 /* Helper for e_source_selector_get_selection() */
1588 static gboolean
1589 source_selector_check_selected (GtkTreeModel *model,
1590                                 GtkTreePath *path,
1591                                 GtkTreeIter *iter,
1592                                 gpointer user_data)
1593 {
1594         ESource *source;
1595
1596         struct {
1597                 ESourceSelector *selector;
1598                 GSList *list;
1599         } *closure = user_data;
1600
1601         gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);
1602
1603         if (e_source_selector_source_is_selected (closure->selector, source))
1604                 closure->list = g_slist_prepend (closure->list, source);
1605         else
1606                 g_object_unref (source);
1607
1608         return FALSE;
1609 }
1610
1611 /**
1612  * e_source_selector_get_selection:
1613  * @selector: an #ESourceSelector
1614  *
1615  * Get the list of selected sources, i.e. those that were enabled through the
1616  * corresponding checkboxes in the tree.
1617  *
1618  * Returns: A list of the ESources currently selected.  The sources will
1619  * be in the same order as they appear on the screen, and the list should be
1620  * freed using e_source_selector_free_selection().
1621  **/
1622 GSList *
1623 e_source_selector_get_selection (ESourceSelector *selector)
1624 {
1625         struct {
1626                 ESourceSelector *selector;
1627                 GSList *list;
1628         } closure;
1629
1630         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
1631
1632         closure.selector = selector;
1633         closure.list = NULL;
1634
1635         gtk_tree_model_foreach (
1636                 gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
1637                 (GtkTreeModelForeachFunc) source_selector_check_selected,
1638                 &closure);
1639
1640         return g_slist_reverse (closure.list);
1641 }
1642
1643 /**
1644  * e_source_list_free_selection:
1645  * @list: A selection list returned by e_source_selector_get_selection().
1646  *
1647  * Free the selection list.
1648  **/
1649 void
1650 e_source_selector_free_selection (GSList *list)
1651 {
1652         g_slist_foreach (list, (GFunc) g_object_unref, NULL);
1653         g_slist_free (list);
1654 }
1655
1656 /**
1657  * e_source_selector_set_select_new:
1658  * @selector: An #ESourceSelector widget
1659  * @state: A gboolean
1660  *
1661  * Set whether or not to select new sources added to @selector.
1662  **/
1663 void
1664 e_source_selector_set_select_new (ESourceSelector *selector,
1665                                   gboolean state)
1666 {
1667         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1668
1669         selector->priv->select_new = state;
1670 }
1671
1672 /**
1673  * e_source_selector_select_source:
1674  * @selector: An #ESourceSelector widget
1675  * @source: An #ESource.
1676  *
1677  * Select @source in @selector.
1678  **/
1679 void
1680 e_source_selector_select_source (ESourceSelector *selector,
1681                                  ESource *source)
1682 {
1683         ESourceSelectorClass *class;
1684         GtkTreeRowReference *reference;
1685         GHashTable *source_index;
1686
1687         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1688         g_return_if_fail (E_IS_SOURCE (source));
1689
1690         /* Make sure the ESource is in our tree model. */
1691         source_index = selector->priv->source_index;
1692         reference = g_hash_table_lookup (source_index, source);
1693         g_return_if_fail (gtk_tree_row_reference_valid (reference));
1694
1695         class = E_SOURCE_SELECTOR_GET_CLASS (selector);
1696         g_return_if_fail (class->set_source_selected != NULL);
1697
1698         class->set_source_selected (selector, source, TRUE);
1699
1700         g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
1701 }
1702
1703 /**
1704  * e_source_selector_unselect_source:
1705  * @selector: An #ESourceSelector widget
1706  * @source: An #ESource.
1707  *
1708  * Unselect @source in @selector.
1709  **/
1710 void
1711 e_source_selector_unselect_source (ESourceSelector *selector,
1712                                    ESource *source)
1713 {
1714         ESourceSelectorClass *class;
1715         GtkTreeRowReference *reference;
1716         GHashTable *source_index;
1717
1718         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1719         g_return_if_fail (E_IS_SOURCE (source));
1720
1721         /* Make sure the ESource is in our tree model. */
1722         source_index = selector->priv->source_index;
1723         reference = g_hash_table_lookup (source_index, source);
1724         g_return_if_fail (gtk_tree_row_reference_valid (reference));
1725
1726         class = E_SOURCE_SELECTOR_GET_CLASS (selector);
1727         g_return_if_fail (class->set_source_selected != NULL);
1728
1729         class->set_source_selected (selector, source, FALSE);
1730
1731         g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
1732 }
1733
1734 /**
1735  * e_source_selector_select_exclusive:
1736  * @selector: An #ESourceSelector widget
1737  * @source: An #ESource.
1738  *
1739  * Select @source in @selector and unselect all others.
1740  *
1741  * Since: 2.30
1742  **/
1743 void
1744 e_source_selector_select_exclusive (ESourceSelector *selector,
1745                                     ESource *source)
1746 {
1747         ESourceSelectorClass *class;
1748         GHashTable *source_index;
1749         GHashTableIter iter;
1750         gpointer key;
1751
1752         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1753         g_return_if_fail (E_IS_SOURCE (source));
1754
1755         class = E_SOURCE_SELECTOR_GET_CLASS (selector);
1756         g_return_if_fail (class->set_source_selected != NULL);
1757
1758         source_index = selector->priv->source_index;
1759         g_hash_table_iter_init (&iter, source_index);
1760
1761         while (g_hash_table_iter_next (&iter, &key, NULL)) {
1762                 gboolean selected = e_source_equal (key, source);
1763                 class->set_source_selected (selector, key, selected);
1764         }
1765
1766         g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
1767 }
1768
1769 /**
1770  * e_source_selector_source_is_selected:
1771  * @selector: An #ESourceSelector widget
1772  * @source: An #ESource.
1773  *
1774  * Check whether @source is selected in @selector.
1775  *
1776  * Returns: %TRUE if @source is currently selected, %FALSE otherwise.
1777  **/
1778 gboolean
1779 e_source_selector_source_is_selected (ESourceSelector *selector,
1780                                       ESource *source)
1781 {
1782         ESourceSelectorClass *class;
1783         GtkTreeRowReference *reference;
1784         GHashTable *source_index;
1785
1786         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
1787         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1788
1789         /* Make sure the ESource is in our tree model. */
1790         source_index = selector->priv->source_index;
1791         reference = g_hash_table_lookup (source_index, source);
1792         g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
1793
1794         class = E_SOURCE_SELECTOR_GET_CLASS (selector);
1795         g_return_val_if_fail (class->get_source_selected != NULL, FALSE);
1796
1797         return class->get_source_selected (selector, source);
1798 }
1799
1800 /**
1801  * e_source_selector_edit_primary_selection:
1802  * @selector: An #ESourceSelector widget
1803  *
1804  * Allows the user to rename the primary selected source by opening an
1805  * entry box directly in @selector.
1806  *
1807  * Since: 2.26
1808  **/
1809 void
1810 e_source_selector_edit_primary_selection (ESourceSelector *selector)
1811 {
1812         GtkTreeRowReference *reference;
1813         GtkTreeSelection *selection;
1814         GtkTreeViewColumn *column;
1815         GtkCellRenderer *renderer;
1816         GtkTreeView *tree_view;
1817         GtkTreeModel *model;
1818         GtkTreePath *path = NULL;
1819         GtkTreeIter iter;
1820         GList *list;
1821
1822         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1823
1824         tree_view = GTK_TREE_VIEW (selector);
1825         column = gtk_tree_view_get_column (tree_view, 0);
1826         reference = selector->priv->saved_primary_selection;
1827         selection = gtk_tree_view_get_selection (tree_view);
1828
1829         if (reference != NULL)
1830                 path = gtk_tree_row_reference_get_path (reference);
1831         else if (gtk_tree_selection_get_selected (selection, &model, &iter))
1832                 path = gtk_tree_model_get_path (model, &iter);
1833
1834         if (path == NULL)
1835                 return;
1836
1837         /* XXX Because we stuff three renderers in a single column,
1838          *     we have to manually hunt for the text renderer. */
1839         renderer = NULL;
1840         list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1841         while (list != NULL) {
1842                 renderer = list->data;
1843                 if (GTK_IS_CELL_RENDERER_TEXT (renderer))
1844                         break;
1845                 list = g_list_delete_link (list, list);
1846         }
1847         g_list_free (list);
1848
1849         /* Make the text cell renderer editable, but only temporarily.
1850          * We don't want editing to be activated by simply clicking on
1851          * the source name.  Too easy for accidental edits to occur. */
1852         g_object_set (renderer, "editable", TRUE, NULL);
1853         gtk_tree_view_expand_to_path (tree_view, path);
1854         gtk_tree_view_set_cursor_on_cell (
1855                 tree_view, path, column, renderer, TRUE);
1856         g_object_set (renderer, "editable", FALSE, NULL);
1857
1858         gtk_tree_path_free (path);
1859 }
1860
1861 /**
1862  * e_source_selector_ref_primary_selection:
1863  * @selector: An #ESourceSelector widget
1864  *
1865  * Get the primary selected source.  The primary selection is the one that is
1866  * highlighted through the normal #GtkTreeView selection mechanism (as opposed
1867  * to the "normal" selection, which is the set of source whose checkboxes are
1868  * checked).
1869  *
1870  * The returned #ESource is referenced for thread-safety and must be
1871  * unreferenced with g_object_unref() when finished with it.
1872  *
1873  * Returns: The selected source.
1874  *
1875  * Since: 3.6
1876  **/
1877 ESource *
1878 e_source_selector_ref_primary_selection (ESourceSelector *selector)
1879 {
1880         ESource *source;
1881         GtkTreeRowReference *reference;
1882         GtkTreeSelection *selection;
1883         GtkTreeView *tree_view;
1884         GtkTreeModel *model;
1885         GtkTreeIter iter;
1886         const gchar *extension_name;
1887         gboolean have_iter = FALSE;
1888
1889         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
1890
1891         tree_view = GTK_TREE_VIEW (selector);
1892         model = gtk_tree_view_get_model (tree_view);
1893         selection = gtk_tree_view_get_selection (tree_view);
1894
1895         reference = selector->priv->saved_primary_selection;
1896
1897         if (gtk_tree_row_reference_valid (reference)) {
1898                 GtkTreePath *path;
1899
1900                 path = gtk_tree_row_reference_get_path (reference);
1901                 have_iter = gtk_tree_model_get_iter (model, &iter, path);
1902                 gtk_tree_path_free (path);
1903         }
1904
1905         if (!have_iter)
1906                 have_iter = gtk_tree_selection_get_selected (
1907                         selection, NULL, &iter);
1908
1909         if (!have_iter)
1910                 return NULL;
1911
1912         gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1913
1914         extension_name = e_source_selector_get_extension_name (selector);
1915
1916         if (!e_source_has_extension (source, extension_name)) {
1917                 g_object_unref (source);
1918                 return NULL;
1919         }
1920
1921         return source;
1922 }
1923
1924 /**
1925  * e_source_selector_set_primary_selection:
1926  * @selector: an #ESourceSelector widget
1927  * @source: an #ESource to select
1928  *
1929  * Highlights @source in @selector.  The highlighted #ESource is called
1930  * the primary selection.
1931  *
1932  * Do not confuse this function with e_source_selector_select_source(),
1933  * which activates the check box next to an #ESource's display name in
1934  * @selector.  This function does not alter the check box.
1935  **/
1936 void
1937 e_source_selector_set_primary_selection (ESourceSelector *selector,
1938                                          ESource *source)
1939 {
1940         GHashTable *source_index;
1941         GtkTreeRowReference *reference;
1942         GtkTreeSelection *selection;
1943         GtkTreeView *tree_view;
1944         GtkTreePath *child_path;
1945         GtkTreePath *parent_path;
1946         const gchar *extension_name;
1947
1948         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
1949         g_return_if_fail (E_IS_SOURCE (source));
1950
1951         tree_view = GTK_TREE_VIEW (selector);
1952         selection = gtk_tree_view_get_selection (tree_view);
1953
1954         source_index = selector->priv->source_index;
1955         reference = g_hash_table_lookup (source_index, source);
1956
1957         /* XXX Maybe we should return a success/fail boolean? */
1958         if (!gtk_tree_row_reference_valid (reference))
1959                 return;
1960
1961         extension_name = e_source_selector_get_extension_name (selector);
1962
1963         /* Return silently if attempting to select a parent node
1964          * lacking the expected extension (e.g. On This Computer). */
1965         if (!e_source_has_extension (source, extension_name))
1966                 return;
1967
1968         /* We block the signal because this all needs to be atomic */
1969         g_signal_handlers_block_matched (
1970                 selection, G_SIGNAL_MATCH_FUNC,
1971                 0, 0, NULL, selection_changed_callback, NULL);
1972         gtk_tree_selection_unselect_all (selection);
1973         g_signal_handlers_unblock_matched (
1974                 selection, G_SIGNAL_MATCH_FUNC,
1975                 0, 0, NULL, selection_changed_callback, NULL);
1976
1977         clear_saved_primary_selection (selector);
1978
1979         child_path = gtk_tree_row_reference_get_path (reference);
1980
1981         parent_path = gtk_tree_path_copy (child_path);
1982         gtk_tree_path_up (parent_path);
1983
1984         if (gtk_tree_view_row_expanded (tree_view, parent_path)) {
1985                 gtk_tree_selection_select_path (selection, child_path);
1986         } else {
1987                 selector->priv->saved_primary_selection =
1988                         gtk_tree_row_reference_copy (reference);
1989                 g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
1990                 g_object_notify (G_OBJECT (selector), "primary-selection");
1991         }
1992
1993         gtk_tree_path_free (child_path);
1994         gtk_tree_path_free (parent_path);
1995 }
1996
1997 /**
1998  * e_source_selector_ref_source_by_path:
1999  * @selector: an #ESourceSelector
2000  * @path: a #GtkTreePath
2001  *
2002  * Returns the #ESource object at @path, or %NULL if @path is invalid.
2003  *
2004  * The returned #ESource is referenced for thread-safety and must be
2005  * unreferenced with g_object_unref() when finished with it.
2006  *
2007  * Returns: the #ESource object at @path, or %NULL
2008  *
2009  * Since: 3.6
2010  **/
2011 ESource *
2012 e_source_selector_ref_source_by_path (ESourceSelector *selector,
2013                                       GtkTreePath *path)
2014 {
2015         ESource *source = NULL;
2016         GtkTreeModel *model;
2017         GtkTreeIter iter;
2018
2019         g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2020         g_return_val_if_fail (path != NULL, NULL);
2021
2022         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
2023
2024         if (gtk_tree_model_get_iter (model, &iter, path))
2025                 gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
2026
2027         return source;
2028 }
2029
2030 /**
2031  * e_source_selector_queue_write:
2032  * @selector: an #ESourceSelecetor
2033  * @source: an #ESource with changes to be written
2034  *
2035  * Queues a main loop idle callback to write changes to @source back to
2036  * the D-Bus registry service.
2037  *
2038  * Since: 3.6
2039  **/
2040 void
2041 e_source_selector_queue_write (ESourceSelector *selector,
2042                                ESource *source)
2043 {
2044         GSource *idle_source;
2045         GHashTable *pending_writes;
2046         GMainContext *main_context;
2047         AsyncContext *async_context;
2048
2049         g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2050         g_return_if_fail (E_IS_SOURCE (source));
2051
2052         main_context = selector->priv->main_context;
2053         pending_writes = selector->priv->pending_writes;
2054
2055         idle_source = g_hash_table_lookup (pending_writes, source);
2056         if (idle_source != NULL && !g_source_is_destroyed (idle_source))
2057                 return;
2058
2059         async_context = g_slice_new0 (AsyncContext);
2060         async_context->selector = g_object_ref (selector);
2061         async_context->source = g_object_ref (source);
2062
2063         /* Set a higher priority so this idle source runs before our
2064          * source_selector_cancel_write() signal handler, which will
2065          * cancel this idle source.  Cancellation is the right thing
2066          * to do when receiving changes from OTHER registry clients,
2067          * but we don't want to cancel our own changes.
2068          *
2069          * XXX This might be an argument for using etags.
2070          */
2071         idle_source = g_idle_source_new ();
2072         g_hash_table_insert (
2073                 pending_writes,
2074                 g_object_ref (source),
2075                 g_source_ref (idle_source));
2076         g_source_set_callback (
2077                 idle_source,
2078                 source_selector_write_idle_cb,
2079                 async_context,
2080                 (GDestroyNotify) async_context_free);
2081         g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
2082         g_source_attach (idle_source, main_context);
2083         g_source_unref (idle_source);
2084 }
2085