/*
 * e-proxy-selector.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.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * SECTION: e-proxy-selector
 * @include: e-util/e-util.h
 * @short_description: Select and manage proxy profiles
 *
 * #EProxySelector displays a list of available proxy profiles, with inline
 * toolbar controls for adding and removing profiles.
 **/

#include "e-proxy-selector.h"

#include <config.h>
#include <glib/gi18n-lib.h>

#define E_PROXY_SELECTOR_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_PROXY_SELECTOR, EProxySelectorPrivate))

typedef struct _AsyncContext AsyncContext;

struct _EProxySelectorPrivate {
	ESourceRegistry *registry;
	gulong source_added_handler_id;
	gulong source_changed_handler_id;
	gulong source_removed_handler_id;

	GtkTreeSelection *selection;
	gulong selection_changed_handler_id;

	guint refresh_idle_id;
};

struct _AsyncContext {
	EProxySelector *selector;
	ESource *scratch_source;
};

enum {
	PROP_0,
	PROP_REGISTRY,
	PROP_SELECTED
};

enum {
	COLUMN_DISPLAY_NAME,
	COLUMN_SOURCE
};

G_DEFINE_TYPE (
	EProxySelector,
	e_proxy_selector,
	E_TYPE_TREE_VIEW_FRAME)

static void
async_context_free (AsyncContext *async_context)
{
	g_clear_object (&async_context->selector);
	g_clear_object (&async_context->scratch_source);

	g_slice_free (AsyncContext, async_context);
}

static gchar *
proxy_selector_pick_display_name (EProxySelector *selector)
{
	ESourceRegistry *registry;
	GList *list, *link;
	const gchar *base_name = _("Custom Proxy");
	const gchar *extension_name;
	gchar *display_name;
	guint ii = 0;

	extension_name = E_SOURCE_EXTENSION_PROXY;
	registry = e_proxy_selector_get_registry (selector);
	list = e_source_registry_list_sources (registry, extension_name);

	/* Convert the list of ESources to a list of display names. */
	for (link = list; link != NULL; link = g_list_next (link)) {
		ESource *source = E_SOURCE (link->data);
		link->data = e_source_dup_display_name (source);
		g_object_unref (source);
	}

	display_name = g_strdup (base_name);

try_again:
	link = g_list_find_custom (
		list, display_name, (GCompareFunc) g_utf8_collate);

	if (link != NULL) {
		g_free (display_name);
		display_name = g_strdup_printf ("%s (%u)", base_name, ++ii);
		goto try_again;
	}

	g_list_free_full (list, (GDestroyNotify) g_free);

	return display_name;
}

static void
proxy_selector_commit_source_cb (GObject *object,
                                 GAsyncResult *result,
                                 gpointer user_data)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = (AsyncContext *) user_data;

	e_source_registry_commit_source_finish (
		E_SOURCE_REGISTRY (object), result, &local_error);

	if (local_error == NULL) {
		/* Refresh the tree model immediately. */
		e_proxy_selector_refresh (async_context->selector);

		/* Select the newly added proxy data source.  Note that
		 * e_proxy_selector_set_selected() uses e_source_equal()
		 * to match the input ESource to a tree model row, so it
		 * should match our scratch ESource to the newly-created
		 * ESource since they both have the same UID. */
		e_proxy_selector_set_selected (
			async_context->selector,
			async_context->scratch_source);
	} else {
		/* FIXME Hand the error off to an EAlertSink. */
		g_warning ("%s: %s", G_STRFUNC, local_error->message);
		g_error_free (local_error);
	}

	gtk_widget_set_sensitive (GTK_WIDGET (async_context->selector), TRUE);

	async_context_free (async_context);
}

static void
proxy_selector_remove_source_cb (GObject *object,
                                 GAsyncResult *result,
                                 gpointer user_data)
{
	EProxySelector *selector;
	GError *local_error = NULL;

	selector = E_PROXY_SELECTOR (user_data);

	e_source_remove_finish (E_SOURCE (object), result, &local_error);

	if (local_error != NULL) {
		/* FIXME Hand the error off to an EAlertSink. */
		g_warning ("%s: %s", G_STRFUNC, local_error->message);
		g_error_free (local_error);
	}

	gtk_widget_set_sensitive (GTK_WIDGET (selector), TRUE);

	g_object_unref (selector);
}

static gboolean
proxy_selector_action_add_cb (EProxySelector *selector,
                              GtkAction *action)
{
	AsyncContext *async_context;
	ESourceRegistry *registry;
	ESourceProxy *extension;
	ESource *scratch_source;
	const gchar *extension_name;
	gchar *display_name;

	const gchar * const ignore_hosts[] = {
		"localhost",
		"127.0.0.0/8",
		"::1",
		NULL
	};

	scratch_source = e_source_new (NULL, NULL, NULL);

	display_name = proxy_selector_pick_display_name (selector);
	e_source_set_display_name (scratch_source, display_name);
	g_free (display_name);

	extension_name = E_SOURCE_EXTENSION_PROXY;
	extension = e_source_get_extension (scratch_source, extension_name);
	e_source_proxy_set_ignore_hosts (extension, ignore_hosts);

	registry = e_proxy_selector_get_registry (selector);

	/* Disable the selector until the commit operation completes. */
	gtk_widget_set_sensitive (GTK_WIDGET (selector), FALSE);

	async_context = g_slice_new0 (AsyncContext);
	async_context->selector = g_object_ref (selector);
	async_context->scratch_source = g_object_ref (scratch_source);

	e_source_registry_commit_source (
		registry, scratch_source, NULL,
		proxy_selector_commit_source_cb,
		async_context);

	g_object_unref (scratch_source);

	return TRUE;
}

static gboolean
proxy_selector_action_remove_cb (EProxySelector *selector,
                                 GtkAction *action)
{
	ESource *selected_source;

	selected_source = e_proxy_selector_ref_selected (selector);
	g_return_val_if_fail (selected_source != NULL, FALSE);

	/* Disable the selector until the remove operation completes. */
	gtk_widget_set_sensitive (GTK_WIDGET (selector), FALSE);

	e_source_remove (
		selected_source, NULL,
		proxy_selector_remove_source_cb,
		g_object_ref (selector));

	g_object_unref (selected_source);

	return TRUE;
}

static gboolean
proxy_selector_refresh_idle_cb (gpointer user_data)
{
	EProxySelector *selector = user_data;

	/* The refresh function will clear the idle ID. */
	e_proxy_selector_refresh (selector);

	return FALSE;
}

static void
proxy_selector_schedule_refresh (EProxySelector *selector)
{
	/* Use an idle callback to limit how frequently we refresh
	 * the tree model in case the registry is emitting lots of
	 * signals at once. */

	if (selector->priv->refresh_idle_id == 0) {
		selector->priv->refresh_idle_id = g_idle_add (
			proxy_selector_refresh_idle_cb, selector);
	}
}

static void
proxy_selector_cell_edited_cb (GtkCellRendererText *renderer,
                               const gchar *path_string,
                               const gchar *new_name,
                               EProxySelector *selector)
{
	ETreeViewFrame *tree_view_frame;
	GtkTreeView *tree_view;
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter;
	ESource *source;

	if (new_name == NULL || *new_name == '\0')
		return;

	tree_view_frame = E_TREE_VIEW_FRAME (selector);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
	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);

	/* EProxyPreferences will detect the change and commit it. */
	e_source_set_display_name (source, new_name);

	e_proxy_selector_refresh (selector);

	g_object_unref (source);
}

static void
proxy_selector_source_added_cb (ESourceRegistry *registry,
                                ESource *source,
                                EProxySelector *selector)
{
	if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY))
		proxy_selector_schedule_refresh (selector);
}

static void
proxy_selector_source_changed_cb (ESourceRegistry *registry,
                                  ESource *source,
                                  EProxySelector *selector)
{
	if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY))
		proxy_selector_schedule_refresh (selector);
}

static void
proxy_selector_source_removed_cb (ESourceRegistry *registry,
                                  ESource *source,
                                  EProxySelector *selector)
{
	if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY))
		proxy_selector_schedule_refresh (selector);
}

static void
proxy_selector_selection_changed_cb (GtkTreeSelection *selection,
                                     EProxySelector *selector)
{
	g_object_notify (G_OBJECT (selector), "selected");
}

static void
proxy_selector_set_registry (EProxySelector *selector,
                             ESourceRegistry *registry)
{
	gulong handler_id;

	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
	g_return_if_fail (selector->priv->registry == NULL);

	selector->priv->registry = g_object_ref (registry);

	handler_id = g_signal_connect (
		registry, "source-added",
		G_CALLBACK (proxy_selector_source_added_cb), selector);
	selector->priv->source_added_handler_id = handler_id;

	handler_id = g_signal_connect (
		registry, "source-changed",
		G_CALLBACK (proxy_selector_source_changed_cb), selector);
	selector->priv->source_changed_handler_id = handler_id;

	handler_id = g_signal_connect (
		registry, "source-removed",
		G_CALLBACK (proxy_selector_source_removed_cb), selector);
	selector->priv->source_removed_handler_id = handler_id;
}

static void
proxy_selector_set_property (GObject *object,
                             guint property_id,
                             const GValue *value,
                             GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_REGISTRY:
			proxy_selector_set_registry (
				E_PROXY_SELECTOR (object),
				g_value_get_object (value));
			return;

		case PROP_SELECTED:
			e_proxy_selector_set_selected (
				E_PROXY_SELECTOR (object),
				g_value_get_object (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
proxy_selector_get_property (GObject *object,
                             guint property_id,
                             GValue *value,
                             GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_REGISTRY:
			g_value_set_object (
				value,
				e_proxy_selector_get_registry (
				E_PROXY_SELECTOR (object)));
			return;

		case PROP_SELECTED:
			g_value_take_object (
				value,
				e_proxy_selector_ref_selected (
				E_PROXY_SELECTOR (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
proxy_selector_dispose (GObject *object)
{
	EProxySelectorPrivate *priv;

	priv = E_PROXY_SELECTOR_GET_PRIVATE (object);

	if (priv->source_added_handler_id > 0) {
		g_signal_handler_disconnect (
			priv->registry,
			priv->source_added_handler_id);
		priv->source_added_handler_id = 0;
	}

	if (priv->source_changed_handler_id > 0) {
		g_signal_handler_disconnect (
			priv->registry,
			priv->source_changed_handler_id);
		priv->source_changed_handler_id = 0;
	}

	if (priv->source_removed_handler_id > 0) {
		g_signal_handler_disconnect (
			priv->registry,
			priv->source_removed_handler_id);
		priv->source_removed_handler_id = 0;
	}

	if (priv->selection_changed_handler_id > 0) {
		g_signal_handler_disconnect (
			priv->selection,
			priv->selection_changed_handler_id);
		priv->selection_changed_handler_id = 0;
	}

	if (priv->refresh_idle_id > 0) {
		g_source_remove (priv->refresh_idle_id);
		priv->refresh_idle_id = 0;
	}

	g_clear_object (&priv->registry);

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_proxy_selector_parent_class)->dispose (object);
}

static void
proxy_selector_constructed (GObject *object)
{
	EProxySelector *selector;
	ETreeViewFrame *tree_view_frame;
	GtkTreeView *tree_view;
	GtkTreeViewColumn *column;
	GtkCellRenderer *renderer;
	GtkTreeSelection *selection;
	GtkListStore *list_store;
	GtkAction *action;
	const gchar *tooltip;
	gulong handler_id;

	/* Chain up to parent's constructed() method. */
	G_OBJECT_CLASS (e_proxy_selector_parent_class)->constructed (object);

	selector = E_PROXY_SELECTOR (object);

	tree_view_frame = E_TREE_VIEW_FRAME (object);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);

	gtk_tree_view_set_reorderable (tree_view, FALSE);
	gtk_tree_view_set_headers_visible (tree_view, FALSE);

	/* Configure the toolbar actions. */

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_ADD);
	tooltip = _("Create a new proxy profile");
	gtk_action_set_tooltip (action, tooltip);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE);
	tooltip = _("Delete the selected proxy profile");
	gtk_action_set_tooltip (action, tooltip);

	/* Configure the tree view column. */

	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (
		G_OBJECT (renderer),
		"editable", TRUE,
		"ellipsize", PANGO_ELLIPSIZE_END, NULL);
	g_signal_connect (
		renderer, "edited",
		G_CALLBACK (proxy_selector_cell_edited_cb), selector);
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_add_attribute (
		column, renderer, "text", COLUMN_DISPLAY_NAME);
	gtk_tree_view_append_column (tree_view, column);

	/* Listen for tree view selection changes. */

	selection = gtk_tree_view_get_selection (tree_view);
	selector->priv->selection = g_object_ref (selection);

	handler_id = g_signal_connect (
		selection, "changed",
		G_CALLBACK (proxy_selector_selection_changed_cb), selector);
	selector->priv->selection_changed_handler_id = handler_id;

	/* Create and populate the tree model. */

	list_store = gtk_list_store_new (2, G_TYPE_STRING, E_TYPE_SOURCE);
	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
	g_object_unref (list_store);

	e_proxy_selector_refresh (E_PROXY_SELECTOR (object));
}

static void
proxy_selector_update_toolbar_actions (ETreeViewFrame *tree_view_frame)
{
	EProxySelector *selector;
	ESource *selected;
	GtkAction *action;
	gboolean sensitive;

	selector = E_PROXY_SELECTOR (tree_view_frame);
	selected = e_proxy_selector_ref_selected (selector);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE);
	sensitive = e_source_get_removable (selected);
	gtk_action_set_sensitive (action, sensitive);

	g_object_unref (selected);

	/* Chain up to parent's update_toolbar_actions() method. */
	E_TREE_VIEW_FRAME_CLASS (e_proxy_selector_parent_class)->
		update_toolbar_actions (tree_view_frame);
}

static void
e_proxy_selector_class_init (EProxySelectorClass *class)
{
	GObjectClass *object_class;
	ETreeViewFrameClass *tree_view_frame_class;

	g_type_class_add_private (class, sizeof (EProxySelectorPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = proxy_selector_set_property;
	object_class->get_property = proxy_selector_get_property;
	object_class->dispose = proxy_selector_dispose;
	object_class->constructed = proxy_selector_constructed;

	tree_view_frame_class = E_TREE_VIEW_FRAME_CLASS (class);
	tree_view_frame_class->update_toolbar_actions =
				proxy_selector_update_toolbar_actions;

	g_object_class_install_property (
		object_class,
		PROP_REGISTRY,
		g_param_spec_object (
			"registry",
			"Registry",
			"Data source registry",
			E_TYPE_SOURCE_REGISTRY,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SELECTED,
		g_param_spec_object (
			"selected",
			"Selected",
			"The selected data source",
			E_TYPE_SOURCE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));
}

static void
e_proxy_selector_init (EProxySelector *selector)
{
	selector->priv = E_PROXY_SELECTOR_GET_PRIVATE (selector);

	/* In this particular case, it's easier to connect handlers
	 * to detailed signal names than to override the class method. */

	g_signal_connect (
		selector,
		"toolbar-action-activate::"
		E_TREE_VIEW_FRAME_ACTION_ADD,
		G_CALLBACK (proxy_selector_action_add_cb), NULL);

	g_signal_connect (
		selector,
		"toolbar-action-activate::"
		E_TREE_VIEW_FRAME_ACTION_REMOVE,
		G_CALLBACK (proxy_selector_action_remove_cb), NULL);
}

/**
 * e_proxy_selector_new:
 * @registry: an #ESourceRegistry
 *
 * Creates a new #EProxySelector widget using #ESource instances in @registry.
 *
 * Returns: a new #EProxySelector
 **/
GtkWidget *
e_proxy_selector_new (ESourceRegistry *registry)
{
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);

	return g_object_new (
		E_TYPE_PROXY_SELECTOR,
		"registry", registry, NULL);
}

/**
 * e_proxy_selector_refresh:
 * @selector: an #EProxySelector
 *
 * Rebuilds the @selector's list store with an updated list of #ESource
 * instances that describe a network proxy profile, without disrupting the
 * previously selected item (if possible).
 *
 * This funtion is called automatically in response to #ESourceRegistry
 * signals which are pertinent to the @selector.
 **/
void
e_proxy_selector_refresh (EProxySelector *selector)
{
	ETreeViewFrame *tree_view_frame;
	ESourceRegistry *registry;
	GtkTreeView *tree_view;
	GtkTreeModel *tree_model;
	ESource *builtin_source;
	ESource *selected;
	GList *list, *link;
	const gchar *extension_name;

	g_return_if_fail (E_IS_PROXY_SELECTOR (selector));

	if (selector->priv->refresh_idle_id > 0) {
		g_source_remove (selector->priv->refresh_idle_id);
		selector->priv->refresh_idle_id = 0;
	}

	tree_view_frame = E_TREE_VIEW_FRAME (selector);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
	tree_model = gtk_tree_view_get_model (tree_view);

	selected = e_proxy_selector_ref_selected (selector);

	gtk_list_store_clear (GTK_LIST_STORE (tree_model));

	extension_name = E_SOURCE_EXTENSION_PROXY;
	registry = e_proxy_selector_get_registry (selector);
	list = e_source_registry_list_enabled (registry, extension_name);

	builtin_source = e_source_registry_ref_builtin_proxy (registry);
	g_warn_if_fail (builtin_source != NULL);

	/* Always list the built-in proxy profile first. */
	link = g_list_find (list, builtin_source);
	if (link != NULL && list != link) {
		list = g_list_remove_link (list, link);
		list = g_list_concat (link, list);
	}

	for (link = list; link != NULL; link = g_list_next (link)) {
		ESource *source;
		GtkTreeIter iter;
		const gchar *display_name;

		source = E_SOURCE (link->data);
		display_name = e_source_get_display_name (source);

		gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);

		gtk_list_store_set (
			GTK_LIST_STORE (tree_model), &iter,
			COLUMN_DISPLAY_NAME, display_name,
			COLUMN_SOURCE, source, -1);
	}

	g_clear_object (&builtin_source);

	g_list_free_full (list, (GDestroyNotify) g_object_unref);

	/* Try and restore the previous selected source or else pick
	 * the built-in proxy profile, which is always listed first. */

	e_proxy_selector_set_selected (selector, selected);

	g_clear_object (&selected);
}

/**
 * e_proxy_selector_get_registry:
 * @selector: an #EProxySelector
 *
 * Returns the #ESourceRegistry passed to e_proxy_selector_get_registry().
 *
 * Returns: an #ESourceRegistry
 **/
ESourceRegistry *
e_proxy_selector_get_registry (EProxySelector *selector)
{
	g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), NULL);

	return selector->priv->registry;
}

/**
 * e_proxy_selector_ref_selected:
 * @selector: an #EProxySelector
 *
 * Returns the selected #ESource in @selector.
 *
 * The function tries to ensure a valid #ESource is always returned,
 * falling back to e_source_registry_ref_builtin_proxy() if necessary.
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Returns: an #ESource
 **/
ESource *
e_proxy_selector_ref_selected (EProxySelector *selector)
{
	ETreeViewFrame *tree_view_frame;
	GtkTreeView *tree_view;
	GtkTreeModel *tree_model;
	GtkTreeSelection *selection;
	GtkTreeIter iter;
	ESource *source = NULL;

	g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), NULL);

	tree_view_frame = E_TREE_VIEW_FRAME (selector);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
	selection = gtk_tree_view_get_selection (tree_view);

	if (gtk_tree_selection_get_selected (selection, &tree_model, &iter)) {
		gtk_tree_model_get (
			tree_model, &iter,
			COLUMN_SOURCE, &source, -1);
	}

	/* The built-in proxy profile is implicitly selected when
	 * no proxy profile is actually selected in the tree view. */
	if (source == NULL) {
		ESourceRegistry *registry;

		registry = e_proxy_selector_get_registry (selector);
		source = e_source_registry_ref_builtin_proxy (registry);
		g_return_val_if_fail (source != NULL, NULL);
	}

	return source;
}

/**
 * e_proxy_selector_set_selected:
 * @selector: an #EProxySelector
 * @source: an #ESource, or %NULL for the built-in proxy profile
 *
 * Finds the corresponding tree model row for @source, selects the row,
 * and returns %TRUE.  If no corresponding tree model row for @source is
 * found, the selection remains unchanged and the function returns %FALSE.
 *
 * Returns: whether @source was selected
 **/
gboolean
e_proxy_selector_set_selected (EProxySelector *selector,
                               ESource *source)
{
	ETreeViewFrame *tree_view_frame;
	GtkTreeView *tree_view;
	GtkTreeModel *tree_model;
	GtkTreeIter iter;
	gboolean iter_valid;

	g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), FALSE);
	g_return_val_if_fail (source == NULL || E_IS_SOURCE (source), FALSE);

	if (source == NULL) {
		ESourceRegistry *registry;

		registry = e_proxy_selector_get_registry (selector);
		source = e_source_registry_ref_builtin_proxy (registry);
		g_return_val_if_fail (source != NULL, FALSE);
	}

	tree_view_frame = E_TREE_VIEW_FRAME (selector);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
	tree_model = gtk_tree_view_get_model (tree_view);

	iter_valid = gtk_tree_model_get_iter_first (tree_model, &iter);

	while (iter_valid) {
		ESource *candidate = NULL;
		gboolean match;

		gtk_tree_model_get (
			tree_model, &iter,
			COLUMN_SOURCE, &candidate, -1);

		match = e_source_equal (source, candidate);

		g_object_unref (candidate);

		if (match)
			break;

		iter_valid = gtk_tree_model_iter_next (tree_model, &iter);
	}

	if (iter_valid) {
		GtkTreeSelection *selection;

		selection = gtk_tree_view_get_selection (tree_view);
		gtk_tree_selection_select_iter (selection, &iter);
	}

	return iter_valid;
}

