summaryrefslogtreecommitdiff
path: root/libedataserverui/e-source-store.c
diff options
context:
space:
mode:
Diffstat (limited to 'libedataserverui/e-source-store.c')
-rw-r--r--libedataserverui/e-source-store.c1120
1 files changed, 1120 insertions, 0 deletions
diff --git a/libedataserverui/e-source-store.c b/libedataserverui/e-source-store.c
new file mode 100644
index 000000000..8575f4143
--- /dev/null
+++ b/libedataserverui/e-source-store.c
@@ -0,0 +1,1120 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-store.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION: e-source-store
+ * @short_description: Store sources and clients in a tree model
+ * @include: libedataserverui/e-source-store.h
+ *
+ * #ESourceStore is a #GtkTreeModel meant for use with widgets that render
+ * sources such as #ESourceSelector and #ESourceComboBox. It can also be
+ * subclassed to open and store #EBook and #ECal objects.
+ **/
+
+#include "e-source-store.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_SOURCE_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SOURCE_STORE, ESourceStorePrivate))
+
+struct _ESourceStorePrivate {
+
+ ESourceList *source_list;
+
+ /* Source UID -> GtkTreeRowReference */
+ GHashTable *index;
+
+ /* Source UID -> GCancellable */
+ GHashTable *requests;
+
+ guint refresh_source_id;
+};
+
+enum {
+ PROP_0,
+ PROP_SOURCE_LIST
+};
+
+enum {
+ SOURCE_ADDED,
+ SOURCE_REMOVED,
+ SOURCE_SELECTED,
+ SOURCE_UNSELECTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (ESourceStore, e_source_store, GTK_TYPE_TREE_STORE)
+
+typedef struct {
+ ESourceStore *source_store;
+ GHashTable *remaining_uids;
+ GPtrArray *deleted_uids;
+ GtkTreeIter iter;
+ gint position;
+} RefreshData;
+
+static RefreshData *
+refresh_data_new (ESourceStore *source_store)
+{
+ RefreshData *data;
+
+ data = g_slice_new0 (RefreshData);
+ data->source_store = g_object_ref (source_store);
+ data->remaining_uids = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) gtk_tree_row_reference_free);
+ data->deleted_uids = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) gtk_tree_row_reference_free);
+
+ return data;
+}
+
+static void
+refresh_data_free (RefreshData *data)
+{
+ g_object_unref (data->source_store);
+ g_hash_table_destroy (data->remaining_uids);
+ g_ptr_array_free (data->deleted_uids, TRUE);
+ g_slice_free (RefreshData, data);
+}
+
+static gint
+source_store_compare_source_names (gconstpointer source_a,
+ gconstpointer source_b)
+{
+ const gchar *source_name_a;
+ const gchar *source_name_b;
+
+ g_return_val_if_fail (E_IS_SOURCE (source_a), -1);
+ g_return_val_if_fail (E_IS_SOURCE (source_b), 1);
+
+ source_name_a = e_source_peek_name (E_SOURCE (source_a));
+ source_name_b = e_source_peek_name (E_SOURCE (source_b));
+
+ return g_utf8_collate (source_name_a, source_name_b);
+}
+
+static gboolean
+source_store_refresh_collect_data (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ RefreshData *data)
+{
+ GtkTreeRowReference *reference;
+ ESourceStore *source_store;
+ ESourceList *source_list;
+ GObject *object;
+ const gchar *uid;
+
+ source_store = E_SOURCE_STORE (model);
+ source_list = e_source_store_get_source_list (source_store);
+ reference = gtk_tree_row_reference_new (model, path);
+
+ /* The object may be an ESource or ESourceGroup. */
+ gtk_tree_model_get (
+ model, iter,
+ E_SOURCE_STORE_COLUMN_SOURCE, &object, -1);
+
+ if (E_IS_SOURCE (object)) {
+ uid = e_source_peek_uid (E_SOURCE (object));
+
+ if (e_source_list_peek_source_by_uid (source_list, uid))
+ g_hash_table_insert (
+ data->remaining_uids,
+ g_strdup (uid), reference);
+ else {
+ guint signal_id;
+
+ g_ptr_array_add (data->deleted_uids, reference);
+
+ g_hash_table_remove (
+ source_store->priv->index,
+ (gpointer) uid);
+
+ signal_id = signals[SOURCE_REMOVED];
+ g_signal_emit (source_store, signal_id, 0, object);
+ }
+
+ } else if (E_IS_SOURCE_GROUP (object)) {
+ uid = e_source_group_peek_uid (E_SOURCE_GROUP (object));
+
+ if (e_source_list_peek_group_by_uid (source_list, uid))
+ g_hash_table_insert (
+ data->remaining_uids,
+ g_strdup (uid), reference);
+ else
+ g_ptr_array_add (data->deleted_uids, reference);
+
+ } else
+ g_warn_if_reached ();
+
+ g_object_unref (object);
+
+ return FALSE;
+}
+
+static void
+source_store_refresh_add_sources (ESource *source,
+ RefreshData *data)
+{
+ GtkTreeRowReference *reference;
+ GtkTreeModel *tree_model;
+ GtkTreeStore *tree_store;
+ GtkTreeIter iter;
+ const gchar *uid;
+
+ tree_model = GTK_TREE_MODEL (data->source_store);
+ tree_store = GTK_TREE_STORE (data->source_store);
+
+ uid = e_source_peek_uid (source);
+ reference = g_hash_table_lookup (data->remaining_uids, uid);
+
+ if (reference == NULL) {
+ GtkTreePath *path;
+ guint signal_id;
+
+ gtk_tree_store_insert (
+ tree_store, &iter, &data->iter, data->position);
+ gtk_tree_store_set (
+ tree_store, &iter,
+ E_SOURCE_STORE_COLUMN_SOURCE, source, -1);
+
+ path = gtk_tree_model_get_path (tree_model, &iter);
+ reference = gtk_tree_row_reference_new (tree_model, path);
+ gtk_tree_path_free (path);
+
+ g_hash_table_insert (
+ data->source_store->priv->index,
+ g_strdup (uid), reference);
+
+ signal_id = signals[SOURCE_ADDED];
+ g_signal_emit (data->source_store, signal_id, 0, source);
+ } else {
+ GtkTreePath *path;
+
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_model_get_iter (tree_model, &iter, path);
+ gtk_tree_model_row_changed (tree_model, path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ data->position++;
+}
+
+static void
+source_store_refresh_add_groups (ESourceGroup *source_group,
+ RefreshData *data)
+{
+ GtkTreeRowReference *reference;
+ GtkTreeModel *tree_model;
+ GtkTreeStore *tree_store;
+ GSList *list;
+ const gchar *uid;
+
+ /* Skip empty source groups. */
+ list = e_source_group_peek_sources (source_group);
+ if (list == NULL)
+ return;
+
+ tree_model = GTK_TREE_MODEL (data->source_store);
+ tree_store = GTK_TREE_STORE (data->source_store);
+
+ /* Copy the list and sort by name. */
+ list = g_slist_copy (list);
+ list = g_slist_sort (list, source_store_compare_source_names);
+
+ uid = e_source_group_peek_uid (source_group);
+ reference = g_hash_table_lookup (data->remaining_uids, uid);
+
+ if (reference == NULL) {
+ gtk_tree_store_append (tree_store, &data->iter, NULL);
+ gtk_tree_store_set (
+ tree_store, &data->iter,
+ E_SOURCE_STORE_COLUMN_SOURCE, source_group, -1);
+ } else {
+ GtkTreePath *path;
+
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_model_get_iter (tree_model, &data->iter, path);
+ gtk_tree_model_row_changed (tree_model, path, &data->iter);
+ gtk_tree_path_free (path);
+ }
+
+ /* Insert new sources in this source group. */
+ data->position = 0;
+ g_slist_foreach (list, (GFunc) source_store_refresh_add_sources, data);
+
+ g_slist_free (list);
+}
+
+static gboolean
+source_store_refresh (ESourceStore *source_store)
+{
+ ESourceList *source_list;
+ GtkTreeModel *tree_model;
+ GtkTreeStore *tree_store;
+ RefreshData *data;
+ GSList *list;
+ guint ii;
+
+ tree_model = GTK_TREE_MODEL (source_store);
+ tree_store = GTK_TREE_STORE (source_store);
+
+ source_list = e_source_store_get_source_list (source_store);
+
+ data = refresh_data_new (source_store);
+
+ gtk_tree_model_foreach (
+ tree_model, (GtkTreeModelForeachFunc)
+ source_store_refresh_collect_data, data);
+
+ /* Remove any deleted sources or source groups. */
+ for (ii = 0; ii < data->deleted_uids->len; ii++) {
+ GtkTreeRowReference *reference;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ reference = g_ptr_array_index (data->deleted_uids, ii);
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_model_get_iter (tree_model, &iter, path);
+ gtk_tree_store_remove (tree_store, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ /* Insert new source groups. */
+ list = e_source_list_peek_groups (source_list);
+ g_slist_foreach (list, (GFunc) source_store_refresh_add_groups, data);
+
+ refresh_data_free (data);
+
+ source_store->priv->refresh_source_id = 0;
+
+ return FALSE;
+}
+
+static void
+source_store_get_client_cb (ESourceStore *source_store,
+ GAsyncResult *result,
+ ESource *source)
+{
+ GHashTable *hash_table;
+ GtkTreeIter iter;
+ GObject *client;
+ const gchar *uid;
+ GError *error = NULL;
+
+ uid = e_source_peek_uid (source);
+ hash_table = source_store->priv->requests;
+ g_hash_table_remove (hash_table, uid);
+
+ client = e_source_store_get_client_finish (
+ source_store, result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
+ g_error_free (error);
+ goto exit;
+
+ } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free (error);
+ goto exit;
+
+ } else if (error != NULL) {
+ e_source_store_unselect_source (source_store, source);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ goto exit;
+ }
+
+ /* The source may have been deleted while we were waiting. */
+ if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+ goto exit;
+
+ gtk_tree_store_set (
+ GTK_TREE_STORE (source_store), &iter,
+ E_SOURCE_STORE_COLUMN_CLIENT, client, -1);
+
+exit:
+ if (client != NULL)
+ g_object_unref (client);
+
+ g_object_unref (source);
+}
+
+static void
+source_store_set_source_list (ESourceStore *source_store,
+ ESourceList *source_list)
+{
+ g_return_if_fail (E_IS_SOURCE_LIST (source_list));
+ g_return_if_fail (source_store->priv->source_list == NULL);
+
+ source_store->priv->source_list = g_object_ref (source_list);
+
+ source_store_refresh (source_store);
+
+ g_signal_connect_swapped (
+ source_list, "changed",
+ G_CALLBACK (e_source_store_queue_refresh), source_store);
+}
+
+static void
+source_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SOURCE_LIST:
+ source_store_set_source_list (
+ E_SOURCE_STORE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SOURCE_LIST:
+ g_value_set_object (
+ value, e_source_store_get_source_list (
+ E_SOURCE_STORE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_store_dispose (GObject *object)
+{
+ ESourceStorePrivate *priv;
+
+ priv = E_SOURCE_STORE_GET_PRIVATE (object);
+
+ if (priv->refresh_source_id > 0) {
+ g_source_remove (priv->refresh_source_id);
+ priv->refresh_source_id = 0;
+ }
+
+ if (priv->source_list != NULL) {
+ g_object_unref (priv->source_list);
+ priv->source_list = NULL;
+ }
+
+ g_hash_table_remove_all (priv->index);
+ g_hash_table_remove_all (priv->requests);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_source_store_parent_class)->dispose (object);
+}
+
+static void
+source_store_finalize (GObject *object)
+{
+ ESourceStorePrivate *priv;
+
+ priv = E_SOURCE_STORE_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->index);
+ g_hash_table_destroy (priv->requests);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_source_store_parent_class)->finalize (object);
+}
+
+static void
+source_store_source_added (ESourceStore *source_store,
+ ESource *source)
+{
+ /* This is a placeholder in case we decide to do something
+ * here in the future. Subclasses should still chain up. */
+}
+
+static void
+source_store_source_removed (ESourceStore *source_store,
+ ESource *source)
+{
+ /* This is a placeholder in case we decide to do something
+ * here in the future. Subclasses should still chain up. */
+}
+
+static void
+source_store_source_selected (ESourceStore *source_store,
+ ESource *source)
+{
+ GCancellable *cancellable;
+ GHashTable *hash_table;
+ const gchar *uid;
+
+ uid = e_source_peek_uid (source);
+ hash_table = source_store->priv->requests;
+ cancellable = g_hash_table_lookup (hash_table, uid);
+
+ if (cancellable != NULL)
+ return;
+
+ cancellable = g_cancellable_new ();
+ g_hash_table_insert (hash_table, g_strdup (uid), cancellable);
+
+ e_source_store_get_client_async (
+ source_store, source, G_PRIORITY_DEFAULT, cancellable,
+ (GAsyncReadyCallback) source_store_get_client_cb,
+ g_object_ref (source));
+}
+
+static void
+source_store_source_unselected (ESourceStore *source_store,
+ ESource *source)
+{
+ GCancellable *cancellable;
+ GHashTable *hash_table;
+ const gchar *uid;
+
+ uid = e_source_peek_uid (source);
+ hash_table = source_store->priv->requests;
+ cancellable = g_hash_table_lookup (hash_table, uid);
+
+ if (cancellable != NULL) {
+ g_cancellable_cancel (cancellable);
+ g_hash_table_remove (hash_table, uid);
+ }
+}
+
+typedef struct {
+ ESource *source;
+ GObject *client;
+} GetClientData;
+
+static void
+source_store_get_client_data_free (GetClientData *data)
+{
+ if (data->source != NULL)
+ g_object_unref (data->source);
+
+ if (data->client != NULL)
+ g_object_unref (data->client);
+
+ g_slice_free (GetClientData, data);
+}
+
+static void
+source_store_get_client_thread (GSimpleAsyncResult *simple,
+ ESourceStore *source_store,
+ GCancellable *cancellable)
+{
+ GetClientData *data;
+ GError *error = NULL;
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+ g_return_if_fail (data != NULL && data->source != NULL);
+
+ data->client = e_source_store_get_client (
+ source_store, data->source, cancellable, &error);
+
+ if (error != NULL) {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+}
+
+static GObject *
+source_store_get_client (ESourceStore *source_store,
+ ESource *source,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Subclasses should override this. */
+
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Operation not supported"));
+
+ return NULL;
+}
+
+static void
+source_store_get_client_async (ESourceStore *source_store,
+ ESource *source,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GObject *object = G_OBJECT (source_store);
+ GSimpleAsyncResult *simple;
+ GetClientData *data;
+
+ data = g_slice_new0 (GetClientData);
+ data->source = g_object_ref (source);
+
+ simple = g_simple_async_result_new (
+ object, callback, user_data,
+ e_source_store_get_client_async);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, data, (GDestroyNotify)
+ source_store_get_client_data_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ source_store_get_client_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+static GObject *
+source_store_get_client_finish (ESourceStore *source_store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ GetClientData *data;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (source_store),
+ e_source_store_get_client_async), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+ g_return_val_if_fail (data != NULL, NULL);
+
+ if (data->client != NULL)
+ return g_object_ref (data->client);
+
+ return NULL;
+}
+
+static void
+e_source_store_class_init (ESourceStoreClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ESourceStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = source_store_set_property;
+ object_class->get_property = source_store_get_property;
+ object_class->dispose = source_store_dispose;
+ object_class->finalize = source_store_finalize;
+
+ class->source_added = source_store_source_added;
+ class->source_removed = source_store_source_removed;
+ class->source_selected = source_store_source_selected;
+ class->source_unselected = source_store_source_unselected;
+
+ class->get_client = source_store_get_client;
+ class->get_client_async = source_store_get_client_async;
+ class->get_client_finish = source_store_get_client_finish;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE_LIST,
+ g_param_spec_object (
+ "source-list",
+ "Source List",
+ NULL,
+ E_TYPE_SOURCE_LIST,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ signals[SOURCE_ADDED] = g_signal_new (
+ "source-added",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ESourceStoreClass, source_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_SOURCE);
+
+ signals[SOURCE_REMOVED] = g_signal_new (
+ "source-removed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ESourceStoreClass, source_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_SOURCE);
+
+ signals[SOURCE_SELECTED] = g_signal_new (
+ "source-selected",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ESourceStoreClass, source_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_SOURCE);
+
+ signals[SOURCE_UNSELECTED] = g_signal_new (
+ "source-unselected",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ESourceStoreClass, source_unselected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_SOURCE);
+}
+
+static void
+e_source_store_init (ESourceStore *source_store)
+{
+ GType types[E_SOURCE_STORE_NUM_COLUMNS];
+ GHashTable *index;
+ GHashTable *requests;
+ gint column = 0;
+
+ index = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) gtk_tree_row_reference_free);
+
+ requests = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ source_store->priv = E_SOURCE_STORE_GET_PRIVATE (source_store);
+
+ source_store->priv->index = index;
+ source_store->priv->requests = requests;
+
+ types[column++] = G_TYPE_OBJECT; /* COLUMN_SOURCE */
+ types[column++] = G_TYPE_BOOLEAN; /* COLUMN_SELECTED */
+ types[column++] = G_TYPE_OBJECT; /* COLUMN_CLIENT */
+
+ g_assert (column == E_SOURCE_STORE_NUM_COLUMNS);
+
+ gtk_tree_store_set_column_types (
+ GTK_TREE_STORE (source_store),
+ G_N_ELEMENTS (types), types);
+}
+
+/**
+ * e_source_store_new:
+ * @source_list: an #ESourceList
+ *
+ * Creates a new #ESourceStore instance that keeps itself synchronized
+ * with @source_list.
+ *
+ * Returns: a new #ESourceStore instance
+ **/
+GtkTreeModel *
+e_source_store_new (ESourceList *source_list)
+{
+ g_return_val_if_fail (E_IS_SOURCE_LIST (source_list), NULL);
+
+ return g_object_new (
+ E_TYPE_SOURCE_STORE,
+ "source-list", source_list, NULL);
+}
+
+/**
+ * e_source_store_get_source_list:
+ * @source_store: an #ESourceStore
+ *
+ * Returns the #ESourceList with which @source_store is synchronizing
+ * itself.
+ *
+ * Returns: the #ESourceList passed to e_source_store_new()
+ **/
+ESourceList *
+e_source_store_get_source_list (ESourceStore *source_store)
+{
+ g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+
+ return source_store->priv->source_list;
+}
+
+/**
+ * e_source_store_queue_refresh:
+ * @source_store: an #ESourceStore
+ *
+ * Queues a request for @source_store to syncrhonize itself with its
+ * internal #ESourceList. This function gets called automatically when
+ * the internal #ESourceList emits change notifications. The actual
+ * synchronization happens in an idle callback.
+ **/
+void
+e_source_store_queue_refresh (ESourceStore *source_store)
+{
+ guint source_id;
+
+ g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+
+ if (source_store->priv->refresh_source_id > 0)
+ return;
+
+ source_id = gdk_threads_add_idle (
+ (GSourceFunc) source_store_refresh, source_store);
+
+ source_store->priv->refresh_source_id = source_id;
+}
+
+/**
+ * e_source_store_get_iter_from_source:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ * @iter: an uninitialized #GtkTreeIter
+ *
+ * Sets @iter to a valid iterator pointing to @source.
+ *
+ * Returns: %TRUE, if @iter was set
+ **/
+gboolean
+e_source_store_get_iter_from_source (ESourceStore *source_store,
+ ESource *source,
+ GtkTreeIter *iter)
+{
+ GtkTreeRowReference *reference;
+ GHashTable *hash_table;
+ const gchar *uid;
+
+ g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), FALSE);
+ g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+ uid = e_source_peek_uid (source);
+ hash_table = source_store->priv->index;
+ reference = g_hash_table_lookup (hash_table, uid);
+
+ if (!gtk_tree_row_reference_valid (reference))
+ return FALSE;
+
+ if (iter != NULL) {
+ GtkTreeModel *model;
+ GtkTreePath *path;
+
+ model = gtk_tree_row_reference_get_model (reference);
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_model_get_iter (model, iter, path);
+ gtk_tree_path_free (path);
+ }
+
+ return TRUE;
+}
+
+/**
+ * e_source_store_select_source:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ *
+ * Marks @source as selected and attempts to open a client connection
+ * for @source via e_source_store_get_client().
+ *
+ * <note>
+ * <para>
+ * #ESourceStore itself does not know how to open client connections.
+ * Subclasses must provide this capability.
+ * </para>
+ * </note>
+ **/
+void
+e_source_store_select_source (ESourceStore *source_store,
+ ESource *source)
+{
+ GtkTreeIter iter;
+ gboolean selected;
+
+ g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+ return;
+
+ /* Avoid emitting unnecessary "row-changed" signals. */
+
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (source_store), &iter,
+ E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+ if (selected)
+ return;
+
+ gtk_tree_store_set (
+ GTK_TREE_STORE (source_store), &iter,
+ E_SOURCE_STORE_COLUMN_SELECTED, TRUE, -1);
+
+ g_signal_emit (source_store, signals[SOURCE_SELECTED], 0, source);
+}
+
+/**
+ * e_source_store_unselect_source:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ *
+ * Marks @source as unselected and closes the corresponding client
+ * connection, if present.
+ **/
+void
+e_source_store_unselect_source (ESourceStore *source_store,
+ ESource *source)
+{
+ GtkTreeIter iter;
+ gboolean selected;
+
+ g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+ return;
+
+ /* Avoid emitting unnecessary "row-changed" signals. */
+
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (source_store), &iter,
+ E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+ if (!selected)
+ return;
+
+ gtk_tree_store_set (
+ GTK_TREE_STORE (source_store), &iter,
+ E_SOURCE_STORE_COLUMN_SELECTED, FALSE,
+ E_SOURCE_STORE_COLUMN_CLIENT, NULL, -1);
+
+ g_signal_emit (source_store, signals[SOURCE_UNSELECTED], 0, source);
+}
+
+/**
+ * e_source_store_is_selected:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ *
+ * Returns %TRUE if @source is selected.
+ *
+ * Returns: %TRUE, if @source is selected
+ **/
+gboolean
+e_source_store_is_selected (ESourceStore *source_store,
+ ESource *source)
+{
+ GtkTreeIter iter;
+ gboolean selected = FALSE;
+
+ g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), FALSE);
+ g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+ if (!e_source_store_get_iter_from_source (source_store, source, &iter))
+ return FALSE;
+
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (source_store), &iter,
+ E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+ return selected;
+}
+
+/* Helper for e_source_store_get_selection() */
+static gboolean
+source_store_get_selection_foreach_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GSList **list)
+{
+ ESource *source;
+ gboolean selected;
+
+ /* Groups are at depth 1, sources are at depth 2. */
+ if (gtk_tree_path_get_depth (path) != 2)
+ return FALSE;
+
+ gtk_tree_model_get (
+ model, iter,
+ E_SOURCE_STORE_COLUMN_SOURCE, &source,
+ E_SOURCE_STORE_COLUMN_SELECTED, &selected, -1);
+
+ g_return_val_if_fail (E_IS_SOURCE (source), TRUE);
+
+ if (selected)
+ *list = g_slist_prepend (*list, source);
+ else
+ g_object_unref (source);
+
+ return FALSE;
+}
+
+/**
+ * e_source_store_get_selected:
+ * @source_store: an #ESourceStore
+ *
+ * Returns a #GSList of currently selected sources. The returned list
+ * should be freed as follows:
+ *
+ * <informalexample>
+ * <programlisting>
+ * g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+ * g_slist_free (list);
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: a #GSList of selected sources
+ **/
+GSList *
+e_source_store_get_selected (ESourceStore *source_store)
+{
+ GSList *list = NULL;
+
+ /* XXX Only returning GSList here because ESourceSelector does.
+ * I would prefer GList, but too late to change it. */
+
+ g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+
+ gtk_tree_model_foreach (
+ GTK_TREE_MODEL (source_store), (GtkTreeModelForeachFunc)
+ source_store_get_selection_foreach_cb, &list);
+
+ return g_slist_reverse (list);
+}
+
+/**
+ * e_source_store_get_client:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ * @cancellable: optional #GCancellable, or %NULL to ignore
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns a client connection for @source, creating and opening it first
+ * if necessary. If an error occurs, the function returns %NULL and sets
+ * @error.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by
+ * triggering the cancellable object from another thread. If the operation
+ * was cancelled, a #G_IO_ERROR_CANCELLED will be reported.
+ *
+ * This function may block for a long time, and should not be called from
+ * a thread running a #GMainLoop. See e_source_store_get_client_async()
+ * for an asynchronous version of this call.
+ *
+ * <note>
+ * <para>
+ * #ESourceStore itself does not know how to open client connections
+ * and will simply report a #G_IO_ERROR_NOT_SUPPORTED and return %NULL.
+ * Subclasses must provide this capability.
+ * </para>
+ * </note>
+ *
+ * Returns: a client connection for @source, or %NULL on error
+ **/
+GObject *
+e_source_store_get_client (ESourceStore *source_store,
+ ESource *source,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESourceStoreClass *class;
+
+ g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+ class = E_SOURCE_STORE_GET_CLASS (source_store);
+ g_return_val_if_fail (class->get_client != NULL, NULL);
+
+ return class->get_client (source_store, source, cancellable, error);
+}
+
+/**
+ * e_source_store_get_client_async:
+ * @source_store: an #ESourceStore
+ * @source: an #ESource
+ * @io_priority: the <link linkend="io-priority">I/O priority</link>
+ * of the request
+ * @cancellable: optional #GCancellable, or %NULL to ignore
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to the callback function
+ *
+ * Asynchronously gets a client connection for @source, creating and
+ * opening it first if necessary.
+ *
+ * For more details, see e_source_store_get_client() which is the
+ * synchronous version of this call.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call e_source_store_get_client_finish() to get the result of the
+ * operation.
+ **/
+void
+e_source_store_get_client_async (ESourceStore *source_store,
+ ESource *source,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ESourceStoreClass *class;
+
+ g_return_if_fail (E_IS_SOURCE_STORE (source_store));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ class = E_SOURCE_STORE_GET_CLASS (source_store);
+ g_return_if_fail (class->get_client_async != NULL);
+
+ class->get_client_async (
+ source_store, source, io_priority,
+ cancellable, callback, user_data);
+}
+
+/**
+ * e_source_store_get_client_finish:
+ * @source_store: an #ESourceStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes an asynchronous client operation started with
+ * e_source_store_get_client_async().
+ *
+ * Returns: a client connection, or %NULL on error
+ **/
+GObject *
+e_source_store_get_client_finish (ESourceStore *source_store,
+ GAsyncResult *result,
+ GError **error)
+{
+ ESourceStoreClass *class;
+
+ g_return_val_if_fail (E_IS_SOURCE_STORE (source_store), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+ if (G_IS_SIMPLE_ASYNC_RESULT (result)) {
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+ }
+
+ class = E_SOURCE_STORE_GET_CLASS (source_store);
+ g_return_val_if_fail (class->get_client_finish != NULL, NULL);
+
+ return class->get_client_finish (source_store, result, error);
+}