diff options
| author | Hans Petter Jansson <hpj@novell.com> | 2004-12-17 04:44:14 +0000 |
|---|---|---|
| committer | Hans Petter <hansp@src.gnome.org> | 2004-12-17 04:44:14 +0000 |
| commit | 4f3649adad735d68bfda59802acb9f9eb1c2829e (patch) | |
| tree | 5ee93042d3adfb52398e1923cb08157fadab1905 /libedataserverui | |
| parent | accd3261a44574cf0560cf78402cc56f57b83e82 (diff) | |
| download | evolution-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.am | 15 | ||||
| -rw-r--r-- | libedataserverui/e-contact-store.c | 23 | ||||
| -rw-r--r-- | libedataserverui/e-contact-store.h | 18 | ||||
| -rw-r--r-- | libedataserverui/e-destination-store.c | 555 | ||||
| -rw-r--r-- | libedataserverui/e-destination-store.h | 80 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-dialog.c | 769 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-dialog.glade | 335 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-dialog.h | 84 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-model.c | 333 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-model.h | 81 | ||||
| -rw-r--r-- | libedataserverui/libedataserverui.pc.in | 1 |
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 (§ion, 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"><b>Show Contacts</b></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"><b>Contacts</b></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 (§ion, 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 |
