/* -*- 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 the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* 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 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);
}