summaryrefslogtreecommitdiff
path: root/libedataserverui
diff options
context:
space:
mode:
authorHans Petter Jansson <hpj@novell.com>2004-12-17 04:44:14 +0000
committerHans Petter <hansp@src.gnome.org>2004-12-17 04:44:14 +0000
commit4f3649adad735d68bfda59802acb9f9eb1c2829e (patch)
tree5ee93042d3adfb52398e1923cb08157fadab1905 /libedataserverui
parentaccd3261a44574cf0560cf78402cc56f57b83e82 (diff)
downloadevolution-data-server-4f3649adad735d68bfda59802acb9f9eb1c2829e.tar.gz
Add NONE:STRING.
2004-12-16 Hans Petter Jansson <hpj@novell.com> * libedataserver/e-data-server-marshal.list: Add NONE:STRING. * libedataserverui/e-destination-store.[ch]: Implement EDestinationStore, a GtkTreeModel interface to a list of EDestinations. * libedataserverui/e-name-selector-model.[ch]: Implement ENameSelectorModel, a model that provides an EContactStore and a list of EDestinationStores whose members are filtered out from the EContactStore. * libedataserverui/e-name-selector-dialog.[ch]: Implement ENameSelectorDialog, a GtkDialog that lets you select EContacts interactively, producing lists of EDestinations. * libedataserverui/e-name-selector-dialog.glade: The Glade XML specification for the ENameSelectorDialog interface. * libedataserverui/e-contact-store.c (e_contact_store_get_contact): Implement. (e_contact_store_get_books): Free a small temp allocation. (e_contact_store_remove_book): Get the pointer to the source struct. * libedataserverui/libedataserverui.pc.in (gladedir): Set this var. * Makefile.am (INCLUDES): Add a define providing the installed Glade directory. (libedataserverui_1_2_la_SOURCES): Add new files. (libedataserveruiinclude_HEADERS): Add new files. (glade_DATA): Add new Glade file. (EXTRA_DIST): Dist glade_DATA.
Diffstat (limited to 'libedataserverui')
-rw-r--r--libedataserverui/Makefile.am15
-rw-r--r--libedataserverui/e-contact-store.c23
-rw-r--r--libedataserverui/e-contact-store.h18
-rw-r--r--libedataserverui/e-destination-store.c555
-rw-r--r--libedataserverui/e-destination-store.h80
-rw-r--r--libedataserverui/e-name-selector-dialog.c769
-rw-r--r--libedataserverui/e-name-selector-dialog.glade335
-rw-r--r--libedataserverui/e-name-selector-dialog.h84
-rw-r--r--libedataserverui/e-name-selector-model.c333
-rw-r--r--libedataserverui/e-name-selector-model.h81
-rw-r--r--libedataserverui/libedataserverui.pc.in1
11 files changed, 2278 insertions, 16 deletions
diff --git a/libedataserverui/Makefile.am b/libedataserverui/Makefile.am
index 43a9e89f6..6461a585c 100644
--- a/libedataserverui/Makefile.am
+++ b/libedataserverui/Makefile.am
@@ -2,6 +2,7 @@ INCLUDES = \
-I$(top_srcdir) \
-I$(top_srcdir)/addressbook \
-DG_LOG_DOMAIN=\"e-data-server-ui\" \
+ -DE_DATA_SERVER_UI_GLADEDIR=\""$(gladedir)"\" \
$(E_DATA_SERVER_UI_CFLAGS)
# The marshallers
@@ -9,11 +10,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 test-contact-store
+noinst_PROGRAMS = \
+ test-source-selector \
+ test-source-option-menu \
+ test-contact-store
libedataserverui_1_2_la_SOURCES = \
$(MARSHAL_GENERATED) \
+ e-destination-store.c \
e-contact-store.c \
+ e-name-selector-dialog.c \
+ e-name-selector-model.c \
e-source-selector.c \
e-source-selector-dialog.c \
e-source-option-menu.c
@@ -28,7 +35,10 @@ libedataserverui_1_2_la_LDFLAGS = \
libedataserveruiincludedir = $(privincludedir)/libedataserverui
libedataserveruiinclude_HEADERS = \
+ e-destination-store.h \
e-contact-store.h \
+ e-name-selector-dialog.h \
+ e-name-selector-model.h \
e-source-selector.h \
e-source-selector-dialog.h \
e-source-option-menu.h
@@ -58,7 +68,10 @@ test_contact_store_LDADD = \
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libedataserverui-$(BASE_VERSION).pc
+glade_DATA = e-name-selector-dialog.glade
+
EXTRA_DIST = \
+ $(glade_DATA) \
$(pkgconfig_DATA:-$(BASE_VERSION).pc=.pc.in)
DISTCLEANFILES = $(pkgconfig_DATA)
diff --git a/libedataserverui/e-contact-store.c b/libedataserverui/e-contact-store.c
index de7bd43f8..fc20af97e 100644
--- a/libedataserverui/e-contact-store.c
+++ b/libedataserverui/e-contact-store.c
@@ -39,7 +39,7 @@ G_STMT_START { \
(iter)->user_data = GINT_TO_POINTER (index); \
} G_STMT_END
-static void e_contact_store_init (EContactStore *file_list);
+static void e_contact_store_init (EContactStore *contact_store);
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);
@@ -177,11 +177,7 @@ e_contact_store_finalize (GObject *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;
+ return E_CONTACT_STORE (g_object_new (E_TYPE_CONTACT_STORE, NULL));
}
/* ------------------ *
@@ -730,6 +726,19 @@ query_contact_source (EContactStore *contact_store, ContactSource *source)
* EContactStore API *
* ----------------- */
+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);
+}
+
GList *
e_contact_store_get_books (EContactStore *contact_store)
{
@@ -764,6 +773,7 @@ e_contact_store_add_book (EContactStore *contact_store, EBook *book)
source = g_new0 (ContactSource, 1);
source->book = g_object_ref (book);
g_array_append_val (contact_store->contact_sources, *source);
+ g_free (source);
source = &g_array_index (contact_store->contact_sources, ContactSource,
contact_store->contact_sources->len - 1);
@@ -787,6 +797,7 @@ e_contact_store_remove_book (EContactStore *contact_store, EBook *book)
return;
}
+ source = &g_array_index (contact_store->contact_sources, ContactSource, source_index);
clear_contact_source (contact_store, source);
g_object_unref (book);
diff --git a/libedataserverui/e-contact-store.h b/libedataserverui/e-contact-store.h
index a13eb28eb..306cca923 100644
--- a/libedataserverui/e-contact-store.h
+++ b/libedataserverui/e-contact-store.h
@@ -38,11 +38,14 @@ G_BEGIN_DECLS
#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;
+typedef struct EContactStore EContactStore;
+typedef struct EContactStoreClass EContactStoreClass;
-struct _EContactStore
-{
+struct EContactStoreClass {
+ GObjectClass parent_class;
+};
+
+struct EContactStore {
GObject parent;
/* Private */
@@ -52,14 +55,11 @@ struct _EContactStore
GArray *contact_sources;
};
-struct _EContactStoreClass
-{
- GObjectClass parent_class;
-};
-
GtkType e_contact_store_get_type (void);
EContactStore *e_contact_store_new (void);
+EContact *e_contact_store_get_contact (EContactStore *contact_store, GtkTreeIter *iter);
+
/* Returns a shallow copy; free the list when done, but don't unref elements */
GList *e_contact_store_get_books (EContactStore *contact_store);
diff --git a/libedataserverui/e-destination-store.c b/libedataserverui/e-destination-store.c
new file mode 100644
index 000000000..5e394e72d
--- /dev/null
+++ b/libedataserverui/e-destination-store.c
@@ -0,0 +1,555 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-destination-store.c - EDestination 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-destination-store.h"
+
+#define ITER_IS_VALID(destination_store, iter) ((iter)->stamp == (destination_store)->stamp)
+#define ITER_GET(iter) GPOINTER_TO_INT (iter->user_data)
+#define ITER_SET(destination_store, iter, index) \
+G_STMT_START { \
+ (iter)->stamp = (destination_store)->stamp; \
+ (iter)->user_data = GINT_TO_POINTER (index); \
+} G_STMT_END
+
+static void e_destination_store_init (EDestinationStore *destination_store);
+static void e_destination_store_class_init (EDestinationStoreClass *class);
+static void e_destination_store_tree_model_init (GtkTreeModelIface *iface);
+static void e_destination_store_finalize (GObject *object);
+static guint e_destination_store_get_flags (GtkTreeModel *tree_model);
+static gint e_destination_store_get_n_columns (GtkTreeModel *tree_model);
+static GType e_destination_store_get_column_type (GtkTreeModel *tree_model,
+ gint index);
+static gboolean e_destination_store_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+static GtkTreePath *e_destination_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static void e_destination_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+static gboolean e_destination_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean e_destination_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+static gboolean e_destination_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gint e_destination_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean e_destination_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n);
+static gboolean e_destination_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+
+static void destination_changed (EDestinationStore *destination_store, EDestination *destination);
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+static GObjectClass *parent_class = NULL;
+static GType column_types [E_DESTINATION_STORE_NUM_COLUMNS];
+
+GType
+e_destination_store_get_type (void)
+{
+ static GType destination_store_type = 0;
+
+ if (!destination_store_type) {
+ static const GTypeInfo destination_store_info =
+ {
+ sizeof (EDestinationStoreClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) e_destination_store_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EDestinationStore),
+ 0,
+ (GInstanceInitFunc) e_destination_store_init,
+ };
+
+ static const GInterfaceInfo tree_model_info =
+ {
+ (GInterfaceInitFunc) e_destination_store_tree_model_init,
+ NULL,
+ NULL
+ };
+
+ column_types [E_DESTINATION_STORE_COLUMN_NAME] = G_TYPE_STRING;
+ column_types [E_DESTINATION_STORE_COLUMN_EMAIL] = G_TYPE_STRING;
+ column_types [E_DESTINATION_STORE_COLUMN_ADDRESS] = G_TYPE_STRING;
+
+ destination_store_type = g_type_register_static (G_TYPE_OBJECT, "EDestinationStore",
+ &destination_store_info, 0);
+ g_type_add_interface_static (destination_store_type,
+ GTK_TYPE_TREE_MODEL,
+ &tree_model_info);
+ }
+
+ return destination_store_type;
+}
+
+static void
+e_destination_store_class_init (EDestinationStoreClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass *) class;
+
+ object_class->finalize = e_destination_store_finalize;
+}
+
+static void
+e_destination_store_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = e_destination_store_get_flags;
+ iface->get_n_columns = e_destination_store_get_n_columns;
+ iface->get_column_type = e_destination_store_get_column_type;
+ iface->get_iter = e_destination_store_get_iter;
+ iface->get_path = e_destination_store_get_path;
+ iface->get_value = e_destination_store_get_value;
+ iface->iter_next = e_destination_store_iter_next;
+ iface->iter_children = e_destination_store_iter_children;
+ iface->iter_has_child = e_destination_store_iter_has_child;
+ iface->iter_n_children = e_destination_store_iter_n_children;
+ iface->iter_nth_child = e_destination_store_iter_nth_child;
+ iface->iter_parent = e_destination_store_iter_parent;
+}
+
+static void
+e_destination_store_init (EDestinationStore *destination_store)
+{
+ destination_store->stamp = g_random_int ();
+ destination_store->destinations = g_ptr_array_new ();
+}
+
+static void
+e_destination_store_finalize (GObject *object)
+{
+ /* TODO: Free stuff */
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+EDestinationStore *
+e_destination_store_new (void)
+{
+ EDestinationStore *destination_store;
+
+ destination_store = E_DESTINATION_STORE (g_object_new (E_TYPE_DESTINATION_STORE, NULL));
+
+ return destination_store;
+}
+
+/* ------------------ *
+ * Row update helpers *
+ * ------------------ */
+
+static void
+row_deleted (EDestinationStore *destination_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 (destination_store), path);
+ gtk_tree_path_free (path);
+}
+
+static void
+row_inserted (EDestinationStore *destination_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 (destination_store), &iter, path))
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (destination_store), path, &iter);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+row_changed (EDestinationStore *destination_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 (destination_store), &iter, path))
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (destination_store), path, &iter);
+
+ gtk_tree_path_free (path);
+}
+
+/* ------------------- *
+ * Destination helpers *
+ * ------------------- */
+
+static gint
+find_destination_by_pointer (EDestinationStore *destination_store, EDestination *destination)
+{
+ gint i;
+
+ for (i = 0; i < destination_store->destinations->len; i++) {
+ EDestination *destination_here = g_ptr_array_index (destination_store->destinations, i);
+
+ if (destination_here == destination)
+ return i;
+ }
+
+ return -1;
+}
+
+static void
+start_destination (EDestinationStore *destination_store, EDestination *destination)
+{
+ g_signal_connect_swapped (destination, "changed",
+ G_CALLBACK (destination_changed), destination_store);
+}
+
+static void
+stop_destination (EDestinationStore *destination_store, EDestination *destination)
+{
+ g_signal_handlers_disconnect_matched (destination, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, destination_store);
+}
+
+/* --------------- *
+ * Signal handlers *
+ * --------------- */
+
+static void
+destination_changed (EDestinationStore *destination_store, EDestination *destination)
+{
+ gint n;
+
+ n = find_destination_by_pointer (destination_store, destination);
+ if (n < 0) {
+ g_warning ("EDestinationStore got change from unknown EDestination!");
+ return;
+ }
+
+ row_changed (destination_store, n);
+}
+
+/* --------------------- *
+ * EDestinationStore API *
+ * --------------------- */
+
+EDestination *
+e_destination_store_get_destination (EDestinationStore *destination_store, GtkTreeIter *iter)
+{
+ gint index;
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (destination_store), NULL);
+ g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), NULL);
+
+ index = ITER_GET (iter);
+
+ return g_ptr_array_index (destination_store->destinations, index);
+}
+
+GList *
+e_destination_store_list_destinations (EDestinationStore *destination_store)
+{
+ GList *destination_list = NULL;
+ gint i;
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (destination_store), NULL);
+
+ for (i = 0; i < destination_store->destinations->len; i++) {
+ EDestination *destination = g_ptr_array_index (destination_store->destinations, i);
+ destination_list = g_list_prepend (destination_list, destination);
+ }
+
+ return destination_list;
+}
+
+void
+e_destination_store_add_destination (EDestinationStore *destination_store, EDestination *destination)
+{
+ g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
+
+ if (find_destination_by_pointer (destination_store, destination) >= 0) {
+ g_warning ("Same destination added more than once to EDestinationStore!");
+ return;
+ }
+
+ g_object_ref (destination);
+
+ g_ptr_array_add (destination_store->destinations, destination);
+ row_inserted (destination_store, destination_store->destinations->len - 1);
+}
+
+void
+e_destination_store_remove_destination (EDestinationStore *destination_store, EDestination *destination)
+{
+ gint n;
+
+ g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
+
+ n = find_destination_by_pointer (destination_store, destination);
+ if (n < 0) {
+ g_warning ("Tried to remove unknown destination from EDestinationStore!");
+ return;
+ }
+
+ g_object_unref (destination);
+
+ g_ptr_array_remove_index (destination_store->destinations, n);
+ row_deleted (destination_store, n);
+}
+
+/* ---------------- *
+ * GtkTreeModel API *
+ * ---------------- */
+
+static guint
+e_destination_store_get_flags (GtkTreeModel *tree_model)
+{
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), 0);
+
+ return GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static gint
+e_destination_store_get_n_columns (GtkTreeModel *tree_model)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), 0);
+
+ return E_CONTACT_FIELD_LAST;
+}
+
+static GType
+e_destination_store_get_column_type (GtkTreeModel *tree_model,
+ gint index)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), G_TYPE_INVALID);
+ g_return_val_if_fail (index >= 0 && index < E_DESTINATION_STORE_NUM_COLUMNS, G_TYPE_INVALID);
+
+ return column_types [index];
+}
+
+static gboolean
+e_destination_store_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+ gint index;
+
+ g_return_val_if_fail (E_IS_DESTINATION_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 >= destination_store->destinations->len)
+ return FALSE;
+
+ ITER_SET (destination_store, iter, index);
+ return TRUE;
+}
+
+static GtkTreePath *
+e_destination_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+ GtkTreePath *path;
+ gint index;
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), NULL);
+ g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), NULL);
+
+ index = ITER_GET (iter);
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, index);
+
+ return path;
+}
+
+static gboolean
+e_destination_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+ gint index;
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+ g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), FALSE);
+
+ index = ITER_GET (iter);
+
+ if (index + 1 < destination_store->destinations->len) {
+ ITER_SET (destination_store, iter, index + 1);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+e_destination_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+ g_return_val_if_fail (E_IS_DESTINATION_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 (destination_store->destinations->len <= 0)
+ return FALSE;
+
+ ITER_SET (destination_store, iter, 0);
+ return TRUE;
+}
+
+static gboolean
+e_destination_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+
+ if (iter == NULL)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gint
+e_destination_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), -1);
+
+ if (iter == NULL)
+ return destination_store->destinations->len;
+
+ g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), -1);
+ return 0;
+}
+
+static gboolean
+e_destination_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+ g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+
+ if (parent)
+ return FALSE;
+
+ if (n < destination_store->destinations->len) {
+ ITER_SET (destination_store, iter, n);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+e_destination_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ return FALSE;
+}
+
+static void
+e_destination_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+ EDestination *destination;
+ const gchar *string;
+ gint row;
+
+ g_return_if_fail (E_IS_DESTINATION_STORE (tree_model));
+ g_return_if_fail (column < E_DESTINATION_STORE_NUM_COLUMNS);
+ g_return_if_fail (ITER_IS_VALID (destination_store, iter));
+
+ g_value_init (value, column_types [column]);
+
+ row = ITER_GET (iter);
+ if (row >= destination_store->destinations->len)
+ return;
+
+ destination = g_ptr_array_index (destination_store->destinations, row);
+ g_assert (destination);
+
+ switch (column) {
+ case E_DESTINATION_STORE_COLUMN_NAME:
+ string = e_destination_get_name (destination);
+ g_value_set_string (value, string);
+ break;
+
+ case E_DESTINATION_STORE_COLUMN_EMAIL:
+ string = e_destination_get_email (destination);
+ g_value_set_string (value, string);
+ break;
+
+ case E_DESTINATION_STORE_COLUMN_ADDRESS:
+ string = e_destination_get_address (destination);
+ g_value_set_string (value, string);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
diff --git a/libedataserverui/e-destination-store.h b/libedataserverui/e-destination-store.h
new file mode 100644
index 000000000..101016520
--- /dev/null
+++ b/libedataserverui/e-destination-store.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-destination-store.h - EDestination 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_DESTINATION_STORE_H
+#define E_DESTINATION_STORE_H
+
+#include <gtk/gtktreemodel.h>
+#include <libebook/e-destination.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_DESTINATION_STORE (e_destination_store_get_type ())
+#define E_DESTINATION_STORE(obj) (GTK_CHECK_CAST ((obj), E_TYPE_DESTINATION_STORE, EDestinationStore))
+#define E_DESTINATION_STORE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPE_DESTINATION_STORE, EDestinationStoreClass))
+#define E_IS_DESTINATION_STORE(obj) (GTK_CHECK_TYPE ((obj), E_TYPE_DESTINATION_STORE))
+#define E_IS_DESTINATION_STORE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), E_TYPE_DESTINATION_STORE))
+#define E_DESTINATION_STORE_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), E_TYPE_DESTINATION_STORE, EDestinationStoreClass))
+
+typedef struct EDestinationStore EDestinationStore;
+typedef struct EDestinationStoreClass EDestinationStoreClass;
+
+struct EDestinationStore {
+ GObject parent;
+
+ /* Private */
+
+ gint stamp;
+ GPtrArray *destinations;
+};
+
+struct EDestinationStoreClass {
+ GObjectClass parent_class;
+};
+
+typedef enum
+{
+ E_DESTINATION_STORE_COLUMN_NAME,
+ E_DESTINATION_STORE_COLUMN_EMAIL,
+ E_DESTINATION_STORE_COLUMN_ADDRESS,
+
+ E_DESTINATION_STORE_NUM_COLUMNS
+}
+EDestinationStoreColumnType;
+
+GtkType e_destination_store_get_type (void);
+EDestinationStore *e_destination_store_new (void);
+
+EDestination *e_destination_store_get_destination (EDestinationStore *destination_store,
+ GtkTreeIter *iter);
+
+/* Returns a shallow copy; free the list when done, but don't unref elements */
+GList *e_destination_store_list_destinations (EDestinationStore *destination_store);
+
+void e_destination_store_add_destination (EDestinationStore *destination_store,
+ EDestination *destination);
+void e_destination_store_remove_destination (EDestinationStore *destination_store,
+ EDestination *destination);
+
+G_END_DECLS
+
+#endif /* E_DESTINATION_STORE_H */
diff --git a/libedataserverui/e-name-selector-dialog.c b/libedataserverui/e-name-selector-dialog.c
new file mode 100644
index 000000000..56b887d2e
--- /dev/null
+++ b/libedataserverui/e-name-selector-dialog.c
@@ -0,0 +1,769 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-dialog.c - Dialog that lets user pick EDestinations.
+ *
+ * Copyright (C) 2004 Novell, Inc.
+ *
+ * This library 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 library; 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>
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gtk/gtkalignment.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkstock.h>
+#include <libgnome/gnome-i18n.h>
+#include <libedataserverui/e-source-option-menu.h>
+#include <libedataserverui/e-destination-store.h>
+#include <libedataserverui/e-contact-store.h>
+#include "e-name-selector-dialog.h"
+
+typedef struct {
+ gchar *name;
+
+ GtkBox *section_box;
+ GtkButton *transfer_button;
+ GtkTreeView *destination_view;
+}
+Section;
+
+static ESource *find_first_source (ESourceList *source_list);
+static void search_changed (ENameSelectorDialog *name_selector_dialog);
+static void source_selected (ENameSelectorDialog *name_selector_dialog, ESource *source);
+static void transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *transfer_button);
+static void contact_selection_changed (ENameSelectorDialog *name_selector_dialog);
+static void setup_name_selector_model (ENameSelectorDialog *name_selector_dialog);
+static void contact_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path);
+static void destination_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path,
+ GtkTreeViewColumn *column, GtkTreeView *tree_view);
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+G_DEFINE_TYPE (ENameSelectorDialog, e_name_selector_dialog, GTK_TYPE_DIALOG);
+
+static void
+e_name_selector_dialog_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+}
+
+static void
+e_name_selector_dialog_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+}
+
+static void
+e_name_selector_dialog_init (ENameSelectorDialog *name_selector_dialog)
+{
+ GtkTreeSelection *contact_selection;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+ GtkWidget *widget;
+ GtkWidget *container;
+ ESourceList *source_list;
+
+ /* Get Glade GUI */
+
+ name_selector_dialog->gui = glade_xml_new (E_DATA_SERVER_UI_GLADEDIR "/e-name-selector-dialog.glade", NULL, NULL);
+
+ widget = glade_xml_get_widget (name_selector_dialog->gui, "name-selector-box");
+ if (!widget) {
+ g_warning ("ENameSelectorDialog can't load Glade interface!");
+ g_object_unref (name_selector_dialog->gui);
+ name_selector_dialog->gui = NULL;
+ return;
+ }
+
+ /* Get addressbook sources */
+
+ if (!e_book_get_addressbooks (&source_list, NULL)) {
+ g_warning ("ENameSelectorDialog can't find any addressbooks!");
+ g_object_unref (name_selector_dialog->gui);
+ return;
+ }
+
+ /* Reparent it to inside ourselves */
+
+ g_object_ref (widget);
+ gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (name_selector_dialog)->vbox), widget, TRUE, TRUE, 0);
+ g_object_unref (widget);
+
+ /* Store pointers to relevant widgets */
+
+ name_selector_dialog->contact_view = GTK_TREE_VIEW (
+ glade_xml_get_widget (name_selector_dialog->gui, "source-tree-view"));
+ name_selector_dialog->status_label = GTK_LABEL (
+ glade_xml_get_widget (name_selector_dialog->gui, "status-message"));
+ name_selector_dialog->destination_box = GTK_BOX (
+ glade_xml_get_widget (name_selector_dialog->gui, "destination-box"));
+ name_selector_dialog->search_entry = GTK_ENTRY (
+ glade_xml_get_widget (name_selector_dialog->gui, "search"));
+
+ /* Create size group for transfer buttons */
+
+ name_selector_dialog->button_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ /* Set up contacts view */
+
+ 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_add_attribute (column, cell_renderer, "text", E_CONTACT_FILE_AS);
+ gtk_tree_view_append_column (name_selector_dialog->contact_view, column);
+ g_signal_connect_swapped (name_selector_dialog->contact_view, "row-activated",
+ G_CALLBACK (contact_activated), name_selector_dialog);
+
+ /* Listen for changes to the contact selection */
+
+ contact_selection = gtk_tree_view_get_selection (name_selector_dialog->contact_view);
+ g_signal_connect_swapped (contact_selection, "changed",
+ G_CALLBACK (contact_selection_changed), name_selector_dialog);
+
+ /* Set up our data structures */
+
+ name_selector_dialog->name_selector_model = e_name_selector_model_new ();
+ name_selector_dialog->sections = g_array_new (FALSE, FALSE, sizeof (Section));
+ name_selector_dialog->source_list = source_list;
+
+ setup_name_selector_model (name_selector_dialog);
+
+ /* Create source menu */
+
+ widget = e_source_option_menu_new (name_selector_dialog->source_list);
+ g_signal_connect_swapped (widget, "source_selected", G_CALLBACK (source_selected), name_selector_dialog);
+ gtk_widget_show (widget);
+
+ container = glade_xml_get_widget (name_selector_dialog->gui, "source-menu-box");
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+
+ /* Set up search-as-you-type signal */
+
+ widget = glade_xml_get_widget (name_selector_dialog->gui, "search");
+ g_signal_connect_swapped (widget, "changed", G_CALLBACK (search_changed), name_selector_dialog);
+
+ /* Display initial source */
+
+ /* TODO: Remember last used source */
+
+ source_selected (name_selector_dialog, find_first_source (name_selector_dialog->source_list));
+
+ /* Set up dialog defaults */
+
+ gtk_dialog_add_buttons (GTK_DIALOG (name_selector_dialog),
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (name_selector_dialog), GTK_RESPONSE_CLOSE);
+ gtk_window_set_modal (GTK_WINDOW (name_selector_dialog), FALSE);
+ gtk_window_set_default_size (GTK_WINDOW (name_selector_dialog), 472, 512);
+ gtk_window_set_resizable (GTK_WINDOW (name_selector_dialog), TRUE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (name_selector_dialog), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (name_selector_dialog), 4);
+ gtk_window_set_title (GTK_WINDOW (name_selector_dialog), _("Select Contacts from Address Book"));
+}
+
+/* Partial, repeatable destruction. Release references. */
+static void
+e_name_selector_dialog_dispose (GObject *object)
+{
+ ENameSelectorDialog *name_selector_dialog = E_NAME_SELECTOR_DIALOG (object);
+
+ if (G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->dispose)
+ G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->dispose (object);
+}
+
+/* Final, one-time destruction. Free all. */
+static void
+e_name_selector_dialog_finalize (GObject *object)
+{
+ ENameSelectorDialog *name_selector_dialog = E_NAME_SELECTOR_DIALOG (object);
+
+ /* TODO: Free stuff */
+
+ if (G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->finalize)
+ G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->finalize (object);
+}
+
+static void
+e_name_selector_dialog_class_init (ENameSelectorDialogClass *name_selector_dialog_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (name_selector_dialog_class);
+
+ object_class->get_property = e_name_selector_dialog_get_property;
+ object_class->set_property = e_name_selector_dialog_set_property;
+ object_class->dispose = e_name_selector_dialog_dispose;
+ object_class->finalize = e_name_selector_dialog_finalize;
+
+ /* Install properties */
+
+ /* Install signals */
+
+}
+
+ENameSelectorDialog *
+e_name_selector_dialog_new (void)
+{
+ return g_object_new (E_TYPE_NAME_SELECTOR_DIALOG, NULL);
+}
+
+/* --------- *
+ * Utilities *
+ * --------- */
+
+static void
+sort_iter_to_contact_store_iter (ENameSelectorDialog *name_selector_dialog, GtkTreeIter *iter)
+{
+ GtkTreeModelFilter *contact_filter;
+ GtkTreeIter child_iter;
+
+ contact_filter = e_name_selector_model_peek_contact_filter (name_selector_dialog->name_selector_model);
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (name_selector_dialog->contact_sort,
+ &child_iter, iter);
+ gtk_tree_model_filter_convert_iter_to_child_iter (contact_filter, iter, &child_iter);
+}
+
+static ESource *
+find_first_source (ESourceList *source_list)
+{
+ GSList *groups, *sources, *l, *m;
+
+ groups = e_source_list_peek_groups (source_list);
+ for (l = groups; l; l = l->next) {
+ ESourceGroup *group = l->data;
+
+ sources = e_source_group_peek_sources (group);
+ for (m = sources; m; m = m->next) {
+ ESource *source = m->data;
+
+ return source;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+add_destination (EDestinationStore *destination_store, EContact *contact)
+{
+ EDestination *destination;
+
+ /* Transfer (actually, copy into a destination and let the model filter out the
+ * source automatically) */
+
+ destination = e_destination_new ();
+ e_destination_set_contact (destination, contact, 0);
+ e_destination_store_add_destination (destination_store, destination);
+ g_object_unref (destination);
+}
+
+/* ------------------ *
+ * Section management *
+ * ------------------ */
+
+static gint
+find_section_by_transfer_button (ENameSelectorDialog *name_selector_dialog, GtkButton *transfer_button)
+{
+ gint i;
+
+ for (i = 0; i < name_selector_dialog->sections->len; i++) {
+ Section *section = &g_array_index (name_selector_dialog->sections, Section, i);
+
+ if (section->transfer_button == transfer_button)
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+find_section_by_tree_view (ENameSelectorDialog *name_selector_dialog, GtkTreeView *tree_view)
+{
+ gint i;
+
+ for (i = 0; i < name_selector_dialog->sections->len; i++) {
+ Section *section = &g_array_index (name_selector_dialog->sections, Section, i);
+
+ if (section->destination_view == tree_view)
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+find_section_by_name (ENameSelectorDialog *name_selector_dialog, const gchar *name)
+{
+ gint i;
+
+ for (i = 0; i < name_selector_dialog->sections->len; i++) {
+ Section *section = &g_array_index (name_selector_dialog->sections, Section, i);
+
+ if (!strcmp (name, section->name))
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+add_section (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name, const gchar *pretty_name, EDestinationStore *destination_store)
+{
+ Section section;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+ GtkWidget *widget;
+
+ g_assert (name != NULL);
+ g_assert (pretty_name != NULL);
+ g_assert (E_IS_DESTINATION_STORE (destination_store));
+
+ memset (&section, 0, sizeof (Section));
+
+ section.name = g_strdup (name);
+ section.section_box = GTK_BOX (gtk_hbox_new (FALSE, 12));
+ section.transfer_button = GTK_BUTTON (gtk_button_new_with_label (pretty_name));
+ section.destination_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+
+ /* Set up transfer button */
+ g_signal_connect_swapped (section.transfer_button, "clicked",
+ G_CALLBACK (transfer_button_clicked), name_selector_dialog);
+
+ /* Set up view */
+ 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_add_attribute (column, cell_renderer,
+ "text", E_DESTINATION_STORE_COLUMN_ADDRESS);
+ gtk_tree_view_append_column (section.destination_view, column);
+ gtk_tree_view_set_headers_visible (section.destination_view, FALSE);
+ gtk_tree_view_set_model (section.destination_view, GTK_TREE_MODEL (destination_store));
+
+ /* Pack button (in an alignment) */
+ widget = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (section.transfer_button));
+ gtk_box_pack_start (section.section_box, widget, FALSE, TRUE, 0);
+ gtk_size_group_add_widget (name_selector_dialog->button_size_group, GTK_WIDGET (section.transfer_button));
+
+ /* Pack view (in a scrolled window) */
+ widget = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (section.destination_view));
+ gtk_box_pack_start (section.section_box, widget, TRUE, TRUE, 0);
+ g_signal_connect_swapped (section.destination_view, "row-activated",
+ G_CALLBACK (destination_activated), name_selector_dialog);
+
+ gtk_widget_show_all (GTK_WIDGET (section.section_box));
+
+ /* Pack this section's box into the dialog */
+ gtk_box_pack_start (name_selector_dialog->destination_box,
+ GTK_WIDGET (section.section_box), TRUE, TRUE, 0);
+
+ g_array_append_val (name_selector_dialog->sections, section);
+
+ /* Make sure UI is consistent */
+ contact_selection_changed (name_selector_dialog);
+
+ return name_selector_dialog->sections->len - 1;
+}
+
+static void
+free_section (ENameSelectorDialog *name_selector_dialog, gint n)
+{
+ Section *section;
+
+ g_assert (n >= 0);
+ g_assert (n < name_selector_dialog->sections->len);
+
+ section = &g_array_index (name_selector_dialog->sections, Section, n);
+
+ g_free (section->name);
+ gtk_widget_destroy (GTK_WIDGET (section->section_box));
+}
+
+static void
+model_section_added (ENameSelectorDialog *name_selector_dialog, const gchar *name)
+{
+ gchar *pretty_name;
+ EDestinationStore *destination_store;
+
+ e_name_selector_model_peek_section (name_selector_dialog->name_selector_model, name,
+ &pretty_name, &destination_store);
+ add_section (name_selector_dialog, name, pretty_name, destination_store);
+ g_free (pretty_name);
+}
+
+static void
+model_section_removed (ENameSelectorDialog *name_selector_dialog, const gchar *name)
+{
+ gint section_index;
+
+ section_index = find_section_by_name (name_selector_dialog, name);
+ g_assert (section_index >= 0);
+
+ free_section (name_selector_dialog, section_index);
+ g_array_remove_index (name_selector_dialog->sections, section_index);
+}
+
+/* -------------------- *
+ * Addressbook selector *
+ * -------------------- */
+
+static void
+book_opened (EBook *book, EBookStatus status, gpointer data)
+{
+ ENameSelectorDialog *name_selector_dialog = E_NAME_SELECTOR_DIALOG (data);
+ EContactStore *contact_store;
+
+ if (status != E_BOOK_ERROR_OK) {
+ /* TODO: Handle errors gracefully */
+ g_warning ("ENameSelectorDialog failed to open book!");
+ return;
+ }
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model);
+ e_contact_store_add_book (contact_store, book);
+ g_object_unref (book);
+ name_selector_dialog->pending_book = NULL;
+}
+
+static void
+source_selected (ENameSelectorDialog *name_selector_dialog, ESource *source)
+{
+ EContactStore *contact_store;
+ ESource *last_source;
+ GList *books;
+ GList *l;
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model);
+
+ /* Remove books (should be just one) being viewed */
+ books = e_contact_store_get_books (contact_store);
+ for (l = books; l; l = g_list_next (l)) {
+ EBook *book = l->data;
+ e_contact_store_remove_book (contact_store, book);
+ }
+ g_list_free (books);
+
+ /* See if we already have a book pending; stop loading it if so */
+ if (name_selector_dialog->pending_book) {
+ e_book_cancel (name_selector_dialog->pending_book, NULL);
+ g_object_unref (name_selector_dialog->pending_book);
+ name_selector_dialog->pending_book = NULL;
+ }
+
+ /* Start loading selected book */
+ name_selector_dialog->pending_book = e_book_new (source, NULL);
+ e_book_async_open (name_selector_dialog->pending_book, TRUE,
+ book_opened, name_selector_dialog);
+}
+
+/* --------------- *
+ * Other UI events *
+ * --------------- */
+
+static void
+search_changed (ENameSelectorDialog *name_selector_dialog)
+{
+ EContactStore *contact_store;
+ EBookQuery *book_query;
+ const gchar *text;
+ gchar *query_string;
+
+ text = gtk_entry_get_text (name_selector_dialog->search_entry);
+ query_string = g_strdup_printf ("(contains \"file_as\" \"%s\")", text);
+ book_query = e_book_query_from_string (query_string);
+ g_free (query_string);
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model);
+ e_contact_store_set_query (contact_store, book_query);
+
+ e_book_query_unref (book_query);
+}
+
+static void
+contact_selection_changed (ENameSelectorDialog *name_selector_dialog)
+{
+ GtkTreeSelection *contact_selection;
+ gboolean have_selection;
+ gint i;
+
+ contact_selection = gtk_tree_view_get_selection (name_selector_dialog->contact_view);
+ have_selection = gtk_tree_selection_get_selected (contact_selection, NULL, NULL);
+
+ for (i = 0; i < name_selector_dialog->sections->len; i++) {
+ Section *section = &g_array_index (name_selector_dialog->sections, Section, i);
+ gtk_widget_set_sensitive (GTK_WIDGET (section->transfer_button), have_selection);
+ }
+}
+
+static void
+contact_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path)
+{
+ EContactStore *contact_store;
+ EDestinationStore *destination_store;
+ EContact *contact;
+ GtkTreeIter iter;
+ Section *section;
+
+ /* When a contact is activated, we transfer it to the first destination on our list */
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model);
+
+ /* If we have no sections, we can't transfer */
+
+ if (name_selector_dialog->sections->len == 0)
+ return;
+
+ /* Get the contact to be transferred */
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (name_selector_dialog->contact_sort),
+ &iter, path))
+ g_assert_not_reached ();
+
+ sort_iter_to_contact_store_iter (name_selector_dialog, &iter);
+
+ contact = e_contact_store_get_contact (contact_store, &iter);
+ if (!contact) {
+ g_warning ("ENameSelectorDialog could not get selected contact!");
+ return;
+ }
+
+ section = &g_array_index (name_selector_dialog->sections, Section, 0);
+ if (!e_name_selector_model_peek_section (name_selector_dialog->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return;
+ }
+
+ add_destination (destination_store, contact);
+}
+
+static void
+destination_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path,
+ GtkTreeViewColumn *column, GtkTreeView *tree_view)
+{
+ gint section_index;
+ EDestinationStore *destination_store;
+ EDestination *destination;
+ Section *section;
+ GtkTreeIter iter;
+
+ /* When a destination is activated, we remove it from the section */
+
+ section_index = find_section_by_tree_view (name_selector_dialog, tree_view);
+ if (section_index < 0) {
+ g_warning ("ENameSelectorDialog got activation from unknown view!");
+ return;
+ }
+
+ section = &g_array_index (name_selector_dialog->sections, Section, section_index);
+ if (!e_name_selector_model_peek_section (name_selector_dialog->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return;
+ }
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (destination_store), &iter, path))
+ g_assert_not_reached ();
+
+ destination = e_destination_store_get_destination (destination_store, &iter);
+ g_assert (destination);
+
+ e_destination_store_remove_destination (destination_store, destination);
+}
+
+static void
+transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *transfer_button)
+{
+ EContactStore *contact_store;
+ EDestinationStore *destination_store;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ EContact *contact;
+ gint section_index;
+ Section *section;
+ EDestination *destination;
+
+ /* Get the contact to be transferred */
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model);
+ selection = gtk_tree_view_get_selection (name_selector_dialog->contact_view);
+
+ if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
+ g_warning ("ENameSelectorDialog transfer button clicked, but no selection!");
+ return;
+ }
+
+ sort_iter_to_contact_store_iter (name_selector_dialog, &iter);
+
+ contact = e_contact_store_get_contact (contact_store, &iter);
+ if (!contact) {
+ g_warning ("ENameSelectorDialog could not get selected contact!");
+ return;
+ }
+
+ /* Get the target section */
+
+ section_index = find_section_by_transfer_button (name_selector_dialog, transfer_button);
+ if (section_index < 0) {
+ g_warning ("ENameSelectorDialog got click from unknown button!");
+ return;
+ }
+
+ section = &g_array_index (name_selector_dialog->sections, Section, section_index);
+ if (!e_name_selector_model_peek_section (name_selector_dialog->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return;
+ }
+
+ add_destination (destination_store, contact);
+}
+
+/* --------------------- *
+ * Main model management *
+ * --------------------- */
+
+static void
+setup_name_selector_model (ENameSelectorDialog *name_selector_dialog)
+{
+ EContactStore *contact_store;
+ GtkTreeModelFilter *contact_filter;
+ GtkTreeSelection *contact_selection;
+ EBookQuery *book_query;
+ GList *new_sections;
+ GList *l;
+ gint i;
+
+ /* Rid UI of previous destination sections */
+
+ for (i = 0; i < name_selector_dialog->sections->len; i++)
+ free_section (name_selector_dialog, i);
+
+ g_array_set_size (name_selector_dialog->sections, 0);
+
+ /* Create new destination sections in UI */
+
+ new_sections = e_name_selector_model_list_sections (name_selector_dialog->name_selector_model);
+
+ for (l = new_sections; l; l = g_list_next (l)) {
+ gchar *name = l->data;
+ gchar *pretty_name;
+ EDestinationStore *destination_store;
+
+ e_name_selector_model_peek_section (name_selector_dialog->name_selector_model,
+ name, &pretty_name, &destination_store);
+
+ add_section (name_selector_dialog, name, pretty_name, destination_store);
+
+ g_free (pretty_name);
+ g_free (name);
+ }
+
+ g_list_free (new_sections);
+
+ /* Connect to section add/remove signals */
+
+ g_signal_connect_swapped (name_selector_dialog->name_selector_model, "section-added",
+ G_CALLBACK (model_section_added), name_selector_dialog);
+ g_signal_connect_swapped (name_selector_dialog->name_selector_model, "section-removed",
+ G_CALLBACK (model_section_removed), name_selector_dialog);
+
+ /* Get contact store and its filter wrapper */
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model);
+ contact_filter = e_name_selector_model_peek_contact_filter (name_selector_dialog->name_selector_model);
+
+ /* Create sorting model on top of filter, assign it to view */
+
+ if (name_selector_dialog->contact_sort)
+ g_object_unref (name_selector_dialog->contact_sort);
+
+ name_selector_dialog->contact_sort = GTK_TREE_MODEL_SORT (
+ gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (contact_filter)));
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (name_selector_dialog->contact_sort),
+ E_CONTACT_FILE_AS, GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model (name_selector_dialog->contact_view,
+ GTK_TREE_MODEL (name_selector_dialog->contact_sort));
+
+ /* Make sure UI is consistent */
+
+ search_changed (name_selector_dialog);
+ contact_selection_changed (name_selector_dialog);
+}
+
+/* ----------------------- *
+ * ENameSelectorDialog API *
+ * ----------------------- */
+
+ENameSelectorModel *
+e_name_selector_dialog_peek_model (ENameSelectorDialog *name_selector_dialog)
+{
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), NULL);
+
+ return name_selector_dialog->name_selector_model;
+}
+
+void
+e_name_selector_dialog_set_model (ENameSelectorDialog *name_selector_dialog,
+ ENameSelectorModel *model)
+{
+ g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+ g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (model));
+
+ if (model == name_selector_dialog->name_selector_model)
+ return;
+
+ g_signal_handlers_disconnect_matched (name_selector_dialog->name_selector_model,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_dialog);
+
+ g_object_unref (name_selector_dialog->name_selector_model);
+ name_selector_dialog->name_selector_model = g_object_ref (model);
+
+ setup_name_selector_model (name_selector_dialog);
+}
+
+/* ----------------------------------- *
+ * Widget creation functions for Glade *
+ * ----------------------------------- */
+
+#if CATEGORIES_COMPONENTS_MOVED
+
+GtkWidget *
+e_name_selector_dialog_create_categories (void)
+{
+ ECategoriesMasterList *ecml;
+ GtkWidget *option_menu;
+
+ ecml = e_categories_master_list_wombat_new ();
+ option_menu = e_categories_master_list_option_menu_new (ecml);
+ g_object_unref (ecml);
+
+ return option_menu;
+}
+
+#endif
diff --git a/libedataserverui/e-name-selector-dialog.glade b/libedataserverui/e-name-selector-dialog.glade
new file mode 100644
index 000000000..47561c75f
--- /dev/null
+++ b/libedataserverui/e-name-selector-dialog.glade
@@ -0,0 +1,335 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="wrapper">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">window1</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="name-selector-box">
+ <property name="border_width">4</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Show Contacts&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label35">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="show_contacts_table">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Address _Book:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkEntry" id="search">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label39">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Search:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="source-menu-box">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label37">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label36">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Contacts&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="source-tree-view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="destination-box">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">12</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="status-message">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">3</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/libedataserverui/e-name-selector-dialog.h b/libedataserverui/e-name-selector-dialog.h
new file mode 100644
index 000000000..62f875e63
--- /dev/null
+++ b/libedataserverui/e-name-selector-dialog.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-dialog.c - Dialog that lets user pick EDestinations.
+ *
+ * Copyright (C) 2004 Novell, Inc.
+ *
+ * This library 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 library; 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_NAME_SELECTOR_DIALOG_H
+#define E_NAME_SELECTOR_DIALOG_H
+
+#include <gtk/gtkbox.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreemodelsort.h>
+#include <gtk/gtksizegroup.h>
+#include <glade/glade.h>
+#include <libebook/e-book.h>
+#include <libedataserverui/e-contact-store.h>
+#include <libedataserverui/e-name-selector-model.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_NAME_SELECTOR_DIALOG (e_name_selector_dialog_get_type ())
+#define E_NAME_SELECTOR_DIALOG(obj) (GTK_CHECK_CAST ((obj), e_name_selector_dialog_get_type (), ENameSelectorDialog))
+#define E_NAME_SELECTOR_DIALOG_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), e_name_selector_dialog_get_type (), ENameSelectorDialogClass))
+#define E_IS_NAME_SELECTOR_DIALOG(obj) (GTK_CHECK_TYPE (obj, e_name_selector_dialog_get_type ()))
+#define E_IS_NAME_SELECTOR_DIALOG_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), e_name_selector_dialog_get_type ()))
+#define E_NAME_SELECTOR_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_NAME_SELECTOR_DIALOG_TYPE, ENameSelectorDialogClass))
+
+typedef struct ENameSelectorDialog ENameSelectorDialog;
+typedef struct ENameSelectorDialogClass ENameSelectorDialogClass;
+
+struct ENameSelectorDialogClass {
+ GtkDialogClass parent_class;
+};
+
+struct ENameSelectorDialog {
+ GtkDialog parent;
+
+ /* Private */
+
+ EBook *pending_book;
+ ESourceList *source_list;
+ ENameSelectorModel *name_selector_model;
+ GtkTreeModelSort *contact_sort;
+
+ GladeXML *gui;
+ GtkTreeView *contact_view;
+ GtkLabel *status_label;
+ GtkBox *destination_box;
+ GtkEntry *search_entry;
+ GtkSizeGroup *button_size_group;
+
+ GArray *sections;
+};
+
+GType e_name_selector_dialog_get_type (void);
+ENameSelectorDialog *e_name_selector_dialog_new (void);
+
+ENameSelectorModel *e_name_selector_dialog_peek_model (ENameSelectorDialog *name_selector_dialog);
+void e_name_selector_dialog_set_model (ENameSelectorDialog *name_selector_dialog,
+ ENameSelectorModel *model);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_DIALOG_H */
diff --git a/libedataserverui/e-name-selector-model.c b/libedataserverui/e-name-selector-model.c
new file mode 100644
index 000000000..3adb5ddbf
--- /dev/null
+++ b/libedataserverui/e-name-selector-model.c
@@ -0,0 +1,333 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-model.c - Model for contact selection.
+ *
+ * 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 <glib.h>
+#include <libgnome/gnome-i18n.h>
+#include <libedataserver/e-data-server-marshal.h>
+#include "e-name-selector-model.h"
+
+typedef struct {
+ gchar *name;
+ gchar *pretty_name;
+
+ EDestinationStore *destination_store;
+}
+Section;
+
+static gboolean filter_show_not_in_destinations (EContactStore *contact_store, GtkTreeIter *iter,
+ ENameSelectorModel *name_selector_model);
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+/* Signals */
+
+enum {
+ SECTION_ADDED,
+ SECTION_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (ENameSelectorModel, e_name_selector_model, G_TYPE_OBJECT);
+
+static void
+e_name_selector_model_init (ENameSelectorModel *name_selector_model)
+{
+ name_selector_model->sections = g_array_new (FALSE, FALSE, sizeof (Section));
+ name_selector_model->contact_store = e_contact_store_new ();
+ name_selector_model->contact_filter = GTK_TREE_MODEL_FILTER (
+ gtk_tree_model_filter_new (GTK_TREE_MODEL (name_selector_model->contact_store), NULL));
+ gtk_tree_model_filter_set_visible_func (name_selector_model->contact_filter,
+ (GtkTreeModelFilterVisibleFunc) filter_show_not_in_destinations,
+ name_selector_model, NULL);
+ g_object_unref (name_selector_model->contact_store);
+}
+
+static void
+e_name_selector_model_finalize (GObject *object)
+{
+ ENameSelectorModel *name_selector_model = E_NAME_SELECTOR_MODEL (object);
+
+ /* TODO: Free sections */
+
+ g_object_unref (name_selector_model->contact_filter);
+
+ if (G_OBJECT_CLASS (e_name_selector_model_parent_class)->finalize)
+ G_OBJECT_CLASS (e_name_selector_model_parent_class)->finalize (object);
+}
+
+static void
+e_name_selector_model_class_init (ENameSelectorModelClass *name_selector_model_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (name_selector_model_class);
+
+ object_class->finalize = e_name_selector_model_finalize;
+
+ signals [SECTION_ADDED] =
+ g_signal_new ("section-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ENameSelectorModelClass, section_added),
+ NULL, NULL,
+ e_data_server_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ signals [SECTION_REMOVED] =
+ g_signal_new ("section-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ENameSelectorModelClass, section_removed),
+ NULL, NULL,
+ e_data_server_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+ENameSelectorModel *
+e_name_selector_model_new (void)
+{
+ return E_NAME_SELECTOR_MODEL (g_object_new (E_TYPE_NAME_SELECTOR_MODEL, NULL));
+}
+
+/* ---------------------------- *
+ * GtkTreeModelFilter filtering *
+ * ---------------------------- */
+
+static gboolean
+filter_show_not_in_destinations (EContactStore *contact_store, GtkTreeIter *iter,
+ ENameSelectorModel *name_selector_model)
+{
+ EContact *contact;
+ const gchar *contact_uid;
+ gboolean result = TRUE;
+ gint i;
+
+ contact = e_contact_store_get_contact (contact_store, iter);
+ g_assert (contact != NULL);
+
+ contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+ if (!contact_uid)
+ return TRUE; /* Can happen with broken databases */
+
+ for (i = 0; i < name_selector_model->sections->len && result == TRUE; i++) {
+ Section *section;
+ GList *destinations;
+ GList *l;
+
+ section = &g_array_index (name_selector_model->sections, Section, i);
+ destinations = e_destination_store_list_destinations (section->destination_store);
+
+ for (l = destinations; l; l = g_list_next (l)) {
+ EDestination *destination = l->data;
+ const gchar *destination_uid;
+
+ destination_uid = e_destination_get_contact_uid (destination);
+ if (destination_uid && !strcmp (contact_uid, destination_uid)) {
+ result = FALSE;
+ break;
+ }
+ }
+
+ g_list_free (destinations);
+ }
+
+ return result;
+}
+
+/* --------------- *
+ * Section helpers *
+ * --------------- */
+
+static void
+destinations_changed (ENameSelectorModel *name_selector_model)
+{
+ gtk_tree_model_filter_refilter (name_selector_model->contact_filter);
+}
+
+static void
+free_section (ENameSelectorModel *name_selector_model, gint n)
+{
+ Section *section;
+
+ g_assert (n >= 0);
+ g_assert (n < name_selector_model->sections->len);
+
+ section = &g_array_index (name_selector_model->sections, Section, n);
+
+ g_signal_handlers_disconnect_matched (section->destination_store, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, name_selector_model);
+
+ g_free (section->name);
+ g_free (section->pretty_name);
+ g_object_unref (section->destination_store);
+}
+
+static gint
+find_section_by_name (ENameSelectorModel *name_selector_model, const gchar *name)
+{
+ gint i;
+
+ g_assert (name != NULL);
+
+ for (i = 0; i < name_selector_model->sections->len; i++) {
+ Section *section = &g_array_index (name_selector_model->sections, Section, i);
+
+ if (!strcmp (name, section->name))
+ return i;
+ }
+
+ return -1;
+}
+
+/* ---------------------- *
+ * ENameSelectorModel API *
+ * ---------------------- */
+
+EContactStore *
+e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_model)
+{
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+
+ return name_selector_model->contact_store;
+}
+
+GtkTreeModelFilter *
+e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model)
+{
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+
+ return name_selector_model->contact_filter;
+}
+
+GList *
+e_name_selector_model_list_sections (ENameSelectorModel *name_selector_model)
+{
+ GList *section_names = NULL;
+ gint i;
+
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+
+ /* Do this backwards so we can use g_list_prepend () and get correct order */
+ for (i = name_selector_model->sections->len - 1; i >= 0; i--) {
+ Section *section = &g_array_index (name_selector_model->sections, Section, i);
+ gchar *name;
+
+ name = g_strdup (section->name);
+ section_names = g_list_prepend (section_names, name);
+ }
+
+ return section_names;
+}
+
+void
+e_name_selector_model_add_section (ENameSelectorModel *name_selector_model,
+ const gchar *name, const gchar *pretty_name,
+ EDestinationStore *destination_store)
+{
+ Section section;
+
+ g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (pretty_name != NULL);
+
+ if (find_section_by_name (name_selector_model, name) >= 0) {
+ g_warning ("ENameSelectorModel already has a section called '%s'!", name);
+ return;
+ }
+
+ memset (&section, 0, sizeof (Section));
+
+ section.name = g_strdup (name);
+ section.pretty_name = g_strdup (pretty_name);
+
+ if (destination_store)
+ section.destination_store = g_object_ref (destination_store);
+ else
+ section.destination_store = e_destination_store_new ();
+
+ g_signal_connect_swapped (section.destination_store, "row-changed",
+ G_CALLBACK (destinations_changed), name_selector_model);
+ g_signal_connect_swapped (section.destination_store, "row-deleted",
+ G_CALLBACK (destinations_changed), name_selector_model);
+ g_signal_connect_swapped (section.destination_store, "row-inserted",
+ G_CALLBACK (destinations_changed), name_selector_model);
+
+ g_array_append_val (name_selector_model->sections, section);
+
+ gtk_tree_model_filter_refilter (name_selector_model->contact_filter);
+ g_signal_emit (name_selector_model, signals [SECTION_ADDED], 0, name);
+}
+
+void
+e_name_selector_model_remove_section (ENameSelectorModel *name_selector_model, const gchar *name)
+{
+ Section *section;
+ gint n;
+
+ g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model));
+ g_return_if_fail (name != NULL);
+
+ n = find_section_by_name (name_selector_model, name);
+ if (n < 0) {
+ g_warning ("ENameSelectorModel does not have a section called '%s'!", name);
+ return;
+ }
+
+ free_section (name_selector_model, n);
+ g_array_remove_index_fast (name_selector_model->sections, n); /* Order doesn't matter */
+
+ gtk_tree_model_filter_refilter (name_selector_model->contact_filter);
+ g_signal_emit (name_selector_model, signals [SECTION_REMOVED], 0, name);
+}
+
+gboolean
+e_name_selector_model_peek_section (ENameSelectorModel *name_selector_model, const gchar *name,
+ gchar **pretty_name, EDestinationStore **destination_store)
+{
+ Section *section;
+ gint n;
+
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ n = find_section_by_name (name_selector_model, name);
+ if (n < 0) {
+ g_warning ("ENameSelectorModel does not have a section called '%s'!", name);
+ return FALSE;
+ }
+
+ section = &g_array_index (name_selector_model->sections, Section, n);
+
+ if (pretty_name)
+ *pretty_name = g_strdup (section->pretty_name);
+ if (destination_store)
+ *destination_store = section->destination_store;
+
+ return TRUE;
+}
diff --git a/libedataserverui/e-name-selector-model.h b/libedataserverui/e-name-selector-model.h
new file mode 100644
index 000000000..ca4916d30
--- /dev/null
+++ b/libedataserverui/e-name-selector-model.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-model.h - Model for contact selection.
+ *
+ * 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_NAME_SELECTOR_MODEL_H
+#define E_NAME_SELECTOR_MODEL_H
+
+#include <glib.h>
+#include <gtk/gtktreemodelfilter.h>
+#include <libedataserverui/e-contact-store.h>
+#include <libedataserverui/e-destination-store.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_NAME_SELECTOR_MODEL (e_name_selector_model_get_type ())
+#define E_NAME_SELECTOR_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModel))
+#define E_NAME_SELECTOR_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModelClass))
+#define E_IS_NAME_SELECTOR_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_NAME_SELECTOR_MODEL))
+#define E_IS_NAME_SELECTOR_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_NAME_SELECTOR_MODEL))
+
+typedef struct ENameSelectorModel ENameSelectorModel;
+typedef struct ENameSelectorModelClass ENameSelectorModelClass;
+
+struct ENameSelectorModel {
+ GObject parent;
+
+ /* Private */
+
+ GArray *sections;
+ EContactStore *contact_store;
+ GtkTreeModelFilter *contact_filter;
+};
+
+struct ENameSelectorModelClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (* section_added) (gchar *name);
+ void (* section_removed) (gchar *name);
+};
+
+GType e_name_selector_model_get_type (void);
+ENameSelectorModel *e_name_selector_model_new (void);
+
+EContactStore *e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_model);
+GtkTreeModelFilter *e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model);
+
+/* Deep copy of section names; free strings and list when you're done */
+GList *e_name_selector_model_list_sections (ENameSelectorModel *name_selector_model);
+
+/* pretty_name will be newly allocated, but destination_store must be reffed if you keep it */
+gboolean e_name_selector_model_peek_section (ENameSelectorModel *name_selector_model,
+ const gchar *name, gchar **pretty_name,
+ EDestinationStore **destination_store);
+void e_name_selector_model_add_section (ENameSelectorModel *name_selector_model,
+ const gchar *name, const gchar *pretty_name,
+ EDestinationStore *destination_store);
+void e_name_selector_model_remove_section (ENameSelectorModel *name_selector_model,
+ const gchar *name);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_MODEL_H */
diff --git a/libedataserverui/libedataserverui.pc.in b/libedataserverui/libedataserverui.pc.in
index ca4054a7b..1c3bba769 100644
--- a/libedataserverui/libedataserverui.pc.in
+++ b/libedataserverui/libedataserverui.pc.in
@@ -7,6 +7,7 @@ idldir=@idldir@
IDL_INCLUDES=-I ${idldir} @IDL_INCLUDES@
privincludedir=@privincludedir@
+gladedir=@gladedir@
Name: libedataserverui
Description: GUI utility library for evolution data server