[almanah] Bug 687229 - Embed ESourceSelector widget from libedataserverui



commit e481f5abdb46d41ac00ee3393332e92f605b55e7
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Dec 13 09:16:58 2012 -0500

    Bug 687229 - Embed ESourceSelector widget from libedataserverui
    
    Evolution developers are merging libedataserverui back into Evolution.
    Drop the libedataserverui dependency and embed the ECellRendererColor
    and ESourceSelector widgets from libedataserverui in event-factories.
    
    See also:
    https://mail.gnome.org/archives/distributor-list/2012-December/msg00000.html

 configure.ac                                |    2 +-
 src/Makefile.am                             |    4 +
 src/event-factories/calendar-sources.c      |    3 +-
 src/event-factories/e-cell-renderer-color.c |  237 +++
 src/event-factories/e-cell-renderer-color.h |   75 +
 src/event-factories/e-source-selector.c     | 2080 +++++++++++++++++++++++++++
 src/event-factories/e-source-selector.h     |  137 ++
 7 files changed, 2536 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a529568..b89ba74 100644
--- a/configure.ac
+++ b/configure.ac
@@ -83,7 +83,7 @@ AC_SUBST(STANDARD_CFLAGS)
 AC_SUBST(STANDARD_LIBS)
 
 dnl Evolution
-PKG_CHECK_MODULES(EVO, libecal-1.2 >= 3.5.91 libedataserver-1.2 libedataserverui-3.0, have_evo=yes, have_evo=no)
+PKG_CHECK_MODULES(EVO, libecal-1.2 >= 3.5.91 libedataserver-1.2, have_evo=yes, have_evo=no)
 if test "x$have_evo" = "xyes"; then
 	AC_DEFINE(HAVE_EVO, 1, [Defined if libecal-1.2 is installed])
 fi
diff --git a/src/Makefile.am b/src/Makefile.am
index 0de37e3..740c9f2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -52,6 +52,10 @@ almanah_SOURCES += \
 	event-factories/calendar-debug.h	\
 	event-factories/calendar-sources.c	\
 	event-factories/calendar-sources.h	\
+	event-factories/e-cell-renderer-color.c	\
+	event-factories/e-cell-renderer-color.h	\
+	event-factories/e-source-selector.c	\
+	event-factories/e-source-selector.h	\
 	events/calendar-appointment.c		\
 	events/calendar-appointment.h		\
 	events/calendar-task.c			\
diff --git a/src/event-factories/calendar-sources.c b/src/event-factories/calendar-sources.c
index 217327d..9451a0d 100644
--- a/src/event-factories/calendar-sources.c
+++ b/src/event-factories/calendar-sources.c
@@ -32,7 +32,8 @@
 #define HANDLE_LIBICAL_MEMORY
 #include <libecal/libecal.h>
 #include <libedataserver/libedataserver.h>
-#include <libedataserverui/libedataserverui.h>
+
+#include "e-source-selector.h"
 
 #undef CALENDAR_ENABLE_DEBUG
 #include "calendar-debug.h"
diff --git a/src/event-factories/e-cell-renderer-color.c b/src/event-factories/e-cell-renderer-color.c
new file mode 100644
index 0000000..748bea5
--- /dev/null
+++ b/src/event-factories/e-cell-renderer-color.c
@@ -0,0 +1,237 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-cell-renderer-color.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-renderer-color.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#define E_CELL_RENDERER_COLOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColorPrivate))
+
+enum {
+	PROP_0,
+	PROP_COLOR
+};
+
+struct _ECellRendererColorPrivate {
+	GdkColor *color;
+};
+
+G_DEFINE_TYPE (
+	ECellRendererColor,
+	e_cell_renderer_color,
+	GTK_TYPE_CELL_RENDERER)
+
+static void
+cell_renderer_color_get_size (GtkCellRenderer *cell,
+                              GtkWidget *widget,
+                              const GdkRectangle *cell_area,
+                              gint *x_offset,
+                              gint *y_offset,
+                              gint *width,
+                              gint *height)
+{
+	gint color_width  = 16;
+	gint color_height = 16;
+	gint calc_width;
+	gint calc_height;
+	gfloat xalign;
+	gfloat yalign;
+	guint xpad;
+	guint ypad;
+
+	g_object_get (
+		cell, "xalign", &xalign, "yalign", &yalign,
+		"xpad", &xpad, "ypad", &ypad, NULL);
+
+	calc_width  = (gint) xpad * 2 + color_width;
+	calc_height = (gint) ypad * 2 + color_height;
+
+	if (cell_area && color_width > 0 && color_height > 0) {
+		if (x_offset) {
+			*x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ?
+					(1.0 - xalign) : xalign) *
+					(cell_area->width - calc_width));
+			*x_offset = MAX (*x_offset, 0);
+		}
+
+		if (y_offset) {
+			*y_offset =(yalign *
+				(cell_area->height - calc_height));
+			*y_offset = MAX (*y_offset, 0);
+		}
+	} else {
+		if (x_offset) *x_offset = 0;
+		if (y_offset) *y_offset = 0;
+	}
+
+	if (width)
+		*width = calc_width;
+
+	if (height)
+		*height = calc_height;
+}
+
+static void
+cell_renderer_color_render (GtkCellRenderer *cell,
+                            cairo_t *cr,
+                            GtkWidget *widget,
+                            const GdkRectangle *background_area,
+                            const GdkRectangle *cell_area,
+                            GtkCellRendererState flags)
+{
+	ECellRendererColorPrivate *priv;
+	GdkRectangle pix_rect;
+	GdkRectangle draw_rect;
+	guint xpad;
+	guint ypad;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (cell);
+
+	if (priv->color == NULL)
+		return;
+
+	cell_renderer_color_get_size (
+		cell, widget, cell_area,
+		&pix_rect.x, &pix_rect.y,
+		&pix_rect.width, &pix_rect.height);
+
+	g_object_get (cell, "xpad", &xpad, "ypad", &ypad, NULL);
+
+	pix_rect.x += cell_area->x + xpad;
+	pix_rect.y += cell_area->y + ypad;
+	pix_rect.width  -= xpad * 2;
+	pix_rect.height -= ypad * 2;
+
+	if (!gdk_rectangle_intersect (cell_area, &pix_rect, &draw_rect))
+		return;
+
+	gdk_cairo_set_source_color (cr, priv->color);
+	cairo_rectangle (cr, pix_rect.x, pix_rect.y, draw_rect.width, draw_rect.height);
+
+	cairo_fill (cr);
+}
+
+static void
+cell_renderer_color_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	ECellRendererColorPrivate *priv;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_COLOR:
+			if (priv->color != NULL)
+				gdk_color_free (priv->color);
+			priv->color = g_value_dup_boxed (value);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cell_renderer_color_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+	ECellRendererColorPrivate *priv;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_COLOR:
+			g_value_set_boxed (value, priv->color);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cell_renderer_color_finalize (GObject *object)
+{
+	ECellRendererColorPrivate *priv;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (object);
+
+	if (priv->color != NULL)
+		gdk_color_free (priv->color);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_cell_renderer_color_parent_class)->finalize (object);
+}
+
+static void
+e_cell_renderer_color_class_init (ECellRendererColorClass *class)
+{
+	GObjectClass *object_class;
+	GtkCellRendererClass *cell_class;
+
+	g_type_class_add_private (class, sizeof (ECellRendererColorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = cell_renderer_color_set_property;
+	object_class->get_property = cell_renderer_color_get_property;
+	object_class->finalize = cell_renderer_color_finalize;
+
+	cell_class = GTK_CELL_RENDERER_CLASS (class);
+	cell_class->get_size = cell_renderer_color_get_size;
+	cell_class->render = cell_renderer_color_render;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLOR,
+		g_param_spec_boxed (
+			"color",
+			"Color Info",
+			"The color to render",
+			GDK_TYPE_COLOR,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_cell_renderer_color_init (ECellRendererColor *cellcolor)
+{
+	cellcolor->priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (cellcolor);
+
+	g_object_set (cellcolor, "xpad", 4, NULL);
+}
+
+/**
+ * e_cell_renderer_color_new:
+ *
+ * Since: 2.22
+ **/
+GtkCellRenderer *
+e_cell_renderer_color_new (void)
+{
+	return g_object_new (E_TYPE_CELL_RENDERER_COLOR, NULL);
+}
diff --git a/src/event-factories/e-cell-renderer-color.h b/src/event-factories/e-cell-renderer-color.h
new file mode 100644
index 0000000..0e0da70
--- /dev/null
+++ b/src/event-factories/e-cell-renderer-color.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-cell-renderer-color.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * 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_CELL_RENDERER_COLOR_H_
+#define _E_CELL_RENDERER_COLOR_H_
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_RENDERER_COLOR \
+	(e_cell_renderer_color_get_type ())
+#define E_CELL_RENDERER_COLOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColor))
+#define E_CELL_RENDERER_COLOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColorClass))
+#define E_IS_CELL_RENDERER_COLOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_RENDERER_COLOR))
+#define E_IS_CELL_RENDERER_COLOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE ((cls), E_TYPE_CELL_RENDERER_COLOR))
+#define E_CELL_RENDERER_COLOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellRendererColor ECellRendererColor;
+typedef struct _ECellRendererColorClass ECellRendererColorClass;
+typedef struct _ECellRendererColorPrivate ECellRendererColorPrivate;
+
+/**
+ * ECellRendererColor:
+ *
+ * Since: 2.22
+ **/
+struct _ECellRendererColor {
+	GtkCellRenderer parent;
+	ECellRendererColorPrivate *priv;
+};
+
+struct _ECellRendererColorClass {
+	GtkCellRendererClass parent_class;
+
+	/* Padding for future expansion */
+	void (*_gtk_reserved1) (void);
+	void (*_gtk_reserved2) (void);
+	void (*_gtk_reserved3) (void);
+	void (*_gtk_reserved4) (void);
+};
+
+GType            e_cell_renderer_color_get_type	(void);
+GtkCellRenderer *e_cell_renderer_color_new	(void);
+
+G_END_DECLS
+
+#endif /* _E_CELL_RENDERER_COLOR_H_ */
diff --git a/src/event-factories/e-source-selector.c b/src/event-factories/e-source-selector.c
new file mode 100644
index 0000000..925d9cd
--- /dev/null
+++ b/src/event-factories/e-source-selector.c
@@ -0,0 +1,2080 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-selector.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * 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.
+ *
+ * Author: Ettore Perazzoli <ettore ximian com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "e-cell-renderer-color.h"
+#include "e-source-selector.h"
+
+#define E_SOURCE_SELECTOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _ESourceSelectorPrivate {
+	ESourceRegistry *registry;
+	GHashTable *source_index;
+	gchar *extension_name;
+
+	GtkTreeRowReference *saved_primary_selection;
+
+	/* ESource -> GSource */
+	GHashTable *pending_writes;
+	GMainContext *main_context;
+
+	gboolean toggled_last;
+	gboolean select_new;
+	gboolean show_colors;
+	gboolean show_toggles;
+};
+
+struct _AsyncContext {
+	ESourceSelector *selector;
+	ESource *source;
+};
+
+enum {
+	PROP_0,
+	PROP_EXTENSION_NAME,
+	PROP_PRIMARY_SELECTION,
+	PROP_REGISTRY,
+	PROP_SHOW_COLORS,
+	PROP_SHOW_TOGGLES
+};
+
+enum {
+	SELECTION_CHANGED,
+	PRIMARY_SELECTION_CHANGED,
+	POPUP_EVENT,
+	DATA_DROPPED,
+	NUM_SIGNALS
+};
+
+enum {
+	COLUMN_NAME,
+	COLUMN_COLOR,
+	COLUMN_ACTIVE,
+	COLUMN_SHOW_COLOR,
+	COLUMN_SHOW_TOGGLE,
+	COLUMN_WEIGHT,
+	COLUMN_SOURCE,
+	NUM_COLUMNS
+};
+
+static guint signals[NUM_SIGNALS];
+
+G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)
+
+/* ESafeToggleRenderer does not emit 'toggled' signal
+ * on 'activate' when mouse is not over the toggle. */
+
+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)
+
+static gboolean
+safe_toggle_activate (GtkCellRenderer *cell,
+                      GdkEvent *event,
+                      GtkWidget *widget,
+                      const gchar *path,
+                      const GdkRectangle *background_area,
+                      const GdkRectangle *cell_area,
+                      GtkCellRendererState flags)
+{
+	gboolean point_in_cell_area = TRUE;
+
+	if (event->type == GDK_BUTTON_PRESS && cell_area != NULL) {
+		cairo_region_t *region;
+
+		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 (!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 *cell_renderer_class;
+
+	cell_renderer_class = GTK_CELL_RENDERER_CLASS (class);
+	cell_renderer_class->activate = safe_toggle_activate;
+}
+
+static void
+e_cell_renderer_safe_toggle_init (ECellRendererSafeToggle *obj)
+{
+}
+
+static GtkCellRenderer *
+e_cell_renderer_safe_toggle_new (void)
+{
+	return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
+}
+
+static void
+clear_saved_primary_selection (ESourceSelector *selector)
+{
+	gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
+	selector->priv->saved_primary_selection = NULL;
+}
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->selector != NULL)
+		g_object_unref (async_context->selector);
+
+	if (async_context->source != NULL)
+		g_object_unref (async_context->source);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static void
+pending_writes_destroy_source (GSource *source)
+{
+	g_source_destroy (source);
+	g_source_unref (source);
+}
+
+static void
+source_selector_write_done_cb (GObject *source_object,
+                               GAsyncResult *result,
+                               gpointer user_data)
+{
+	ESource *source;
+	ESourceSelector *selector;
+	GError *error = NULL;
+
+	source = E_SOURCE (source_object);
+	selector = E_SOURCE_SELECTOR (user_data);
+
+	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 gboolean
+source_selector_write_idle_cb (gpointer user_data)
+{
+	AsyncContext *async_context = user_data;
+	GHashTable *pending_writes;
+
+	/* 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
+source_selector_cancel_write (ESourceSelector *selector,
+                              ESource *source)
+{
+	GHashTable *pending_writes;
+
+	/* 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 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;
+
+	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 we do have a row reference, it should be valid. */
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	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);
+
+	display_name = e_source_get_display_name (source);
+
+	extension_name = e_source_selector_get_extension_name (selector);
+	selected = e_source_selector_source_is_selected (selector, source);
+
+	if (e_source_has_extension (source, extension_name))
+		extension = e_source_get_extension (source, extension_name);
+
+	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);
+	}
+}
+
+static gboolean
+source_selector_traverse (GNode *node,
+                          ESourceSelector *selector)
+{
+	ESource *source;
+	GHashTable *source_index;
+	GtkTreeRowReference *reference = NULL;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	/* Skip the root node. */
+	if (G_NODE_IS_ROOT (node))
+		return FALSE;
+
+	source_index = selector->priv->source_index;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+
+	if (node->parent != NULL && node->parent->data != NULL)
+		reference = g_hash_table_lookup (
+			source_index, node->parent->data);
+
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreeIter parent;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		gtk_tree_model_get_iter (model, &parent, path);
+		gtk_tree_path_free (path);
+
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
+	} else
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+
+	source = E_SOURCE (node->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);
+
+	source_selector_update_row (selector, source);
+
+	return FALSE;
+}
+
+static void
+source_selector_save_expanded (GtkTreeView *tree_view,
+                               GtkTreePath *path,
+                               GQueue *queue)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	ESource *source;
+
+	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
+source_selector_build_model (ESourceSelector *selector)
+{
+	ESourceRegistry *registry;
+	GQueue queue = G_QUEUE_INIT;
+	GHashTable *source_index;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	ESource *selected;
+	const gchar *extension_name;
+	GNode *root;
+
+	tree_view = GTK_TREE_VIEW (selector);
+
+	registry = e_source_selector_get_registry (selector);
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	/* 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;
+
+	source_index = selector->priv->source_index;
+	selected = e_source_selector_ref_primary_selection (selector);
+
+	/* Save expanded sources to restore later. */
+	gtk_tree_view_map_expanded_rows (
+		tree_view, (GtkTreeViewMappingFunc)
+		source_selector_save_expanded, &queue);
+
+	model = gtk_tree_view_get_model (tree_view);
+	gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+	g_hash_table_remove_all (source_index);
+
+	root = e_source_registry_build_display_tree (registry, extension_name);
+
+	g_node_traverse (
+		root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) source_selector_traverse,
+		selector);
+
+	e_source_registry_free_display_tree (root);
+
+	/* Restore previously expanded sources. */
+	while (!g_queue_is_empty (&queue)) {
+		GtkTreeRowReference *reference;
+		ESource *source;
+
+		source = g_queue_pop_head (&queue);
+		reference = g_hash_table_lookup (source_index, source);
+
+		if (gtk_tree_row_reference_valid (reference)) {
+			GtkTreePath *path;
+
+			path = gtk_tree_row_reference_get_path (reference);
+			gtk_tree_view_expand_to_path (tree_view, path);
+			gtk_tree_path_free (path);
+		}
+
+		g_object_unref (source);
+	}
+
+	/* Restore the primary selection. */
+	if (selected != NULL) {
+		e_source_selector_set_primary_selection (selector, selected);
+		g_object_unref (selected);
+	}
+
+	/* 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 void
+source_selector_expand_to_source (ESourceSelector *selector,
+                                  ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreePath *path;
+
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* If the ESource is not in our tree model then return silently. */
+	if (reference == NULL)
+		return;
+
+	/* If we do have a row reference, it should be valid. */
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	/* 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 void
+source_selector_source_added_cb (ESourceRegistry *registry,
+                                 ESource *source,
+                                 ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+
+	source_selector_expand_to_source (selector, source);
+}
+
+static void
+source_selector_source_changed_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
+{
+	source_selector_cancel_write (selector, source);
+
+	source_selector_update_row (selector, source);
+}
+
+static void
+source_selector_source_removed_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+}
+
+static void
+source_selector_source_enabled_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+
+	source_selector_expand_to_source (selector, source);
+}
+
+static void
+source_selector_source_disabled_cb (ESourceRegistry *registry,
+                                    ESource *source,
+                                    ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+}
+
+static gboolean
+same_source_name_exists (ESourceSelector *selector,
+                         const gchar *display_name)
+{
+	GHashTable *source_index;
+	GHashTableIter iter;
+	gpointer key;
+
+	source_index = selector->priv->source_index;
+	g_hash_table_iter_init (&iter, source_index);
+
+	while (g_hash_table_iter_next (&iter, &key, NULL)) {
+		ESource *source = E_SOURCE (key);
+		const gchar *source_name;
+
+		source_name = e_source_get_display_name (source);
+		if (g_strcmp0 (display_name, source_name) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+selection_func (GtkTreeSelection *selection,
+                GtkTreeModel *model,
+                GtkTreePath *path,
+                gboolean path_currently_selected,
+                ESourceSelector *selector)
+{
+	ESource *source;
+	GtkTreeIter iter;
+	const gchar *extension_name;
+
+	if (selector->priv->toggled_last) {
+		selector->priv->toggled_last = FALSE;
+		return FALSE;
+	}
+
+	if (path_currently_selected)
+		return TRUE;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		return FALSE;
+
+	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 (source);
+
+	return TRUE;
+}
+
+static void
+text_cell_edited_cb (ESourceSelector *selector,
+                     const gchar *path_string,
+                     const gchar *new_name)
+{
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	ESource *source;
+
+	tree_view = GTK_TREE_VIEW (selector);
+	model = gtk_tree_view_get_model (tree_view);
+	path = gtk_tree_path_new_from_string (path_string);
+
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+	gtk_tree_path_free (path);
+
+	if (new_name == NULL || *new_name == '\0')
+		return;
+
+	if (same_source_name_exists (selector, new_name))
+		return;
+
+	e_source_set_display_name (source, new_name);
+
+	e_source_selector_queue_write (selector, source);
+}
+
+static void
+cell_toggled_callback (GtkCellRendererToggle *renderer,
+                       const gchar *path_string,
+                       ESourceSelector *selector)
+{
+	ESource *source;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	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)) {
+		gtk_tree_path_free (path);
+		return;
+	}
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (e_source_selector_source_is_selected (selector, source))
+		e_source_selector_unselect_source (selector, source);
+	else
+		e_source_selector_select_source (selector, source);
+
+	selector->priv->toggled_last = TRUE;
+
+	gtk_tree_path_free (path);
+
+	g_object_unref (source);
+}
+
+static void
+selection_changed_callback (GtkTreeSelection *selection,
+                            ESourceSelector *selector)
+{
+	g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
+	g_object_notify (G_OBJECT (selector), "primary-selection");
+}
+
+static void
+source_selector_set_extension_name (ESourceSelector *selector,
+                                    const gchar *extension_name)
+{
+	g_return_if_fail (extension_name != NULL);
+	g_return_if_fail (selector->priv->extension_name == NULL);
+
+	selector->priv->extension_name = g_strdup (extension_name);
+}
+
+static void
+source_selector_set_registry (ESourceSelector *selector,
+                              ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (selector->priv->registry == NULL);
+
+	selector->priv->registry = g_object_ref (registry);
+}
+
+static void
+source_selector_set_property (GObject *object,
+                              guint property_id,
+                              const GValue *value,
+                              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_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);
+}
+
+static void
+source_selector_get_property (GObject *object,
+                              guint property_id,
+                              GValue *value,
+                              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,
+				e_source_selector_ref_primary_selection (
+				E_SOURCE_SELECTOR (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				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;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_selector_dispose (GObject *object)
+{
+	ESourceSelectorPrivate *priv;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
+
+	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;
+	}
+
+	g_hash_table_remove_all (priv->source_index);
+	g_hash_table_remove_all (priv->pending_writes);
+
+	clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_selector_parent_class)->dispose (object);
+}
+
+static void
+source_selector_finalize (GObject *object)
+{
+	ESourceSelectorPrivate *priv;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
+
+	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)
+{
+	ESourceSelector *selector;
+	GtkWidgetClass *widget_class;
+	GtkTreePath *path;
+	ESource *source = NULL;
+	ESource *primary;
+	gboolean right_click = FALSE;
+	gboolean triple_click = FALSE;
+	gboolean row_exists;
+	gboolean res = FALSE;
+
+	selector = E_SOURCE_SELECTOR (widget);
+
+	selector->priv->toggled_last = FALSE;
+
+	/* Triple-clicking a source selects it exclusively. */
+
+	if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+		right_click = TRUE;
+	else if (event->button == 1 && event->type == GDK_3BUTTON_PRESS)
+		triple_click = TRUE;
+	else
+		goto chainup;
+
+	row_exists = gtk_tree_view_get_path_at_pos (
+		GTK_TREE_VIEW (widget), event->x, event->y,
+		&path, NULL, NULL, NULL);
+
+	/* Get the source/group */
+	if (row_exists) {
+		GtkTreeModel *model;
+		GtkTreeIter iter;
+
+		model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+		gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+	}
+
+	if (source == NULL)
+		goto chainup;
+
+	primary = e_source_selector_ref_primary_selection (selector);
+	if (source != primary)
+		e_source_selector_set_primary_selection (selector, source);
+	if (primary != NULL)
+		g_object_unref (primary);
+
+	if (right_click)
+		g_signal_emit (
+			widget, signals[POPUP_EVENT], 0, source, event, &res);
+
+	if (triple_click) {
+		e_source_selector_select_exclusive (selector, source);
+		res = TRUE;
+	}
+
+	g_object_unref (source);
+
+	return res;
+
+chainup:
+
+	/* Chain up to parent's button_press_event() method. */
+	widget_class = GTK_WIDGET_CLASS (e_source_selector_parent_class);
+	return widget_class->button_press_event (widget, event);
+}
+
+static void
+source_selector_drag_leave (GtkWidget *widget,
+                            GdkDragContext *context,
+                            guint time_)
+{
+	GtkTreeView *tree_view;
+	GtkTreeViewDropPosition pos;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	pos = GTK_TREE_VIEW_DROP_BEFORE;
+
+	gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos);
+}
+
+static gboolean
+source_selector_drag_motion (GtkWidget *widget,
+                             GdkDragContext *context,
+                             gint x,
+                             gint y,
+                             guint time_)
+{
+	ESource *source = NULL;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+	GtkTreeViewDropPosition pos;
+	GdkDragAction action = 0;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+
+	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
+		goto exit;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		goto exit;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (!e_source_get_writable (source))
+		goto exit;
+
+	pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
+	gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
+
+	if (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)
+		action = GDK_ACTION_MOVE;
+	else
+		action = gdk_drag_context_get_suggested_action (context);
+
+exit:
+	if (path != NULL)
+		gtk_tree_path_free (path);
+
+	if (source != NULL)
+		g_object_unref (source);
+
+	gdk_drag_status (context, action, time_);
+
+	return TRUE;
+}
+
+static gboolean
+source_selector_drag_drop (GtkWidget *widget,
+                           GdkDragContext *context,
+                           gint x,
+                           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;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+
+	if (!gtk_tree_view_get_path_at_pos (
+		tree_view, x, y, &path, NULL, NULL, NULL))
+		return FALSE;
+
+	valid = gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+	g_return_val_if_fail (valid, FALSE);
+
+	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;
+}
+
+static void
+source_selector_drag_data_received (GtkWidget *widget,
+                                    GdkDragContext *context,
+                                    gint x,
+                                    gint y,
+                                    GtkSelectionData *selection_data,
+                                    guint info,
+                                    guint time_)
+{
+	ESource *source = NULL;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+	GdkDragAction action;
+	gboolean delete;
+	gboolean success = FALSE;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+
+	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;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		goto exit;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (!e_source_get_writable (source))
+		goto exit;
+
+	g_signal_emit (
+		widget, signals[DATA_DROPPED], 0, selection_data,
+		source, gdk_drag_context_get_selected_action (context),
+		info, &success);
+
+exit:
+	if (path != NULL)
+		gtk_tree_path_free (path);
+
+	if (source != NULL)
+		g_object_unref (source);
+
+	gtk_drag_finish (context, success, delete, time_);
+}
+
+static gboolean
+source_selector_popup_menu (GtkWidget *widget)
+{
+	ESourceSelector *selector;
+	ESource *source;
+	gboolean res = FALSE;
+
+	selector = E_SOURCE_SELECTOR (widget);
+	source = e_source_selector_ref_primary_selection (selector);
+	g_signal_emit (selector, signals[POPUP_EVENT], 0, source, NULL, &res);
+
+	if (source != NULL)
+		g_object_unref (source);
+
+	return res;
+}
+
+static gboolean
+source_selector_test_collapse_row (GtkTreeView *tree_view,
+                                   GtkTreeIter *iter,
+                                   GtkTreePath *path)
+{
+	ESourceSelectorPrivate *priv;
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter child_iter;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
+
+	/* Clear this because something else has been clicked on now */
+	priv->toggled_last = FALSE;
+
+	if (priv->saved_primary_selection)
+		return FALSE;
+
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	if (!gtk_tree_selection_get_selected (selection, &model, &child_iter))
+		return FALSE;
+
+	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
+		GtkTreeRowReference *reference;
+		GtkTreePath *child_path;
+
+		child_path = gtk_tree_model_get_path (model, &child_iter);
+		reference = gtk_tree_row_reference_new (model, child_path);
+		priv->saved_primary_selection = reference;
+		gtk_tree_path_free (child_path);
+	}
+
+	return FALSE;
+}
+
+static void
+source_selector_row_expanded (GtkTreeView *tree_view,
+                              GtkTreeIter *iter,
+                              GtkTreePath *path)
+{
+	ESourceSelectorPrivate *priv;
+	GtkTreeModel *model;
+	GtkTreePath *child_path;
+	GtkTreeIter child_iter;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
+
+	if (!priv->saved_primary_selection)
+		return;
+
+	model = gtk_tree_view_get_model (tree_view);
+
+	child_path = gtk_tree_row_reference_get_path (
+		priv->saved_primary_selection);
+	gtk_tree_model_get_iter (model, &child_iter, child_path);
+
+	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
+		GtkTreeSelection *selection;
+
+		selection = gtk_tree_view_get_selection (tree_view);
+		gtk_tree_selection_select_iter (selection, &child_iter);
+
+		clear_saved_primary_selection (E_SOURCE_SELECTOR (tree_view));
+	}
+
+	gtk_tree_path_free (child_path);
+}
+
+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 v_boolean;
+
+	v_boolean = g_value_get_boolean (in);
+	g_value_set_boolean (out, v_boolean);
+
+	return !v_boolean;
+}
+
+static void
+e_source_selector_class_init (ESourceSelectorClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkTreeViewClass *tree_view_class;
+
+	g_type_class_add_private (class, sizeof (ESourceSelectorPrivate));
+
+	object_class = G_OBJECT_CLASS (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;
+	widget_class->drag_leave = source_selector_drag_leave;
+	widget_class->drag_motion = source_selector_drag_motion;
+	widget_class->drag_drop = source_selector_drag_drop;
+	widget_class->drag_data_received = source_selector_drag_data_received;
+	widget_class->popup_menu = source_selector_popup_menu;
+
+	tree_view_class = GTK_TREE_VIEW_CLASS (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,
+		g_param_spec_object (
+			"primary-selection",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			NULL,
+			NULL,
+			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),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, selection_changed),
+		NULL, NULL, NULL,
+		G_TYPE_NONE, 0);
+
+	/* XXX Consider this signal deprecated.  Connect
+	 *     to "notify::primary-selection" instead. */
+	signals[PRIMARY_SELECTION_CHANGED] = g_signal_new (
+		"primary-selection-changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, primary_selection_changed),
+		NULL, NULL, NULL,
+		G_TYPE_NONE, 0);
+
+	signals[POPUP_EVENT] = g_signal_new (
+		"popup-event",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, popup_event),
+		ess_bool_accumulator, NULL, NULL,
+		G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	signals[DATA_DROPPED] = g_signal_new (
+		"data-dropped",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, data_dropped),
+		NULL, NULL, NULL,
+		G_TYPE_BOOLEAN, 4,
+		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
+		E_TYPE_SOURCE,
+		GDK_TYPE_DRAG_ACTION,
+		G_TYPE_UINT);
+}
+
+static void
+e_source_selector_init (ESourceSelector *selector)
+{
+	GHashTable *pending_writes;
+	GtkTreeViewColumn *column;
+	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, COLUMN_SOURCE);
+	gtk_tree_view_set_enable_search (tree_view, TRUE);
+
+	selector->priv->toggled_last = FALSE;
+	selector->priv->select_new = FALSE;
+	selector->priv->show_colors = TRUE;
+	selector->priv->show_toggles = TRUE;
+
+	selector->priv->source_index = g_hash_table_new_full (
+		(GHashFunc) e_source_hash,
+		(GEqualFunc) e_source_equal,
+		(GDestroyNotify) g_object_unref,
+		(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 */
+
+	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);
+
+	renderer = e_cell_renderer_color_new ();
+	g_object_set (
+		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 (
+		renderer, "toggled",
+		G_CALLBACK (cell_toggled_callback), selector);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (
+		G_OBJECT (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_attributes (
+		column, renderer,
+		"text", COLUMN_NAME,
+		"weight", COLUMN_WEIGHT,
+		NULL);
+
+	selection = gtk_tree_view_get_selection (tree_view);
+	gtk_tree_selection_set_select_function (
+		selection, (GtkTreeSelectionFunc)
+		selection_func, selector, NULL);
+	g_signal_connect_object (
+		selection, "changed",
+		G_CALLBACK (selection_changed_callback),
+		G_OBJECT (selector), 0);
+
+	gtk_tree_view_set_headers_visible (tree_view, FALSE);
+}
+
+/**
+ * e_source_selector_new:
+ * @registry: an #ESourceRegistry
+ * @extension_name: the name of an #ESource extension
+ *
+ * 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: a new #ESourceSelector
+ **/
+GtkWidget *
+e_source_selector_new (ESourceRegistry *registry,
+                       const gchar *extension_name)
+{
+	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, "registry", registry,
+		"extension-name", extension_name, NULL);
+}
+
+/**
+ * e_source_selector_get_registry:
+ * @selector: an #ESourceSelector
+ *
+ * Returns the #ESourceRegistry that @selector is getting sources from.
+ *
+ * Returns: an #ESourceRegistry
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_source_selector_get_registry (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	return selector->priv->registry;
+}
+
+/**
+ * e_source_selector_get_extension_name:
+ * @selector: an #ESourceSelector
+ *
+ * Returns the extension name used to filter which sources are displayed.
+ *
+ * Returns: the #ESource extension name
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_source_selector_get_extension_name (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	return selector->priv->extension_name;
+}
+
+/**
+ * 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 selector->priv->show_colors;
+}
+
+/**
+ * e_source_selector_set_show_colors:
+ * @selector: an #ESourceSelector
+ * @show_colors: whether to show colors
+ *
+ * Sets whether to show colors next to data sources.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_selector_set_show_colors (ESourceSelector *selector,
+                                   gboolean show_colors)
+{
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	if ((show_colors ? 1 : 0) == (selector->priv->show_colors ? 1 : 0))
+		return;
+
+	selector->priv->show_colors = show_colors;
+
+	g_object_notify (G_OBJECT (selector), "show-colors");
+
+	source_selector_build_model (selector);
+}
+
+/**
+ * e_source_selector_get_show_toggles:
+ * @selector: an #ESourceSelector
+ *
+ * Returns whether toggles are shown next to data sources.
+ *
+ * Returns: %TRUE if toggles are being shown
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_selector_get_show_toggles (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+
+	return selector->priv->show_toggles;
+}
+
+/**
+ * e_source_selector_set_show_toggles:
+ * @selector: an #ESourceSelector
+ * @show_toggles: whether to show toggles
+ *
+ * Sets whether to show toggles next to data sources.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_selector_set_show_toggles (ESourceSelector *selector,
+                                   gboolean show_toggles)
+{
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	if ((show_toggles ? 1 : 0) == (selector->priv->show_toggles ? 1 : 0))
+		return;
+
+	selector->priv->show_toggles = show_toggles;
+
+	g_object_notify (G_OBJECT (selector), "show-toggles");
+
+	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_get_selection:
+ * @selector: an #ESourceSelector
+ *
+ * Get the list of selected sources, i.e. those that were enabled through the
+ * corresponding checkboxes in the tree.
+ *
+ * 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().
+ **/
+GSList *
+e_source_selector_get_selection (ESourceSelector *selector)
+{
+	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 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);
+}
+
+/**
+ * e_source_selector_set_select_new:
+ * @selector: An #ESourceSelector widget
+ * @state: A gboolean
+ *
+ * Set whether or not to select new sources added to @selector.
+ **/
+void
+e_source_selector_set_select_new (ESourceSelector *selector,
+                                  gboolean state)
+{
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	selector->priv->select_new = state;
+}
+
+/**
+ * e_source_selector_select_source:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Select @source in @selector.
+ **/
+void
+e_source_selector_select_source (ESourceSelector *selector,
+                                 ESource *source)
+{
+	ESourceSelectorClass *class;
+	GtkTreeRowReference *reference;
+	GHashTable *source_index;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (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));
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_if_fail (class->set_source_selected != NULL);
+
+	class->set_source_selected (selector, source, TRUE);
+
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+}
+
+/**
+ * e_source_selector_unselect_source:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Unselect @source in @selector.
+ **/
+void
+e_source_selector_unselect_source (ESourceSelector *selector,
+                                   ESource *source)
+{
+	ESourceSelectorClass *class;
+	GtkTreeRowReference *reference;
+	GHashTable *source_index;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (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));
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_if_fail (class->set_source_selected != NULL);
+
+	class->set_source_selected (selector, source, FALSE);
+
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+}
+
+/**
+ * e_source_selector_select_exclusive:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Select @source in @selector and unselect all others.
+ *
+ * Since: 2.30
+ **/
+void
+e_source_selector_select_exclusive (ESourceSelector *selector,
+                                    ESource *source)
+{
+	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));
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_if_fail (class->set_source_selected != NULL);
+
+	source_index = selector->priv->source_index;
+	g_hash_table_iter_init (&iter, source_index);
+
+	while (g_hash_table_iter_next (&iter, &key, NULL)) {
+		gboolean selected = e_source_equal (key, source);
+		class->set_source_selected (selector, key, selected);
+	}
+
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+}
+
+/**
+ * e_source_selector_source_is_selected:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Check whether @source is selected in @selector.
+ *
+ * Returns: %TRUE if @source is currently selected, %FALSE otherwise.
+ **/
+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);
+
+	/* 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 class->get_source_selected (selector, source);
+}
+
+/**
+ * e_source_selector_edit_primary_selection:
+ * @selector: An #ESourceSelector widget
+ *
+ * Allows the user to rename the primary selected source by opening an
+ * entry box directly in @selector.
+ *
+ * Since: 2.26
+ **/
+void
+e_source_selector_edit_primary_selection (ESourceSelector *selector)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+	GList *list;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	tree_view = GTK_TREE_VIEW (selector);
+	column = gtk_tree_view_get_column (tree_view, 0);
+	reference = selector->priv->saved_primary_selection;
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	if (reference != NULL)
+		path = gtk_tree_row_reference_get_path (reference);
+	else if (gtk_tree_selection_get_selected (selection, &model, &iter))
+		path = gtk_tree_model_get_path (model, &iter);
+
+	if (path == NULL)
+		return;
+
+	/* XXX Because we stuff three renderers in a single column,
+	 *     we have to manually hunt for the text renderer. */
+	renderer = NULL;
+	list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+	while (list != NULL) {
+		renderer = list->data;
+		if (GTK_IS_CELL_RENDERER_TEXT (renderer))
+			break;
+		list = g_list_delete_link (list, list);
+	}
+	g_list_free (list);
+
+	/* Make the text cell renderer editable, but only temporarily.
+	 * We don't want editing to be activated by simply clicking on
+	 * the source name.  Too easy for accidental edits to occur. */
+	g_object_set (renderer, "editable", TRUE, NULL);
+	gtk_tree_view_expand_to_path (tree_view, path);
+	gtk_tree_view_set_cursor_on_cell (
+		tree_view, path, column, renderer, TRUE);
+	g_object_set (renderer, "editable", FALSE, NULL);
+
+	gtk_tree_path_free (path);
+}
+
+/**
+ * e_source_selector_ref_primary_selection:
+ * @selector: An #ESourceSelector widget
+ *
+ * Get the primary selected source.  The primary selection is the one that is
+ * highlighted through the normal #GtkTreeView selection mechanism (as opposed
+ * to the "normal" selection, which is the set of source whose checkboxes are
+ * checked).
+ *
+ * The returned #ESource is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: The selected source.
+ *
+ * Since: 3.6
+ **/
+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;
+
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	tree_view = GTK_TREE_VIEW (selector);
+	model = gtk_tree_view_get_model (tree_view);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	reference = selector->priv->saved_primary_selection;
+
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreePath *path;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		have_iter = gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_path_free (path);
+	}
+
+	if (!have_iter)
+		have_iter = gtk_tree_selection_get_selected (
+			selection, NULL, &iter);
+
+	if (!have_iter)
+		return NULL;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	if (!e_source_has_extension (source, extension_name)) {
+		g_object_unref (source);
+		return NULL;
+	}
+
+	return source;
+}
+
+/**
+ * e_source_selector_set_primary_selection:
+ * @selector: an #ESourceSelector widget
+ * @source: an #ESource to select
+ *
+ * Highlights @source in @selector.  The highlighted #ESource is called
+ * the primary selection.
+ *
+ * Do not confuse this function with e_source_selector_select_source(),
+ * which activates the check box next to an #ESource's display name in
+ * @selector.  This function does not alter the check box.
+ **/
+void
+e_source_selector_set_primary_selection (ESourceSelector *selector,
+                                         ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreePath *child_path;
+	GtkTreePath *parent_path;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	tree_view = GTK_TREE_VIEW (selector);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* XXX Maybe we should return a success/fail boolean? */
+	if (!gtk_tree_row_reference_valid (reference))
+		return;
+
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	/* Return silently if attempting to select a parent node
+	 * lacking the expected extension (e.g. On This Computer). */
+	if (!e_source_has_extension (source, extension_name))
+		return;
+
+	/* 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);
+
+	child_path = gtk_tree_row_reference_get_path (reference);
+
+	parent_path = gtk_tree_path_copy (child_path);
+	gtk_tree_path_up (parent_path);
+
+	if (gtk_tree_view_row_expanded (tree_view, parent_path)) {
+		gtk_tree_selection_select_path (selection, child_path);
+	} else {
+		selector->priv->saved_primary_selection =
+			gtk_tree_row_reference_copy (reference);
+		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);
+}
+
+/**
+ * e_source_selector_ref_source_by_path:
+ * @selector: an #ESourceSelector
+ * @path: a #GtkTreePath
+ *
+ * Returns the #ESource object at @path, or %NULL if @path is invalid.
+ *
+ * The returned #ESource is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: the #ESource object at @path, or %NULL
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_source_selector_ref_source_by_path (ESourceSelector *selector,
+                                      GtkTreePath *path)
+{
+	ESource *source = NULL;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+
+	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, 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);
+}
+
diff --git a/src/event-factories/e-source-selector.h b/src/event-factories/e-source-selector.h
new file mode 100644
index 0000000..b6d3770
--- /dev/null
+++ b/src/event-factories/e-source-selector.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-selector.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * 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.
+ *
+ * Author: Ettore Perazzoli <ettore ximian com>
+ */
+
+#ifndef E_SOURCE_SELECTOR_H
+#define E_SOURCE_SELECTOR_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_SELECTOR \
+	(e_source_selector_get_type ())
+#define E_SOURCE_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelector))
+#define E_SOURCE_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_SELECTOR, ESourceSelectorClass))
+#define E_IS_SOURCE_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_SELECTOR))
+#define E_IS_SOURCE_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_SELECTOR))
+#define E_SOURCE_SELECTOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceSelector ESourceSelector;
+typedef struct _ESourceSelectorClass ESourceSelectorClass;
+typedef struct _ESourceSelectorPrivate ESourceSelectorPrivate;
+
+struct _ESourceSelector {
+	GtkTreeView parent;
+	ESourceSelectorPrivate *priv;
+};
+
+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);
+	gboolean	(*popup_event)		(ESourceSelector *selector,
+						 ESource *primary,
+						 GdkEventButton *event);
+	gboolean	(*data_dropped)		(ESourceSelector *selector,
+						 GtkSelectionData *data,
+						 ESource *destination,
+						 GdkDragAction action,
+						 guint target_info);
+
+	gpointer padding1;
+	gpointer padding2;
+	gpointer padding3;
+};
+
+GType		e_source_selector_get_type	(void);
+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
+						(ESourceSelector *selector,
+						 ESource *source);
+void		e_source_selector_select_exclusive
+						(ESourceSelector *selector,
+						 ESource *source);
+gboolean	e_source_selector_source_is_selected
+						(ESourceSelector *selector,
+						 ESource *source);
+GSList *	e_source_selector_get_selection	(ESourceSelector *selector);
+void		e_source_selector_free_selection
+						(GSList *list);
+void		e_source_selector_set_select_new
+						(ESourceSelector *selector,
+						 gboolean state);
+void		e_source_selector_edit_primary_selection
+						(ESourceSelector *selector);
+ESource *	e_source_selector_ref_primary_selection
+						(ESourceSelector *selector);
+void		e_source_selector_set_primary_selection
+						(ESourceSelector *selector,
+						 ESource *source);
+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
+
+#endif /* E_SOURCE_SELECTOR_H */



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