[evolution-data-server/e-source-store: 2/2] Prototype and document ESourceStore.



commit 07e5feb0cb0590442eafd15c4b9bb6079d4089eb
Author: Matthew Barnes <mbarnes redhat com>
Date:   Tue Jan 26 14:57:36 2010 -0500

    Prototype and document ESourceStore.

 .../libedataserverui/libedataserverui-docs.sgml    |    1 +
 .../libedataserverui/libedataserverui-sections.txt |   30 +
 .../libedataserverui/tmpl/e-source-selector.sgml   |   11 +-
 .../libedataserverui/tmpl/e-source-store.sgml      |  137 +++
 libedataserverui/Makefile.am                       |    2 +
 libedataserverui/e-source-selector.c               |  684 ++++---------
 libedataserverui/e-source-selector.h               |    6 +-
 libedataserverui/e-source-store.c                  | 1120 ++++++++++++++++++++
 libedataserverui/e-source-store.h                  |  136 +++
 libedataserverui/test-source-selector.c            |    4 +
 10 files changed, 1647 insertions(+), 484 deletions(-)
---
diff --git a/docs/reference/libedataserverui/libedataserverui-docs.sgml b/docs/reference/libedataserverui/libedataserverui-docs.sgml
index 7b539f5..2071469 100644
--- a/docs/reference/libedataserverui/libedataserverui-docs.sgml
+++ b/docs/reference/libedataserverui/libedataserverui-docs.sgml
@@ -25,6 +25,7 @@
     <xi:include href="xml/e-source-option-menu.xml"/>
     <xi:include href="xml/e-source-selector.xml"/>
     <xi:include href="xml/e-source-selector-dialog.xml"/>
+    <xi:include href="xml/e-source-store.xml"/>
     <xi:include href="xml/e-tree-model-generator.xml"/>
   </chapter>
 
diff --git a/docs/reference/libedataserverui/libedataserverui-sections.txt b/docs/reference/libedataserverui/libedataserverui-sections.txt
index e649ec0..b3ca8df 100644
--- a/docs/reference/libedataserverui/libedataserverui-sections.txt
+++ b/docs/reference/libedataserverui/libedataserverui-sections.txt
@@ -268,6 +268,7 @@ e_name_selector_model_get_type
 <TITLE>ESourceSelector</TITLE>
 ESourceSelector
 e_source_selector_new
+e_source_selector_new_with_store
 e_source_selector_get_source_list
 e_source_selector_select_source
 e_source_selector_unselect_source
@@ -287,6 +288,7 @@ E_IS_SOURCE_SELECTOR
 E_TYPE_SOURCE_SELECTOR
 E_SOURCE_SELECTOR_CLASS
 E_IS_SOURCE_SELECTOR_CLASS
+E_SOURCE_SELECTOR_GET_CLASS
 ESourceSelectorClass
 <SUBSECTION Private>
 ESourceSelectorPrivate
@@ -294,6 +296,34 @@ e_source_selector_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-source-store</FILE>
+<TITLE>ESourceStore</TITLE>
+ESourceStore
+e_source_store_new
+e_source_store_get_source_list
+e_source_store_queue_refresh
+e_source_store_get_iter_from_source
+e_source_store_select_source
+e_source_store_unselect_source
+e_source_store_is_selected
+e_source_store_get_selected
+e_source_store_get_client
+e_source_store_get_client_async
+e_source_store_get_client_finish
+<SUBSECTION Standard>
+E_SOURCE_STORE
+E_IS_SOURCE_STORE
+E_TYPE_SOURCE_STORE
+E_SOURCE_STORE_CLASS
+E_IS_SOURCE_STORE_CLASS
+E_SOURCE_STORE_GET_CLASS
+ESourceStoreClass
+<SUBSECTION Private>
+ESourceStorePrivate
+e_source_store_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-name-selector-list</FILE>
 <TITLE>ENameSelectorList</TITLE>
 ENameSelectorList
diff --git a/docs/reference/libedataserverui/tmpl/e-source-selector.sgml b/docs/reference/libedataserverui/tmpl/e-source-selector.sgml
index 7fc5168..6e3932c 100644
--- a/docs/reference/libedataserverui/tmpl/e-source-selector.sgml
+++ b/docs/reference/libedataserverui/tmpl/e-source-selector.sgml
@@ -69,7 +69,16 @@ ESourceSelector
 
 </para>
 
- list: 
+ source_list: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_selector_new_with_store ##### -->
+<para>
+
+</para>
+
+ source_store: 
 @Returns: 
 
 
diff --git a/docs/reference/libedataserverui/tmpl/e-source-store.sgml b/docs/reference/libedataserverui/tmpl/e-source-store.sgml
new file mode 100644
index 0000000..ccc599e
--- /dev/null
+++ b/docs/reference/libedataserverui/tmpl/e-source-store.sgml
@@ -0,0 +1,137 @@
+<!-- ##### SECTION Title ##### -->
+ESourceStore
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### STRUCT ESourceStore ##### -->
+<para>
+
+</para>
+
+ parent: 
+ priv: 
+
+<!-- ##### FUNCTION e_source_store_new ##### -->
+<para>
+
+</para>
+
+ source_list: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_store_get_source_list ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_store_queue_refresh ##### -->
+<para>
+
+</para>
+
+ source_store: 
+
+
+<!-- ##### FUNCTION e_source_store_get_iter_from_source ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ source: 
+ iter: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_store_select_source ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ source: 
+
+
+<!-- ##### FUNCTION e_source_store_unselect_source ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ source: 
+
+
+<!-- ##### FUNCTION e_source_store_is_selected ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ source: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_store_get_selected ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_store_get_client ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ source: 
+ cancellable: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e_source_store_get_client_async ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ source: 
+ io_priority: 
+ cancellable: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e_source_store_get_client_finish ##### -->
+<para>
+
+</para>
+
+ source_store: 
+ result: 
+ error: 
+ Returns: 
+
+
diff --git a/libedataserverui/Makefile.am b/libedataserverui/Makefile.am
index a5814cb..7c33d53 100644
--- a/libedataserverui/Makefile.am
+++ b/libedataserverui/Makefile.am
@@ -37,6 +37,7 @@ libedataserverui_1_2_la_SOURCES =	\
 	e-source-selector-dialog.c	\
 	e-source-combo-box.c		\
 	e-source-option-menu.c		\
+	e-source-store.c		\
 	e-tree-model-generator.c	\
 	e-cell-renderer-color.c
 
@@ -66,6 +67,7 @@ libedataserveruiinclude_HEADERS =	\
 	e-source-selector-dialog.h	\
 	e-source-combo-box.h		\
 	e-source-option-menu.h		\
+	e-source-store.h		\
 	e-tree-model-generator.h	\
 	e-cell-renderer-color.h
 
diff --git a/libedataserverui/e-source-selector.c b/libedataserverui/e-source-selector.c
index fc2b991..0c85d38 100644
--- a/libedataserverui/e-source-selector.c
+++ b/libedataserverui/e-source-selector.c
@@ -35,28 +35,16 @@
 	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))
 
 struct _ESourceSelectorPrivate {
-	ESourceList *list;
+	ESourceList *source_list;
 
-	GHashTable *selected_sources;
 	GtkTreeRowReference *saved_primary_selection;
 	ESourceGroup *primary_source_group;
 
-	gint rebuild_model_idle_id;
-
 	gboolean toggled_last;
 	gboolean checkboxes_shown;
 	gboolean select_new;
 };
 
-typedef struct {
-	ESourceSelector *selector;
-
-	GHashTable *remaining_uids;
-	GSList *deleted_uids;
-
-	gboolean selection_changed;
-} ESourceSelectorRebuildData;
-
 enum {
 	PROP_0,
 	PROP_SOURCE_LIST
@@ -132,32 +120,10 @@ e_cell_renderer_safe_toggle_new (void)
 
 /* Selection management.  */
 
-static ESourceSelectorRebuildData *
-create_rebuild_data (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;
-}
-
 static void
-free_rebuild_data (ESourceSelectorRebuildData *rebuild_data)
+source_selector_emit_selection_changed (ESourceSelector *selector)
 {
-	GSList *p;
-
-	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);
-
-	g_free (rebuild_data);
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
 }
 
 static void
@@ -168,38 +134,6 @@ clear_saved_primary_selection (ESourceSelector *selector)
 }
 
 static gboolean
-source_is_selected (ESourceSelector *selector,
-                    ESource *source)
-{
-	if (g_hash_table_lookup (selector->priv->selected_sources, source) == NULL)
-		return FALSE;
-	else
-		return TRUE;
-}
-
-static void
-select_source (ESourceSelector *selector,
-               ESource *source)
-{
-	if (g_hash_table_lookup (selector->priv->selected_sources, source) != NULL)
-		return;
-
-	g_hash_table_insert (selector->priv->selected_sources, source, source);
-	g_object_ref (source);
-}
-
-static void
-unselect_source (ESourceSelector *selector,
-		 ESource *source)
-{
-	if (g_hash_table_lookup (selector->priv->selected_sources, source) == NULL)
-		return;
-
-	/* (This will unref the source.)  */
-	g_hash_table_remove (selector->priv->selected_sources, source);
-}
-
-static gboolean
 find_source_iter (ESourceSelector *selector,
                   ESource *source,
                   GtkTreeIter *parent_iter,
@@ -233,65 +167,19 @@ find_source_iter (ESourceSelector *selector,
 	return FALSE;
 }
 
-/* Setting up the model.  */
-static gboolean
-rebuild_existing_cb (GtkTreeModel *model,
-                     GtkTreePath *path,
-                     GtkTreeIter *iter,
-                     gpointer data)
-{
-	ESourceSelectorRebuildData *rebuild_data = data;
-	ESourceList *source_list;
-	GtkTreeRowReference *reference;
-	gpointer node;
-	const gchar *uid;
-
-	gtk_tree_model_get (model, iter, 0, &node, -1);
-
-	source_list = rebuild_data->selector->priv->list;
-	reference = gtk_tree_row_reference_new (model, path);
-
-	if (E_IS_SOURCE_GROUP (node)) {
-		uid = e_source_group_peek_uid (E_SOURCE_GROUP (node));
-
-		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_prepend (
-				rebuild_data->deleted_uids, reference);
-	} else {
-		uid = e_source_peek_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;
-		}
-	}
-
-	g_object_unref (node);
-
-	return FALSE;
-}
-
 static ESource *
 find_source (ESourceSelector *selector,
              ESource *source)
 {
+	ESourceList *source_list;
 	GSList *groups, *p;
 
 	g_return_val_if_fail (selector != NULL, source);
 	g_return_val_if_fail (E_IS_SOURCE (source), source);
 
-	groups = e_source_list_peek_groups (selector->priv->list);
+	source_list = e_source_selector_get_source_list (selector);
+
+	groups = e_source_list_peek_groups (source_list);
 	for (p = groups; p != NULL; p = p->next) {
 		ESourceGroup *group = E_SOURCE_GROUP (p->data);
 		GSList *sources, *q;
@@ -308,132 +196,6 @@ find_source (ESourceSelector *selector,
 	return source;
 }
 
-/**
- * compare_source_names
- * Compares sources by name.
- **/
-static gint
-compare_source_names (gconstpointer a, gconstpointer b)
-{
-	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_peek_name (E_SOURCE (a));
-	name_b = e_source_peek_name (E_SOURCE (b));
-
-	return g_utf8_collate (name_a, name_b);
-}
-
-static void
-rebuild_model (ESourceSelector *selector)
-{
-	ESourceSelectorRebuildData *rebuild_data;
-	ESource *source;
-	GtkTreeModel *model;
-	GtkTreeStore *store;
-	GtkTreeIter iter;
-	GSList *groups, *p;
-	gboolean set_primary;
-
-	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
-	store = GTK_TREE_STORE (model);
-
-	rebuild_data = create_rebuild_data (selector);
-	set_primary = e_source_selector_peek_primary_selection (selector) != NULL;
-
-	gtk_tree_model_foreach (model, rebuild_existing_cb, rebuild_data);
-
-	/* Remove any delete sources or groups */
-	for (p = rebuild_data->deleted_uids; p; p = p->next) {
-		GtkTreeRowReference *row_ref = p->data;
-		GtkTreePath *path;
-
-		path = gtk_tree_row_reference_get_path (row_ref);
-		gtk_tree_model_get_iter (model, &iter, path);
-		gtk_tree_store_remove (store, &iter);
-
-		gtk_tree_path_free (path);
-	}
-
-	/* 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);
-		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);
-
-		reference = g_hash_table_lookup (
-			rebuild_data->remaining_uids,
-			e_source_group_peek_uid (group));
-
-		if (reference == NULL) {
-			gtk_tree_store_append (store, &iter, NULL);
-			gtk_tree_store_set (store, &iter, 0, group, -1);
-		} else {
-			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_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_peek_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);
-	}
-
-	if (rebuild_data->selection_changed)
-		g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
-
-	source = e_source_selector_peek_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);
-	}
-
-	free_rebuild_data (rebuild_data);
-}
-
 static gboolean
 same_source_name_exists (ESourceSelector *selector,
                          const gchar *name)
@@ -472,65 +234,39 @@ same_source_name_exists (ESourceSelector *selector,
 	return FALSE;
 }
 
-static gint
-on_idle_rebuild_model_callback (ESourceSelector *selector)
-{
-	rebuild_model (selector);
-	selector->priv->rebuild_model_idle_id = 0;
-
-	return FALSE;
-}
-
-static void
-list_changed_callback (ESourceList *list,
-		       ESourceSelector *selector)
-{
-	ESourceSelectorPrivate *priv = selector->priv;
-
-	if (priv->rebuild_model_idle_id == 0)
-		priv->rebuild_model_idle_id = g_idle_add ((GSourceFunc) on_idle_rebuild_model_callback,
-							  selector);
-}
-
 /* Data functions for rendering the model.  */
 
 static void
 toggle_cell_data_func (GtkTreeViewColumn *column,
-		       GtkCellRenderer *renderer,
-		       GtkTreeModel *model,
-		       GtkTreeIter *iter,
-		       ESourceSelector *selector)
+                       GtkCellRenderer *renderer,
+                       GtkTreeModel *model,
+                       GtkTreeIter *iter,
+                       ESourceSelector *selector)
 {
-	gpointer data;
+	GObject *object;
+	gboolean visible = FALSE;
+	gint column_id;
 
-	gtk_tree_model_get (model, iter, 0, &data, -1);
-	if (data == NULL) {
-		g_object_set (renderer, "visible", FALSE, NULL);
-		return;
-	}
+	column_id = E_SOURCE_STORE_COLUMN_SOURCE;
+	gtk_tree_model_get (model, iter, column_id, &object, -1);
 
-	if (E_IS_SOURCE_GROUP (data)) {
-		g_object_set (renderer, "visible", FALSE, NULL);
-	} else {
-		g_assert (E_IS_SOURCE (data));
+	if (E_IS_SOURCE (object))
+		visible = selector->priv->checkboxes_shown;
 
-		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_set (renderer, "visible", visible, NULL);
 
-	g_object_unref (data);
+	if (object != NULL)
+		g_object_unref (object);
 }
 
 static void
 text_cell_data_func (GtkTreeViewColumn *column,
-		     GtkCellRenderer *renderer,
-		     GtkTreeModel *model,
-		     GtkTreeIter *iter,
-		     ESourceSelector *selector)
+                     GtkCellRenderer *renderer,
+                     GtkTreeModel *model,
+                     GtkTreeIter *iter,
+                     ESourceSelector *selector)
 {
+	const gchar *name;
 	gpointer data;
 
 	gtk_tree_model_get (model, iter, 0, &data, -1);
@@ -540,33 +276,33 @@ text_cell_data_func (GtkTreeViewColumn *column,
 	}
 
 	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_peek_name (source),
-			      "weight", PANGO_WEIGHT_NORMAL,
-			      "foreground_set", FALSE,
-			      NULL);
-	}
+		name = e_source_group_peek_name (E_SOURCE_GROUP (data));
+		g_object_set (
+			renderer,
+			"text", name,
+			"weight", PANGO_WEIGHT_BOLD,
+			"foreground_set", FALSE,
+			NULL);
+	} else if (E_IS_SOURCE (data)) {
+		name = e_source_peek_name (E_SOURCE (data));
+		g_object_set (
+			renderer,
+			"text", name,
+			"weight", PANGO_WEIGHT_NORMAL,
+			"foreground_set", FALSE,
+			NULL);
+	} else
+		g_warn_if_reached ();
 
 	g_object_unref (data);
 }
 
 static void
 pixbuf_cell_data_func (GtkTreeViewColumn *column,
-		       GtkCellRenderer *renderer,
-		       GtkTreeModel *model,
-		       GtkTreeIter *iter,
-		       ESourceSelector *selector)
+                       GtkCellRenderer *renderer,
+                       GtkTreeModel *model,
+                       GtkTreeIter *iter,
+                       ESourceSelector *selector)
 {
 	gpointer data;
 
@@ -684,30 +420,32 @@ cell_toggled_callback (GtkCellRendererToggle *renderer,
 	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);
 
-	if (! gtk_tree_model_get_iter (model, &iter, path)) {
+	if (!gtk_tree_model_get_iter (model, &iter, path)) {
 		gtk_tree_path_free (path);
 		return;
 	}
 
-	gtk_tree_model_get (model, &iter, 0, &data, -1);
-	if (!E_IS_SOURCE_GROUP (data)) {
-		source = E_SOURCE (data);
+	gtk_tree_model_get (
+		model, &iter,
+		E_SOURCE_STORE_COLUMN_SOURCE, &data, -1);
+
+	if (E_IS_SOURCE (data)) {
+		ESource *source = E_SOURCE (data);
+		ESourceStore *source_store;
 
-		if (source_is_selected (selector, source))
-			unselect_source (selector, source);
+		source_store = E_SOURCE_STORE (model);
+
+		if (e_source_store_is_selected (source_store, source))
+			e_source_store_unselect_source (source_store, source);
 		else
-			select_source (selector, source);
+			e_source_store_select_source (source_store, source);
 
 		selector->priv->toggled_last = TRUE;
-
-		gtk_tree_model_row_changed (model, path, &iter);
-		g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
 	}
 
 	gtk_tree_path_free (path);
@@ -733,7 +471,9 @@ group_search_function (GtkTreeModel *model,
 	const gchar *name = NULL;
 	gboolean status = TRUE;
 
-	gtk_tree_model_get (model, iter, 0, &data, -1);
+	gtk_tree_model_get (
+		model, iter,
+		E_SOURCE_STORE_COLUMN_SOURCE, &data, -1);
 
 	if (E_IS_SOURCE_GROUP (data))
 		name = e_source_group_peek_name (E_SOURCE_GROUP (data));
@@ -756,18 +496,9 @@ source_selector_set_source_list (ESourceSelector *selector,
                                  ESourceList *source_list)
 {
 	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 (selector->priv->source_list == 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->source_list = g_object_ref (source_list);
 }
 
 static void
@@ -809,16 +540,9 @@ source_selector_dispose (GObject *object)
 {
 	ESourceSelectorPrivate *priv = E_SOURCE_SELECTOR (object)->priv;
 
-	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->list != NULL) {
-		g_object_unref (priv->list);
-		priv->list = NULL;
+	if (priv->source_list != NULL) {
+		g_object_unref (priv->source_list);
+		priv->source_list = NULL;
 	}
 
 	clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
@@ -828,14 +552,82 @@ source_selector_dispose (GObject *object)
 }
 
 static void
-source_selector_finalize (GObject *object)
+source_selector_constructed (GObject *object)
 {
-	ESourceSelectorPrivate *priv = E_SOURCE_SELECTOR (object)->priv;
+	ESourceSelector *selector;
+	ESourceList *source_list;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+
+	selector = E_SOURCE_SELECTOR (object);
+	source_list = e_source_selector_get_source_list (selector);
+
+	tree_view = GTK_TREE_VIEW (object);
+	model = gtk_tree_view_get_model (tree_view);
+
+	/* Create a default store if one was not provided. */
+	if (model == NULL) {
+		model = e_source_store_new (source_list);
+		gtk_tree_view_set_model (tree_view, model);
+	}
 
-	g_hash_table_destroy (priv->selected_sources);
+	gtk_tree_view_expand_all (tree_view);
 
-	/* Chain up to parent's finalize() method. */
-	G_OBJECT_CLASS (e_source_selector_parent_class)->finalize (object);
+	gtk_tree_view_set_enable_search (tree_view, TRUE);
+	gtk_tree_view_set_headers_visible (tree_view, FALSE);
+	gtk_tree_view_set_search_column (tree_view, 0);
+	gtk_tree_view_set_search_equal_func (
+		tree_view, group_search_function, NULL, NULL);
+
+	/* Configure cell renderers. */
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_append_column (tree_view, column);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	g_object_set (
+		renderer, "mode",
+		GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (
+		column, renderer, (GtkTreeCellDataFunc)
+		pixbuf_cell_data_func, selector, NULL);
+
+	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",
+		E_SOURCE_STORE_COLUMN_SELECTED);
+	gtk_tree_view_column_set_cell_data_func (
+		column, renderer, (GtkTreeCellDataFunc)
+		toggle_cell_data_func, selector, NULL);
+	g_signal_connect (
+		renderer, "toggled",
+		G_CALLBACK (cell_toggled_callback), selector);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+	g_signal_connect_swapped (
+		renderer, "edited",
+		G_CALLBACK (text_cell_edited_cb), selector);
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (
+		column, renderer, (GtkTreeCellDataFunc)
+		text_cell_data_func, selector, NULL);
+
+	/* Listen for model signals. */
+
+	g_signal_connect_swapped (
+		model, "source-selected",
+		G_CALLBACK (source_selector_emit_selection_changed),
+		selector);
+
+	g_signal_connect_swapped (
+		model, "source-unselected",
+		G_CALLBACK (source_selector_emit_selection_changed),
+		selector);
 }
 
 static gboolean
@@ -870,7 +662,9 @@ source_selector_button_press_event (GtkWidget *widget,
 		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);
+			gtk_tree_model_get (
+				model, &iter,
+				E_SOURCE_STORE_COLUMN_SOURCE, &data, -1);
 
 			/* Do not emit popup since we will
 			 * not be able to get the ESource. */
@@ -941,7 +735,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,
+		E_SOURCE_STORE_COLUMN_SOURCE, &object, -1);
 
 	if (E_IS_SOURCE_GROUP (object) || e_source_get_readonly (object))
 		goto exit;
@@ -992,7 +788,9 @@ 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);
+	gtk_tree_model_get (
+		model, &iter,
+		E_SOURCE_STORE_COLUMN_SOURCE, &object, -1);
 	drop_zone = E_IS_SOURCE (object);
 	g_object_unref (object);
 
@@ -1026,7 +824,9 @@ 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,
+		E_SOURCE_STORE_COLUMN_SOURCE, &object, -1);
 
 	if (!E_IS_SOURCE (object) || e_source_get_readonly (object))
 		goto exit;
@@ -1155,7 +955,7 @@ e_source_selector_class_init (ESourceSelectorClass *class)
 	object_class->set_property = source_selector_set_property;
 	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;
@@ -1226,10 +1026,7 @@ static void
 e_source_selector_init (ESourceSelector *selector)
 {
 	ESourceSelectorPrivate *priv;
-	GtkTreeViewColumn *column;
-	GtkCellRenderer *cell_renderer;
 	GtkTreeSelection *selection;
-	GtkTreeStore *tree_store;
 	GtkTreeView *tree_view;
 
 	selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);
@@ -1237,55 +1034,10 @@ e_source_selector_init (ESourceSelector *selector)
 
 	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_enable_search (tree_view, TRUE);
-
 	priv->toggled_last = FALSE;
 	priv->checkboxes_shown = TRUE;
 	priv->select_new = FALSE;
 
-	priv->selected_sources = g_hash_table_new_full (
-		g_direct_hash, g_direct_equal,
-		(GDestroyNotify) g_object_unref,
-		(GDestroyNotify) NULL);
-
-	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 ();
-	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_signal_connect (
-		cell_renderer, "toggled",
-		G_CALLBACK (cell_toggled_callback), selector);
-
-	cell_renderer = gtk_cell_renderer_text_new ();
-	g_object_set (
-		G_OBJECT (cell_renderer),
-		"ellipsize", PANGO_ELLIPSIZE_END, NULL);
-	g_signal_connect_swapped (
-		cell_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);
-
 	selection = gtk_tree_view_get_selection (tree_view);
 	gtk_tree_selection_set_select_function (
 		selection, (GtkTreeSelectionFunc)
@@ -1294,28 +1046,49 @@ e_source_selector_init (ESourceSelector *selector)
 		selection, "changed",
 		G_CALLBACK (selection_changed_callback),
 		G_OBJECT (selector), 0);
-
-	gtk_tree_view_set_headers_visible (tree_view, FALSE);
 }
 
 /* Public API.  */
 
 /**
  * e_source_selector_new:
- * @list: A source list.
+ * @source_list: an #ESourceList
+ *
+ * Create a new selector for @source_list.  The contents will update
+ * automatically when the source list changes.
+ *
+ * Returns: a new #ESourceSelector widget
+ **/
+GtkWidget *
+e_source_selector_new (ESourceList *source_list)
+{
+	g_return_val_if_fail (E_IS_SOURCE_LIST (source_list), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_SELECTOR, "source-list", source_list, NULL);
+}
+
+/**
+ * e_source_selector_new_with_store:
+ * @source_store: an #ESourceStore
  *
- * Create a new view for @list.  The view will update automatically when @list
- * changes.
+ * Create a new selector for the #ESourceList in @source_store.  The
+ * contents will update automatically when the source list changes.
  *
- * Return value: The newly created widget.
+ * Returns: a new #ESourceSelector widget
  **/
 GtkWidget *
-e_source_selector_new (ESourceList *list)
+e_source_selector_new_with_store (ESourceStore *source_store)
 {
-	g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
+	ESourceList *source_list;
+
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+
+	source_list = e_source_store_get_source_list (source_store);
 
 	return g_object_new (
-		E_TYPE_SOURCE_SELECTOR, "source-list", list, NULL);
+		E_TYPE_SOURCE_SELECTOR,
+		"source-list", source_list, "model", source_store, NULL);
 }
 
 /**
@@ -1331,7 +1104,7 @@ e_source_selector_get_source_list (ESourceSelector *selector)
 {
 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
 
-	return selector->priv->list;
+	return selector->priv->source_list;
 }
 
 /**
@@ -1348,32 +1121,13 @@ e_source_selector_get_source_list (ESourceSelector *selector)
 GSList *
 e_source_selector_get_selection (ESourceSelector *selector)
 {
-	GSList *selection_list;
-	GSList *groups;
-	GSList *p;
+	GtkTreeModel *model;
 
 	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);
-
-			if (source_is_selected (selector, source)) {
-				selection_list = g_slist_prepend (selection_list, source);
-				g_object_ref (source);
-			}
-		}
-	}
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
 
-	return g_slist_reverse (selection_list);
+	return e_source_store_get_selected (E_SOURCE_STORE (model));
 }
 
 /**
@@ -1477,30 +1231,13 @@ void
 e_source_selector_select_source (ESourceSelector *selector,
                                  ESource *source)
 {
-	GtkTreeIter parent_iter, source_iter;
+	GtkTreeModel *model;
 
 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
 	g_return_if_fail (E_IS_SOURCE (source));
 
-	source = find_source (selector, source);
-
-	if (!source || source_is_selected (selector, source))
-		return;
-
-	select_source (selector, source);
-
-	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);
-	}
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	e_source_store_select_source (E_SOURCE_STORE (model), source);
 }
 
 /**
@@ -1514,30 +1251,13 @@ void
 e_source_selector_unselect_source (ESourceSelector *selector,
                                    ESource *source)
 {
-	GtkTreeIter parent_iter, source_iter;
+	GtkTreeModel *model;
 
 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
 	g_return_if_fail (E_IS_SOURCE (source));
 
-	source = find_source (selector, source);
-
-	if (!source || !source_is_selected (selector, source))
-		return;
-
-	unselect_source (selector, source);
-
-	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);
-	}
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	e_source_store_unselect_source (E_SOURCE_STORE (model), source);
 }
 
 /**
@@ -1553,12 +1273,14 @@ gboolean
 e_source_selector_source_is_selected (ESourceSelector *selector,
                                       ESource *source)
 {
+	GtkTreeModel *model;
+
 	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);
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
 
-	return source && source_is_selected (selector, source);
+	return e_source_store_is_selected (E_SOURCE_STORE (model), source);
 }
 
 /**
@@ -1657,7 +1379,9 @@ e_source_selector_peek_primary_selection (ESourceSelector *selector)
 	if (!have_iter && ! gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector)), NULL, &iter))
 		return NULL;
 
-	gtk_tree_model_get (model, &iter, 0, &data, -1);
+	gtk_tree_model_get (
+		model, &iter,
+		E_SOURCE_STORE_COLUMN_SOURCE, &data, -1);
 	if (!data)
 		return NULL;
 
@@ -1726,10 +1450,8 @@ e_source_selector_set_primary_selection (ESourceSelector *selector,
 			gtk_tree_path_free (child_path);
 
 			/* 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);
+			if (!e_source_selector_source_is_selected (selector, source)) {
+				e_source_selector_select_source (selector, source);
 			}
 
 			g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
diff --git a/libedataserverui/e-source-selector.h b/libedataserverui/e-source-selector.h
index 4f46d52..ee7d4ef 100644
--- a/libedataserverui/e-source-selector.h
+++ b/libedataserverui/e-source-selector.h
@@ -25,7 +25,8 @@
 #define E_SOURCE_SELECTOR_H
 
 #include <gtk/gtk.h>
-#include "libedataserver/e-source-list.h"
+#include <libedataserver/e-source-list.h>
+#include <libedataserverui/e-source-store.h>
 
 /* Standard GObject macros */
 #define E_TYPE_SOURCE_SELECTOR \
@@ -78,7 +79,8 @@ struct _ESourceSelectorClass {
 };
 
 GType		e_source_selector_get_type	(void);
-GtkWidget *	e_source_selector_new		(ESourceList *list);
+GtkWidget *	e_source_selector_new		(ESourceList *source_list);
+GtkWidget *	e_source_selector_new_with_store(ESourceStore *source_store);
 ESourceList *	e_source_selector_get_source_list
 						(ESourceSelector *selector);
 void		e_source_selector_select_source	(ESourceSelector *selector,
diff --git a/libedataserverui/e-source-store.c b/libedataserverui/e-source-store.c
new file mode 100644
index 0000000..8575f41
--- /dev/null
+++ b/libedataserverui/e-source-store.c
@@ -0,0 +1,1120 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-store.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION: e-source-store
+ * @short_description: Store sources and clients in a tree model
+ * @include: libedataserverui/e-source-store.h
+ *
+ * #ESourceStore is a #GtkTreeModel meant for use with widgets that render
+ * sources such as #ESourceSelector and #ESourceComboBox.  It can also be
+ * subclassed to open and store #EBook and #ECal objects.
+ **/
+
+#include "e-source-store.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_SOURCE_STORE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_STORE, ESourceStorePrivate))
+
+struct _ESourceStorePrivate {
+
+	ESourceList *source_list;
+
+	/* Source UID -> GtkTreeRowReference */
+	GHashTable *index;
+
+	/* Source UID -> GCancellable */
+	GHashTable *requests;
+
+	guint refresh_source_id;
+};
+
+enum {
+	PROP_0,
+	PROP_SOURCE_LIST
+};
+
+enum {
+	SOURCE_ADDED,
+	SOURCE_REMOVED,
+	SOURCE_SELECTED,
+	SOURCE_UNSELECTED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (ESourceStore, e_source_store, GTK_TYPE_TREE_STORE)
+
+typedef struct {
+	ESourceStore *source_store;
+	GHashTable *remaining_uids;
+	GPtrArray *deleted_uids;
+	GtkTreeIter iter;
+	gint position;
+} RefreshData;
+
+static RefreshData *
+refresh_data_new (ESourceStore *source_store)
+{
+	RefreshData *data;
+
+	data = g_slice_new0 (RefreshData);
+	data->source_store = g_object_ref (source_store);
+	data->remaining_uids = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+	data->deleted_uids = g_ptr_array_new_with_free_func (
+		(GDestroyNotify) gtk_tree_row_reference_free);
+
+	return data;
+}
+
+static void
+refresh_data_free (RefreshData *data)
+{
+	g_object_unref (data->source_store);
+	g_hash_table_destroy (data->remaining_uids);
+	g_ptr_array_free (data->deleted_uids, TRUE);
+	g_slice_free (RefreshData, data);
+}
+
+static gint
+source_store_compare_source_names (gconstpointer source_a,
+                                   gconstpointer source_b)
+{
+	const gchar *source_name_a;
+	const gchar *source_name_b;
+
+	g_return_val_if_fail (E_IS_SOURCE (source_a), -1);
+	g_return_val_if_fail (E_IS_SOURCE (source_b),  1);
+
+	source_name_a = e_source_peek_name (E_SOURCE (source_a));
+	source_name_b = e_source_peek_name (E_SOURCE (source_b));
+
+	return g_utf8_collate (source_name_a, source_name_b);
+}
+
+static gboolean
+source_store_refresh_collect_data (GtkTreeModel *model,
+                                   GtkTreePath *path,
+                                   GtkTreeIter *iter,
+                                   RefreshData *data)
+{
+	GtkTreeRowReference *reference;
+	ESourceStore *source_store;
+	ESourceList *source_list;
+	GObject *object;
+	const gchar *uid;
+
+	source_store = E_SOURCE_STORE (model);
+	source_list = e_source_store_get_source_list (source_store);
+	reference = gtk_tree_row_reference_new (model, path);
+
+	/* The object may be an ESource or ESourceGroup. */
+	gtk_tree_model_get (
+		model, iter,
+		E_SOURCE_STORE_COLUMN_SOURCE, &object, -1);
+
+	if (E_IS_SOURCE (object)) {
+		uid = e_source_peek_uid (E_SOURCE (object));
+
+		if (e_source_list_peek_source_by_uid (source_list, uid))
+			g_hash_table_insert (
+				data->remaining_uids,
+				g_strdup (uid), reference);
+		else {
+			guint signal_id;
+
+			g_ptr_array_add (data->deleted_uids, reference);
+
+			g_hash_table_remove (
+				source_store->priv->index,
+				(gpointer) uid);
+
+			signal_id = signals[SOURCE_REMOVED];
+			g_signal_emit (source_store, signal_id, 0, object);
+		}
+
+	} else if (E_IS_SOURCE_GROUP (object)) {
+		uid = e_source_group_peek_uid (E_SOURCE_GROUP (object));
+
+		if (e_source_list_peek_group_by_uid (source_list, uid))
+			g_hash_table_insert (
+				data->remaining_uids,
+				g_strdup (uid), reference);
+		else
+			g_ptr_array_add (data->deleted_uids, reference);
+
+	} else
+		g_warn_if_reached ();
+
+	g_object_unref (object);
+
+	return FALSE;
+}
+
+static void
+source_store_refresh_add_sources (ESource *source,
+                                  RefreshData *data)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *tree_model;
+	GtkTreeStore *tree_store;
+	GtkTreeIter iter;
+	const gchar *uid;
+
+	tree_model = GTK_TREE_MODEL (data->source_store);
+	tree_store = GTK_TREE_STORE (data->source_store);
+
+	uid = e_source_peek_uid (source);
+	reference = g_hash_table_lookup (data->remaining_uids, uid);
+
+	if (reference == NULL) {
+		GtkTreePath *path;
+		guint signal_id;
+
+		gtk_tree_store_insert (
+			tree_store, &iter, &data->iter, data->position);
+		gtk_tree_store_set (
+			tree_store, &iter,
+			E_SOURCE_STORE_COLUMN_SOURCE, source, -1);
+
+		path = gtk_tree_model_get_path (tree_model, &iter);
+		reference = gtk_tree_row_reference_new (tree_model, path);
+		gtk_tree_path_free (path);
+
+		g_hash_table_insert (
+			data->source_store->priv->index,
+			g_strdup (uid), reference);
+
+		signal_id = signals[SOURCE_ADDED];
+		g_signal_emit (data->source_store, signal_id, 0, source);
+	} else {
+		GtkTreePath *path;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		gtk_tree_model_get_iter (tree_model, &iter, path);
+		gtk_tree_model_row_changed (tree_model, path, &iter);
+		gtk_tree_path_free (path);
+	}
+
+	data->position++;
+}
+
+static void
+source_store_refresh_add_groups (ESourceGroup *source_group,
+                                 RefreshData *data)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *tree_model;
+	GtkTreeStore *tree_store;
+	GSList *list;
+	const gchar *uid;
+
+	/* Skip empty source groups. */
+	list = e_source_group_peek_sources (source_group);
+	if (list == NULL)
+		return;
+
+	tree_model = GTK_TREE_MODEL (data->source_store);
+	tree_store = GTK_TREE_STORE (data->source_store);
+
+	/* Copy the list and sort by name. */
+	list = g_slist_copy (list);
+	list = g_slist_sort (list, source_store_compare_source_names);
+
+	uid = e_source_group_peek_uid (source_group);
+	reference = g_hash_table_lookup (data->remaining_uids, uid);
+
+	if (reference == NULL) {
+		gtk_tree_store_append (tree_store, &data->iter, NULL);
+		gtk_tree_store_set (
+			tree_store, &data->iter,
+			E_SOURCE_STORE_COLUMN_SOURCE, source_group, -1);
+	} else {
+		GtkTreePath *path;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		gtk_tree_model_get_iter (tree_model, &data->iter, path);
+		gtk_tree_model_row_changed (tree_model, path, &data->iter);
+		gtk_tree_path_free (path);
+	}
+
+	/* Insert new sources in this source group. */
+	data->position = 0;
+	g_slist_foreach (list, (GFunc) source_store_refresh_add_sources, data);
+
+	g_slist_free (list);
+}
+
+static gboolean
+source_store_refresh (ESourceStore *source_store)
+{
+	ESourceList *source_list;
+	GtkTreeModel *tree_model;
+	GtkTreeStore *tree_store;
+	RefreshData *data;
+	GSList *list;
+	guint ii;
+
+	tree_model = GTK_TREE_MODEL (source_store);
+	tree_store = GTK_TREE_STORE (source_store);
+
+	source_list = e_source_store_get_source_list (source_store);
+
+	data = refresh_data_new (source_store);
+
+	gtk_tree_model_foreach (
+		tree_model, (GtkTreeModelForeachFunc)
+		source_store_refresh_collect_data, data);
+
+	/* Remove any deleted sources or source groups. */
+	for (ii = 0; ii < data->deleted_uids->len; ii++) {
+		GtkTreeRowReference *reference;
+		GtkTreePath *path;
+		GtkTreeIter iter;
+
+		reference = g_ptr_array_index (data->deleted_uids, ii);
+		path = gtk_tree_row_reference_get_path (reference);
+		gtk_tree_model_get_iter (tree_model, &iter, path);
+		gtk_tree_store_remove (tree_store, &iter);
+		gtk_tree_path_free (path);
+	}
+
+	/* Insert new source groups. */
+	list = e_source_list_peek_groups (source_list);
+	g_slist_foreach (list, (GFunc) source_store_refresh_add_groups, data);
+
+	refresh_data_free (data);
+
+	source_store->priv->refresh_source_id = 0;
+
+	return FALSE;
+}
+
+static void
+source_store_get_client_cb (ESourceStore *source_store,
+                            GAsyncResult *result,
+                            ESource *source)
+{
+	GHashTable *hash_table;
+	GtkTreeIter iter;
+	GObject *client;
+	const gchar *uid;
+	GError *error = NULL;
+
+	uid = e_source_peek_uid (source);
+	hash_table = source_store->priv->requests;
+	g_hash_table_remove (hash_table, uid);
+
+	client = e_source_store_get_client_finish (
+		source_store, result, &error);
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
+		g_error_free (error);
+		goto exit;
+
+	} else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_error_free (error);
+		goto exit;
+
+	} else if (error != NULL) {
+		e_source_store_unselect_source (source_store, source);
+		g_warning ("%s", error->message);
+		g_error_free (error);
+		goto exit;
+	}
+
+	/* The source may have been deleted while we were waiting. */
+	if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+		goto exit;
+
+	gtk_tree_store_set (
+		GTK_TREE_STORE (source_store), &iter,
+		E_SOURCE_STORE_COLUMN_CLIENT, client, -1);
+
+exit:
+	if (client != NULL)
+		g_object_unref (client);
+
+	g_object_unref (source);
+}
+
+static void
+source_store_set_source_list (ESourceStore *source_store,
+                              ESourceList *source_list)
+{
+	g_return_if_fail (E_IS_SOURCE_LIST (source_list));
+	g_return_if_fail (source_store->priv->source_list == NULL);
+
+	source_store->priv->source_list = g_object_ref (source_list);
+
+	source_store_refresh (source_store);
+
+	g_signal_connect_swapped (
+		source_list, "changed",
+		G_CALLBACK (e_source_store_queue_refresh), source_store);
+}
+
+static void
+source_store_set_property (GObject *object,
+                           guint property_id,
+                           const GValue *value,
+                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SOURCE_LIST:
+			source_store_set_source_list (
+				E_SOURCE_STORE (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_store_get_property (GObject *object,
+                           guint property_id,
+                           GValue *value,
+                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SOURCE_LIST:
+			g_value_set_object (
+				value, e_source_store_get_source_list (
+				E_SOURCE_STORE (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_store_dispose (GObject *object)
+{
+	ESourceStorePrivate *priv;
+
+	priv = E_SOURCE_STORE_GET_PRIVATE (object);
+
+	if (priv->refresh_source_id > 0) {
+		g_source_remove (priv->refresh_source_id);
+		priv->refresh_source_id = 0;
+	}
+
+	if (priv->source_list != NULL) {
+		g_object_unref (priv->source_list);
+		priv->source_list = NULL;
+	}
+
+	g_hash_table_remove_all (priv->index);
+	g_hash_table_remove_all (priv->requests);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_store_parent_class)->dispose (object);
+}
+
+static void
+source_store_finalize (GObject *object)
+{
+	ESourceStorePrivate *priv;
+
+	priv = E_SOURCE_STORE_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->index);
+	g_hash_table_destroy (priv->requests);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_store_parent_class)->finalize (object);
+}
+
+static void
+source_store_source_added (ESourceStore *source_store,
+                           ESource *source)
+{
+	/* This is a placeholder in case we decide to do something
+	 * here in the future.  Subclasses should still chain up. */
+}
+
+static void
+source_store_source_removed (ESourceStore *source_store,
+                             ESource *source)
+{
+	/* This is a placeholder in case we decide to do something
+	 * here in the future.  Subclasses should still chain up. */
+}
+
+static void
+source_store_source_selected (ESourceStore *source_store,
+                              ESource *source)
+{
+	GCancellable *cancellable;
+	GHashTable *hash_table;
+	const gchar *uid;
+
+	uid = e_source_peek_uid (source);
+	hash_table = source_store->priv->requests;
+	cancellable = g_hash_table_lookup (hash_table, uid);
+
+	if (cancellable != NULL)
+		return;
+
+	cancellable = g_cancellable_new ();
+	g_hash_table_insert (hash_table, g_strdup (uid), cancellable);
+
+	e_source_store_get_client_async (
+		source_store, source, G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) source_store_get_client_cb,
+		g_object_ref (source));
+}
+
+static void
+source_store_source_unselected (ESourceStore *source_store,
+                                ESource *source)
+{
+	GCancellable *cancellable;
+	GHashTable *hash_table;
+	const gchar *uid;
+
+	uid = e_source_peek_uid (source);
+	hash_table = source_store->priv->requests;
+	cancellable = g_hash_table_lookup (hash_table, uid);
+
+	if (cancellable != NULL) {
+		g_cancellable_cancel (cancellable);
+		g_hash_table_remove (hash_table, uid);
+	}
+}
+
+typedef struct {
+	ESource *source;
+	GObject *client;
+} GetClientData;
+
+static void
+source_store_get_client_data_free (GetClientData *data)
+{
+	if (data->source != NULL)
+		g_object_unref (data->source);
+
+	if (data->client != NULL)
+		g_object_unref (data->client);
+
+	g_slice_free (GetClientData, data);
+}
+
+static void
+source_store_get_client_thread (GSimpleAsyncResult *simple,
+                                ESourceStore *source_store,
+                                GCancellable *cancellable)
+{
+	GetClientData *data;
+	GError *error = NULL;
+
+	data = g_simple_async_result_get_op_res_gpointer (simple);
+	g_return_if_fail (data != NULL && data->source != NULL);
+
+	data->client = e_source_store_get_client (
+		source_store, data->source, cancellable, &error);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (simple, error);
+		g_error_free (error);
+	}
+}
+
+static GObject *
+source_store_get_client (ESourceStore *source_store,
+                         ESource *source,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+	/* Subclasses should override this. */
+
+	g_set_error (
+		error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+		_("Operation not supported"));
+
+	return NULL;
+}
+
+static void
+source_store_get_client_async (ESourceStore *source_store,
+                               ESource *source,
+                               gint io_priority,
+                               GCancellable *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+	GObject *object = G_OBJECT (source_store);
+	GSimpleAsyncResult *simple;
+	GetClientData *data;
+
+	data = g_slice_new0 (GetClientData);
+	data->source = g_object_ref (source);
+
+	simple = g_simple_async_result_new (
+		object, callback, user_data,
+		e_source_store_get_client_async);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, data, (GDestroyNotify)
+		source_store_get_client_data_free);
+
+	g_simple_async_result_run_in_thread (
+		simple, (GSimpleAsyncThreadFunc)
+		source_store_get_client_thread,
+		io_priority, cancellable);
+
+	g_object_unref (simple);
+}
+
+static GObject *
+source_store_get_client_finish (ESourceStore *source_store,
+                                GAsyncResult *result,
+                                GError **error)
+{
+	GSimpleAsyncResult *simple;
+	GetClientData *data;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (source_store),
+		e_source_store_get_client_async), NULL);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	data = g_simple_async_result_get_op_res_gpointer (simple);
+	g_return_val_if_fail (data != NULL, NULL);
+
+	if (data->client != NULL)
+		return g_object_ref (data->client);
+
+	return NULL;
+}
+
+static void
+e_source_store_class_init (ESourceStoreClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ESourceStorePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_store_set_property;
+	object_class->get_property = source_store_get_property;
+	object_class->dispose = source_store_dispose;
+	object_class->finalize = source_store_finalize;
+
+	class->source_added = source_store_source_added;
+	class->source_removed = source_store_source_removed;
+	class->source_selected = source_store_source_selected;
+	class->source_unselected = source_store_source_unselected;
+
+	class->get_client = source_store_get_client;
+	class->get_client_async = source_store_get_client_async;
+	class->get_client_finish = source_store_get_client_finish;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE_LIST,
+		g_param_spec_object (
+			"source-list",
+			"Source List",
+			NULL,
+			E_TYPE_SOURCE_LIST,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY));
+
+	signals[SOURCE_ADDED] = g_signal_new (
+		"source-added",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceStoreClass, source_added),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+
+	signals[SOURCE_REMOVED] = g_signal_new (
+		"source-removed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceStoreClass, source_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+
+	signals[SOURCE_SELECTED] = g_signal_new (
+		"source-selected",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceStoreClass, source_selected),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+
+	signals[SOURCE_UNSELECTED] = g_signal_new (
+		"source-unselected",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceStoreClass, source_unselected),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+}
+
+static void
+e_source_store_init (ESourceStore *source_store)
+{
+	GType types[E_SOURCE_STORE_NUM_COLUMNS];
+	GHashTable *index;
+	GHashTable *requests;
+	gint column = 0;
+
+	index = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+
+	requests = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	source_store->priv = E_SOURCE_STORE_GET_PRIVATE (source_store);
+
+	source_store->priv->index = index;
+	source_store->priv->requests = requests;
+
+	types[column++] = G_TYPE_OBJECT;	/* COLUMN_SOURCE */
+	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_SELECTED */
+	types[column++] = G_TYPE_OBJECT;	/* COLUMN_CLIENT */
+
+	g_assert (column == E_SOURCE_STORE_NUM_COLUMNS);
+
+	gtk_tree_store_set_column_types (
+		GTK_TREE_STORE (source_store),
+		G_N_ELEMENTS (types), types);
+}
+
+/**
+ * e_source_store_new:
+ * @source_list: an #ESourceList
+ *
+ * Creates a new #ESourceStore instance that keeps itself synchronized
+ * with @source_list.
+ *
+ * Returns: a new #ESourceStore instance
+ **/
+GtkTreeModel *
+e_source_store_new (ESourceList *source_list)
+{
+	g_return_val_if_fail (E_IS_SOURCE_LIST (source_list), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_STORE,
+		"source-list", source_list, NULL);
+}
+
+/**
+ * e_source_store_get_source_list:
+ * @source_store: an #ESourceStore
+ *
+ * Returns the #ESourceList with which @source_store is synchronizing
+ * itself.
+ *
+ * Returns: the #ESourceList passed to e_source_store_new()
+ **/
+ESourceList *
+e_source_store_get_source_list (ESourceStore *source_store)
+{
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+
+	return source_store->priv->source_list;
+}
+
+/**
+ * e_source_store_queue_refresh:
+ * @source_store: an #ESourceStore
+ *
+ * Queues a request for @source_store to syncrhonize itself with its
+ * internal #ESourceList.  This function gets called automatically when
+ * the internal #ESourceList emits change notifications.  The actual
+ * synchronization happens in an idle callback.
+ **/
+void
+e_source_store_queue_refresh (ESourceStore *source_store)
+{
+	guint source_id;
+
+	g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+
+	if (source_store->priv->refresh_source_id > 0)
+		return;
+
+	source_id = gdk_threads_add_idle (
+		(GSourceFunc) source_store_refresh, source_store);
+
+	source_store->priv->refresh_source_id = source_id;
+}
+
+/**
+ * e_source_store_get_iter_from_source:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ * @iter: an uninitialized #GtkTreeIter
+ *
+ * Sets @iter to a valid iterator pointing to @source.
+ *
+ * Returns: %TRUE, if @iter was set
+ **/
+gboolean
+e_source_store_get_iter_from_source (ESourceStore *source_store,
+                                     ESource *source,
+                                     GtkTreeIter *iter)
+{
+	GtkTreeRowReference *reference;
+	GHashTable *hash_table;
+	const gchar *uid;
+
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+	uid = e_source_peek_uid (source);
+	hash_table = source_store->priv->index;
+	reference = g_hash_table_lookup (hash_table, uid);
+
+	if (!gtk_tree_row_reference_valid (reference))
+		return FALSE;
+
+	if (iter != NULL) {
+		GtkTreeModel *model;
+		GtkTreePath *path;
+
+		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);
+	}
+
+	return TRUE;
+}
+
+/**
+ * e_source_store_select_source:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ *
+ * Marks @source as selected and attempts to open a client connection
+ * for @source via e_source_store_get_client().
+ *
+ * <note>
+ *   <para>
+ *     #ESourceStore itself does not know how to open client connections.
+ *     Subclasses must provide this capability.
+ *   </para>
+ * </note>
+ **/
+void
+e_source_store_select_source (ESourceStore *source_store,
+                              ESource *source)
+{
+	GtkTreeIter iter;
+	gboolean selected;
+
+	g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+		return;
+
+	/* Avoid emitting unnecessary "row-changed" signals. */
+
+	gtk_tree_model_get (
+		GTK_TREE_MODEL (source_store), &iter,
+		E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+	if (selected)
+		return;
+
+	gtk_tree_store_set (
+		GTK_TREE_STORE (source_store), &iter,
+		E_SOURCE_STORE_COLUMN_SELECTED, TRUE, -1);
+
+	g_signal_emit (source_store, signals[SOURCE_SELECTED], 0, source);
+}
+
+/**
+ * e_source_store_unselect_source:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ *
+ * Marks @source as unselected and closes the corresponding client
+ * connection, if present.
+ **/
+void
+e_source_store_unselect_source (ESourceStore *source_store,
+                                ESource *source)
+{
+	GtkTreeIter iter;
+	gboolean selected;
+
+	g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+		return;
+
+	/* Avoid emitting unnecessary "row-changed" signals. */
+
+	gtk_tree_model_get (
+		GTK_TREE_MODEL (source_store), &iter,
+		E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+	if (!selected)
+		return;
+
+	gtk_tree_store_set (
+		GTK_TREE_STORE (source_store), &iter,
+		E_SOURCE_STORE_COLUMN_SELECTED, FALSE,
+		E_SOURCE_STORE_COLUMN_CLIENT, NULL, -1);
+
+	g_signal_emit (source_store, signals[SOURCE_UNSELECTED], 0, source);
+}
+
+/**
+ * e_source_store_is_selected:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ *
+ * Returns %TRUE if @source is selected.
+ *
+ * Returns: %TRUE, if @source is selected
+ **/
+gboolean
+e_source_store_is_selected (ESourceStore *source_store,
+                            ESource *source)
+{
+	GtkTreeIter iter;
+	gboolean selected = FALSE;
+
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+	if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+		return FALSE;
+
+	gtk_tree_model_get (
+		GTK_TREE_MODEL (source_store), &iter,
+		E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+	return selected;
+}
+
+/* Helper for e_source_store_get_selection() */
+static gboolean
+source_store_get_selection_foreach_cb (GtkTreeModel *model,
+                                       GtkTreePath *path,
+                                       GtkTreeIter *iter,
+                                       GSList **list)
+{
+	ESource *source;
+	gboolean selected;
+
+	/* Groups are at depth 1, sources are at depth 2. */
+	if (gtk_tree_path_get_depth (path) != 2)
+		return FALSE;
+
+	gtk_tree_model_get (
+		model, iter,
+		E_SOURCE_STORE_COLUMN_SOURCE, &source,
+		E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+	g_return_val_if_fail (E_IS_SOURCE (source), TRUE);
+
+	if (selected)
+		*list = g_slist_prepend (*list, source);
+	else
+		g_object_unref (source);
+
+	return FALSE;
+}
+
+/**
+ * e_source_store_get_selected:
+ * @source_store: an #ESourceStore
+ *
+ * Returns a #GSList of currently selected sources.  The returned list
+ * should be freed as follows:
+ *
+ * <informalexample>
+ *   <programlisting>
+ *     g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+ *     g_slist_free (list);
+ *   </programlisting>
+ * </informalexample>
+ *
+ * Returns: a #GSList of selected sources
+ **/
+GSList *
+e_source_store_get_selected (ESourceStore *source_store)
+{
+	GSList *list = NULL;
+
+	/* XXX Only returning GSList here because ESourceSelector does.
+	 *     I would prefer GList, but too late to change it. */
+
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+
+	gtk_tree_model_foreach (
+		GTK_TREE_MODEL (source_store), (GtkTreeModelForeachFunc)
+		source_store_get_selection_foreach_cb, &list);
+
+	return g_slist_reverse (list);
+}
+
+/**
+ * e_source_store_get_client:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ * @cancellable: optional #GCancellable, or %NULL to ignore
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns a client connection for @source, creating and opening it first
+ * if necessary.  If an error occurs, the function returns %NULL and sets
+ * @error.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by
+ * triggering the cancellable object from another thread.  If the operation
+ * was cancelled, a #G_IO_ERROR_CANCELLED will be reported.
+ *
+ * This function may block for a long time, and should not be called from
+ * a thread running a #GMainLoop.  See e_source_store_get_client_async()
+ * for an asynchronous version of this call.
+ *
+ * <note>
+ *   <para>
+ *     #ESourceStore itself does not know how to open client connections
+ *     and will simply report a #G_IO_ERROR_NOT_SUPPORTED and return %NULL.
+ *     Subclasses must provide this capability.
+ *   </para>
+ * </note>
+ *
+ * Returns: a client connection for @source, or %NULL on error
+ **/
+GObject *
+e_source_store_get_client (ESourceStore *source_store,
+                           ESource *source,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+	ESourceStoreClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+	class = E_SOURCE_STORE_GET_CLASS (source_store);
+	g_return_val_if_fail (class->get_client != NULL, NULL);
+
+	return class->get_client (source_store, source, cancellable, error);
+}
+
+/**
+ * e_source_store_get_client_async:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ * @io_priority: the <link linkend="io-priority">I/O priority</link>
+ *     of the request
+ * @cancellable: optional #GCancellable, or %NULL to ignore
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to the callback function
+ *
+ * Asynchronously gets a client connection for @source, creating and
+ * opening it first if necessary.
+ *
+ * For more details, see e_source_store_get_client() which is the
+ * synchronous version of this call.
+ *
+ * When the operation is finished, @callback will be called.  You can
+ * then call e_source_store_get_client_finish() to get the result of the
+ * operation.
+ **/
+void
+e_source_store_get_client_async (ESourceStore *source_store,
+                                 ESource *source,
+                                 gint io_priority,
+                                 GCancellable *cancellable,
+                                 GAsyncReadyCallback callback,
+                                 gpointer user_data)
+{
+	ESourceStoreClass *class;
+
+	g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	class = E_SOURCE_STORE_GET_CLASS (source_store);
+	g_return_if_fail (class->get_client_async != NULL);
+
+	class->get_client_async (
+		source_store, source, io_priority,
+		cancellable, callback, user_data);
+}
+
+/**
+ * e_source_store_get_client_finish:
+ * @source_store: an #ESourceStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes an asynchronous client operation started with
+ * e_source_store_get_client_async().
+ *
+ * Returns: a client connection, or %NULL on error
+ **/
+GObject *
+e_source_store_get_client_finish (ESourceStore *source_store,
+                                  GAsyncResult *result,
+                                  GError **error)
+{
+	ESourceStoreClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+	if (G_IS_SIMPLE_ASYNC_RESULT (result)) {
+		GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+		if (g_simple_async_result_propagate_error (simple, error))
+			return NULL;
+	}
+
+	class = E_SOURCE_STORE_GET_CLASS (source_store);
+	g_return_val_if_fail (class->get_client_finish != NULL, NULL);
+
+	return class->get_client_finish (source_store, result, error);
+}
diff --git a/libedataserverui/e-source-store.h b/libedataserverui/e-source-store.h
new file mode 100644
index 0000000..c68b8f6
--- /dev/null
+++ b/libedataserverui/e-source-store.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-store.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef E_SOURCE_STORE_H
+#define E_SOURCE_STORE_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/e-source-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_STORE \
+	(e_source_store_get_type ())
+#define E_SOURCE_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_STORE, ESourceStore))
+#define E_SOURCE_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_STORE, ESourceStoreClass))
+#define E_IS_SOURCE_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_STORE))
+#define E_IS_SOURCE_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_STORE))
+#define E_SOURCE_STORE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_STORE, ESourceStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceStore ESourceStore;
+typedef struct _ESourceStoreClass ESourceStoreClass;
+typedef struct _ESourceStorePrivate ESourceStorePrivate;
+
+enum {
+	E_SOURCE_STORE_COLUMN_SOURCE,		/* G_TYPE_OBJECT */
+	E_SOURCE_STORE_COLUMN_SELECTED,		/* G_TYPE_BOOLEAN */
+	E_SOURCE_STORE_COLUMN_CLIENT,		/* G_TYPE_OBJECT */
+	E_SOURCE_STORE_NUM_COLUMNS
+};
+
+struct _ESourceStore {
+	GtkTreeStore parent;
+	ESourceStorePrivate *priv;
+};
+
+struct _ESourceStoreClass {
+	GtkTreeStoreClass parent_class;
+
+	/* Signals */
+	void		(*source_added)		(ESourceStore *source_store,
+						 ESource *source);
+	void		(*source_removed)	(ESourceStore *source_store,
+						 ESource *source);
+	void		(*source_selected)	(ESourceStore *source_store,
+						 ESource *source);
+	void		(*source_unselected)	(ESourceStore *source_store,
+						 ESource *source);
+
+	/* Padding for additional signals. */
+	void		(*_reserved_signal_0)	(void);
+	void		(*_reserved_signal_1)	(void);
+	void		(*_reserved_signal_2)	(void);
+	void		(*_reserved_signal_3)	(void);
+
+	/* Methods */
+	GObject *	(*get_client)		(ESourceStore *source_store,
+						 ESource *source,
+						 GCancellable *cancellable,
+						 GError **error);
+	void		(*get_client_async)	(ESourceStore *source_store,
+						 ESource *source,
+						 gint io_priority,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+	GObject *	(*get_client_finish)	(ESourceStore *source_store,
+						 GAsyncResult *result,
+						 GError **error);
+
+	/* Padding for additional methods. */
+	void		(*_reserved_method_0)	(void);
+	void		(*_reserved_method_1)	(void);
+	void		(*_reserved_method_2)	(void);
+	void		(*_reserved_method_3)	(void);
+	void		(*_reserved_method_4)	(void);
+	void		(*_reserved_method_5)	(void);
+};
+
+GType		e_source_store_get_type		(void);
+GtkTreeModel *	e_source_store_new		(ESourceList *source_list);
+ESourceList *	e_source_store_get_source_list	(ESourceStore *source_store);
+void		e_source_store_queue_refresh	(ESourceStore *source_store);
+gboolean	e_source_store_get_iter_from_source
+						(ESourceStore *source_store,
+						 ESource *source,
+						 GtkTreeIter *iter);
+void		e_source_store_select_source	(ESourceStore *source_store,
+						 ESource *source);
+void		e_source_store_unselect_source	(ESourceStore *source_store,
+						 ESource *source);
+gboolean	e_source_store_is_selected	(ESourceStore *source_store,
+						 ESource *source);
+GSList *	e_source_store_get_selected	(ESourceStore *source_store);
+GObject *	e_source_store_get_client	(ESourceStore *source_store,
+						 ESource *source,
+						 GCancellable *cancellable,
+						 GError **error);
+void		e_source_store_get_client_async	(ESourceStore *source_store,
+						 ESource *source,
+						 gint io_priority,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+GObject *	e_source_store_get_client_finish(ESourceStore *source_store,
+						 GAsyncResult *result,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_STORE_H */
diff --git a/libedataserverui/test-source-selector.c b/libedataserverui/test-source-selector.c
index 70833e2..8764e6a 100644
--- a/libedataserverui/test-source-selector.c
+++ b/libedataserverui/test-source-selector.c
@@ -81,6 +81,10 @@ on_idle_create_widget (const gchar *gconf_path)
 	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 	gtk_window_set_default_size (GTK_WINDOW (window), 200, 300);
 
+	g_signal_connect (
+		window, "delete-event",
+		G_CALLBACK (gtk_main_quit), NULL);
+
 	vbox = gtk_vbox_new (FALSE, 3);
 	gtk_container_add (GTK_CONTAINER (window), vbox);
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]