diff options
Diffstat (limited to 'e-util/e-contact-store.c')
-rw-r--r-- | e-util/e-contact-store.c | 1370 |
1 files changed, 1370 insertions, 0 deletions
diff --git a/e-util/e-contact-store.c b/e-util/e-contact-store.c new file mode 100644 index 0000000000..4e49399e82 --- /dev/null +++ b/e-util/e-contact-store.c @@ -0,0 +1,1370 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* e-contact-store.c - Contacts store with GtkTreeModel interface. + * + * 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 Lesser 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. + * + * Authors: Hans Petter Jansson <hpj@novell.com> + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <glib/gi18n-lib.h> + +#include "e-contact-store.h" + +#define ITER_IS_VALID(contact_store, iter) \ + ((iter)->stamp == (contact_store)->priv->stamp) +#define ITER_GET(iter) \ + GPOINTER_TO_INT (iter->user_data) +#define ITER_SET(contact_store, iter, index) \ + G_STMT_START { \ + (iter)->stamp = (contact_store)->priv->stamp; \ + (iter)->user_data = GINT_TO_POINTER (index); \ + } G_STMT_END + +#define E_CONTACT_STORE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CONTACT_STORE, EContactStorePrivate)) + +struct _EContactStorePrivate { + gint stamp; + EBookQuery *query; + GArray *contact_sources; +}; + +/* Signals */ + +enum { + START_CLIENT_VIEW, + STOP_CLIENT_VIEW, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void e_contact_store_tree_model_init (GtkTreeModelIface *iface); + +G_DEFINE_TYPE_WITH_CODE ( + EContactStore, e_contact_store, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, e_contact_store_tree_model_init)) + +static GtkTreeModelFlags e_contact_store_get_flags (GtkTreeModel *tree_model); +static gint e_contact_store_get_n_columns (GtkTreeModel *tree_model); +static GType e_contact_store_get_column_type (GtkTreeModel *tree_model, + gint index); +static gboolean e_contact_store_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path); +static GtkTreePath *e_contact_store_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static void e_contact_store_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value); +static gboolean e_contact_store_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean e_contact_store_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent); +static gboolean e_contact_store_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gint e_contact_store_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean e_contact_store_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n); +static gboolean e_contact_store_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child); + +typedef struct +{ + EBookClient *book_client; + + EBookClientView *client_view; + GPtrArray *contacts; + + EBookClientView *client_view_pending; + GPtrArray *contacts_pending; +} +ContactSource; + +static void free_contact_ptrarray (GPtrArray *contacts); +static void clear_contact_source (EContactStore *contact_store, ContactSource *source); +static void stop_view (EContactStore *contact_store, EBookClientView *view); + +static void +contact_store_dispose (GObject *object) +{ + EContactStorePrivate *priv; + gint ii; + + priv = E_CONTACT_STORE_GET_PRIVATE (object); + + /* Free sources and cached contacts */ + for (ii = 0; ii < priv->contact_sources->len; ii++) { + ContactSource *source; + + /* clear from back, because clear_contact_source can later access freed memory */ + source = &g_array_index ( + priv->contact_sources, ContactSource, priv->contact_sources->len - ii - 1); + + clear_contact_source (E_CONTACT_STORE (object), source); + free_contact_ptrarray (source->contacts); + g_object_unref (source->book_client); + } + g_array_set_size (priv->contact_sources, 0); + + if (priv->query != NULL) { + e_book_query_unref (priv->query); + priv->query = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_contact_store_parent_class)->dispose (object); +} + +static void +contact_store_finalize (GObject *object) +{ + EContactStorePrivate *priv; + + priv = E_CONTACT_STORE_GET_PRIVATE (object); + + g_array_free (priv->contact_sources, TRUE); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_contact_store_parent_class)->finalize (object); +} + +static void +e_contact_store_class_init (EContactStoreClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (EContactStorePrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = contact_store_dispose; + object_class->finalize = contact_store_finalize; + + signals[START_CLIENT_VIEW] = g_signal_new ( + "start-client-view", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EContactStoreClass, start_client_view), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_BOOK_CLIENT_VIEW); + + signals[STOP_CLIENT_VIEW] = g_signal_new ( + "stop-client-view", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EContactStoreClass, stop_client_view), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_BOOK_CLIENT_VIEW); +} + +static void +e_contact_store_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = e_contact_store_get_flags; + iface->get_n_columns = e_contact_store_get_n_columns; + iface->get_column_type = e_contact_store_get_column_type; + iface->get_iter = e_contact_store_get_iter; + iface->get_path = e_contact_store_get_path; + iface->get_value = e_contact_store_get_value; + iface->iter_next = e_contact_store_iter_next; + iface->iter_children = e_contact_store_iter_children; + iface->iter_has_child = e_contact_store_iter_has_child; + iface->iter_n_children = e_contact_store_iter_n_children; + iface->iter_nth_child = e_contact_store_iter_nth_child; + iface->iter_parent = e_contact_store_iter_parent; +} + +static void +e_contact_store_init (EContactStore *contact_store) +{ + GArray *contact_sources; + + contact_sources = g_array_new (FALSE, FALSE, sizeof (ContactSource)); + + contact_store->priv = E_CONTACT_STORE_GET_PRIVATE (contact_store); + contact_store->priv->stamp = g_random_int (); + contact_store->priv->contact_sources = contact_sources; +} + +/** + * e_contact_store_new: + * + * Creates a new #EContactStore. + * + * Returns: A new #EContactStore. + **/ +EContactStore * +e_contact_store_new (void) +{ + return g_object_new (E_TYPE_CONTACT_STORE, NULL); +} + +/* ------------------ * + * Row update helpers * + * ------------------ */ + +static void +row_deleted (EContactStore *contact_store, + gint n) +{ + GtkTreePath *path; + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, n); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (contact_store), path); + gtk_tree_path_free (path); +} + +static void +row_inserted (EContactStore *contact_store, + gint n) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, n); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (contact_store), &iter, path)) + gtk_tree_model_row_inserted (GTK_TREE_MODEL (contact_store), path, &iter); + + gtk_tree_path_free (path); +} + +static void +row_changed (EContactStore *contact_store, + gint n) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, n); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (contact_store), &iter, path)) + gtk_tree_model_row_changed (GTK_TREE_MODEL (contact_store), path, &iter); + + gtk_tree_path_free (path); +} + +/* ---------------------- * + * Contact source helpers * + * ---------------------- */ + +static gint +find_contact_source_by_client (EContactStore *contact_store, + EBookClient *book_client) +{ + GArray *array; + gint i; + + array = contact_store->priv->contact_sources; + + for (i = 0; i < array->len; i++) { + ContactSource *source; + + source = &g_array_index (array, ContactSource, i); + if (source->book_client == book_client) + return i; + } + + return -1; +} + +static gint +find_contact_source_by_view (EContactStore *contact_store, + EBookClientView *client_view) +{ + GArray *array; + gint i; + + array = contact_store->priv->contact_sources; + + for (i = 0; i < array->len; i++) { + ContactSource *source; + + source = &g_array_index (array, ContactSource, i); + if (source->client_view == client_view || + source->client_view_pending == client_view) + return i; + } + + return -1; +} + +static gint +find_contact_source_by_offset (EContactStore *contact_store, + gint offset) +{ + GArray *array; + gint i; + + array = contact_store->priv->contact_sources; + + for (i = 0; i < array->len; i++) { + ContactSource *source; + + source = &g_array_index (array, ContactSource, i); + if (source->contacts->len > offset) + return i; + + offset -= source->contacts->len; + } + + return -1; +} + +static gint +find_contact_source_by_pointer (EContactStore *contact_store, + ContactSource *source) +{ + GArray *array; + gint i; + + array = contact_store->priv->contact_sources; + + i = ((gchar *) source - (gchar *) array->data) / sizeof (ContactSource); + + if (i < 0 || i >= array->len) + return -1; + + return i; +} + +static gint +get_contact_source_offset (EContactStore *contact_store, + gint contact_source_index) +{ + GArray *array; + gint offset = 0; + gint i; + + array = contact_store->priv->contact_sources; + + g_assert (contact_source_index < array->len); + + for (i = 0; i < contact_source_index; i++) { + ContactSource *source; + + source = &g_array_index (array, ContactSource, i); + offset += source->contacts->len; + } + + return offset; +} + +static gint +count_contacts (EContactStore *contact_store) +{ + GArray *array; + gint count = 0; + gint i; + + array = contact_store->priv->contact_sources; + + for (i = 0; i < array->len; i++) { + ContactSource *source; + + source = &g_array_index (array, ContactSource, i); + count += source->contacts->len; + } + + return count; +} + +static gint +find_contact_by_view_and_uid (EContactStore *contact_store, + EBookClientView *find_view, + const gchar *find_uid) +{ + GArray *array; + ContactSource *source; + GPtrArray *contacts; + gint source_index; + gint i; + + g_return_val_if_fail (find_uid != NULL, -1); + + source_index = find_contact_source_by_view (contact_store, find_view); + if (source_index < 0) + return -1; + + array = contact_store->priv->contact_sources; + source = &g_array_index (array, ContactSource, source_index); + + if (find_view == source->client_view) + contacts = source->contacts; /* Current view */ + else + contacts = source->contacts_pending; /* Pending view */ + + for (i = 0; i < contacts->len; i++) { + EContact *contact = g_ptr_array_index (contacts, i); + const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID); + + if (uid && !strcmp (find_uid, uid)) + return i; + } + + return -1; +} + +static gint +find_contact_by_uid (EContactStore *contact_store, + const gchar *find_uid) +{ + GArray *array; + gint i; + + array = contact_store->priv->contact_sources; + + for (i = 0; i < array->len; i++) { + ContactSource *source = &g_array_index (array, ContactSource, i); + gint j; + + for (j = 0; j < source->contacts->len; j++) { + EContact *contact = g_ptr_array_index (source->contacts, j); + const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID); + + if (!strcmp (find_uid, uid)) + return get_contact_source_offset (contact_store, i) + j; + } + } + + return -1; +} + +static EBookClient * +get_book_at_row (EContactStore *contact_store, + gint row) +{ + GArray *array; + ContactSource *source; + gint source_index; + + source_index = find_contact_source_by_offset (contact_store, row); + if (source_index < 0) + return NULL; + + array = contact_store->priv->contact_sources; + source = &g_array_index (array, ContactSource, source_index); + + return source->book_client; +} + +static EContact * +get_contact_at_row (EContactStore *contact_store, + gint row) +{ + GArray *array; + ContactSource *source; + gint source_index; + gint offset; + + source_index = find_contact_source_by_offset (contact_store, row); + if (source_index < 0) + return NULL; + + array = contact_store->priv->contact_sources; + source = &g_array_index (array, ContactSource, source_index); + offset = get_contact_source_offset (contact_store, source_index); + row -= offset; + + g_assert (row < source->contacts->len); + + return g_ptr_array_index (source->contacts, row); +} + +static gboolean +find_contact_source_details_by_view (EContactStore *contact_store, + EBookClientView *client_view, + ContactSource **contact_source, + gint *offset) +{ + GArray *array; + gint source_index; + + source_index = find_contact_source_by_view (contact_store, client_view); + if (source_index < 0) + return FALSE; + + array = contact_store->priv->contact_sources; + *contact_source = &g_array_index (array, ContactSource, source_index); + *offset = get_contact_source_offset (contact_store, source_index); + + return TRUE; +} + +/* ------------------------- * + * EBookView signal handlers * + * ------------------------- */ + +static void +view_contacts_added (EContactStore *contact_store, + const GSList *contacts, + EBookClientView *client_view) +{ + ContactSource *source; + gint offset; + const GSList *l; + + if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) { + g_warning ("EContactStore got 'contacts_added' signal from unknown EBookView!"); + return; + } + + for (l = contacts; l; l = g_slist_next (l)) { + EContact *contact = l->data; + + g_object_ref (contact); + + if (client_view == source->client_view) { + /* Current view */ + g_ptr_array_add (source->contacts, contact); + row_inserted (contact_store, offset + source->contacts->len - 1); + } else { + /* Pending view */ + g_ptr_array_add (source->contacts_pending, contact); + } + } +} + +static void +view_contacts_removed (EContactStore *contact_store, + const GSList *uids, + EBookClientView *client_view) +{ + ContactSource *source; + gint offset; + const GSList *l; + + if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) { + g_warning ("EContactStore got 'contacts_removed' signal from unknown EBookView!"); + return; + } + + for (l = uids; l; l = g_slist_next (l)) { + const gchar *uid = l->data; + gint n = find_contact_by_view_and_uid (contact_store, client_view, uid); + EContact *contact; + + if (n < 0) { + g_warning ("EContactStore got 'contacts_removed' on unknown contact!"); + continue; + } + + if (client_view == source->client_view) { + /* Current view */ + contact = g_ptr_array_index (source->contacts, n); + g_object_unref (contact); + g_ptr_array_remove_index (source->contacts, n); + row_deleted (contact_store, offset + n); + } else { + /* Pending view */ + contact = g_ptr_array_index (source->contacts_pending, n); + g_object_unref (contact); + g_ptr_array_remove_index (source->contacts_pending, n); + } + } +} + +static void +view_contacts_modified (EContactStore *contact_store, + const GSList *contacts, + EBookClientView *client_view) +{ + GPtrArray *cached_contacts; + ContactSource *source; + gint offset; + const GSList *l; + + if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) { + g_warning ("EContactStore got 'contacts_changed' signal from unknown EBookView!"); + return; + } + + if (client_view == source->client_view) + cached_contacts = source->contacts; + else + cached_contacts = source->contacts_pending; + + for (l = contacts; l; l = g_slist_next (l)) { + EContact *cached_contact; + EContact *contact = l->data; + const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID); + gint n = find_contact_by_view_and_uid (contact_store, client_view, uid); + + if (n < 0) { + g_warning ("EContactStore got change notification on unknown contact!"); + continue; + } + + cached_contact = g_ptr_array_index (cached_contacts, n); + + /* Update cached contact */ + if (cached_contact != contact) { + g_object_unref (cached_contact); + cached_contacts->pdata[n] = g_object_ref (contact); + } + + /* Emit changes for current view only */ + if (client_view == source->client_view) + row_changed (contact_store, offset + n); + } +} + +static void +view_complete (EContactStore *contact_store, + const GError *error, + EBookClientView *client_view) +{ + ContactSource *source; + gint offset; + gint i; + + if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) { + g_warning ("EContactStore got 'complete' signal from unknown EBookClientView!"); + return; + } + + /* If current view finished, do nothing */ + if (client_view == source->client_view) { + stop_view (contact_store, source->client_view); + return; + } + + g_assert (client_view == source->client_view_pending); + + /* However, if it was a pending view, calculate and emit the differences between that + * and the current view, and move the pending view up to current. + * + * This is O(m * n), and can be sped up with a temporary hash table if needed. */ + + /* Deletions */ + for (i = 0; i < source->contacts->len; i++) { + EContact *old_contact = g_ptr_array_index (source->contacts, i); + const gchar *old_uid = e_contact_get_const (old_contact, E_CONTACT_UID); + gint result; + + result = find_contact_by_view_and_uid (contact_store, source->client_view_pending, old_uid); + if (result < 0) { + /* Contact is not in new view; removed */ + g_object_unref (old_contact); + g_ptr_array_remove_index (source->contacts, i); + row_deleted (contact_store, offset + i); + i--; /* Stay in place */ + } + } + + /* Insertions */ + for (i = 0; i < source->contacts_pending->len; i++) { + EContact *new_contact = g_ptr_array_index (source->contacts_pending, i); + const gchar *new_uid = e_contact_get_const (new_contact, E_CONTACT_UID); + gint result; + + result = find_contact_by_view_and_uid (contact_store, source->client_view, new_uid); + if (result < 0) { + /* Contact is not in old view; inserted */ + g_ptr_array_add (source->contacts, new_contact); + row_inserted (contact_store, offset + source->contacts->len - 1); + } else { + /* Contact already in old view; drop the new one */ + g_object_unref (new_contact); + } + } + + /* Move pending view up to current */ + stop_view (contact_store, source->client_view); + g_object_unref (source->client_view); + source->client_view = source->client_view_pending; + source->client_view_pending = NULL; + + /* Free array of pending contacts (members have been either moved or unreffed) */ + g_ptr_array_free (source->contacts_pending, TRUE); + source->contacts_pending = NULL; +} + +/* --------------------- * + * View/Query management * + * --------------------- */ + +static void +start_view (EContactStore *contact_store, + EBookClientView *view) +{ + g_signal_emit (contact_store, signals[START_CLIENT_VIEW], 0, view); + + g_signal_connect_swapped ( + view, "objects-added", + G_CALLBACK (view_contacts_added), contact_store); + g_signal_connect_swapped ( + view, "objects-removed", + G_CALLBACK (view_contacts_removed), contact_store); + g_signal_connect_swapped ( + view, "objects-modified", + G_CALLBACK (view_contacts_modified), contact_store); + g_signal_connect_swapped ( + view, "complete", + G_CALLBACK (view_complete), contact_store); + + e_book_client_view_start (view, NULL); +} + +static void +stop_view (EContactStore *contact_store, + EBookClientView *view) +{ + e_book_client_view_stop (view, NULL); + + g_signal_handlers_disconnect_matched ( + view, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, contact_store); + + g_signal_emit (contact_store, signals[STOP_CLIENT_VIEW], 0, view); +} + +static void +clear_contact_ptrarray (GPtrArray *contacts) +{ + gint i; + + for (i = 0; i < contacts->len; i++) { + EContact *contact = g_ptr_array_index (contacts, i); + g_object_unref (contact); + } + + g_ptr_array_set_size (contacts, 0); +} + +static void +free_contact_ptrarray (GPtrArray *contacts) +{ + clear_contact_ptrarray (contacts); + g_ptr_array_free (contacts, TRUE); +} + +static void +clear_contact_source (EContactStore *contact_store, + ContactSource *source) +{ + gint source_index; + gint offset; + + source_index = find_contact_source_by_pointer (contact_store, source); + g_assert (source_index >= 0); + + offset = get_contact_source_offset (contact_store, source_index); + g_assert (offset >= 0); + + /* Inform listeners that contacts went away */ + + if (source->contacts && source->contacts->len > 0) { + GtkTreePath *path = gtk_tree_path_new (); + gint i; + + gtk_tree_path_append_index (path, source->contacts->len); + + for (i = source->contacts->len - 1; i >= 0; i--) { + EContact *contact = g_ptr_array_index (source->contacts, i); + + g_object_unref (contact); + g_ptr_array_remove_index_fast (source->contacts, i); + + gtk_tree_path_prev (path); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (contact_store), path); + } + + gtk_tree_path_free (path); + } + + /* Free main and pending views, clear cached contacts */ + + if (source->client_view) { + stop_view (contact_store, source->client_view); + g_object_unref (source->client_view); + + source->client_view = NULL; + } + + if (source->client_view_pending) { + stop_view (contact_store, source->client_view_pending); + g_object_unref (source->client_view_pending); + free_contact_ptrarray (source->contacts_pending); + + source->client_view_pending = NULL; + source->contacts_pending = NULL; + } +} + +static void +client_view_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EContactStore *contact_store = user_data; + gint source_idx; + EBookClient *book_client; + EBookClientView *client_view = NULL; + + g_return_if_fail (contact_store != NULL); + g_return_if_fail (source_object != NULL); + + book_client = E_BOOK_CLIENT (source_object); + g_return_if_fail (book_client != NULL); + + if (!e_book_client_get_view_finish (book_client, result, &client_view, NULL)) + client_view = NULL; + + source_idx = find_contact_source_by_client (contact_store, book_client); + if (source_idx >= 0) { + ContactSource *source; + + source = &g_array_index (contact_store->priv->contact_sources, ContactSource, source_idx); + + if (source->client_view) { + if (source->client_view_pending) { + stop_view (contact_store, source->client_view_pending); + g_object_unref (source->client_view_pending); + free_contact_ptrarray (source->contacts_pending); + } + + source->client_view_pending = client_view; + + if (source->client_view_pending) { + source->contacts_pending = g_ptr_array_new (); + start_view (contact_store, client_view); + } else { + source->contacts_pending = NULL; + } + } else { + source->client_view = client_view; + + if (source->client_view) { + start_view (contact_store, client_view); + } + } + } + + g_object_unref (contact_store); +} + +static void +query_contact_source (EContactStore *contact_store, + ContactSource *source) +{ + gboolean is_opened; + + g_assert (source->book_client != NULL); + + if (!contact_store->priv->query) { + clear_contact_source (contact_store, source); + return; + } + + is_opened = e_client_is_opened (E_CLIENT (source->book_client)); + + if (source->client_view) { + if (source->client_view_pending) { + stop_view (contact_store, source->client_view_pending); + g_object_unref (source->client_view_pending); + free_contact_ptrarray (source->contacts_pending); + source->client_view_pending = NULL; + source->contacts_pending = NULL; + } + } + + if (is_opened) { + gchar *query_str; + + query_str = e_book_query_to_string (contact_store->priv->query); + e_book_client_get_view (source->book_client, query_str, NULL, client_view_ready_cb, g_object_ref (contact_store)); + g_free (query_str); + } +} + +/* ----------------- * + * EContactStore API * + * ----------------- */ + +/** + * e_contact_store_get_client: + * @contact_store: an #EContactStore + * @iter: a #GtkTreeIter from @contact_store + * + * Gets the #EBookClient that provided the contact at @iter. + * + * Returns: An #EBookClient. + * + * Since: 3.2 + **/ +EBookClient * +e_contact_store_get_client (EContactStore *contact_store, + GtkTreeIter *iter) +{ + gint index; + + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL); + g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), NULL); + + index = ITER_GET (iter); + + return get_book_at_row (contact_store, index); +} + +/** + * e_contact_store_get_contact: + * @contact_store: an #EContactStore + * @iter: a #GtkTreeIter from @contact_store + * + * Gets the #EContact at @iter. + * + * Returns: An #EContact. + **/ +EContact * +e_contact_store_get_contact (EContactStore *contact_store, + GtkTreeIter *iter) +{ + gint index; + + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL); + g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), NULL); + + index = ITER_GET (iter); + + return get_contact_at_row (contact_store, index); +} + +/** + * e_contact_store_find_contact: + * @contact_store: an #EContactStore + * @uid: a unique contact identifier + * @iter: a destination #GtkTreeIter to set + * + * Sets @iter to point to the contact row matching @uid. + * + * Returns: %TRUE if the contact was found, and @iter was set. %FALSE otherwise. + **/ +gboolean +e_contact_store_find_contact (EContactStore *contact_store, + const gchar *uid, + GtkTreeIter *iter) +{ + gint index; + + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), FALSE); + g_return_val_if_fail (uid != NULL, FALSE); + + index = find_contact_by_uid (contact_store, uid); + if (index < 0) + return FALSE; + + ITER_SET (contact_store, iter, index); + return TRUE; +} + +/** + * e_contact_store_get_clients: + * @contact_store: an #EContactStore + * + * Gets the list of book clients that provide contacts for @contact_store. + * + * Returns: A #GSList of pointers to #EBookClient. The caller owns the list, + * but not the book clients. + * + * Since: 3.2 + **/ +GSList * +e_contact_store_get_clients (EContactStore *contact_store) +{ + GArray *array; + GSList *client_list = NULL; + gint i; + + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL); + + array = contact_store->priv->contact_sources; + + for (i = 0; i < array->len; i++) { + ContactSource *source; + + source = &g_array_index (array, ContactSource, i); + client_list = g_slist_prepend (client_list, source->book_client); + } + + return client_list; +} + +/** + * e_contact_store_add_client: + * @contact_store: an #EContactStore + * @book_client: an #EBookClient + * + * Adds @book_client to the list of book clients that provide contacts for @contact_store. + * The @contact_store adds a reference to @book_client, if added. + * + * Since: 3.2 + **/ +void +e_contact_store_add_client (EContactStore *contact_store, + EBookClient *book_client) +{ + GArray *array; + ContactSource source; + ContactSource *indexed_source; + + g_return_if_fail (E_IS_CONTACT_STORE (contact_store)); + g_return_if_fail (E_IS_BOOK_CLIENT (book_client)); + + if (find_contact_source_by_client (contact_store, book_client) >= 0) { + g_warning ("Same book client added more than once to EContactStore!"); + return; + } + + array = contact_store->priv->contact_sources; + + memset (&source, 0, sizeof (ContactSource)); + source.book_client = g_object_ref (book_client); + source.contacts = g_ptr_array_new (); + g_array_append_val (array, source); + + indexed_source = &g_array_index (array, ContactSource, array->len - 1); + + query_contact_source (contact_store, indexed_source); +} + +/** + * e_contact_store_remove_client: + * @contact_store: an #EContactStore + * @book_client: an #EBookClient + * + * Removes @book from the list of book clients that provide contacts for @contact_store. + * + * Since: 3.2 + **/ +void +e_contact_store_remove_client (EContactStore *contact_store, + EBookClient *book_client) +{ + GArray *array; + ContactSource *source; + gint source_index; + + g_return_if_fail (E_IS_CONTACT_STORE (contact_store)); + g_return_if_fail (E_IS_BOOK_CLIENT (book_client)); + + source_index = find_contact_source_by_client (contact_store, book_client); + if (source_index < 0) { + g_warning ("Tried to remove unknown book client from EContactStore!"); + return; + } + + array = contact_store->priv->contact_sources; + + source = &g_array_index (array, ContactSource, source_index); + clear_contact_source (contact_store, source); + free_contact_ptrarray (source->contacts); + g_object_unref (book_client); + + g_array_remove_index (array, source_index); /* Preserve order */ +} + +/** + * e_contact_store_set_query: + * @contact_store: an #EContactStore + * @book_query: an #EBookQuery + * + * Sets @book_query to be the query used to fetch contacts from the books + * assigned to @contact_store. + **/ +void +e_contact_store_set_query (EContactStore *contact_store, + EBookQuery *book_query) +{ + GArray *array; + gint i; + + g_return_if_fail (E_IS_CONTACT_STORE (contact_store)); + + if (book_query == contact_store->priv->query) + return; + + if (contact_store->priv->query) + e_book_query_unref (contact_store->priv->query); + + contact_store->priv->query = book_query; + if (book_query) + e_book_query_ref (book_query); + + /* Query books */ + array = contact_store->priv->contact_sources; + for (i = 0; i < array->len; i++) { + ContactSource *contact_source; + + contact_source = &g_array_index (array, ContactSource, i); + query_contact_source (contact_store, contact_source); + } +} + +/** + * e_contact_store_peek_query: + * @contact_store: an #EContactStore + * + * Gets the query that's being used to fetch contacts from the books + * assigned to @contact_store. + * + * Returns: The #EBookQuery being used. + **/ +EBookQuery * +e_contact_store_peek_query (EContactStore *contact_store) +{ + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL); + + return contact_store->priv->query; +} + +/* ---------------- * + * GtkTreeModel API * + * ---------------- */ + +static GtkTreeModelFlags +e_contact_store_get_flags (GtkTreeModel *tree_model) +{ + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), 0); + + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint +e_contact_store_get_n_columns (GtkTreeModel *tree_model) +{ + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), 0); + + return E_CONTACT_FIELD_LAST; +} + +static GType +get_column_type (EContactStore *contact_store, + gint column) +{ + const gchar *field_name; + GObjectClass *contact_class; + GParamSpec *param_spec; + GType value_type; + + /* Silently suppress requests for columns lower than the first EContactField. + * GtkTreeView automatically queries the type of all columns up to the maximum + * provided, and we have to return a valid value type, so let it be a generic + * pointer. */ + if (column < E_CONTACT_FIELD_FIRST) { + return G_TYPE_POINTER; + } + + field_name = e_contact_field_name (column); + contact_class = g_type_class_ref (E_TYPE_CONTACT); + param_spec = g_object_class_find_property (contact_class, field_name); + value_type = G_PARAM_SPEC_VALUE_TYPE (param_spec); + g_type_class_unref (contact_class); + + return value_type; +} + +static GType +e_contact_store_get_column_type (GtkTreeModel *tree_model, + gint index) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), G_TYPE_INVALID); + g_return_val_if_fail (index >= 0 && index < E_CONTACT_FIELD_LAST, G_TYPE_INVALID); + + return get_column_type (contact_store, index); +} + +static gboolean +e_contact_store_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + gint index; + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE); + g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); + + index = gtk_tree_path_get_indices (path)[0]; + if (index >= count_contacts (contact_store)) + return FALSE; + + ITER_SET (contact_store, iter, index); + return TRUE; +} + +static GtkTreePath * +e_contact_store_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + GtkTreePath *path; + gint index; + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), NULL); + g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), NULL); + + index = ITER_GET (iter); + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, index); + + return path; +} + +static gboolean +e_contact_store_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + gint index; + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE); + g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), FALSE); + + index = ITER_GET (iter); + + if (index + 1 < count_contacts (contact_store)) { + ITER_SET (contact_store, iter, index + 1); + return TRUE; + } + + return FALSE; +} + +static gboolean +e_contact_store_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE); + + /* This is a list, nodes have no children. */ + if (parent) + return FALSE; + + /* But if parent == NULL we return the list itself as children of the root. */ + if (count_contacts (contact_store) <= 0) + return FALSE; + + ITER_SET (contact_store, iter, 0); + return TRUE; +} + +static gboolean +e_contact_store_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE); + + if (iter == NULL) + return TRUE; + + return FALSE; +} + +static gint +e_contact_store_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), -1); + + if (iter == NULL) + return count_contacts (contact_store); + + g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), -1); + return 0; +} + +static gboolean +e_contact_store_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + + g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE); + + if (parent) + return FALSE; + + if (n < count_contacts (contact_store)) { + ITER_SET (contact_store, iter, n); + return TRUE; + } + + return FALSE; +} + +static gboolean +e_contact_store_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + return FALSE; +} + +static void +e_contact_store_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + EContact *contact; + const gchar *field_name; + gint row; + + g_return_if_fail (E_IS_CONTACT_STORE (tree_model)); + g_return_if_fail (column < E_CONTACT_FIELD_LAST); + g_return_if_fail (ITER_IS_VALID (contact_store, iter)); + + g_value_init (value, get_column_type (contact_store, column)); + + row = ITER_GET (iter); + contact = get_contact_at_row (contact_store, row); + if (!contact || column < E_CONTACT_FIELD_FIRST) + return; + + field_name = e_contact_field_name (column); + g_object_get_property (G_OBJECT (contact), field_name, value); +} |