diff options
| author | Hans Petter Jansson <hpj@novell.com> | 2004-12-10 02:58:08 +0000 |
|---|---|---|
| committer | Hans Petter <hansp@src.gnome.org> | 2004-12-10 02:58:08 +0000 |
| commit | b4752e57ce427c90923303a6a22f1836e016c374 (patch) | |
| tree | 792c7d6152f3502096df750379a9f0811b8cc08b | |
| parent | 34c640da442da9582d7e91c26c25c72a775b6df2 (diff) | |
| download | evolution-data-server-b4752e57ce427c90923303a6a22f1836e016c374.tar.gz | |
Implement EContactStore, a GtkTreeModel interface to EBook views.
2004-12-09 Hans Petter Jansson <hpj@novell.com>
* libedataserverui/e-contact-store.[ch]: Implement EContactStore,
a GtkTreeModel interface to EBook views.
* libedataserverui/test-contact-store.c: Implement an interactive
test for EContactStore.
* libedataserverui/Makefile.am (libedataserveruiinclude_HEADERS)
(libedataserverui_1_2_la_SOURCES): Build e-contact-store.c and install
e-contact-store.h.
(test_contact_store_SOURCES)
(test_contact_store_LDADD): Build the interactive test for
EContactStore.
| -rw-r--r-- | ChangeLog | 15 | ||||
| -rw-r--r-- | libedataserverui/Makefile.am | 16 | ||||
| -rw-r--r-- | libedataserverui/e-contact-store.c | 1047 | ||||
| -rw-r--r-- | libedataserverui/e-contact-store.h | 74 | ||||
| -rw-r--r-- | libedataserverui/test-contact-store.c | 143 |
5 files changed, 1292 insertions, 3 deletions
@@ -1,3 +1,18 @@ +2004-12-09 Hans Petter Jansson <hpj@novell.com> + + * libedataserverui/e-contact-store.[ch]: Implement EContactStore, + a GtkTreeModel interface to EBook views. + + * libedataserverui/test-contact-store.c: Implement an interactive + test for EContactStore. + + * libedataserverui/Makefile.am (libedataserveruiinclude_HEADERS) + (libedataserverui_1_2_la_SOURCES): Build e-contact-store.c and install + e-contact-store.h. + (test_contact_store_SOURCES) + (test_contact_store_LDADD): Build the interactive test for + EContactStore. + 2004-12-09 JP Rosevear <jpr@novell.com> * configure.in: check for zlib, for the gzip mime filter diff --git a/libedataserverui/Makefile.am b/libedataserverui/Makefile.am index f97b784e1..43a9e89f6 100644 --- a/libedataserverui/Makefile.am +++ b/libedataserverui/Makefile.am @@ -1,5 +1,6 @@ INCLUDES = \ -I$(top_srcdir) \ + -I$(top_srcdir)/addressbook \ -DG_LOG_DOMAIN=\"e-data-server-ui\" \ $(E_DATA_SERVER_UI_CFLAGS) @@ -8,15 +9,17 @@ MARSHAL_GENERATED = e-data-server-ui-marshal.c e-data-server-ui-marshal.h @EVO_MARSHAL_RULE@ lib_LTLIBRARIES = libedataserverui-1.2.la -noinst_PROGRAMS = test-source-selector test-source-option-menu +noinst_PROGRAMS = test-source-selector test-source-option-menu test-contact-store libedataserverui_1_2_la_SOURCES = \ $(MARSHAL_GENERATED) \ + e-contact-store.c \ e-source-selector.c \ e-source-selector-dialog.c \ e-source-option-menu.c -libedataserverui_1_2_la_LIBADD = \ +libedataserverui_1_2_la_LIBADD = \ + $(top_builddir)/addressbook/libebook/libebook-1.2.la \ $(E_DATA_SERVER_LIBS) libedataserverui_1_2_la_LDFLAGS = \ @@ -25,6 +28,7 @@ libedataserverui_1_2_la_LDFLAGS = \ libedataserveruiincludedir = $(privincludedir)/libedataserverui libedataserveruiinclude_HEADERS = \ + e-contact-store.h \ e-source-selector.h \ e-source-selector-dialog.h \ e-source-option-menu.h @@ -42,6 +46,12 @@ test_source_option_menu_LDADD = \ $(top_builddir)/libedataserver/libedataserver-1.2.la \ $(E_DATA_SERVER_UI_LIBS) +test_contact_store_SOURCES = test-contact-store.c +test_contact_store_LDADD = \ + libedataserverui-1.2.la \ + $(top_builddir)/libedataserver/libedataserver-1.2.la \ + $(E_DATA_SERVER_UI_LIBS) + %-$(BASE_VERSION).pc: %.pc cp $< $@ @@ -51,4 +61,4 @@ pkgconfig_DATA = libedataserverui-$(BASE_VERSION).pc EXTRA_DIST = \ $(pkgconfig_DATA:-$(BASE_VERSION).pc=.pc.in) -DISTCLEANFILES = $(pkgconfig_DATA)
\ No newline at end of file +DISTCLEANFILES = $(pkgconfig_DATA) diff --git a/libedataserverui/e-contact-store.c b/libedataserverui/e-contact-store.c new file mode 100644 index 000000000..de7bd43f8 --- /dev/null +++ b/libedataserverui/e-contact-store.c @@ -0,0 +1,1047 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* e-contact-store.c - Contacts store with GtkTreeModel interface. + * + * Copyright (C) 2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Hans Petter Jansson <hpj@novell.com> + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <gtk/gtktreemodel.h> +#include <gtk/gtksignal.h> +#include <gtk/gtktreednd.h> +#include <libgnome/gnome-i18n.h> +#include "e-contact-store.h" + +#define ITER_IS_VALID(contact_store, iter) ((iter)->stamp == (contact_store)->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)->stamp; \ + (iter)->user_data = GINT_TO_POINTER (index); \ +} G_STMT_END + +static void e_contact_store_init (EContactStore *file_list); +static void e_contact_store_class_init (EContactStoreClass *class); +static void e_contact_store_tree_model_init (GtkTreeModelIface *iface); +static void e_contact_store_finalize (GObject *object); +static guint 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); + +static void stop_view (EContactStore *contact_store, EBookView *view); + +typedef struct +{ + EBook *book; + + EBookView *book_view; + GPtrArray *contacts; + + EBookView *book_view_pending; + GPtrArray *contacts_pending; +} +ContactSource; + +/* ------------------ * + * Class/object setup * + * ------------------ */ + +static GObjectClass *parent_class = NULL; + +GType +e_contact_store_get_type (void) +{ + static GType contact_store_type = 0; + + if (!contact_store_type) { + static const GTypeInfo contact_store_info = + { + sizeof (EContactStoreClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) e_contact_store_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EContactStore), + 0, + (GInstanceInitFunc) e_contact_store_init, + }; + + static const GInterfaceInfo tree_model_info = + { + (GInterfaceInitFunc) e_contact_store_tree_model_init, + NULL, + NULL + }; + + contact_store_type = g_type_register_static (G_TYPE_OBJECT, "EContactStore", + &contact_store_info, 0); + g_type_add_interface_static (contact_store_type, + GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + return contact_store_type; +} + +static void +e_contact_store_class_init (EContactStoreClass *class) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (class); + object_class = (GObjectClass *) class; + + object_class->finalize = e_contact_store_finalize; +} + +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) +{ + contact_store->stamp = g_random_int (); + contact_store->query = NULL; + contact_store->contact_sources = g_array_new (FALSE, FALSE, sizeof (ContactSource)); +} + +static void +e_contact_store_finalize (GObject *object) +{ + /* TODO: Free stuff */ + + if (G_OBJECT_CLASS (parent_class)->finalize) + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +EContactStore * +e_contact_store_new (void) +{ + EContactStore *contact_store; + + contact_store = E_CONTACT_STORE (g_object_new (e_contact_store_get_type (), NULL)); + + return contact_store; +} + +/* ------------------ * + * 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_book (EContactStore *contact_store, EBook *book) +{ + gint i; + + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *source; + + source = &g_array_index (contact_store->contact_sources, ContactSource, i); + if (source->book == book) + return i; + } + + return -1; +} + +static gint +find_contact_source_by_view (EContactStore *contact_store, EBookView *book_view) +{ + gint i; + + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *source; + + source = &g_array_index (contact_store->contact_sources, ContactSource, i); + if (source->book_view == book_view || + source->book_view_pending == book_view) + return i; + } + + return -1; +} + +static gint +find_contact_source_by_offset (EContactStore *contact_store, gint offset) +{ + gint i; + + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *source; + + source = &g_array_index (contact_store->contact_sources, 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) +{ + gint i; + + i = ((gchar *) source - (gchar *) contact_store->contact_sources->data) / sizeof (ContactSource); + + if (i < 0 || i >= contact_store->contact_sources->len) + return -1; + + return i; +} + +static gint +get_contact_source_offset (EContactStore *contact_store, gint contact_source_index) +{ + gint offset = 0; + gint i; + + g_assert (contact_source_index < contact_store->contact_sources->len); + + for (i = 0; i < contact_source_index; i++) { + ContactSource *source; + + source = &g_array_index (contact_store->contact_sources, ContactSource, i); + offset += source->contacts->len; + } + + return offset; +} + +static gint +count_contacts (EContactStore *contact_store) +{ + gint count = 0; + gint i; + + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *source; + + source = &g_array_index (contact_store->contact_sources, ContactSource, i); + count += source->contacts->len; + } + + return count; +} + +static gint +find_contact_by_view_and_uid (EContactStore *contact_store, EBookView *find_view, const gchar *find_uid) +{ + gint source_index; + ContactSource *source; + GPtrArray *contacts; + gint i; + + source_index = find_contact_source_by_view (contact_store, find_view); + if (source_index < 0) + return -1; + + source = &g_array_index (contact_store->contact_sources, ContactSource, source_index); + + if (find_view == source->book_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 (!strcmp (find_uid, uid)) + return i; + } + + return -1; +} + +static gint +find_contact_by_uid (EContactStore *contact_store, const gchar *find_uid) +{ + gint i, j; + + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *source = &g_array_index (contact_store->contact_sources, 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 EContact * +get_contact_at_row (EContactStore *contact_store, gint row) +{ + ContactSource *source; + gint source_index; + gint offset; + + source_index = find_contact_source_by_offset (contact_store, row); + if (source_index < 0) + return NULL; + + source = &g_array_index (contact_store->contact_sources, 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, EBookView *book_view, + ContactSource **contact_source, gint *offset) +{ + ContactSource *source; + gint source_index; + + source_index = find_contact_source_by_view (contact_store, book_view); + if (source_index < 0) + return FALSE; + + *contact_source = &g_array_index (contact_store->contact_sources, 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 GList *contacts, EBookView *book_view) +{ + ContactSource *source; + gint offset; + const GList *l; + + if (!find_contact_source_details_by_view (contact_store, book_view, &source, &offset)) { + g_warning ("EContactStore got 'contacts_added' signal from unknown EBookView!"); + return; + } + + for (l = contacts; l; l = g_list_next (l)) { + EContact *contact = l->data; + + g_object_ref (contact); + + if (book_view == source->book_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 GList *uids, EBookView *book_view) +{ + ContactSource *source; + gint offset; + const GList *l; + + if (!find_contact_source_details_by_view (contact_store, book_view, &source, &offset)) { + g_warning ("EContactStore got 'contacts_removed' signal from unknown EBookView!"); + return; + } + + for (l = uids; l; l = g_list_next (l)) { + const gchar *uid = l->data; + gint n = find_contact_by_view_and_uid (contact_store, book_view, uid); + EContact *contact; + + if (n < 0) { + g_warning ("EContactStore got 'contacts_removed' on unknown contact!"); + continue; + } + + if (book_view == source->book_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_changed (EContactStore *contact_store, const GList *contacts, EBookView *book_view) +{ + GPtrArray *cached_contacts; + ContactSource *source; + gint offset; + const GList *l; + + if (!find_contact_source_details_by_view (contact_store, book_view, &source, &offset)) { + g_warning ("EContactStore got 'contacts_changed' signal from unknown EBookView!"); + return; + } + + if (book_view == source->book_view) + cached_contacts = source->contacts; + else + cached_contacts = source->contacts_pending; + + for (l = contacts; l; l = g_list_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, book_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 (book_view == source->book_view) + row_changed (contact_store, offset + n); + } +} + +static void +view_sequence_complete (EContactStore *contact_store, EBookViewStatus status, EBookView *book_view) +{ + ContactSource *source; + gint offset; + gint i; + + if (!find_contact_source_details_by_view (contact_store, book_view, &source, &offset)) { + g_warning ("EContactStore got 'sequence_complete' signal from unknown EBookView!"); + return; + } + + /* If current view finished, do nothing */ + if (book_view == source->book_view) + return; + + g_assert (book_view == source->book_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->book_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->book_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->book_view); + g_object_unref (source->book_view); + source->book_view = source->book_view_pending; + source->book_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, EBookView *view) +{ + g_signal_connect_swapped (view, "contacts_added", + G_CALLBACK (view_contacts_added), contact_store); + g_signal_connect_swapped (view, "contacts_removed", + G_CALLBACK (view_contacts_removed), contact_store); + g_signal_connect_swapped (view, "contacts_changed", + G_CALLBACK (view_contacts_changed), contact_store); + g_signal_connect_swapped (view, "sequence_complete", + G_CALLBACK (view_sequence_complete), contact_store); + + e_book_view_start (view); +} + +static void +stop_view (EContactStore *contact_store, EBookView *view) +{ + e_book_view_stop (view); + + g_signal_handlers_disconnect_matched (view, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, contact_store); +} + +static void +free_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_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, cached contacts */ + + if (source->book_view) { + stop_view (contact_store, source->book_view); + g_object_unref (source->book_view); + free_contact_ptrarray (source->contacts); /* This array should be empty here */ + + source->book_view = NULL; + source->contacts = NULL; + } + + if (source->book_view_pending) { + stop_view (contact_store, source->book_view_pending); + g_object_unref (source->book_view_pending); + free_contact_ptrarray (source->contacts_pending); + + source->book_view_pending = NULL; + source->contacts_pending = NULL; + } +} + +static void +query_contact_source (EContactStore *contact_store, ContactSource *source) +{ + EBookView *view; + + g_assert (source->book != NULL); + + if (!contact_store->query) { + clear_contact_source (contact_store, source); + return; + } + + /* TODO: Error checking */ + e_book_get_book_view (source->book, contact_store->query, NULL, -1, &view, NULL); + + if (source->book_view) { + if (source->book_view_pending) { + stop_view (contact_store, source->book_view_pending); + g_object_unref (source->book_view_pending); + free_contact_ptrarray (source->contacts_pending); + } + + source->book_view_pending = view; + source->contacts_pending = g_ptr_array_new (); + + start_view (contact_store, view); + } else { + source->book_view = view; + source->contacts = g_ptr_array_new (); + start_view (contact_store, view); + } +} + +/* ----------------- * + * EContactStore API * + * ----------------- */ + +GList * +e_contact_store_get_books (EContactStore *contact_store) +{ + GList *book_list = NULL; + gint i; + + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL); + + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *source; + + source = &g_array_index (contact_store->contact_sources, ContactSource, i); + book_list = g_list_prepend (book_list, source->book); + } + + return book_list; +} + +void +e_contact_store_add_book (EContactStore *contact_store, EBook *book) +{ + ContactSource *source; + EBookView *view; + + g_return_if_fail (E_IS_CONTACT_STORE (contact_store)); + + if (find_contact_source_by_book (contact_store, book) >= 0) { + g_warning ("Same book added more than once to EContactStore!"); + return; + } + + source = g_new0 (ContactSource, 1); + source->book = g_object_ref (book); + g_array_append_val (contact_store->contact_sources, *source); + + source = &g_array_index (contact_store->contact_sources, ContactSource, + contact_store->contact_sources->len - 1); + + query_contact_source (contact_store, source); +} + +void +e_contact_store_remove_book (EContactStore *contact_store, EBook *book) +{ + ContactSource *source; + gint source_index; + gint offset; + gint i; + + g_return_if_fail (E_IS_CONTACT_STORE (contact_store)); + + source_index = find_contact_source_by_book (contact_store, book); + if (source_index < 0) { + g_warning ("Tried to remove unknown book from EContactStore!"); + return; + } + + clear_contact_source (contact_store, source); + g_object_unref (book); + + g_array_remove_index (contact_store->contact_sources, source_index); /* Preserve order */ +} + +void +e_contact_store_set_query (EContactStore *contact_store, EBookQuery *book_query) +{ + gint i; + + g_return_if_fail (E_IS_CONTACT_STORE (contact_store)); + + if (book_query == contact_store->query) + return; + + if (contact_store->query) + e_book_query_unref (contact_store->query); + + contact_store->query = book_query; + if (book_query) + e_book_query_ref (book_query); + + /* Query books */ + for (i = 0; i < contact_store->contact_sources->len; i++) { + ContactSource *contact_source; + + contact_source = &g_array_index (contact_store->contact_sources, ContactSource, i); + query_contact_source (contact_store, contact_source); + } +} + +EBookQuery * +e_contact_store_peek_query (EContactStore *contact_store) +{ + g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL); + + return contact_store->query; +} + +/* ---------------- * + * GtkTreeModel API * + * ---------------- */ + +static guint +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) +{ + EContactStore *contact_store = E_CONTACT_STORE (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) +{ + EContactStore *contact_store = E_CONTACT_STORE (tree_model); + + 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); +} diff --git a/libedataserverui/e-contact-store.h b/libedataserverui/e-contact-store.h new file mode 100644 index 000000000..a13eb28eb --- /dev/null +++ b/libedataserverui/e-contact-store.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* e-contact-store.h - Contacts store with GtkTreeModel interface. + * + * Copyright (C) 2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Hans Petter Jansson <hpj@novell.com> + */ + +#ifndef E_CONTACT_STORE_H +#define E_CONTACT_STORE_H + +#include <gtk/gtktreemodel.h> +#include <libebook/e-contact.h> +#include <libebook/e-book.h> +#include <libebook/e-book-query.h> +#include <libebook/e-book-types.h> + +G_BEGIN_DECLS + +#define E_TYPE_CONTACT_STORE (e_contact_store_get_type ()) +#define E_CONTACT_STORE(obj) (GTK_CHECK_CAST ((obj), E_TYPE_CONTACT_STORE, EContactStore)) +#define E_CONTACT_STORE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPE_CONTACT_STORE, EContactStoreClass)) +#define E_IS_CONTACT_STORE(obj) (GTK_CHECK_TYPE ((obj), E_TYPE_CONTACT_STORE)) +#define E_IS_CONTACT_STORE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), E_TYPE_CONTACT_STORE)) +#define E_CONTACT_STORE_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), E_TYPE_CONTACT_STORE, EContactStoreClass)) + +typedef struct _EContactStore EContactStore; +typedef struct _EContactStoreClass EContactStoreClass; + +struct _EContactStore +{ + GObject parent; + + /* Private */ + + gint stamp; + EBookQuery *query; + GArray *contact_sources; +}; + +struct _EContactStoreClass +{ + GObjectClass parent_class; +}; + +GtkType e_contact_store_get_type (void); +EContactStore *e_contact_store_new (void); + +/* Returns a shallow copy; free the list when done, but don't unref elements */ +GList *e_contact_store_get_books (EContactStore *contact_store); + +void e_contact_store_add_book (EContactStore *contact_store, EBook *book); +void e_contact_store_remove_book (EContactStore *contact_store, EBook *book); + +void e_contact_store_set_query (EContactStore *contact_store, EBookQuery *book_query); +EBookQuery *e_contact_store_peek_query (EContactStore *contact_store); + +G_END_DECLS + +#endif /* E_CONTACT_STORE_H */ diff --git a/libedataserverui/test-contact-store.c b/libedataserverui/test-contact-store.c new file mode 100644 index 000000000..08fc47a4c --- /dev/null +++ b/libedataserverui/test-contact-store.c @@ -0,0 +1,143 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* test-contact-store.c - Test program for EContactStore. + * + * Copyright (C) 2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Hans Petter Jansson <hpj@novell.com> + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "e-contact-store.h" +#include <gtk/gtk.h> +#include <libgnomeui/gnome-ui-init.h> + +static void +entry_changed (GtkWidget *entry, EContactStore *contact_store) +{ + const gchar *text; + EBookQuery *query; + + text = gtk_entry_get_text (GTK_ENTRY (entry)); + + query = e_book_query_any_field_contains (text); + e_contact_store_set_query (contact_store, query); + e_book_query_unref (query); +} + +static GtkTreeViewColumn * +create_text_column_for_field (EContactField field_id) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *cell_renderer; + + column = gtk_tree_view_column_new (); + cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ()); + gtk_tree_view_column_pack_start (column, cell_renderer, TRUE); + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_title (column, e_contact_pretty_name (field_id)); + gtk_tree_view_column_add_attribute (column, cell_renderer, "text", field_id); + gtk_tree_view_column_set_sort_column_id (column, field_id); + + return column; +} + +static gint +start_test (const char *gconf_path) +{ + EContactStore *contact_store; + GtkTreeModel *model_sort; + GtkWidget *scrolled_window; + GtkWidget *window; + GtkWidget *select_names_entry; + GtkWidget *tree_view; + GtkWidget *box; + GtkWidget *entry; + GtkTreeViewColumn *column; + EBook *book; + EBookQuery *book_query; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + box = gtk_vbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (window), box); + + entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (box), entry, FALSE, TRUE, 0); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_box_pack_start (GTK_BOX (box), scrolled_window, TRUE, TRUE, 0); + + contact_store = e_contact_store_new (); + model_sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (contact_store)); + tree_view = GTK_WIDGET (gtk_tree_view_new ()); + gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model_sort); + + column = create_text_column_for_field (E_CONTACT_FILE_AS); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + column = create_text_column_for_field (E_CONTACT_FULL_NAME); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + column = create_text_column_for_field (E_CONTACT_EMAIL_1); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view); + + book = e_book_new_default_addressbook (NULL); + e_book_open (book, TRUE, NULL); + e_contact_store_add_book (contact_store, book); + g_object_unref (book); + + book_query = e_book_query_any_field_contains (""); + e_contact_store_set_query (contact_store, book_query); + e_book_query_unref (book_query); + + g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), contact_store); + + gtk_widget_show_all (window); + + return FALSE; +} + +int +main (int argc, char **argv) +{ + GnomeProgram *program; + const char *gconf_path; + + program = gnome_program_init ("test-source-selector", "0.0", + LIBGNOMEUI_MODULE, argc, argv, + NULL); + + if (bonobo_init (&argc, argv) == FALSE) + g_error ("Could not initialize Bonobo."); + + if (argc < 2) + gconf_path = "/apps/evolution/addressbook/sources"; + else + gconf_path = argv [1]; + + g_idle_add ((GSourceFunc) start_test, (void *) gconf_path); + + bonobo_main (); + + return 0; +} |
