/* -*- 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 */ #ifdef HAVE_CONFIG_H # include #endif #include #include #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 gpointer contact_store_stop_view_in_thread (gpointer user_data) { EBookClientView *view = user_data; g_return_val_if_fail (E_IS_BOOK_CLIENT_VIEW (view), NULL); /* this does blocking D-Bus call, thus do it in a dedicated thread */ e_book_client_view_stop (view, NULL); g_object_unref (view); return NULL; } 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) { GThread *thread; thread = g_thread_new (NULL, contact_store_stop_view_in_thread, g_object_ref (view)); g_thread_unref (thread); 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); e_book_client_get_view_finish ( book_client, result, &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) { gchar *query_str; g_assert (source->book_client != NULL); if (!contact_store->priv->query) { clear_contact_source (contact_store, source); return; } 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; } } 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 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)); /* Return silently if we already have this EBookClient. */ if (find_contact_source_by_client (contact_store, book_client) >= 0) 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_client from the list of clients that provide contacts for * @contact_store. * * Returns: whether @book_client was found and removed * * Since: 3.2 **/ gboolean e_contact_store_remove_client (EContactStore *contact_store, EBookClient *book_client) { GArray *array; ContactSource *source; gint source_index; g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), FALSE); g_return_val_if_fail (E_IS_BOOK_CLIENT (book_client), FALSE); source_index = find_contact_source_by_client (contact_store, book_client); if (source_index < 0) return FALSE; 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 */ return TRUE; } /** * 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); }