Adapt ESourceSelector to the new ESource API.
authorMatthew Barnes <mbarnes@redhat.com>
Sat, 4 Dec 2010 22:59:53 +0000 (16:59 -0600)
committerMatthew Barnes <mbarnes@redhat.com>
Sun, 3 Jun 2012 23:51:09 +0000 (19:51 -0400)
docs/reference/libedataserverui/libedataserverui-sections.txt
libedataserverui/e-source-selector.c
libedataserverui/e-source-selector.h
tests/libedataserverui/test-source-selector.c

index 3cde69f..6e4beca 100644 (file)
@@ -397,21 +397,24 @@ e_tree_model_generator_get_type
 <TITLE>ESourceSelector</TITLE>
 ESourceSelector
 e_source_selector_new
-e_source_selector_get_source_list
+e_source_selector_get_registry
+e_source_selector_get_extension_name
+e_source_selector_get_show_colors
+e_source_selector_set_show_colors
+e_source_selector_get_show_toggles
+e_source_selector_set_show_toggles
 e_source_selector_select_source
 e_source_selector_unselect_source
 e_source_selector_select_exclusive
 e_source_selector_source_is_selected
 e_source_selector_get_selection
 e_source_selector_free_selection
-e_source_selector_show_selection
-e_source_selector_selection_shown
 e_source_selector_set_select_new
 e_source_selector_edit_primary_selection
 e_source_selector_ref_primary_selection
 e_source_selector_set_primary_selection
-e_source_selector_get_primary_source_group
 e_source_selector_ref_source_by_path
+e_source_selector_queue_write
 <SUBSECTION Standard>
 E_SOURCE_SELECTOR
 E_IS_SOURCE_SELECTOR
index 2d6a5af..fb1af20 100644 (file)
@@ -27,6 +27,9 @@
 
 #include <string.h>
 
+#include <libedataserver/e-source-selectable.h>
+
+#include "e-cell-renderer-color.h"
 #include "e-data-server-ui-marshal.h"
 #include "e-source-selector.h"
 
        (G_TYPE_INSTANCE_GET_PRIVATE \
        ((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))
 
+typedef struct _AsyncContext AsyncContext;
+
 struct _ESourceSelectorPrivate {
-       ESourceList *list;
+       ESourceRegistry *registry;
+       GHashTable *source_index;
+       gchar *extension_name;
 
-       GHashTable *selected_sources;
        GtkTreeRowReference *saved_primary_selection;
-       ESourceGroup *primary_source_group;
 
-       gint rebuild_model_idle_id;
+       /* ESource -> GSource */
+       GHashTable *pending_writes;
+       GMainContext *main_context;
 
        gboolean toggled_last;
-       gboolean checkboxes_shown;
        gboolean select_new;
+       gboolean show_colors;
+       gboolean show_toggles;
 };
 
-typedef struct {
+struct _AsyncContext {
        ESourceSelector *selector;
-
-       GHashTable *remaining_uids;
-       GSList *deleted_uids;
-
-       gboolean selection_changed;
-} ESourceSelectorRebuildData;
+       ESource *source;
+};
 
 enum {
        PROP_0,
+       PROP_EXTENSION_NAME,
        PROP_PRIMARY_SELECTION,
-       PROP_SOURCE_LIST
+       PROP_REGISTRY,
+       PROP_SHOW_COLORS,
+       PROP_SHOW_TOGGLES
 };
 
 enum {
@@ -70,23 +77,35 @@ enum {
        DATA_DROPPED,
        NUM_SIGNALS
 };
-static guint signals[NUM_SIGNALS] = { 0 };
 
-G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)
+enum {
+       COLUMN_NAME,
+       COLUMN_COLOR,
+       COLUMN_ACTIVE,
+       COLUMN_SHOW_COLOR,
+       COLUMN_SHOW_TOGGLE,
+       COLUMN_WEIGHT,
+       COLUMN_SOURCE,
+       NUM_COLUMNS
+};
+
+static guint signals[NUM_SIGNALS];
 
-/* safe-toggle-renderer definition - it'll not call 'toggled' signal on 'activate', when mouse is not over the toggle */
+G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)
 
-typedef struct _ECellRendererSafeToggle {
-       GtkCellRendererToggle parent;
-} ECellRendererSafeToggle;
+/* ESafeToggleRenderer does not emit 'toggled' signal
+ * on 'activate' when mouse is not over the toggle. */
 
-typedef struct _ECellRendererSafeToggleClass {
-       GtkCellRendererToggleClass parent_class;
-} ECellRendererSafeToggleClass;
+typedef GtkCellRendererToggle ECellRendererSafeToggle;
+typedef GtkCellRendererToggleClass ECellRendererSafeToggleClass;
 
+/* Forward Declarations */
 GType e_cell_renderer_safe_toggle_get_type (void);
 
-G_DEFINE_TYPE (ECellRendererSafeToggle, e_cell_renderer_safe_toggle, GTK_TYPE_CELL_RENDERER_TOGGLE)
+G_DEFINE_TYPE (
+       ECellRendererSafeToggle,
+       e_cell_renderer_safe_toggle,
+       GTK_TYPE_CELL_RENDERER_TOGGLE)
 
 static gboolean
 safe_toggle_activate (GtkCellRenderer *cell,
@@ -97,34 +116,32 @@ safe_toggle_activate (GtkCellRenderer *cell,
                       const GdkRectangle *cell_area,
                       GtkCellRendererState flags)
 {
-       if (event->type == GDK_BUTTON_PRESS && cell_area) {
-               cairo_region_t *region;
-               gboolean contains_point;
+       gboolean point_in_cell_area = TRUE;
 
-               if (cell_area->width > 0 && cell_area->height > 0)
-                       region = cairo_region_create_rectangle (cell_area);
-               else
-                       region = cairo_region_create ();
+       if (event->type == GDK_BUTTON_PRESS && cell_area != NULL) {
+               cairo_region_t *region;
 
-               contains_point = cairo_region_contains_point (
+               region = cairo_region_create_rectangle (cell_area);
+               point_in_cell_area = cairo_region_contains_point (
                        region, event->button.x, event->button.y);
-
                cairo_region_destroy (region);
-
-               if (!contains_point)
-                       return FALSE;
        }
 
-       return GTK_CELL_RENDERER_CLASS (e_cell_renderer_safe_toggle_parent_class)->activate (cell, event, widget, path, background_area, cell_area, flags);
+       if (!point_in_cell_area)
+               return FALSE;
+
+       return GTK_CELL_RENDERER_CLASS (
+               e_cell_renderer_safe_toggle_parent_class)->activate (
+               cell, event, widget, path, background_area, cell_area, flags);
 }
 
 static void
 e_cell_renderer_safe_toggle_class_init (ECellRendererSafeToggleClass *class)
 {
-       GtkCellRendererClass *rndr_class;
+       GtkCellRendererClass *cell_renderer_class;
 
-       rndr_class = GTK_CELL_RENDERER_CLASS (class);
-       rndr_class->activate = safe_toggle_activate;
+       cell_renderer_class = GTK_CELL_RENDERER_CLASS (class);
+       cell_renderer_class->activate = safe_toggle_activate;
 }
 
 static void
@@ -138,514 +155,399 @@ e_cell_renderer_safe_toggle_new (void)
        return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
 }
 
-/* Selection management.  */
-
-static ESourceSelectorRebuildData *
-create_rebuild_data (ESourceSelector *selector)
+static void
+clear_saved_primary_selection (ESourceSelector *selector)
 {
-       ESourceSelectorRebuildData *rebuild_data;
-
-       rebuild_data = g_new0 (ESourceSelectorRebuildData, 1);
-
-       rebuild_data->selector = selector;
-       rebuild_data->remaining_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
-                                                             (GDestroyNotify) gtk_tree_row_reference_free);
-       rebuild_data->deleted_uids = NULL;
-
-       return rebuild_data;
+       gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
+       selector->priv->saved_primary_selection = NULL;
 }
 
 static void
-free_rebuild_data (ESourceSelectorRebuildData *rebuild_data)
+async_context_free (AsyncContext *async_context)
 {
-       GSList *p;
+       if (async_context->selector != NULL)
+               g_object_unref (async_context->selector);
 
-       g_hash_table_destroy (rebuild_data->remaining_uids);
-       for (p = rebuild_data->deleted_uids; p; p = p->next)
-               gtk_tree_row_reference_free (p->data);
-       g_slist_free (rebuild_data->deleted_uids);
+       if (async_context->source != NULL)
+               g_object_unref (async_context->source);
 
-       g_free (rebuild_data);
+       g_slice_free (AsyncContext, async_context);
 }
 
 static void
-clear_saved_primary_selection (ESourceSelector *selector)
+pending_writes_destroy_source (GSource *source)
 {
-       gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
-       selector->priv->saved_primary_selection = NULL;
+       g_source_destroy (source);
+       g_source_unref (source);
 }
 
-static gboolean
-source_is_selected (ESourceSelector *selector,
-                    ESource *source)
+static void
+source_selector_write_done_cb (GObject *source_object,
+                               GAsyncResult *result,
+                               gpointer user_data)
 {
-       GHashTable *hash_table;
+       ESource *source;
+       ESourceSelector *selector;
+       GError *error = NULL;
 
-       hash_table = selector->priv->selected_sources;
+       source = E_SOURCE (source_object);
+       selector = E_SOURCE_SELECTOR (user_data);
 
-       return (g_hash_table_lookup (hash_table, source) != NULL);
+       e_source_write_finish (source, result, &error);
+
+       /* FIXME Display the error in the selector somehow? */
+       if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+
+       g_object_unref (selector);
 }
 
-static void
-select_source (ESourceSelector *selector,
-               ESource *source)
+static gboolean
+source_selector_write_idle_cb (gpointer user_data)
 {
-       if (source_is_selected (selector, source))
-               return;
+       AsyncContext *async_context = user_data;
+       GHashTable *pending_writes;
 
-       g_hash_table_insert (
-               selector->priv->selected_sources,
-               source, g_object_ref (source));
+       /* XXX This operation is not cancellable. */
+       e_source_write (
+               async_context->source, NULL,
+               source_selector_write_done_cb,
+               g_object_ref (async_context->selector));
+
+       pending_writes = async_context->selector->priv->pending_writes;
+       g_hash_table_remove (pending_writes, async_context->source);
+
+       return FALSE;
 }
 
 static void
-unselect_source (ESourceSelector *selector,
-                 ESource *source)
+source_selector_cancel_write (ESourceSelector *selector,
+                              ESource *source)
 {
-       if (!source_is_selected (selector, source))
-               return;
+       GHashTable *pending_writes;
 
-       /* (This will unref the source.)  */
-       g_hash_table_remove (selector->priv->selected_sources, source);
+       /* Cancel any pending writes for this ESource so as not
+        * to overwrite whatever change we're being notified of. */
+       pending_writes = selector->priv->pending_writes;
+       g_hash_table_remove (pending_writes, source);
 }
 
-static gboolean
-find_source_iter (ESourceSelector *selector,
-                  ESource *source,
-                  GtkTreeIter *parent_iter,
-                  GtkTreeIter *source_iter)
+static void
+source_selector_update_row (ESourceSelector *selector,
+                            ESource *source)
 {
+       GHashTable *source_index;
+       ESourceExtension *extension = NULL;
+       GtkTreeRowReference *reference;
        GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
+       const gchar *extension_name;
+       const gchar *display_name;
+       gboolean selected;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       source_index = selector->priv->source_index;
+       reference = g_hash_table_lookup (source_index, source);
+
+       /* This function runs when ANY ESource in the registry changes.
+        * If the ESource is not in our tree model then return silently. */
+       if (reference == NULL)
+               return;
 
-       if (gtk_tree_model_get_iter_first (model, parent_iter)) {
-               do {
-                       if (gtk_tree_model_iter_children (model, source_iter, parent_iter)) {
-                               do {
-                                       gpointer data;
+       /* If we do have a row reference, it should be valid. */
+       g_return_if_fail (gtk_tree_row_reference_valid (reference));
 
-                                       gtk_tree_model_get (model, source_iter, 0, &data, -1);
-                                       g_assert (E_IS_SOURCE (data));
+       model = gtk_tree_row_reference_get_model (reference);
+       path = gtk_tree_row_reference_get_path (reference);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
 
-                                       if (E_SOURCE (data) == source) {
-                                               g_object_unref (data);
+       display_name = e_source_get_display_name (source);
 
-                                               return TRUE;
-                                       }
+       extension_name = e_source_selector_get_extension_name (selector);
+       selected = e_source_selector_source_is_selected (selector, source);
 
-                                       g_object_unref (data);
-                               } while (gtk_tree_model_iter_next (model, source_iter));
-                       }
-               } while (gtk_tree_model_iter_next (model, parent_iter));
-       }
+       if (e_source_has_extension (source, extension_name))
+               extension = e_source_get_extension (source, extension_name);
 
-       return FALSE;
+       if (extension != NULL) {
+               GdkColor color;
+               const gchar *color_spec = NULL;
+               gboolean show_color = FALSE;
+               gboolean show_toggle;
+
+               show_color =
+                       E_IS_SOURCE_SELECTABLE (extension) &&
+                       e_source_selector_get_show_colors (selector);
+
+               if (show_color)
+                       color_spec = e_source_selectable_get_color (
+                               E_SOURCE_SELECTABLE (extension));
+
+               if (color_spec != NULL && *color_spec != '\0')
+                       show_color = gdk_color_parse (color_spec, &color);
+
+               show_toggle = e_source_selector_get_show_toggles (selector);
+
+               gtk_tree_store_set (
+                       GTK_TREE_STORE (model), &iter,
+                       COLUMN_NAME, display_name,
+                       COLUMN_COLOR, show_color ? &color : NULL,
+                       COLUMN_ACTIVE, selected,
+                       COLUMN_SHOW_COLOR, show_color,
+                       COLUMN_SHOW_TOGGLE, show_toggle,
+                       COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
+                       COLUMN_SOURCE, source,
+                       -1);
+       } else {
+               gtk_tree_store_set (
+                       GTK_TREE_STORE (model), &iter,
+                       COLUMN_NAME, display_name,
+                       COLUMN_COLOR, NULL,
+                       COLUMN_ACTIVE, FALSE,
+                       COLUMN_SHOW_COLOR, FALSE,
+                       COLUMN_SHOW_TOGGLE, FALSE,
+                       COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
+                       COLUMN_SOURCE, source,
+                       -1);
+       }
 }
 
-/* Setting up the model.  */
 static gboolean
-rebuild_existing_cb (GtkTreeModel *model,
-                     GtkTreePath *path,
-                     GtkTreeIter *iter,
-                     gpointer data)
+source_selector_traverse (GNode *node,
+                          ESourceSelector *selector)
 {
-       ESourceSelectorRebuildData *rebuild_data = data;
-       ESourceList *source_list;
-       GtkTreeRowReference *reference;
-       gpointer node;
-       const gchar *uid;
-
-       gtk_tree_model_get (model, iter, 0, &node, -1);
+       ESource *source;
+       GHashTable *source_index;
+       GtkTreeRowReference *reference = NULL;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
 
-       source_list = rebuild_data->selector->priv->list;
-       reference = gtk_tree_row_reference_new (model, path);
+       /* Skip the root node. */
+       if (G_NODE_IS_ROOT (node))
+               return FALSE;
 
-       if (E_IS_SOURCE_GROUP (node)) {
-               uid = e_source_group_peek_uid (E_SOURCE_GROUP (node));
+       source_index = selector->priv->source_index;
 
-               if (e_source_list_peek_group_by_uid (source_list, uid))
-                       g_hash_table_insert (
-                               rebuild_data->remaining_uids,
-                               g_strdup (uid), reference);
-               else
-                       rebuild_data->deleted_uids = g_slist_append (
-                               rebuild_data->deleted_uids, reference);
-       } else {
-               uid = e_source_get_uid (E_SOURCE (node));
-
-               if (e_source_list_peek_source_by_uid (source_list, uid)) {
-                       g_hash_table_insert (
-                               rebuild_data->remaining_uids,
-                               g_strdup (uid), reference);
-               } else {
-                       rebuild_data->deleted_uids = g_slist_prepend (
-                               rebuild_data->deleted_uids, reference);
-
-                       if (g_hash_table_remove (rebuild_data->selector->priv->selected_sources, node))
-                               rebuild_data->selection_changed = TRUE;
-               }
-       }
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
 
-       g_object_unref (node);
+       if (node->parent != NULL && node->parent->data != NULL)
+               reference = g_hash_table_lookup (
+                       source_index, node->parent->data);
 
-       return FALSE;
-}
+       if (gtk_tree_row_reference_valid (reference)) {
+               GtkTreeIter parent;
 
-static ESource *
-find_source (ESourceSelector *selector,
-             ESource *source)
-{
-       GSList *groups, *p;
+               path = gtk_tree_row_reference_get_path (reference);
+               gtk_tree_model_get_iter (model, &parent, path);
+               gtk_tree_path_free (path);
 
-       g_return_val_if_fail (selector != NULL, source);
-       g_return_val_if_fail (E_IS_SOURCE (source), source);
+               gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
+       } else
+               gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
 
-       groups = e_source_list_peek_groups (selector->priv->list);
-       for (p = groups; p != NULL; p = p->next) {
-               ESourceGroup *group = E_SOURCE_GROUP (p->data);
-               GSList *sources, *q;
+       source = E_SOURCE (node->data);
 
-               sources = e_source_group_peek_sources (group);
-               for (q = sources; q != NULL; q = q->next) {
-                       ESource *s = E_SOURCE (q->data);
+       path = gtk_tree_model_get_path (model, &iter);
+       reference = gtk_tree_row_reference_new (model, path);
+       g_hash_table_insert (source_index, g_object_ref (source), reference);
+       gtk_tree_path_free (path);
 
-                       if (e_source_equal (s, source))
-                               return s;
-               }
-       }
+       source_selector_update_row (selector, source);
 
-       return source;
+       return FALSE;
 }
 
-/**
- * compare_source_names
- * Compares sources by name.
- **/
-static gint
-compare_source_names (gconstpointer a,
-                      gconstpointer b)
+static void
+source_selector_save_expanded (GtkTreeView *tree_view,
+                               GtkTreePath *path,
+                               GQueue *queue)
 {
-       const gchar *name_a;
-       const gchar *name_b;
-
-       g_return_val_if_fail (E_IS_SOURCE (a), -1);
-       g_return_val_if_fail (E_IS_SOURCE (b),  1);
-
-       name_a = e_source_get_display_name (E_SOURCE (a));
-       name_b = e_source_get_display_name (E_SOURCE (b));
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       ESource *source;
 
-       return g_utf8_collate (name_a, name_b);
+       model = gtk_tree_view_get_model (tree_view);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+       g_queue_push_tail (queue, source);
 }
 
 static void
-rebuild_model (ESourceSelector *selector)
+source_selector_build_model (ESourceSelector *selector)
 {
-       ESourceSelectorRebuildData *rebuild_data;
-       ESource *source;
+       ESourceRegistry *registry;
+       GQueue queue = G_QUEUE_INIT;
+       GHashTable *source_index;
+       GtkTreeView *tree_view;
        GtkTreeModel *model;
-       GtkTreeStore *store;
-       GtkTreeIter iter;
-       GSList *groups, *p;
-       gboolean set_primary;
+       ESource *selected;
+       const gchar *extension_name;
+       GNode *root;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
-       store = GTK_TREE_STORE (model);
+       tree_view = GTK_TREE_VIEW (selector);
 
-       rebuild_data = create_rebuild_data (selector);
-       source = e_source_selector_ref_primary_selection (selector);
+       registry = e_source_selector_get_registry (selector);
+       extension_name = e_source_selector_get_extension_name (selector);
 
-       if (source != NULL) {
-               set_primary = TRUE;
-               g_object_unref (source);
-       } else {
-               set_primary = FALSE;
-       }
+       /* Make sure we have what we need to build the model, since
+        * this can get called early in the initialization phase. */
+       if (registry == NULL || extension_name == NULL)
+               return;
 
-       gtk_tree_model_foreach (model, rebuild_existing_cb, rebuild_data);
+       source_index = selector->priv->source_index;
+       selected = e_source_selector_ref_primary_selection (selector);
 
-       /* Remove any delete sources or groups */
-       for (p = rebuild_data->deleted_uids; p; p = p->next) {
-               GtkTreeRowReference *row_ref = p->data;
-               GtkTreePath *path;
-               GtkTreeIter parent_iter;
-               gboolean parent_exists = FALSE;
-
-               path = gtk_tree_row_reference_get_path (row_ref);
-               if (!path) {
-                       /* skip this, if the reference is not valid any more, like when
-                        * removing a group, which got removed in the previous iteration
-                        * because it had no children */
-                       continue;
-               }
+       /* Save expanded sources to restore later. */
+       gtk_tree_view_map_expanded_rows (
+               tree_view, (GtkTreeViewMappingFunc)
+               source_selector_save_expanded, &queue);
 
-               gtk_tree_model_get_iter (model, &iter, path);
+       model = gtk_tree_view_get_model (tree_view);
+       gtk_tree_store_clear (GTK_TREE_STORE (model));
 
-               /* If it is the last source in the group, delete the group from the tree */
-               if (gtk_tree_model_iter_parent (model, &parent_iter, &iter))
-                       parent_exists = TRUE;
+       g_hash_table_remove_all (source_index);
 
-               gtk_tree_store_remove (store, &iter);
+       root = e_source_registry_build_display_tree (registry, extension_name);
 
-               if (parent_exists && !gtk_tree_model_iter_has_child (model, &parent_iter))
-                       gtk_tree_store_remove (store, &parent_iter);
+       g_node_traverse (
+               root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+               (GNodeTraverseFunc) source_selector_traverse,
+               selector);
 
-               gtk_tree_path_free (path);
-       }
+       e_source_registry_free_display_tree (root);
 
-       /* Add new sources/groups or call row_changed in case they were renamed */
-       groups = e_source_list_peek_groups (selector->priv->list);
-       for (p = groups; p != NULL; p = p->next) {
-               ESourceGroup *group = E_SOURCE_GROUP (p->data);
+       /* Restore previously expanded sources. */
+       while (!g_queue_is_empty (&queue)) {
                GtkTreeRowReference *reference;
-               GSList *sources, *q;
-               gint position;
-
-               sources = e_source_group_peek_sources (group);
-               if (sources == NULL)
-                       continue;
-
-               /* Copy the list and sort by name. */
-               sources = g_slist_copy (sources);
-               sources = g_slist_sort (sources, compare_source_names);
+               ESource *source;
 
-               reference = g_hash_table_lookup (
-                       rebuild_data->remaining_uids,
-                       e_source_group_peek_uid (group));
+               source = g_queue_pop_head (&queue);
+               reference = g_hash_table_lookup (source_index, source);
 
-               if (reference == NULL) {
-                       gtk_tree_store_append (store, &iter, NULL);
-                       gtk_tree_store_set (store, &iter, 0, group, -1);
-               } else {
+               if (gtk_tree_row_reference_valid (reference)) {
                        GtkTreePath *path;
 
                        path = gtk_tree_row_reference_get_path (reference);
-                       gtk_tree_model_get_iter (model, &iter, path);
-                       gtk_tree_model_row_changed (model, path, &iter);
+                       gtk_tree_view_expand_to_path (tree_view, path);
                        gtk_tree_path_free (path);
                }
 
-               for (q = sources, position = 0; q != NULL; q = q->next, position++) {
-                       ESource *source = E_SOURCE (q->data);
-                       GtkTreeIter child_iter;
-
-                       reference = g_hash_table_lookup (
-                               rebuild_data->remaining_uids,
-                               e_source_get_uid (source));
-
-                       if (reference == NULL) {
-                               if (selector->priv->select_new) {
-                                       select_source (selector, source);
-                                       rebuild_data->selection_changed = TRUE;
-                               }
-
-                               gtk_tree_store_insert (
-                                       store, &child_iter, &iter, position);
-                               gtk_tree_store_set (
-                                       store, &child_iter, 0, source, -1);
-                       } else {
-                               GtkTreePath *path;
-
-                               path = gtk_tree_row_reference_get_path (reference);
-                               gtk_tree_model_get_iter (model, &child_iter, path);
-                               gtk_tree_model_row_changed (model, path, &child_iter);
-                               gtk_tree_path_free (path);
-                       }
-               }
-
-               g_slist_free (sources);
+               g_object_unref (source);
        }
 
-       if (rebuild_data->selection_changed)
-               g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
-
-       source = e_source_selector_ref_primary_selection (selector);
-       if (set_primary && source == NULL) {
-               ESourceList *source_list = selector->priv->list;
-               source = e_source_list_peek_source_any (source_list);
-               e_source_selector_set_primary_selection (selector, source);
-       } else if (source != NULL) {
-               g_object_unref (source);
+       /* Restore the primary selection. */
+       if (selected != NULL) {
+               e_source_selector_set_primary_selection (selector, selected);
+               g_object_unref (selected);
        }
 
-       free_rebuild_data (rebuild_data);
+       /* Make sure we have a primary selection.  If not, pick one. */
+       selected = e_source_selector_ref_primary_selection (selector);
+       if (selected == NULL) {
+               selected = e_source_registry_ref_default_for_extension_name (
+                       registry, extension_name);
+               e_source_selector_set_primary_selection (selector, selected);
+       }
+       g_object_unref (selected);
 }
 
-static gboolean
-same_source_name_exists (ESourceSelector *selector,
-                         const gchar *name)
+static void
+source_selector_expand_to_source (ESourceSelector *selector,
+                                  ESource *source)
 {
-       GtkTreeModel *model;
-       GtkTreeIter parent_iter, source_iter;
-
-       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
-       g_return_val_if_fail (name != NULL, FALSE);
-
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
-
-       if (gtk_tree_model_get_iter_first (model, &parent_iter)) {
-               do {
-                       if (gtk_tree_model_iter_children (model, &source_iter, &parent_iter)) {
-                               do {
-                                       gpointer data;
-                                       const gchar *source_name;
-
-                                       gtk_tree_model_get (model, &source_iter, 0, &data, -1);
-                                       g_assert (E_IS_SOURCE (data));
+       GHashTable *source_index;
+       GtkTreeRowReference *reference;
+       GtkTreePath *path;
 
-                                       source_name = e_source_get_display_name (E_SOURCE (data));
-                                       if (source_name && g_str_equal (name, source_name)) {
-                                               g_object_unref (data);
+       source_index = selector->priv->source_index;
+       reference = g_hash_table_lookup (source_index, source);
 
-                                               return TRUE;
-                                       }
+       /* If the ESource is not in our tree model then return silently. */
+       if (reference == NULL)
+               return;
 
-                                       g_object_unref (data);
-                               } while (gtk_tree_model_iter_next (model, &source_iter));
-                       }
-               } while (gtk_tree_model_iter_next (model, &parent_iter));
-       }
+       /* If we do have a row reference, it should be valid. */
+       g_return_if_fail (gtk_tree_row_reference_valid (reference));
 
-       return FALSE;
+       /* Expand the tree view to the path containing the ESource */
+       path = gtk_tree_row_reference_get_path (reference);
+       gtk_tree_view_expand_to_path (GTK_TREE_VIEW (selector), path);
+       gtk_tree_path_free (path);
 }
 
-static gint
-on_idle_rebuild_model_callback (ESourceSelector *selector)
+static void
+source_selector_source_added_cb (ESourceRegistry *registry,
+                                 ESource *source,
+                                 ESourceSelector *selector)
 {
-       rebuild_model (selector);
-       selector->priv->rebuild_model_idle_id = 0;
+       source_selector_build_model (selector);
 
-       return FALSE;
+       source_selector_expand_to_source (selector, source);
 }
 
 static void
-list_changed_callback (ESourceList *list,
-                       ESourceSelector *selector)
+source_selector_source_changed_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
 {
-       ESourceSelectorPrivate *priv = selector->priv;
+       source_selector_cancel_write (selector, source);
 
-       if (priv->rebuild_model_idle_id == 0)
-               priv->rebuild_model_idle_id = g_idle_add ((GSourceFunc) on_idle_rebuild_model_callback,
-                                                         selector);
+       source_selector_update_row (selector, source);
 }
 
-/* Data functions for rendering the model.  */
-
 static void
-toggle_cell_data_func (GtkTreeViewColumn *column,
-                       GtkCellRenderer *renderer,
-                       GtkTreeModel *model,
-                       GtkTreeIter *iter,
-                       ESourceSelector *selector)
+source_selector_source_removed_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
 {
-       gpointer data;
-
-       gtk_tree_model_get (model, iter, 0, &data, -1);
-       if (data == NULL) {
-               g_object_set (renderer, "visible", FALSE, NULL);
-               return;
-       }
-
-       if (E_IS_SOURCE_GROUP (data)) {
-               g_object_set (renderer, "visible", FALSE, NULL);
-       } else {
-               g_assert (E_IS_SOURCE (data));
-
-               g_object_set (renderer, "visible", selector->priv->checkboxes_shown, NULL);
-               if (source_is_selected (selector, E_SOURCE (data)))
-                       g_object_set (renderer, "active", TRUE, NULL);
-               else
-                       g_object_set (renderer, "active", FALSE, NULL);
-       }
-
-       g_object_unref (data);
+       source_selector_build_model (selector);
 }
 
 static void
-text_cell_data_func (GtkTreeViewColumn *column,
-                     GtkCellRenderer *renderer,
-                     GtkTreeModel *model,
-                     GtkTreeIter *iter,
-                     ESourceSelector *selector)
+source_selector_source_enabled_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
 {
-       gpointer data;
-
-       gtk_tree_model_get (model, iter, 0, &data, -1);
-       if (data == NULL) {
-               g_object_set (renderer, "visible", FALSE, NULL);
-               return;
-       }
-
-       if (E_IS_SOURCE_GROUP (data)) {
-               g_object_set (renderer,
-                             "text", e_source_group_peek_name (E_SOURCE_GROUP (data)),
-                             "weight", PANGO_WEIGHT_BOLD,
-                             "foreground_set", FALSE,
-                             NULL);
-       } else {
-               ESource *source;
-
-               g_assert (E_IS_SOURCE (data));
-               source = E_SOURCE (data);
-
-               g_object_set (renderer,
-                             "text", e_source_get_display_name (source),
-                             "weight", PANGO_WEIGHT_NORMAL,
-                             "foreground_set", FALSE,
-                             NULL);
-       }
+       source_selector_build_model (selector);
 
-       g_object_unref (data);
+       source_selector_expand_to_source (selector, source);
 }
 
 static void
-pixbuf_cell_data_func (GtkTreeViewColumn *column,
-                       GtkCellRenderer *renderer,
-                       GtkTreeModel *model,
-                       GtkTreeIter *iter,
-                       ESourceSelector *selector)
+source_selector_source_disabled_cb (ESourceRegistry *registry,
+                                    ESource *source,
+                                    ESourceSelector *selector)
 {
-       gpointer data;
-
-       gtk_tree_model_get (model, iter, 0, &data, -1);
-       g_return_if_fail (G_IS_OBJECT (data));
-
-       if (E_IS_SOURCE_GROUP (data)) {
-               g_object_set (renderer, "visible", FALSE, NULL);
-
-       } else if (E_IS_SOURCE (data)) {
-               ESource *source;
-               GdkPixbuf *pixbuf = NULL;
-               const gchar *color_spec;
-               GdkColor color;
-
-               source = E_SOURCE (data);
-               color_spec = e_source_peek_color_spec (source);
-               if (color_spec != NULL && gdk_color_parse (color_spec, &color)) {
-                       guint32 rgba;
-
-                       pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 16, 16);
+       source_selector_build_model (selector);
+}
 
-                       rgba = (((color.red & 0xff00) << 8) |
-                               ((color.green & 0xff00)) |
-                               ((color.blue & 0xff00) >> 8)) << 8;
+static gboolean
+same_source_name_exists (ESourceSelector *selector,
+                         const gchar *display_name)
+{
+       GHashTable *source_index;
+       GHashTableIter iter;
+       gpointer key;
 
-                       gdk_pixbuf_fill (pixbuf, rgba);
-               }
+       source_index = selector->priv->source_index;
+       g_hash_table_iter_init (&iter, source_index);
 
-               g_object_set (renderer,
-                             "visible", pixbuf != NULL,
-                             "pixbuf", pixbuf,
-                             NULL);
+       while (g_hash_table_iter_next (&iter, &key, NULL)) {
+               ESource *source = E_SOURCE (key);
+               const gchar *source_name;
 
-               if (pixbuf)
-                       g_object_unref (pixbuf);
+               source_name = e_source_get_display_name (source);
+               if (g_strcmp0 (display_name, source_name) == 0)
+                       return TRUE;
        }
 
-       g_object_unref (data);
+       return FALSE;
 }
 
-/* Custom selection function to make groups non selectable.  */
 static gboolean
 selection_func (GtkTreeSelection *selection,
                 GtkTreeModel *model,
@@ -653,12 +555,12 @@ selection_func (GtkTreeSelection *selection,
                 gboolean path_currently_selected,
                 ESourceSelector *selector)
 {
+       ESource *source;
        GtkTreeIter iter;
-       gpointer data;
+       const gchar *extension_name;
 
        if (selector->priv->toggled_last) {
                selector->priv->toggled_last = FALSE;
-
                return FALSE;
        }
 
@@ -668,21 +570,21 @@ selection_func (GtkTreeSelection *selection,
        if (!gtk_tree_model_get_iter (model, &iter, path))
                return FALSE;
 
-       gtk_tree_model_get (model, &iter, 0, &data, -1);
-       if (E_IS_SOURCE_GROUP (data)) {
-               g_object_unref (data);
+       extension_name = e_source_selector_get_extension_name (selector);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
 
+       if (!e_source_has_extension (source, extension_name)) {
+               g_object_unref (source);
                return FALSE;
        }
 
        clear_saved_primary_selection (selector);
-       g_object_unref (data);
+
+       g_object_unref (source);
 
        return TRUE;
 }
 
-/* Callbacks.  */
-
 static void
 text_cell_edited_cb (ESourceSelector *selector,
                      const gchar *path_string,
@@ -699,18 +601,18 @@ text_cell_edited_cb (ESourceSelector *selector,
        path = gtk_tree_path_new_from_string (path_string);
 
        gtk_tree_model_get_iter (model, &iter, path);
-       gtk_tree_model_get (model, &iter, 0, &source, -1);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
        gtk_tree_path_free (path);
 
-       g_return_if_fail (E_IS_SOURCE (source));
-
        if (new_name == NULL || *new_name == '\0')
                return;
 
        if (same_source_name_exists (selector, new_name))
                return;
 
-       e_source_set_name (source, new_name);
+       e_source_set_display_name (source, new_name);
+
+       e_source_selector_queue_write (selector, source);
 }
 
 static void
@@ -718,11 +620,10 @@ cell_toggled_callback (GtkCellRendererToggle *renderer,
                        const gchar *path_string,
                        ESourceSelector *selector)
 {
+       ESource *source;
        GtkTreeModel *model;
        GtkTreePath *path;
        GtkTreeIter iter;
-       ESource *source;
-       gpointer data;
 
        model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
        path = gtk_tree_path_new_from_string (path_string);
@@ -732,24 +633,18 @@ cell_toggled_callback (GtkCellRendererToggle *renderer,
                return;
        }
 
-       gtk_tree_model_get (model, &iter, 0, &data, -1);
-       if (!E_IS_SOURCE_GROUP (data)) {
-               source = E_SOURCE (data);
-
-               if (source_is_selected (selector, source))
-                       unselect_source (selector, source);
-               else
-                       select_source (selector, source);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
 
-               selector->priv->toggled_last = TRUE;
+       if (e_source_selector_source_is_selected (selector, source))
+               e_source_selector_unselect_source (selector, source);
+       else
+               e_source_selector_select_source (selector, source);
 
-               gtk_tree_model_row_changed (model, path, &iter);
-               g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
-       }
+       selector->priv->toggled_last = TRUE;
 
        gtk_tree_path_free (path);
 
-       g_object_unref (data);
+       g_object_unref (source);
 }
 
 static void
@@ -760,52 +655,24 @@ selection_changed_callback (GtkTreeSelection *selection,
        g_object_notify (G_OBJECT (selector), "primary-selection");
 }
 
-static gboolean
-group_search_function (GtkTreeModel *model,
-                       gint column,
-                       const gchar *key,
-                       GtkTreeIter *iter,
-                       gpointer dummy)
+static void
+source_selector_set_extension_name (ESourceSelector *selector,
+                                    const gchar *extension_name)
 {
-       gpointer data;
-       const gchar *name = NULL;
-       gboolean status = TRUE;
+       g_return_if_fail (extension_name != NULL);
+       g_return_if_fail (selector->priv->extension_name == NULL);
 
-       gtk_tree_model_get (model, iter, 0, &data, -1);
-
-       if (E_IS_SOURCE_GROUP (data))
-               name = e_source_group_peek_name (E_SOURCE_GROUP (data));
-       else {
-               g_assert (E_IS_SOURCE (data));
-
-               name = e_source_get_display_name (E_SOURCE (data));
-       }
-
-       if (name)
-               status = g_ascii_strncasecmp (name, key, strlen (key)) != 0;
-
-       g_object_unref (data);
-
-       return status;
+       selector->priv->extension_name = g_strdup (extension_name);
 }
 
 static void
-source_selector_set_source_list (ESourceSelector *selector,
-                                 ESourceList *source_list)
+source_selector_set_registry (ESourceSelector *selector,
+                              ESourceRegistry *registry)
 {
-       g_return_if_fail (E_IS_SOURCE_LIST (source_list));
-       g_return_if_fail (selector->priv->list == NULL);
-
-       selector->priv->list = g_object_ref (source_list);
-
-       rebuild_model (selector);
+       g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+       g_return_if_fail (selector->priv->registry == NULL);
 
-       g_signal_connect_object (
-               source_list, "changed",
-               G_CALLBACK (list_changed_callback),
-               G_OBJECT (selector), 0);
-
-       gtk_tree_view_expand_all (GTK_TREE_VIEW (selector));
+       selector->priv->registry = g_object_ref (registry);
 }
 
 static void
@@ -815,17 +682,35 @@ source_selector_set_property (GObject *object,
                               GParamSpec *pspec)
 {
        switch (property_id) {
+               case PROP_EXTENSION_NAME:
+                       source_selector_set_extension_name (
+                               E_SOURCE_SELECTOR (object),
+                               g_value_get_string (value));
+                       return;
+
                case PROP_PRIMARY_SELECTION:
                        e_source_selector_set_primary_selection (
                                E_SOURCE_SELECTOR (object),
                                g_value_get_object (value));
                        return;
 
-               case PROP_SOURCE_LIST:
-                       source_selector_set_source_list (
+               case PROP_REGISTRY:
+                       source_selector_set_registry (
                                E_SOURCE_SELECTOR (object),
                                g_value_get_object (value));
                        return;
+
+               case PROP_SHOW_COLORS:
+                       e_source_selector_set_show_colors (
+                               E_SOURCE_SELECTOR (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_SHOW_TOGGLES:
+                       e_source_selector_set_show_toggles (
+                               E_SOURCE_SELECTOR (object),
+                               g_value_get_boolean (value));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -838,6 +723,13 @@ source_selector_get_property (GObject *object,
                               GParamSpec *pspec)
 {
        switch (property_id) {
+               case PROP_EXTENSION_NAME:
+                       g_value_set_string (
+                               value,
+                               e_source_selector_get_extension_name (
+                               E_SOURCE_SELECTOR (object)));
+                       return;
+
                case PROP_PRIMARY_SELECTION:
                        g_value_take_object (
                                value,
@@ -845,10 +737,24 @@ source_selector_get_property (GObject *object,
                                E_SOURCE_SELECTOR (object)));
                        return;
 
-               case PROP_SOURCE_LIST:
+               case PROP_REGISTRY:
                        g_value_set_object (
                                value,
-                               e_source_selector_get_source_list (
+                               e_source_selector_get_registry (
+                               E_SOURCE_SELECTOR (object)));
+                       return;
+
+               case PROP_SHOW_COLORS:
+                       g_value_set_boolean (
+                               value,
+                               e_source_selector_get_show_colors (
+                               E_SOURCE_SELECTOR (object)));
+                       return;
+
+               case PROP_SHOW_TOGGLES:
+                       g_value_set_boolean (
+                               value,
+                               e_source_selector_get_show_toggles (
                                E_SOURCE_SELECTOR (object)));
                        return;
        }
@@ -863,17 +769,17 @@ source_selector_dispose (GObject *object)
 
        priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
 
-       g_hash_table_remove_all (priv->selected_sources);
-
-       if (priv->rebuild_model_idle_id != 0) {
-               g_source_remove (priv->rebuild_model_idle_id);
-               priv->rebuild_model_idle_id = 0;
+       if (priv->registry != NULL) {
+               g_signal_handlers_disconnect_matched (
+                       priv->registry,
+                       G_SIGNAL_MATCH_DATA,
+                       0, 0, NULL, NULL, object);
+               g_object_unref (priv->registry);
+               priv->registry = NULL;
        }
 
-       if (priv->list != NULL) {
-               g_object_unref (priv->list);
-               priv->list = NULL;
-       }
+       g_hash_table_remove_all (priv->source_index);
+       g_hash_table_remove_all (priv->pending_writes);
 
        clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
 
@@ -888,12 +794,52 @@ source_selector_finalize (GObject *object)
 
        priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
 
-       g_hash_table_destroy (priv->selected_sources);
+       g_hash_table_destroy (priv->source_index);
+       g_hash_table_destroy (priv->pending_writes);
+
+       g_free (priv->extension_name);
+
+       if (priv->main_context != NULL)
+               g_main_context_unref (priv->main_context);
 
        /* Chain up to parent's finalize() method. */
        G_OBJECT_CLASS (e_source_selector_parent_class)->finalize (object);
 }
 
+static void
+source_selector_constructed (GObject *object)
+{
+       ESourceRegistry *registry;
+       ESourceSelector *selector;
+
+       selector = E_SOURCE_SELECTOR (object);
+       registry = e_source_selector_get_registry (selector);
+
+       g_signal_connect (
+               registry, "source-added",
+               G_CALLBACK (source_selector_source_added_cb), selector);
+
+       g_signal_connect (
+               registry, "source-changed",
+               G_CALLBACK (source_selector_source_changed_cb), selector);
+
+       g_signal_connect (
+               registry, "source-removed",
+               G_CALLBACK (source_selector_source_removed_cb), selector);
+
+       g_signal_connect (
+               registry, "source-enabled",
+               G_CALLBACK (source_selector_source_enabled_cb), selector);
+
+       g_signal_connect (
+               registry, "source-disabled",
+               G_CALLBACK (source_selector_source_disabled_cb), selector);
+
+       source_selector_build_model (selector);
+
+       gtk_tree_view_expand_all (GTK_TREE_VIEW (selector));
+}
+
 static gboolean
 source_selector_button_press_event (GtkWidget *widget,
                                     GdkEventButton *event)
@@ -929,25 +875,11 @@ source_selector_button_press_event (GtkWidget *widget,
        if (row_exists) {
                GtkTreeModel *model;
                GtkTreeIter iter;
-               gpointer data;
 
                model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
 
-               if (gtk_tree_model_get_iter (model, &iter, path)) {
-                       gtk_tree_model_get (model, &iter, 0, &data, -1);
-
-                       /* Do not emit popup since we will
-                        * not be able to get the ESource. */
-                       if (E_IS_SOURCE_GROUP (data)) {
-                               selector->priv->primary_source_group =
-                                       g_object_ref (data);
-                               /* Data shuld be unreffed after
-                                * creating the new source. */
-                               goto chainup;
-                       }
-
-                       source = E_SOURCE (data);
-               }
+               gtk_tree_model_get_iter (model, &iter, path);
+               gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
        }
 
        if (source == NULL)
@@ -1000,13 +932,13 @@ source_selector_drag_motion (GtkWidget *widget,
                              gint y,
                              guint time_)
 {
+       ESource *source = NULL;
        GtkTreeView *tree_view;
        GtkTreeModel *model;
        GtkTreePath *path = NULL;
        GtkTreeIter iter;
        GtkTreeViewDropPosition pos;
        GdkDragAction action = 0;
-       gpointer object = NULL;
 
        tree_view = GTK_TREE_VIEW (widget);
        model = gtk_tree_view_get_model (tree_view);
@@ -1017,9 +949,9 @@ source_selector_drag_motion (GtkWidget *widget,
        if (!gtk_tree_model_get_iter (model, &iter, path))
                goto exit;
 
-       gtk_tree_model_get (model, &iter, 0, &object, -1);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
 
-       if (E_IS_SOURCE_GROUP (object) || e_source_get_readonly (object))
+       if (!e_source_get_writable (source))
                goto exit;
 
        pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
@@ -1034,8 +966,8 @@ exit:
        if (path != NULL)
                gtk_tree_path_free (path);
 
-       if (object != NULL)
-               g_object_unref (object);
+       if (source != NULL)
+               g_object_unref (source);
 
        gdk_drag_status (context, action, time_);
 
@@ -1049,13 +981,15 @@ source_selector_drag_drop (GtkWidget *widget,
                            gint y,
                            guint time_)
 {
+       ESource *source;
+       ESourceSelector *selector;
        GtkTreeView *tree_view;
        GtkTreeModel *model;
        GtkTreePath *path;
        GtkTreeIter iter;
+       const gchar *extension_name;
        gboolean drop_zone;
        gboolean valid;
-       gpointer object;
 
        tree_view = GTK_TREE_VIEW (widget);
        model = gtk_tree_view_get_model (tree_view);
@@ -1068,9 +1002,13 @@ source_selector_drag_drop (GtkWidget *widget,
        gtk_tree_path_free (path);
        g_return_val_if_fail (valid, FALSE);
 
-       gtk_tree_model_get (model, &iter, 0, &object, -1);
-       drop_zone = E_IS_SOURCE (object);
-       g_object_unref (object);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+       selector = E_SOURCE_SELECTOR (widget);
+       extension_name = e_source_selector_get_extension_name (selector);
+       drop_zone = e_source_has_extension (source, extension_name);
+
+       g_object_unref (source);
 
        return drop_zone;
 }
@@ -1084,17 +1022,20 @@ source_selector_drag_data_received (GtkWidget *widget,
                                     guint info,
                                     guint time_)
 {
+       ESource *source = NULL;
        GtkTreeView *tree_view;
        GtkTreeModel *model;
        GtkTreePath *path = NULL;
        GtkTreeIter iter;
-       gpointer object = NULL;
+       GdkDragAction action;
        gboolean delete;
        gboolean success = FALSE;
 
        tree_view = GTK_TREE_VIEW (widget);
        model = gtk_tree_view_get_model (tree_view);
-       delete = (gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE);
+
+       action = gdk_drag_context_get_selected_action (context);
+       delete = (action == GDK_ACTION_MOVE);
 
        if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
                goto exit;
@@ -1102,22 +1043,22 @@ source_selector_drag_data_received (GtkWidget *widget,
        if (!gtk_tree_model_get_iter (model, &iter, path))
                goto exit;
 
-       gtk_tree_model_get (model, &iter, 0, &object, -1);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
 
-       if (!E_IS_SOURCE (object) || e_source_get_readonly (object))
+       if (!e_source_get_writable (source))
                goto exit;
 
        g_signal_emit (
                widget, signals[DATA_DROPPED], 0, selection_data,
-               object, gdk_drag_context_get_selected_action (context),
+               source, gdk_drag_context_get_selected_action (context),
                info, &success);
 
 exit:
        if (path != NULL)
                gtk_tree_path_free (path);
 
-       if (object != NULL)
-               g_object_unref (object);
+       if (source != NULL)
+               g_object_unref (source);
 
        gtk_drag_finish (context, success, delete, time_);
 }
@@ -1208,18 +1149,63 @@ source_selector_row_expanded (GtkTreeView *tree_view,
        gtk_tree_path_free (child_path);
 }
 
-/* Initialization.  */
+static gboolean
+source_selector_get_source_selected (ESourceSelector *selector,
+                                     ESource *source)
+{
+       ESourceSelectable *extension;
+       const gchar *extension_name;
+       gboolean selected = TRUE;
+
+       extension_name = e_source_selector_get_extension_name (selector);
+
+       if (!e_source_has_extension (source, extension_name))
+               return FALSE;
+
+       extension = e_source_get_extension (source, extension_name);
+
+       if (E_IS_SOURCE_SELECTABLE (extension))
+               selected = e_source_selectable_get_selected (extension);
+
+       return selected;
+}
+
+static void
+source_selector_set_source_selected (ESourceSelector *selector,
+                                     ESource *source,
+                                     gboolean selected)
+{
+       ESourceSelectable *extension;
+       const gchar *extension_name;
+
+       extension_name = e_source_selector_get_extension_name (selector);
+
+       if (!e_source_has_extension (source, extension_name))
+               return;
+
+       extension = e_source_get_extension (source, extension_name);
+
+       if (!E_IS_SOURCE_SELECTABLE (extension))
+               return;
+
+       if (selected != e_source_selectable_get_selected (extension)) {
+               e_source_selectable_set_selected (extension, selected);
+               e_source_selector_queue_write (selector, source);
+       }
+}
+
 static gboolean
 ess_bool_accumulator (GSignalInvocationHint *ihint,
                       GValue *out,
                       const GValue *in,
                       gpointer data)
 {
-       gboolean val = g_value_get_boolean (in);
+       gboolean v_boolean;
 
-       g_value_set_boolean (out, val);
+       v_boolean = g_value_get_boolean (in);
+       g_value_set_boolean (out, v_boolean);
 
-       return !val;
+       return !v_boolean;
 }
 
 static void
@@ -1236,6 +1222,7 @@ e_source_selector_class_init (ESourceSelectorClass *class)
        object_class->get_property = source_selector_get_property;
        object_class->dispose  = source_selector_dispose;
        object_class->finalize = source_selector_finalize;
+       object_class->constructed = source_selector_constructed;
 
        widget_class = GTK_WIDGET_CLASS (class);
        widget_class->button_press_event = source_selector_button_press_event;
@@ -1249,6 +1236,21 @@ e_source_selector_class_init (ESourceSelectorClass *class)
        tree_view_class->test_collapse_row = source_selector_test_collapse_row;
        tree_view_class->row_expanded = source_selector_row_expanded;
 
+       class->get_source_selected = source_selector_get_source_selected;
+       class->set_source_selected = source_selector_set_source_selected;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_EXTENSION_NAME,
+               g_param_spec_string (
+                       "extension-name",
+                       NULL,
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
        g_object_class_install_property (
                object_class,
                PROP_PRIMARY_SELECTION,
@@ -1262,16 +1264,38 @@ e_source_selector_class_init (ESourceSelectorClass *class)
 
        g_object_class_install_property (
                object_class,
-               PROP_SOURCE_LIST,
+               PROP_REGISTRY,
                g_param_spec_object (
-                       "source-list",
+                       "registry",
                        NULL,
                        NULL,
-                       E_TYPE_SOURCE_LIST,
+                       E_TYPE_SOURCE_REGISTRY,
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT_ONLY |
                        G_PARAM_STATIC_STRINGS));
 
+       g_object_class_install_property (
+               object_class,
+               PROP_SHOW_COLORS,
+               g_param_spec_boolean (
+                       "show-colors",
+                       NULL,
+                       NULL,
+                       TRUE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SHOW_TOGGLES,
+               g_param_spec_boolean (
+                       "show-toggles",
+                       NULL,
+                       NULL,
+                       TRUE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
        signals[SELECTION_CHANGED] = g_signal_new (
                "selection-changed",
                G_OBJECT_CLASS_TYPE (object_class),
@@ -1319,64 +1343,91 @@ e_source_selector_class_init (ESourceSelectorClass *class)
 static void
 e_source_selector_init (ESourceSelector *selector)
 {
+       GHashTable *pending_writes;
        GtkTreeViewColumn *column;
-       GtkCellRenderer *cell_renderer;
        GtkTreeSelection *selection;
+       GtkCellRenderer *renderer;
        GtkTreeStore *tree_store;
        GtkTreeView *tree_view;
 
+       pending_writes = g_hash_table_new_full (
+               (GHashFunc) g_direct_hash,
+               (GEqualFunc) g_direct_equal,
+               (GDestroyNotify) g_object_unref,
+               (GDestroyNotify) pending_writes_destroy_source);
+
        selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);
 
+       selector->priv->pending_writes = pending_writes;
+
+       selector->priv->main_context = g_main_context_get_thread_default ();
+       if (selector->priv->main_context != NULL)
+               g_main_context_ref (selector->priv->main_context);
+
        tree_view = GTK_TREE_VIEW (selector);
 
-       gtk_tree_view_set_search_column (tree_view, 0);
-       gtk_tree_view_set_search_equal_func (tree_view, group_search_function, NULL, NULL);
+       gtk_tree_view_set_search_column (tree_view, COLUMN_SOURCE);
        gtk_tree_view_set_enable_search (tree_view, TRUE);
 
        selector->priv->toggled_last = FALSE;
-       selector->priv->checkboxes_shown = TRUE;
        selector->priv->select_new = FALSE;
+       selector->priv->show_colors = TRUE;
+       selector->priv->show_toggles = TRUE;
 
-       selector->priv->selected_sources = g_hash_table_new_full (
-               g_direct_hash, g_direct_equal,
+       selector->priv->source_index = g_hash_table_new_full (
+               (GHashFunc) e_source_hash,
+               (GEqualFunc) e_source_equal,
                (GDestroyNotify) g_object_unref,
-               (GDestroyNotify) NULL);
+               (GDestroyNotify) gtk_tree_row_reference_free);
+
+       tree_store = gtk_tree_store_new (
+               NUM_COLUMNS,
+               G_TYPE_STRING,          /* COLUMN_NAME */
+               GDK_TYPE_COLOR,         /* COLUMN_COLOR */
+               G_TYPE_BOOLEAN,         /* COLUMN_ACTIVE */
+               G_TYPE_BOOLEAN,         /* COLUMN_SHOW_COLOR */
+               G_TYPE_BOOLEAN,         /* COLUMN_SHOW_TOGGLE */
+               G_TYPE_INT,             /* COLUMN_WEIGHT */
+               E_TYPE_SOURCE);         /* COLUMN_SOURCE */
 
-       tree_store = gtk_tree_store_new (1, G_TYPE_OBJECT);
        gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));
 
        column = gtk_tree_view_column_new ();
        gtk_tree_view_append_column (tree_view, column);
 
-       cell_renderer = gtk_cell_renderer_pixbuf_new ();
+       renderer = e_cell_renderer_color_new ();
        g_object_set (
-               G_OBJECT (cell_renderer),
-               "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
-       gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
-       gtk_tree_view_column_set_cell_data_func (
-               column, cell_renderer, (GtkTreeCellDataFunc)
-               pixbuf_cell_data_func, selector, NULL);
-
-       cell_renderer = e_cell_renderer_safe_toggle_new ();
-       gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
-       gtk_tree_view_column_set_cell_data_func (
-               column, cell_renderer, (GtkTreeCellDataFunc)
-               toggle_cell_data_func, selector, NULL);
+               G_OBJECT (renderer), "mode",
+               GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
+       gtk_tree_view_column_pack_start (column, renderer, FALSE);
+       gtk_tree_view_column_add_attribute (
+               column, renderer, "color", COLUMN_COLOR);
+       gtk_tree_view_column_add_attribute (
+               column, renderer, "visible", COLUMN_SHOW_COLOR);
+
+       renderer = e_cell_renderer_safe_toggle_new ();
+       gtk_tree_view_column_pack_start (column, renderer, FALSE);
+       gtk_tree_view_column_add_attribute (
+               column, renderer, "active", COLUMN_ACTIVE);
+       gtk_tree_view_column_add_attribute (
+               column, renderer, "visible", COLUMN_SHOW_TOGGLE);
        g_signal_connect (
-               cell_renderer, "toggled",
+               renderer, "toggled",
                G_CALLBACK (cell_toggled_callback), selector);
 
-       cell_renderer = gtk_cell_renderer_text_new ();
+       renderer = gtk_cell_renderer_text_new ();
        g_object_set (
-               G_OBJECT (cell_renderer),
+               G_OBJECT (renderer),
                "ellipsize", PANGO_ELLIPSIZE_END, NULL);
        g_signal_connect_swapped (
-               cell_renderer, "edited",
+               renderer, "edited",
                G_CALLBACK (text_cell_edited_cb), selector);
-       gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
-       gtk_tree_view_column_set_cell_data_func (
-               column, cell_renderer, (GtkTreeCellDataFunc)
-               text_cell_data_func, selector, NULL);
+       gtk_tree_view_column_pack_start (column, renderer, TRUE);
+       gtk_tree_view_column_set_attributes (
+               column, renderer,
+               "text", COLUMN_NAME,
+               "weight", COLUMN_WEIGHT,
+               NULL);
 
        selection = gtk_tree_view_get_selection (tree_view);
        gtk_tree_selection_set_select_function (
@@ -1392,154 +1443,216 @@ e_source_selector_init (ESourceSelector *selector)
 
 /**
  * e_source_selector_new:
- * @list: A source list.
+ * @registry: an #ESourceRegistry
+ * @extension_name: the name of an #ESource extension
  *
- * Create a new view for @list.  The view will update automatically when @list
- * changes.
+ * Displays a list of sources from @registry having an extension named
+ * @extension_name.  The sources are grouped by backend or groupware
+ * account, which are described by the parent source.
  *
- * Returns: The newly created widget.
+ * Returns: a new #ESourceSelector
  **/
 GtkWidget *
-e_source_selector_new (ESourceList *list)
+e_source_selector_new (ESourceRegistry *registry,
+                       const gchar *extension_name)
 {
-       g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+       g_return_val_if_fail (extension_name != NULL, NULL);
 
        return g_object_new (
-               E_TYPE_SOURCE_SELECTOR, "source-list", list, NULL);
+               E_TYPE_SOURCE_SELECTOR, "registry", registry,
+               "extension-name", extension_name, NULL);
 }
 
 /**
- * e_source_selector_get_source_list:
+ * e_source_selector_get_registry:
  * @selector: an #ESourceSelector
  *
- * Returns the #ESourceList that @selector is rendering.
+ * Returns the #ESourceRegistry that @selector is getting sources from.
  *
- * Returns: an #ESourceList
+ * Returns: an #ESourceRegistry
  *
- * Since: 2.24
+ * Since: 3.6
  **/
-ESourceList *
-e_source_selector_get_source_list (ESourceSelector *selector)
+ESourceRegistry *
+e_source_selector_get_registry (ESourceSelector *selector)
 {
        g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
 
-       return selector->priv->list;
+       return selector->priv->registry;
 }
 
 /**
- * e_source_selector_get_selection:
+ * e_source_selector_get_extension_name:
  * @selector: an #ESourceSelector
  *
- * Get the list of selected sources, i.e. those that were enabled through the
- * corresponding checkboxes in the tree.
+ * Returns the extension name used to filter which sources are displayed.
  *
- * Returns: A list of the ESources currently selected.  The sources will
- * be in the same order as they appear on the screen, and the list should be
- * freed using e_source_selector_free_selection().
+ * Returns: the #ESource extension name
+ *
+ * Since: 3.6
  **/
-GSList *
-e_source_selector_get_selection (ESourceSelector *selector)
+const gchar *
+e_source_selector_get_extension_name (ESourceSelector *selector)
 {
-       GSList *selection_list;
-       GSList *groups;
-       GSList *p;
-
        g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
 
-       selection_list = NULL;
-
-       groups = e_source_list_peek_groups (selector->priv->list);
-       for (p = groups; p != NULL; p = p->next) {
-               ESourceGroup *group = E_SOURCE_GROUP (p->data);
-               GSList *sources;
-               GSList *q;
-
-               sources = e_source_group_peek_sources (group);
-               for (q = sources; q != NULL; q = q->next) {
-                       ESource *source = E_SOURCE (q->data);
+       return selector->priv->extension_name;
+}
 
-                       if (source_is_selected (selector, source)) {
-                               selection_list = g_slist_prepend (selection_list, source);
-                               g_object_ref (source);
-                       }
-               }
-       }
+/**
+ * e_source_selector_get_show_colors:
+ * @selector: an #ESourceSelector
+ *
+ * Returns whether colors are shown next to data sources.
+ *
+ * Returns: %TRUE if colors are being shown
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_selector_get_show_colors (ESourceSelector *selector)
+{
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
 
-       return g_slist_reverse (selection_list);
+       return selector->priv->show_colors;
 }
 
 /**
- * e_source_selector_get_primary_source_group:
+ * e_source_selector_set_show_colors:
  * @selector: an #ESourceSelector
+ * @show_colors: whether to show colors
  *
- * Gets the primary source group associated with the selector.
+ * Sets whether to show colors next to data sources.
  *
- * Returns: primary_source_group if selector is valid, NULL otherwise.
+ * Since: 3.6
  **/
-ESourceGroup *
-e_source_selector_get_primary_source_group (ESourceSelector *selector)
+void
+e_source_selector_set_show_colors (ESourceSelector *selector,
+                                   gboolean show_colors)
 {
-       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+       g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
 
-       return selector->priv->primary_source_group;
+       if (show_colors == selector->priv->show_colors)
+               return;
+
+       selector->priv->show_colors = show_colors;
 
+       g_object_notify (G_OBJECT (selector), "show-colors");
+
+       source_selector_build_model (selector);
 }
 
 /**
- * e_source_list_free_selection:
- * @list: A selection list returned by e_source_selector_get_selection().
+ * e_source_selector_get_show_toggles:
+ * @selector: an #ESourceSelector
  *
- * Free the selection list.
+ * Returns whether toggles are shown next to data sources.
+ *
+ * Returns: %TRUE if toggles are being shown
+ *
+ * Since: 3.6
  **/
-void
-e_source_selector_free_selection (GSList *list)
+gboolean
+e_source_selector_get_show_toggles (ESourceSelector *selector)
 {
-       g_slist_foreach (list, (GFunc) g_object_unref, NULL);
-       g_slist_free (list);
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+
+       return selector->priv->show_toggles;
 }
 
 /**
- * e_source_selector_show_selection:
- * @selector: An ESourceSelector widget
+ * e_source_selector_set_show_toggles:
+ * @selector: an #ESourceSelector
+ * @show_toggles: whether to show toggles
  *
- * Specify whether the checkboxes in the ESourceSelector should be shown or
- * not.
+ * Sets whether to show toggles next to data sources.
+ *
+ * Since: 3.6
  **/
 void
-e_source_selector_show_selection (ESourceSelector *selector,
-                                  gboolean show)
+e_source_selector_set_show_toggles (ESourceSelector *selector,
+                                   gboolean show_toggles)
 {
-       GtkTreeModel *model;
-
        g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
 
-       show = !! show;
-       if (show == selector->priv->checkboxes_shown)
+       if (show_toggles == selector->priv->show_toggles)
                return;
 
-       selector->priv->checkboxes_shown = show;
+       selector->priv->show_toggles = show_toggles;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       g_object_notify (G_OBJECT (selector), "show-toggles");
 
-       gtk_tree_model_foreach (
-               model, (GtkTreeModelForeachFunc)
-               gtk_tree_model_row_changed, NULL);
+       source_selector_build_model (selector);
+}
+
+/* Helper for e_source_selector_get_selection() */
+static gboolean
+source_selector_check_selected (GtkTreeModel *model,
+                                GtkTreePath *path,
+                                GtkTreeIter *iter,
+                                gpointer user_data)
+{
+       ESource *source;
+
+       struct {
+               ESourceSelector *selector;
+               GSList *list;
+       } *closure = user_data;
+
+       gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);
+
+       if (e_source_selector_source_is_selected (closure->selector, source))
+               closure->list = g_slist_prepend (closure->list, source);
+       else
+               g_object_unref (source);
+
+       return FALSE;
 }
 
 /**
- * e_source_selector_selection_shown:
+ * e_source_selector_get_selection:
  * @selector: an #ESourceSelector
  *
- * Check whether the checkboxes in the ESourceSelector are being shown or not.
+ * Get the list of selected sources, i.e. those that were enabled through the
+ * corresponding checkboxes in the tree.
  *
- * Returns: %TRUE if the checkboxes are shown, %FALSE otherwise.
+ * Returns: A list of the ESources currently selected.  The sources will
+ * be in the same order as they appear on the screen, and the list should be
+ * freed using e_source_selector_free_selection().
  **/
-gboolean
-e_source_selector_selection_shown (ESourceSelector *selector)
+GSList *
+e_source_selector_get_selection (ESourceSelector *selector)
 {
-       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+       struct {
+               ESourceSelector *selector;
+               GSList *list;
+       } closure;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+       closure.selector = selector;
+       closure.list = NULL;
+
+       gtk_tree_model_foreach (
+               gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
+               (GtkTreeModelForeachFunc) source_selector_check_selected,
+               &closure);
 
-       return selector->priv->checkboxes_shown;
+       return g_slist_reverse (closure.list);
+}
+
+/**
+ * e_source_list_free_selection:
+ * @list: A selection list returned by e_source_selector_get_selection().
+ *
+ * Free the selection list.
+ **/
+void
+e_source_selector_free_selection (GSList *list)
+{
+       g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+       g_slist_free (list);
 }
 
 /**
@@ -1569,31 +1682,24 @@ void
 e_source_selector_select_source (ESourceSelector *selector,
                                  ESource *source)
 {
-       GtkTreeIter parent_iter, source_iter;
+       ESourceSelectorClass *class;
+       GtkTreeRowReference *reference;
+       GHashTable *source_index;
 
        g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
        g_return_if_fail (E_IS_SOURCE (source));
 
-       source = find_source (selector, source);
-       g_return_if_fail (source != NULL);
-
-       if (source_is_selected (selector, source))
-               return;
-
-       select_source (selector, source);
-
-       if (find_source_iter (selector, source, &parent_iter, &source_iter)) {
-               GtkTreeModel *model;
-               GtkTreePath *path;
+       /* Make sure the ESource is in our tree model. */
+       source_index = selector->priv->source_index;
+       reference = g_hash_table_lookup (source_index, source);
+       g_return_if_fail (gtk_tree_row_reference_valid (reference));
 
-               model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+       g_return_if_fail (class->set_source_selected != NULL);
 
-               path = gtk_tree_model_get_path (model, &source_iter);
-               gtk_tree_model_row_changed (model, path, &source_iter);
-               gtk_tree_path_free (path);
+       class->set_source_selected (selector, source, TRUE);
 
-               g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
-       }
+       g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
 }
 
 /**
@@ -1607,62 +1713,24 @@ void
 e_source_selector_unselect_source (ESourceSelector *selector,
                                    ESource *source)
 {
-       GtkTreeIter parent_iter, source_iter;
+       ESourceSelectorClass *class;
+       GtkTreeRowReference *reference;
+       GHashTable *source_index;
 
        g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
        g_return_if_fail (E_IS_SOURCE (source));
 
-       source = find_source (selector, source);
-       g_return_if_fail (source != NULL);
-
-       if (!source_is_selected (selector, source))
-               return;
-
-       unselect_source (selector, source);
+       /* Make sure the ESource is in our tree model. */
+       source_index = selector->priv->source_index;
+       reference = g_hash_table_lookup (source_index, source);
+       g_return_if_fail (gtk_tree_row_reference_valid (reference));
 
-       if (find_source_iter (selector, source, &parent_iter, &source_iter)) {
-               GtkTreeModel *model;
-               GtkTreePath *path;
-
-               model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
-
-               path = gtk_tree_model_get_path (model, &source_iter);
-               gtk_tree_model_row_changed (model, path, &source_iter);
-               gtk_tree_path_free (path);
-
-               g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
-       }
-}
-
-/* Helper for e_source_selector_select_exclusive() */
-static gboolean
-source_selector_select_exclusive_foreach (GtkTreeModel *model,
-                                          GtkTreePath *path,
-                                          GtkTreeIter *iter,
-                                          gpointer user_data)
-{
-       ESource *source;
+       class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+       g_return_if_fail (class->set_source_selected != NULL);
 
-       struct {
-               ESourceSelector *selector;
-               ESource *source_to_select;
-       } *data = user_data;
+       class->set_source_selected (selector, source, FALSE);
 
-       if (gtk_tree_path_get_depth (path) != 2)
-               return FALSE;
-
-       gtk_tree_model_get (model, iter, 0, &source, -1);
-
-       if (source == data->source_to_select)
-               select_source (data->selector, source);
-       else
-               unselect_source (data->selector, source);
-
-       gtk_tree_model_row_changed (model, path, iter);
-
-       g_object_unref (source);
-
-       return FALSE;
+       g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
 }
 
 /**
@@ -1678,27 +1746,24 @@ void
 e_source_selector_select_exclusive (ESourceSelector *selector,
                                     ESource *source)
 {
-       GtkTreeModel *model;
-
-       struct {
-               ESourceSelector *selector;
-               ESource *source_to_select;
-       } data;
+       ESourceSelectorClass *class;
+       GHashTable *source_index;
+       GHashTableIter iter;
+       gpointer key;
 
        g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
        g_return_if_fail (E_IS_SOURCE (source));
 
-       source = find_source (selector, source);
-       g_return_if_fail (source != NULL);
-
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+       g_return_if_fail (class->set_source_selected != NULL);
 
-       data.selector = selector;
-       data.source_to_select = source;
+       source_index = selector->priv->source_index;
+       g_hash_table_iter_init (&iter, source_index);
 
-       gtk_tree_model_foreach (
-               model, (GtkTreeModelForeachFunc)
-               source_selector_select_exclusive_foreach, &data);
+       while (g_hash_table_iter_next (&iter, &key, NULL)) {
+               gboolean selected = e_source_equal (key, source);
+               class->set_source_selected (selector, source, selected);
+       }
 
        g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
 }
@@ -1716,13 +1781,22 @@ gboolean
 e_source_selector_source_is_selected (ESourceSelector *selector,
                                       ESource *source)
 {
+       ESourceSelectorClass *class;
+       GtkTreeRowReference *reference;
+       GHashTable *source_index;
+
        g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
        g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
 
-       source = find_source (selector, source);
-       g_return_val_if_fail (source != NULL, FALSE);
+       /* Make sure the ESource is in our tree model. */
+       source_index = selector->priv->source_index;
+       reference = g_hash_table_lookup (source_index, source);
+       g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
+
+       class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+       g_return_val_if_fail (class->get_source_selected != NULL, FALSE);
 
-       return source_is_selected (selector, source);
+       return class->get_source_selected (selector, source);
 }
 
 /**
@@ -1805,13 +1879,14 @@ e_source_selector_edit_primary_selection (ESourceSelector *selector)
 ESource *
 e_source_selector_ref_primary_selection (ESourceSelector *selector)
 {
+       ESource *source;
        GtkTreeRowReference *reference;
        GtkTreeSelection *selection;
        GtkTreeView *tree_view;
        GtkTreeModel *model;
        GtkTreeIter iter;
+       const gchar *extension_name;
        gboolean have_iter = FALSE;
-       gpointer data = NULL;
 
        g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
 
@@ -1836,17 +1911,16 @@ e_source_selector_ref_primary_selection (ESourceSelector *selector)
        if (!have_iter)
                return NULL;
 
-       gtk_tree_model_get (model, &iter, 0, &data, -1);
+       gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
 
-       if (data == NULL)
-               return NULL;
+       extension_name = e_source_selector_get_extension_name (selector);
 
-       if (!E_IS_SOURCE (data)) {
-               g_object_unref (data);
+       if (!e_source_has_extension (source, extension_name)) {
+               g_object_unref (source);
                return NULL;
        }
 
-       return E_SOURCE (data);
+       return source;
 }
 
 /**
@@ -1860,64 +1934,66 @@ void
 e_source_selector_set_primary_selection (ESourceSelector *selector,
                                          ESource *source)
 {
-       ESourceSelectorPrivate *priv;
-       GtkTreeIter parent_iter, source_iter;
+       GHashTable *source_index;
+       GtkTreeRowReference *reference;
+       GtkTreeSelection *selection;
+       GtkTreeView *tree_view;
+       GtkTreePath *child_path;
+       GtkTreePath *parent_path;
+       const gchar *extension_name;
 
-       g_return_if_fail (selector != NULL);
        g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
-       g_return_if_fail (source != NULL);
        g_return_if_fail (E_IS_SOURCE (source));
 
-       priv = selector->priv;
+       tree_view = GTK_TREE_VIEW (selector);
+       selection = gtk_tree_view_get_selection (tree_view);
 
-       source = find_source (selector, source);
-       g_return_if_fail (source != NULL);
+       source_index = selector->priv->source_index;
+       reference = g_hash_table_lookup (source_index, source);
 
-       if (find_source_iter (selector, source, &parent_iter, &source_iter)) {
-               GtkTreeSelection *selection;
-               GtkTreeView *tree_view;
-               GtkTreeModel *model;
-               GtkTreePath *path;
+       /* XXX Maybe we should return a success/fail boolean? */
+       if (!gtk_tree_row_reference_valid (reference))
+               return;
 
-               tree_view = GTK_TREE_VIEW (selector);
-               model = gtk_tree_view_get_model (tree_view);
-               selection = gtk_tree_view_get_selection (tree_view);
+       extension_name = e_source_selector_get_extension_name (selector);
+       g_return_if_fail (e_source_has_extension (source, extension_name));
 
-               /* We block the signal because this all needs to be atomic */
-               g_signal_handlers_block_matched (selection, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, selection_changed_callback, NULL);
-               gtk_tree_selection_unselect_all (selection);
-               g_signal_handlers_unblock_matched (selection, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, selection_changed_callback, NULL);
+       /* We block the signal because this all needs to be atomic */
+       g_signal_handlers_block_matched (
+               selection, G_SIGNAL_MATCH_FUNC,
+               0, 0, NULL, selection_changed_callback, NULL);
+       gtk_tree_selection_unselect_all (selection);
+       g_signal_handlers_unblock_matched (
+               selection, G_SIGNAL_MATCH_FUNC,
+               0, 0, NULL, selection_changed_callback, NULL);
 
-               clear_saved_primary_selection (selector);
+       clear_saved_primary_selection (selector);
 
-               path = gtk_tree_model_get_path (model, &parent_iter);
+       child_path = gtk_tree_row_reference_get_path (reference);
 
-               if (gtk_tree_view_row_expanded (tree_view, path)) {
-                       gtk_tree_selection_select_iter (selection, &source_iter);
-               } else {
-                       GtkTreePath *child_path;
+       parent_path = gtk_tree_path_copy (child_path);
+       gtk_tree_path_up (parent_path);
 
-                       child_path = gtk_tree_model_get_path (model, &source_iter);
-                       priv->saved_primary_selection = gtk_tree_row_reference_new (model, child_path);
-                       gtk_tree_path_free (child_path);
+       if (gtk_tree_view_row_expanded (tree_view, parent_path)) {
+               gtk_tree_selection_select_path (selection, child_path);
+       } else {
+               ESourceSelectorClass *class;
 
-                       /* We do this by hand because we aren't changing the tree selection */
-                       if (!source_is_selected (selector, source)) {
-                               select_source (selector, source);
-                               gtk_tree_model_row_changed (model, path, &source_iter);
-                               g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
-                       }
+               class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+               g_return_if_fail (class->set_source_selected != NULL);
 
-                       g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
-                       g_object_notify (G_OBJECT (selector), "primary-selection");
-               }
+               selector->priv->saved_primary_selection =
+                       gtk_tree_row_reference_copy (reference);
 
-               gtk_tree_path_free (path);
-       } else {
-               g_warning (G_STRLOC ": Cannot find source %p (%s) in selector %p",
-                          (gpointer) source, e_source_get_display_name (source),
-                          (gpointer) selector);
+               class->set_source_selected (selector, source, TRUE);
+
+               g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+               g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
+               g_object_notify (G_OBJECT (selector), "primary-selection");
        }
+
+       gtk_tree_path_free (child_path);
+       gtk_tree_path_free (parent_path);
 }
 
 /**
@@ -1948,7 +2024,64 @@ e_source_selector_ref_source_by_path (ESourceSelector *selector,
        model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
 
        if (gtk_tree_model_get_iter (model, &iter, path))
-               gtk_tree_model_get (model, &iter, 0, &source, -1);
+               gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
 
        return source;
 }
+
+/**
+ * e_source_selector_queue_write:
+ * @selector: an #ESourceSelecetor
+ * @source: an #ESource with changes to be written
+ *
+ * Queues a main loop idle callback to write changes to @source back to
+ * the D-Bus registry service.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_selector_queue_write (ESourceSelector *selector,
+                               ESource *source)
+{
+       GSource *idle_source;
+       GHashTable *pending_writes;
+       GMainContext *main_context;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       main_context = selector->priv->main_context;
+       pending_writes = selector->priv->pending_writes;
+
+       idle_source = g_hash_table_lookup (pending_writes, source);
+       if (idle_source != NULL && !g_source_is_destroyed (idle_source))
+               return;
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->selector = g_object_ref (selector);
+       async_context->source = g_object_ref (source);
+
+       /* Set a higher priority so this idle source runs before our
+        * source_selector_cancel_write() signal handler, which will
+        * cancel this idle source.  Cancellation is the right thing
+        * to do when receiving changes from OTHER registry clients,
+        * but we don't want to cancel our own changes.
+        *
+        * XXX This might be an argument for using etags.
+        */
+       idle_source = g_idle_source_new ();
+       g_hash_table_insert (
+               pending_writes,
+               g_object_ref (source),
+               g_source_ref (idle_source));
+       g_source_set_callback (
+               idle_source,
+               source_selector_write_idle_cb,
+               async_context,
+               (GDestroyNotify) async_context_free);
+       g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
+       g_source_attach (idle_source, main_context);
+       g_source_unref (idle_source);
+}
+
index 4ee3cc8..af1e247 100644 (file)
@@ -25,7 +25,7 @@
 #define E_SOURCE_SELECTOR_H
 
 #include <gtk/gtk.h>
-#include <libedataserver/e-source-list.h>
+#include <libedataserver/e-source-registry.h>
 
 /* Standard GObject macros */
 #define E_TYPE_SOURCE_SELECTOR \
@@ -60,6 +60,14 @@ struct _ESourceSelector {
 struct _ESourceSelectorClass {
        GtkTreeViewClass parent_class;
 
+       /* Methods */
+       gboolean        (*get_source_selected)  (ESourceSelector *selector,
+                                                ESource *source);
+       void            (*set_source_selected)  (ESourceSelector *selector,
+                                                ESource *source,
+                                                gboolean selected);
+
+       /* Signals */
        void            (*selection_changed)    (ESourceSelector *selector);
        void            (*primary_selection_changed)
                                                (ESourceSelector *selector);
@@ -78,9 +86,22 @@ struct _ESourceSelectorClass {
 };
 
 GType          e_source_selector_get_type      (void);
-GtkWidget *    e_source_selector_new           (ESourceList *list);
-ESourceList *  e_source_selector_get_source_list
+GtkWidget *    e_source_selector_new           (ESourceRegistry *registry,
+                                                const gchar *extension_name);
+ESourceRegistry *
+               e_source_selector_get_registry  (ESourceSelector *selector);
+const gchar *  e_source_selector_get_extension_name
+                                               (ESourceSelector *selector);
+gboolean       e_source_selector_get_show_colors
+                                               (ESourceSelector *selector);
+void           e_source_selector_set_show_colors
+                                               (ESourceSelector *selector,
+                                                gboolean show_colors);
+gboolean       e_source_selector_get_show_toggles
                                                (ESourceSelector *selector);
+void           e_source_selector_set_show_toggles
+                                               (ESourceSelector *selector,
+                                                gboolean show_toggles);
 void           e_source_selector_select_source (ESourceSelector *selector,
                                                 ESource *source);
 void           e_source_selector_unselect_source
@@ -95,11 +116,6 @@ gboolean    e_source_selector_source_is_selected
 GSList *       e_source_selector_get_selection (ESourceSelector *selector);
 void           e_source_selector_free_selection
                                                (GSList *list);
-void           e_source_selector_show_selection
-                                               (ESourceSelector *selector,
-                                                gboolean show);
-gboolean       e_source_selector_selection_shown
-                                               (ESourceSelector *selector);
 void           e_source_selector_set_select_new
                                                (ESourceSelector *selector,
                                                 gboolean state);
@@ -110,11 +126,11 @@ ESource * e_source_selector_ref_primary_selection
 void           e_source_selector_set_primary_selection
                                                (ESourceSelector *selector,
                                                 ESource *source);
-ESourceGroup * e_source_selector_get_primary_source_group
-                                               (ESourceSelector *selector);
 ESource *      e_source_selector_ref_source_by_path
                                                (ESourceSelector *selector,
                                                 GtkTreePath *path);
+void           e_source_selector_queue_write   (ESourceSelector *selector,
+                                                ESource *source);
 
 G_END_DECLS
 
index 93dc577..95a959b 100644 (file)
  * Author: Ettore Perazzoli <ettore@ximian.com>
  */
 
+#include <libedataserver/e-source-address-book.h>
 #include <libedataserverui/e-source-selector.h>
 
+static const gchar *extension_name;
+
 static void
 dump_selection (ESourceSelector *selector)
 {
@@ -36,10 +39,15 @@ dump_selection (ESourceSelector *selector)
 
                for (p = selection; p != NULL; p = p->next) {
                        ESource *source = E_SOURCE (p->data);
+                       ESourceBackend *extension;
+
+                       extension = e_source_get_extension (
+                               source, extension_name);
 
-                       g_print ("\tSource %s (group %s)\n",
-                                e_source_get_display_name (source),
-                                e_source_group_peek_name (e_source_peek_group (source)));
+                       g_print (
+                               "\tSource %s (backend %s)\n",
+                               e_source_get_display_name (source),
+                               e_source_backend_get_backend_name (extension));
                }
        }
 
@@ -47,57 +55,65 @@ dump_selection (ESourceSelector *selector)
 }
 
 static void
-selection_changed_callback (ESourceSelector *selector,
-                            gpointer unused_data)
+selection_changed_callback (ESourceSelector *selector)
 {
        g_print ("Selection changed!\n");
        dump_selection (selector);
 }
 
-static void
-check_toggled_callback (GtkToggleButton *button,
-                        ESourceSelector *selector)
-{
-       e_source_selector_show_selection (selector, gtk_toggle_button_get_active (button));
-}
-
 static gint
-on_idle_create_widget (const gchar *gconf_path)
+on_idle_create_widget (ESourceRegistry *registry)
 {
        GtkWidget *window;
        GtkWidget *vbox;
        GtkWidget *selector;
        GtkWidget *scrolled_window;
        GtkWidget *check;
-       ESourceList *list;
-       GConfClient *gconf_client;
 
        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        gtk_window_set_default_size (GTK_WINDOW (window), 200, 300);
 
-       vbox = gtk_vbox_new (FALSE, 3);
+       g_signal_connect (
+               window, "delete-event",
+               G_CALLBACK (gtk_main_quit), NULL);
+
+       vbox = gtk_vbox_new (FALSE, 6);
        gtk_container_add (GTK_CONTAINER (window), vbox);
 
-       gconf_client = gconf_client_get_default ();
-       list = e_source_list_new_for_gconf (gconf_client, gconf_path);
-       selector = e_source_selector_new (list);
-       g_signal_connect (selector, "selection_changed", G_CALLBACK (selection_changed_callback), NULL);
+       selector = e_source_selector_new (registry, extension_name);
+       g_signal_connect (
+               selector, "selection_changed",
+               G_CALLBACK (selection_changed_callback), NULL);
 
        scrolled_window = gtk_scrolled_window_new (NULL, NULL);
-       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
+       gtk_scrolled_window_set_policy (
+               GTK_SCROLLED_WINDOW (scrolled_window),
+               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_set_shadow_type (
+               GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
        gtk_container_add (GTK_CONTAINER (scrolled_window), selector);
-       gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 3);
+       gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
 
-       check = gtk_check_button_new_with_label ("Show checkboxes");
-       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
-                                     e_source_selector_selection_shown (E_SOURCE_SELECTOR (selector)));
-       g_signal_connect (check, "toggled", G_CALLBACK (check_toggled_callback), selector);
-       gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 3);
+       check = gtk_check_button_new_with_label ("Show colors");
+       gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0);
+
+       g_object_bind_property (
+               selector, "show-colors",
+               check, "active",
+               G_BINDING_BIDIRECTIONAL |
+               G_BINDING_SYNC_CREATE);
+
+       check = gtk_check_button_new_with_label ("Show toggles");
+       gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0);
+
+       g_object_bind_property (
+               selector, "show-toggles",
+               check, "active",
+               G_BINDING_BIDIRECTIONAL |
+               G_BINDING_SYNC_CREATE);
 
        gtk_widget_show_all (window);
 
-       g_object_unref (gconf_client);
        return FALSE;
 }
 
@@ -105,15 +121,25 @@ gint
 main (gint argc,
       gchar **argv)
 {
-       const gchar *gconf_path;
+       ESourceRegistry *registry;
+       GError *error = NULL;
 
        gtk_init (&argc, &argv);
+
        if (argc < 2)
-               gconf_path = "/apps/evolution/calendar/sources";
+               extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
        else
-               gconf_path = argv[1];
+               extension_name = argv[1];
+
+       registry = e_source_registry_new_sync (NULL, &error);
+
+       if (error != NULL) {
+               g_error ("Failed to load ESource registry: %s",
+                       error->message);
+               g_assert_not_reached ();
+       }
 
-       g_idle_add ((GSourceFunc) on_idle_create_widget, (gpointer) gconf_path);
+       g_idle_add ((GSourceFunc) on_idle_create_widget, registry);
 
        gtk_main ();