aboutsummaryrefslogtreecommitdiffstats
path: root/modules/addressbook
diff options
context:
space:
mode:
Diffstat (limited to 'modules/addressbook')
-rw-r--r--modules/addressbook/Makefile.am91
-rw-r--r--modules/addressbook/autocompletion-config.c225
-rw-r--r--modules/addressbook/autocompletion-config.h36
-rw-r--r--modules/addressbook/e-book-config-hook.c75
-rw-r--r--modules/addressbook/e-book-config-hook.h33
-rw-r--r--modules/addressbook/e-book-shell-backend.c524
-rw-r--r--modules/addressbook/e-book-shell-backend.h67
-rw-r--r--modules/addressbook/e-book-shell-content.c749
-rw-r--r--modules/addressbook/e-book-shell-content.h117
-rw-r--r--modules/addressbook/e-book-shell-migrate.c41
-rw-r--r--modules/addressbook/e-book-shell-migrate.h40
-rw-r--r--modules/addressbook/e-book-shell-sidebar.c319
-rw-r--r--modules/addressbook/e-book-shell-sidebar.h83
-rw-r--r--modules/addressbook/e-book-shell-view-actions.c1422
-rw-r--r--modules/addressbook/e-book-shell-view-actions.h101
-rw-r--r--modules/addressbook/e-book-shell-view-private.c609
-rw-r--r--modules/addressbook/e-book-shell-view-private.h120
-rw-r--r--modules/addressbook/e-book-shell-view.c430
-rw-r--r--modules/addressbook/e-book-shell-view.h69
-rw-r--r--modules/addressbook/eab-composer-util.c206
-rw-r--r--modules/addressbook/eab-composer-util.h33
-rw-r--r--modules/addressbook/evolution-module-addressbook.c53
-rw-r--r--modules/addressbook/openldap-extract.h1449
23 files changed, 6892 insertions, 0 deletions
diff --git a/modules/addressbook/Makefile.am b/modules/addressbook/Makefile.am
new file mode 100644
index 0000000000..8faf8a3967
--- /dev/null
+++ b/modules/addressbook/Makefile.am
@@ -0,0 +1,91 @@
+module_LTLIBRARIES = module-addressbook.la
+
+module_addressbook_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DG_LOG_DOMAIN=\"evolution-addressbook\" \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/shell \
+ -I$(top_builddir)/shell \
+ -I$(top_srcdir)/addressbook/util \
+ -I$(top_srcdir)/addressbook/gui/contact-editor \
+ -I$(top_srcdir)/addressbook/gui/contact-list-editor \
+ -I$(top_srcdir)/addressbook/gui/widgets \
+ -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \
+ -DEVOLUTION_GALVIEWSDIR=\""$(viewsdir)"\" \
+ -DEVOLUTION_UIDIR=\""$(uidir)"\" \
+ -DEVOLUTION_LOCALEDIR=\""$(localedir)"\" \
+ -DEVOLUTION_UIDIR=\""$(uidir)"\" \
+ -DPREFIX=\""$(prefix)"\" \
+ $(EVOLUTION_DATA_SERVER_CFLAGS) \
+ $(GNOME_PLATFORM_CFLAGS) \
+ $(CHAMPLAIN_CFLAGS) \
+ $(GTKHTML_CFLAGS) \
+ $(LDAP_CFLAGS)
+
+module_addressbook_la_SOURCES = \
+ evolution-module-addressbook.c \
+ autocompletion-config.c \
+ autocompletion-config.h \
+ eab-composer-util.c \
+ eab-composer-util.h \
+ e-book-config-hook.c \
+ e-book-config-hook.h \
+ e-book-shell-backend.c \
+ e-book-shell-backend.h \
+ e-book-shell-content.c \
+ e-book-shell-content.h \
+ e-book-shell-migrate.c \
+ e-book-shell-migrate.h \
+ e-book-shell-sidebar.c \
+ e-book-shell-sidebar.h \
+ e-book-shell-view.c \
+ e-book-shell-view.h \
+ e-book-shell-view-actions.c \
+ e-book-shell-view-actions.h \
+ e-book-shell-view-private.c \
+ e-book-shell-view-private.h
+
+if ENABLE_SMIME
+SMIME_LIB=$(top_builddir)/smime/gui/libevolution-smime.la
+endif
+
+module_addressbook_la_LIBADD = \
+ $(SMIME_LIB) \
+ $(top_builddir)/e-util/libevolution-util.la \
+ $(top_builddir)/composer/libevolution-mail-composer.la \
+ $(top_builddir)/addressbook/printing/libecontactprint.la \
+ $(top_builddir)/shell/libevolution-shell.la \
+ $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \
+ $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \
+ $(top_builddir)/addressbook/util/libeabutil.la \
+ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
+ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
+ $(top_builddir)/addressbook/importers/libevolution-addressbook-importers.la \
+ $(EVOLUTION_DATA_SERVER_LIBS) \
+ $(GNOME_PLATFORM_LIBS) \
+ $(CHAMPLAIN_LIBS) \
+ $(GTKHTML_LIBS) \
+ $(LDAP_LIBS)
+
+
+module_addressbook_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+EXTRA_DIST = \
+ openldap-extract.h
+
+dist-hook:
+ cd $(distdir); rm -f $(BUILT_SOURCES)
+
+if ENABLE_PURIFY
+PLINK = $(LIBTOOL) --mode=link $(PURIFY) $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@
+
+all-local: evolution-addressbook.pure
+
+evolution-addressbook.pure: evolution-addressbook
+ @rm -f evolution-addressbook.pure
+ $(PLINK) $(evolution_addressbook_LDFLAGS) $(evolution_addressbook_OBJECTS) $(evolution_addressbook_LDADD) $(LIBS)
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/addressbook/autocompletion-config.c b/modules/addressbook/autocompletion-config.c
new file mode 100644
index 0000000000..bad6040c09
--- /dev/null
+++ b/modules/addressbook/autocompletion-config.c
@@ -0,0 +1,225 @@
+/*
+ * e-shell-config-autocompletion.h - Configuration page for addressbook autocompletion.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "autocompletion-config.h"
+
+#include <glib/gi18n.h>
+
+#include "addressbook/gui/widgets/eab-config.h"
+
+static GtkWidget *
+add_section (GtkWidget *container,
+ const gchar *caption,
+ gboolean expand)
+{
+ GtkWidget *widget;
+ gchar *markup;
+
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, expand, expand, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ markup = g_markup_printf_escaped ("<b>%s</b>", caption);
+ widget = gtk_label_new (markup);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 12, 0);
+ gtk_box_pack_start (GTK_BOX (container), widget, expand, expand, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ return widget;
+}
+
+static GtkWidget *
+get_main_notebook (EConfig *config,
+ EConfigItem *item,
+ GtkWidget *parent,
+ GtkWidget *old,
+ gint position,
+ gpointer user_data)
+{
+ GtkWidget *notebook;
+
+ if (old != NULL)
+ return old;
+
+ notebook = gtk_notebook_new ();
+ gtk_widget_show (notebook);
+
+ return notebook;
+}
+
+static GtkWidget *
+get_general_page (EConfig *config,
+ EConfigItem *item,
+ GtkWidget *parent,
+ GtkWidget *old,
+ gint position,
+ gpointer user_data)
+{
+ GSettings *settings;
+ ESourceRegistry *registry;
+ GtkWidget *container;
+ GtkWidget *itembox;
+ GtkWidget *widget;
+ GtkWidget *vbox;
+ EShell *shell;
+
+ if (old != NULL)
+ return old;
+
+ shell = E_SHELL (user_data);
+ registry = e_shell_get_registry (shell);
+
+ settings = g_settings_new ("org.gnome.evolution.addressbook");
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_notebook_append_page (
+ GTK_NOTEBOOK (parent), vbox,
+ gtk_label_new (_("General")));
+ gtk_widget_show (vbox);
+
+ itembox = add_section (vbox, _("Date/Time Format"), FALSE);
+
+ widget = gtk_table_new (1, 3, FALSE);
+ gtk_box_pack_start (GTK_BOX (itembox), widget, FALSE, FALSE, 0);
+ e_datetime_format_add_setup_widget (
+ widget, 0, "addressbook", "table",
+ DTFormatKindDateTime, _("_Table column:"));
+ gtk_widget_show (widget);
+
+ itembox = add_section (vbox, _("Address formatting"), FALSE);
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("_Format address according to standard of its destination country"));
+ g_settings_bind (
+ settings, "address-formatting",
+ widget, "active",
+ G_SETTINGS_BIND_DEFAULT);
+ gtk_box_pack_start (GTK_BOX (itembox), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ itembox = add_section (vbox, _("Autocompletion"), TRUE);
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("Always _show address of the autocompleted contact"));
+ g_settings_bind (
+ settings, "completion-show-address",
+ widget, "active",
+ G_SETTINGS_BIND_DEFAULT);
+ gtk_box_pack_start (GTK_BOX (itembox), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ 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_box_pack_start (GTK_BOX (itembox), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_autocomplete_selector_new (registry);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ g_object_unref (settings);
+
+ return vbox;
+}
+
+static EConfigItem config_items[] = {
+ { E_CONFIG_BOOK, (gchar *) "", (gchar *) "main-notebook", get_main_notebook },
+ { E_CONFIG_PAGE, (gchar *) "00.general", (gchar *) "general", get_general_page }
+};
+
+static void
+config_items_free (EConfig *config,
+ GSList *items,
+ gpointer user_data)
+{
+ g_slist_free (items);
+}
+
+GtkWidget *
+autocompletion_config_new (EPreferencesWindow *window)
+{
+ EShell *shell;
+ EABConfig *config;
+ EABConfigTargetPrefs *target;
+ GSettings *settings;
+ GtkWidget *vbox;
+ GtkWidget *widget;
+ GSList *items = NULL;
+ gint ii;
+
+ shell = e_preferences_window_get_shell (window);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 0);
+ gtk_widget_show (vbox);
+
+ /** @HookPoint-EABConfig: Contacts Preferences Page
+ * @Id: org.gnome.evolution.addressbook.prefs
+ * @Type: E_CONFIG_BOOK
+ * @Class: org.gnome.evolution.addressbook.config:1.0
+ * @Target: EABConfigTargetPrefs
+ *
+ * The main contacts preferences page.
+ */
+ config = eab_config_new ("org.gnome.evolution.addressbook.prefs");
+
+ for (ii = 0; ii < G_N_ELEMENTS (config_items); ii++)
+ items = g_slist_prepend (items, &config_items[ii]);
+ e_config_add_items (
+ E_CONFIG (config), items, config_items_free, shell);
+
+ settings = g_settings_new ("org.gnome.evolution.addressbook");
+
+ target = eab_config_target_new_prefs (config, settings);
+ e_config_set_target (E_CONFIG (config), (EConfigTarget *) target);
+ widget = e_config_create_widget (E_CONFIG (config));
+ gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0);
+
+ g_object_unref (settings);
+
+ return vbox;
+}
+
diff --git a/modules/addressbook/autocompletion-config.h b/modules/addressbook/autocompletion-config.h
new file mode 100644
index 0000000000..2c60301922
--- /dev/null
+++ b/modules/addressbook/autocompletion-config.h
@@ -0,0 +1,36 @@
+/*
+ * e-shell-config-autocompletion.h - Configuration page for addressbook autocompletion.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef _AUTOCOMPLETION_CONFIG_H
+#define _AUTOCOMPLETION_CONFIG_H
+
+#include <shell/e-shell.h>
+
+G_BEGIN_DECLS
+
+GtkWidget * autocompletion_config_new (EPreferencesWindow *window);
+
+G_END_DECLS
+
+#endif /* _AUTOCOMPLETION_CONFIG_H */
diff --git a/modules/addressbook/e-book-config-hook.c b/modules/addressbook/e-book-config-hook.c
new file mode 100644
index 0000000000..8ee342473e
--- /dev/null
+++ b/modules/addressbook/e-book-config-hook.c
@@ -0,0 +1,75 @@
+/*
+ * e-book-config-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-config-hook.h"
+
+#include "addressbook/gui/widgets/eab-config.h"
+
+static const EConfigHookTargetMask no_masks[] = {
+ { NULL }
+};
+
+static const EConfigHookTargetMap targets[] = {
+ { "source", EAB_CONFIG_TARGET_SOURCE, no_masks },
+ { "prefs", EAB_CONFIG_TARGET_PREFS, no_masks },
+ { NULL }
+};
+
+static void
+book_config_hook_class_init (EConfigHookClass *class)
+{
+ EPluginHookClass *plugin_hook_class;
+ gint ii;
+
+ plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
+ plugin_hook_class->id = "org.gnome.evolution.addressbook.config:1.0";
+
+ class->config_class = g_type_class_ref (eab_config_get_type ());
+
+ for (ii = 0; targets[ii].type != NULL; ii++)
+ e_config_hook_class_add_target_map (
+ (EConfigHookClass *) class, &targets[ii]);
+}
+
+void
+e_book_config_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EConfigHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) book_config_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EConfigHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ g_type_module_register_type (
+ type_module, e_config_hook_get_type (),
+ "EBookConfigHook", &type_info, 0);
+}
diff --git a/modules/addressbook/e-book-config-hook.h b/modules/addressbook/e-book-config-hook.h
new file mode 100644
index 0000000000..a5d9e06457
--- /dev/null
+++ b/modules/addressbook/e-book-config-hook.h
@@ -0,0 +1,33 @@
+/*
+ * e-book-config-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_CONFIG_HOOK_H
+#define E_BOOK_CONFIG_HOOK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_book_config_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_BOOK_CONFIG_HOOK_H */
diff --git a/modules/addressbook/e-book-shell-backend.c b/modules/addressbook/e-book-shell-backend.c
new file mode 100644
index 0000000000..bfa19826ef
--- /dev/null
+++ b/modules/addressbook/e-book-shell-backend.c
@@ -0,0 +1,524 @@
+/*
+ * e-book-shell-backend.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-shell-backend.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libebook/libebook.h>
+
+#include "shell/e-shell.h"
+#include "shell/e-shell-window.h"
+
+#include "addressbook/gui/widgets/eab-gui-util.h"
+#include "addressbook/gui/widgets/e-addressbook-view.h"
+#include "addressbook/gui/contact-editor/e-contact-editor.h"
+#include "addressbook/gui/contact-editor/e-contact-quick-add.h"
+#include "addressbook/gui/contact-list-editor/e-contact-list-editor.h"
+#include "addressbook/importers/evolution-addressbook-importers.h"
+
+#include "autocompletion-config.h"
+
+#include "e-book-shell-content.h"
+#include "e-book-shell-migrate.h"
+#include "e-book-shell-view.h"
+
+#ifdef ENABLE_SMIME
+#include "smime/gui/component.h"
+#include "smime/gui/certificate-manager.h"
+#endif
+
+#define E_BOOK_SHELL_BACKEND_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_BOOK_SHELL_BACKEND, EBookShellBackendPrivate))
+
+struct _EBookShellBackendPrivate {
+ gint placeholder;
+};
+
+G_DEFINE_DYNAMIC_TYPE (
+ EBookShellBackend,
+ e_book_shell_backend,
+ E_TYPE_SHELL_BACKEND)
+
+static void
+book_shell_backend_init_importers (void)
+{
+ EImportClass *import_class;
+ EImportImporter *importer;
+
+ import_class = g_type_class_ref (e_import_get_type ());
+
+ importer = evolution_ldif_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = evolution_vcard_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = evolution_csv_outlook_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = evolution_csv_mozilla_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = evolution_csv_evolution_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+}
+
+static void
+book_shell_backend_new_contact_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EShell *shell = user_data;
+ EClient *client;
+ EContact *contact;
+ EABEditor *editor;
+ GError *error = NULL;
+
+ client = e_client_cache_get_client_finish (
+ E_CLIENT_CACHE (source_object), result, &error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ /* XXX Handle errors better. */
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ goto exit;
+ }
+
+ contact = e_contact_new ();
+
+ editor = e_contact_editor_new (
+ shell, E_BOOK_CLIENT (client), contact, TRUE, TRUE);
+
+ eab_editor_show (editor);
+
+ g_object_unref (contact);
+ g_object_unref (client);
+
+exit:
+ g_object_unref (shell);
+}
+
+static void
+book_shell_backend_new_contact_list_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EShell *shell = user_data;
+ EClient *client;
+ EContact *contact;
+ EABEditor *editor;
+ GError *error = NULL;
+
+ client = e_client_cache_get_client_finish (
+ E_CLIENT_CACHE (source_object), result, &error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ /* XXX Handle errors better. */
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ goto exit;
+ }
+
+ contact = e_contact_new ();
+
+ editor = e_contact_list_editor_new (
+ shell, E_BOOK_CLIENT (client), contact, TRUE, TRUE);
+
+ eab_editor_show (editor);
+
+ g_object_unref (contact);
+ g_object_unref (client);
+
+exit:
+ g_object_unref (shell);
+}
+
+static void
+action_contact_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ EShell *shell;
+ ESource *source = NULL;
+ ESourceRegistry *registry;
+ EClientCache *client_cache;
+ const gchar *action_name;
+
+ /* This callback is used for both contacts and contact lists. */
+
+ shell = e_shell_window_get_shell (shell_window);
+ client_cache = e_shell_get_client_cache (shell);
+
+ if (g_strcmp0 (e_shell_window_get_active_view (shell_window), "addressbook") == 0) {
+ EShellView *shell_view = e_shell_window_get_shell_view (shell_window, "addressbook");
+
+ if (shell_view && E_IS_BOOK_SHELL_VIEW (shell_view)) {
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ EBookClient *book_client;
+
+ book_shell_content = NULL;
+ g_object_get (G_OBJECT (shell_view), "shell-content", &book_shell_content, NULL);
+ g_return_if_fail (book_shell_content != NULL);
+
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ model = e_addressbook_view_get_model (view);
+ book_client = e_addressbook_model_get_client (model);
+ g_return_if_fail (book_client != NULL);
+
+ source = g_object_ref (e_client_get_source (E_CLIENT (book_client)));
+
+ g_object_unref (book_shell_content);
+ }
+ }
+
+ if (!source) {
+ registry = e_shell_get_registry (shell);
+ source = e_source_registry_ref_default_address_book (registry);
+ }
+
+ /* Use a callback function appropriate for the action. */
+ action_name = gtk_action_get_name (action);
+ if (strcmp (action_name, "contact-new") == 0)
+ e_client_cache_get_client (
+ client_cache, source,
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ NULL,
+ book_shell_backend_new_contact_cb,
+ g_object_ref (shell));
+ if (strcmp (action_name, "contact-new-list") == 0)
+ e_client_cache_get_client (
+ client_cache, source,
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ NULL,
+ book_shell_backend_new_contact_list_cb,
+ g_object_ref (shell));
+
+ g_object_unref (source);
+}
+
+static void
+action_address_book_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ EShell *shell;
+ ESourceRegistry *registry;
+ GtkWidget *config;
+ GtkWidget *dialog;
+ const gchar *icon_name;
+
+ shell = e_shell_window_get_shell (shell_window);
+
+ registry = e_shell_get_registry (shell);
+ config = e_book_source_config_new (registry, NULL);
+
+ dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config));
+
+ gtk_window_set_transient_for (
+ GTK_WINDOW (dialog), GTK_WINDOW (shell_window));
+
+ icon_name = gtk_action_get_icon_name (action);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("New Address Book"));
+
+ gtk_widget_show (dialog);
+}
+
+static GtkActionEntry item_entries[] = {
+
+ { "contact-new",
+ "contact-new",
+ NC_("New", "_Contact"),
+ "<Shift><Control>c",
+ N_("Create a new contact"),
+ G_CALLBACK (action_contact_new_cb) },
+
+ { "contact-new-list",
+ "stock_contact-list",
+ NC_("New", "Contact _List"),
+ "<Shift><Control>l",
+ N_("Create a new contact list"),
+ G_CALLBACK (action_contact_new_cb) }
+};
+
+static GtkActionEntry source_entries[] = {
+
+ { "address-book-new",
+ "address-book-new",
+ NC_("New", "Address _Book"),
+ NULL,
+ N_("Create a new address book"),
+ G_CALLBACK (action_address_book_new_cb) }
+};
+
+static gboolean
+book_shell_backend_init_preferences (EShell *shell)
+{
+ GtkWidget *preferences_window;
+
+ /* This is a main loop idle callback. */
+
+ preferences_window = e_shell_get_preferences_window (shell);
+
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "contacts",
+ "preferences-autocompletion",
+ _("Contacts"),
+ "index#contacts",
+ autocompletion_config_new,
+ 200);
+
+#ifdef ENABLE_SMIME
+ preferences_window = e_shell_get_preferences_window (shell);
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "certificates",
+ "preferences-certificates",
+ _("Certificates"),
+ "mail-encryption-s-mime-manage",
+ e_cert_manager_config_new,
+ 700);
+#endif
+
+ return FALSE;
+}
+
+static void
+book_shell_backend_quick_add_email_cb (EShell *shell,
+ const gchar *email)
+{
+ EClientCache *client_cache;
+
+ /* XXX This is an ugly hack but it's the only way I could think
+ * of to integrate this feature with other shell modules. */
+
+ client_cache = e_shell_get_client_cache (shell);
+ e_contact_quick_add_email (client_cache, email, NULL, NULL);
+}
+
+static void
+book_shell_backend_quick_add_vcard_cb (EShell *shell,
+ const gchar *vcard)
+{
+ EClientCache *client_cache;
+
+ /* XXX This is an ugly hack but it's the only way I could think
+ * of to integrate this feature with other shell modules. */
+
+ client_cache = e_shell_get_client_cache (shell);
+ e_contact_quick_add_vcard (client_cache, vcard, NULL, NULL);
+}
+
+static gboolean
+book_shell_backend_handle_uri_cb (EShellBackend *shell_backend,
+ const gchar *uri)
+{
+ SoupURI *soup_uri;
+ const gchar *cp;
+ gchar *source_uid = NULL;
+ gchar *contact_uid = NULL;
+
+ if (!g_str_has_prefix (uri, "contacts:"))
+ return FALSE;
+
+ soup_uri = soup_uri_new (uri);
+
+ if (soup_uri == NULL)
+ return FALSE;
+
+ cp = soup_uri_get_query (soup_uri);
+
+ if (cp == NULL) {
+ soup_uri_free (soup_uri);
+ return FALSE;
+ }
+
+ while (*cp != '\0') {
+ gchar *header;
+ gchar *content;
+ gsize length;
+ gsize content_length;
+
+ length = strcspn (cp, "=&");
+
+ /* If it's malformed, give up. */
+ if (cp[length] != '=')
+ break;
+
+ header = (gchar *) cp;
+ header[length] = '\0';
+ cp += length + 1;
+
+ content_length = strcspn (cp, "&");
+ content = g_strndup (cp, content_length);
+
+ if (g_ascii_strcasecmp (header, "source-uid") == 0)
+ source_uid = g_strdup (content);
+
+ if (g_ascii_strcasecmp (header, "contact-uid") == 0)
+ contact_uid = g_strdup (content);
+
+ g_free (content);
+
+ cp += content_length;
+ if (*cp == '&') {
+ cp++;
+ if (strcmp (cp, "amp;"))
+ cp += 4;
+ }
+ }
+
+ /* FIXME */
+ /*addressbook_view_edit_contact (view, source_uid, contact_uid);*/
+
+ g_free (source_uid);
+ g_free (contact_uid);
+
+ soup_uri_free (soup_uri);
+
+ return TRUE;
+}
+
+static void
+book_shell_backend_window_added_cb (EShellBackend *shell_backend,
+ GtkWindow *window)
+{
+ const gchar *backend_name;
+
+ if (!E_IS_SHELL_WINDOW (window))
+ return;
+
+ backend_name = E_SHELL_BACKEND_GET_CLASS (shell_backend)->name;
+
+ e_shell_window_register_new_item_actions (
+ E_SHELL_WINDOW (window), backend_name,
+ item_entries, G_N_ELEMENTS (item_entries));
+
+ e_shell_window_register_new_source_actions (
+ E_SHELL_WINDOW (window), backend_name,
+ source_entries, G_N_ELEMENTS (source_entries));
+}
+
+static void
+book_shell_backend_constructed (GObject *object)
+{
+ EShell *shell;
+ EShellBackend *shell_backend;
+
+ shell_backend = E_SHELL_BACKEND (object);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ /* XXX Why is this here? Address books aren't the only
+ * things that use S/MIME. Maybe put it in EShell? */
+#ifdef ENABLE_SMIME
+ smime_component_init ();
+#endif
+
+ book_shell_backend_init_importers ();
+
+ g_signal_connect (
+ shell, "event::contact-quick-add-email",
+ G_CALLBACK (book_shell_backend_quick_add_email_cb), NULL);
+
+ g_signal_connect_swapped (
+ shell, "event::contact-quick-add-vcard",
+ G_CALLBACK (book_shell_backend_quick_add_vcard_cb), NULL);
+
+ g_signal_connect_swapped (
+ shell, "handle-uri",
+ G_CALLBACK (book_shell_backend_handle_uri_cb),
+ shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "window-added",
+ G_CALLBACK (book_shell_backend_window_added_cb),
+ shell_backend);
+
+ /* Initialize preferences after the main loop starts so
+ * that all EPlugins and EPluginHooks are loaded first. */
+ g_idle_add ((GSourceFunc) book_shell_backend_init_preferences, shell);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_book_shell_backend_parent_class)->constructed (object);
+}
+
+static void
+e_book_shell_backend_class_init (EBookShellBackendClass *class)
+{
+ GObjectClass *object_class;
+ EShellBackendClass *shell_backend_class;
+
+ g_type_class_add_private (class, sizeof (EBookShellBackendPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = book_shell_backend_constructed;
+
+ shell_backend_class = E_SHELL_BACKEND_CLASS (class);
+ shell_backend_class->shell_view_type = E_TYPE_BOOK_SHELL_VIEW;
+ shell_backend_class->name = "addressbook";
+ shell_backend_class->aliases = "contacts";
+ shell_backend_class->schemes = "";
+ shell_backend_class->sort_order = 300;
+ shell_backend_class->preferences_page = "contacts";
+ shell_backend_class->start = NULL;
+ shell_backend_class->migrate = e_book_shell_backend_migrate;
+}
+
+static void
+e_book_shell_backend_class_finalize (EBookShellBackendClass *class)
+{
+}
+
+static void
+e_book_shell_backend_init (EBookShellBackend *book_shell_backend)
+{
+ book_shell_backend->priv =
+ E_BOOK_SHELL_BACKEND_GET_PRIVATE (book_shell_backend);
+}
+
+void
+e_book_shell_backend_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_book_shell_backend_register_type (type_module);
+}
diff --git a/modules/addressbook/e-book-shell-backend.h b/modules/addressbook/e-book-shell-backend.h
new file mode 100644
index 0000000000..73a799a130
--- /dev/null
+++ b/modules/addressbook/e-book-shell-backend.h
@@ -0,0 +1,67 @@
+/*
+ * e-book-shell-backend.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_BACKEND_H
+#define E_BOOK_SHELL_BACKEND_H
+
+#include <shell/e-shell-backend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_SHELL_BACKEND \
+ (e_book_shell_backend_get_type ())
+#define E_BOOK_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOOK_SHELL_BACKEND, EBookShellBackend))
+#define E_BOOK_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_BOOK_SHELL_BACKEND, EBookShellBackendClass))
+#define E_IS_BOOK_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_BOOK_SHELL_BACKEND))
+#define E_IS_BOOK_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_BOOK_SHELL_BACKEND))
+#define E_BOOK_SHELL_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_BOOK_SHELL_BACKEND, EBookShellBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookShellBackend EBookShellBackend;
+typedef struct _EBookShellBackendClass EBookShellBackendClass;
+typedef struct _EBookShellBackendPrivate EBookShellBackendPrivate;
+
+struct _EBookShellBackend {
+ EShellBackend parent;
+ EBookShellBackendPrivate *priv;
+};
+
+struct _EBookShellBackendClass {
+ EShellBackendClass parent_class;
+};
+
+GType e_book_shell_backend_get_type (void);
+void e_book_shell_backend_type_register
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SHELL_BACKEND_H */
diff --git a/modules/addressbook/e-book-shell-content.c b/modules/addressbook/e-book-shell-content.c
new file mode 100644
index 0000000000..26b370f6d6
--- /dev/null
+++ b/modules/addressbook/e-book-shell-content.c
@@ -0,0 +1,749 @@
+/*
+ * e-book-shell-content.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-shell-content.h"
+
+#include <glib/gi18n.h>
+
+#include "shell/e-shell-utils.h"
+#include "e-book-shell-view.h"
+
+#define E_BOOK_SHELL_CONTENT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_BOOK_SHELL_CONTENT, EBookShellContentPrivate))
+
+struct _EBookShellContentPrivate {
+ GtkWidget *paned;
+ GtkWidget *notebook;
+ GtkWidget *preview_pane;
+
+ GtkOrientation orientation;
+
+ gboolean preview_show_maps;
+ guint preview_visible : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_CURRENT_VIEW,
+ PROP_ORIENTATION,
+ PROP_PREVIEW_CONTACT,
+ PROP_PREVIEW_VISIBLE,
+ PROP_PREVIEW_SHOW_MAPS
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+ EBookShellContent,
+ e_book_shell_content,
+ E_TYPE_SHELL_CONTENT,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (
+ GTK_TYPE_ORIENTABLE, NULL))
+
+static void
+book_shell_content_send_message_cb (EBookShellContent *book_shell_content,
+ EDestination *destination,
+ EABContactDisplay *display)
+{
+ EShell *shell;
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GSList node = { destination, NULL };
+
+ shell_content = E_SHELL_CONTENT (book_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ eab_send_as_to (shell, &node);
+}
+
+static void
+book_shell_content_restore_state_cb (EShellWindow *shell_window,
+ EShellView *shell_view,
+ EShellContent *shell_content)
+{
+ EBookShellContentPrivate *priv;
+ GSettings *settings;
+
+ priv = E_BOOK_SHELL_CONTENT_GET_PRIVATE (shell_content);
+
+ /* Bind GObject properties to GSettings keys. */
+
+ settings = g_settings_new ("org.gnome.evolution.addressbook");
+
+ g_settings_bind (
+ settings, "hpane-position",
+ priv->paned, "hposition",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "vpane-position",
+ priv->paned, "vposition",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_object_unref (settings);
+}
+
+static GtkOrientation
+book_shell_content_get_orientation (EBookShellContent *book_shell_content)
+{
+ return book_shell_content->priv->orientation;
+}
+
+static void
+book_shell_content_set_orientation (EBookShellContent *book_shell_content,
+ GtkOrientation orientation)
+{
+ if (book_shell_content->priv->orientation == orientation)
+ return;
+
+ book_shell_content->priv->orientation = orientation;
+
+ g_object_notify (G_OBJECT (book_shell_content), "orientation");
+}
+
+static void
+book_shell_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_VIEW:
+ e_book_shell_content_set_current_view (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_ORIENTATION:
+ book_shell_content_set_orientation (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_PREVIEW_CONTACT:
+ e_book_shell_content_set_preview_contact (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ e_book_shell_content_set_preview_visible (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_PREVIEW_SHOW_MAPS:
+ e_book_shell_content_set_preview_show_maps (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+book_shell_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_VIEW:
+ g_value_set_object (
+ value,
+ e_book_shell_content_get_current_view (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_ORIENTATION:
+ g_value_set_enum (
+ value,
+ book_shell_content_get_orientation (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_CONTACT:
+ g_value_set_object (
+ value,
+ e_book_shell_content_get_preview_contact (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ g_value_set_boolean (
+ value,
+ e_book_shell_content_get_preview_visible (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_SHOW_MAPS:
+ g_value_set_boolean (
+ value,
+ e_book_shell_content_get_preview_show_maps (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+book_shell_content_dispose (GObject *object)
+{
+ EBookShellContentPrivate *priv;
+
+ priv = E_BOOK_SHELL_CONTENT_GET_PRIVATE (object);
+
+ if (priv->paned != NULL) {
+ g_object_unref (priv->paned);
+ priv->paned = NULL;
+ }
+
+ if (priv->notebook != NULL) {
+ g_object_unref (priv->notebook);
+ priv->notebook = NULL;
+ }
+
+ if (priv->preview_pane != NULL) {
+ g_object_unref (priv->preview_pane);
+ priv->preview_pane = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_book_shell_content_parent_class)->dispose (object);
+}
+
+static void
+book_shell_content_constructed (GObject *object)
+{
+ EBookShellContentPrivate *priv;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ EShellTaskbar *shell_taskbar;
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ priv = E_BOOK_SHELL_CONTENT_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_book_shell_content_parent_class)->constructed (object);
+
+ shell_content = E_SHELL_CONTENT (object);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_taskbar = e_shell_view_get_shell_taskbar (shell_view);
+
+ container = GTK_WIDGET (object);
+
+ widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->paned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ object, "orientation",
+ widget, "orientation",
+ G_BINDING_SYNC_CREATE);
+
+ container = widget;
+
+ widget = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ priv->notebook = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = eab_contact_display_new ();
+ eab_contact_display_set_mode (
+ EAB_CONTACT_DISPLAY (widget),
+ EAB_CONTACT_DISPLAY_RENDER_NORMAL);
+
+ eab_contact_display_set_show_maps (
+ EAB_CONTACT_DISPLAY (widget),
+ priv->preview_show_maps);
+
+ g_object_bind_property (
+ object, "preview-show-maps",
+ widget, "show-maps",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "send-message",
+ G_CALLBACK (book_shell_content_send_message_cb), object);
+
+ g_signal_connect_swapped (
+ widget, "status-message",
+ G_CALLBACK (e_shell_taskbar_set_message),
+ shell_taskbar);
+
+ widget = e_preview_pane_new (E_WEB_VIEW (widget));
+ gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ priv->preview_pane = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ object, "preview-visible",
+ widget, "visible",
+ G_BINDING_SYNC_CREATE);
+
+ /* Restore pane positions from the last session once
+ * the shell view is fully initialized and visible. */
+ g_signal_connect (
+ shell_window, "shell-view-created::addressbook",
+ G_CALLBACK (book_shell_content_restore_state_cb),
+ shell_content);
+}
+
+static void
+book_shell_content_check_state_foreach (gint row,
+ gpointer user_data)
+{
+ EContact *contact;
+
+ struct {
+ EAddressbookModel *model;
+ GList *list;
+ } *foreach_data = user_data;
+
+ contact = e_addressbook_model_get_contact (foreach_data->model, row);
+ g_return_if_fail (E_IS_CONTACT (contact));
+
+ foreach_data->list = g_list_prepend (foreach_data->list, contact);
+}
+
+static guint32
+book_shell_content_check_state (EShellContent *shell_content)
+{
+ EBookShellContent *book_shell_content;
+ ESelectionModel *selection_model;
+ EAddressbookModel *model;
+ EAddressbookView *view;
+ GtkNotebook *notebook;
+ gboolean has_email = TRUE;
+ gboolean is_contact_list = TRUE;
+ guint32 state = 0;
+ gint n_selected;
+
+ struct {
+ EAddressbookModel *model;
+ GList *list;
+ } foreach_data;
+
+ book_shell_content = E_BOOK_SHELL_CONTENT (shell_content);
+
+ /* This function may be triggered at startup before any address
+ * book views are added. Check for that and return silently. */
+ notebook = GTK_NOTEBOOK (book_shell_content->priv->notebook);
+ if (gtk_notebook_get_n_pages (notebook) == 0)
+ return 0;
+
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ model = e_addressbook_view_get_model (view);
+
+ selection_model = e_addressbook_view_get_selection_model (view);
+ n_selected = (selection_model != NULL) ?
+ e_selection_model_selected_count (selection_model) : 0;
+
+ foreach_data.model = model;
+ foreach_data.list = NULL;
+
+ if (selection_model != NULL)
+ e_selection_model_foreach (
+ selection_model, (EForeachFunc)
+ book_shell_content_check_state_foreach,
+ &foreach_data);
+
+ while (foreach_data.list != NULL) {
+ EContact *contact = E_CONTACT (foreach_data.list->data);
+ GList *email_list;
+
+ email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+ has_email &= (email_list != NULL);
+ g_list_foreach (email_list, (GFunc) g_free, NULL);
+ g_list_free (email_list);
+
+ is_contact_list &=
+ (e_contact_get (contact, E_CONTACT_IS_LIST) != NULL);
+
+ g_object_unref (contact);
+
+ foreach_data.list = g_list_delete_link (
+ foreach_data.list, foreach_data.list);
+ }
+
+ if (n_selected == 1)
+ state |= E_BOOK_SHELL_CONTENT_SELECTION_SINGLE;
+ if (n_selected > 1)
+ state |= E_BOOK_SHELL_CONTENT_SELECTION_MULTIPLE;
+ if (n_selected > 0 && has_email)
+ state |= E_BOOK_SHELL_CONTENT_SELECTION_HAS_EMAIL;
+ if (n_selected == 1 && is_contact_list)
+ state |= E_BOOK_SHELL_CONTENT_SELECTION_IS_CONTACT_LIST;
+ if (e_addressbook_model_can_stop (model))
+ state |= E_BOOK_SHELL_CONTENT_SOURCE_IS_BUSY;
+ if (e_addressbook_model_get_editable (model))
+ state |= E_BOOK_SHELL_CONTENT_SOURCE_IS_EDITABLE;
+
+ return state;
+}
+
+static void
+book_shell_content_focus_search_results (EShellContent *shell_content)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = E_BOOK_SHELL_CONTENT (shell_content);
+ view = e_book_shell_content_get_current_view (book_shell_content);
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+e_book_shell_content_class_init (EBookShellContentClass *class)
+{
+ GObjectClass *object_class;
+ EShellContentClass *shell_content_class;
+
+ g_type_class_add_private (class, sizeof (EBookShellContentPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = book_shell_content_set_property;
+ object_class->get_property = book_shell_content_get_property;
+ object_class->dispose = book_shell_content_dispose;
+ object_class->constructed = book_shell_content_constructed;
+
+ shell_content_class = E_SHELL_CONTENT_CLASS (class);
+ shell_content_class->check_state = book_shell_content_check_state;
+ shell_content_class->focus_search_results =
+ book_shell_content_focus_search_results;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CURRENT_VIEW,
+ g_param_spec_object (
+ "current-view",
+ "Current View",
+ "The currently selected address book view",
+ E_TYPE_ADDRESSBOOK_VIEW,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_CONTACT,
+ g_param_spec_object (
+ "preview-contact",
+ "Previewed Contact",
+ "The contact being shown in the preview pane",
+ E_TYPE_CONTACT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_VISIBLE,
+ g_param_spec_boolean (
+ "preview-visible",
+ "Preview is Visible",
+ "Whether the preview pane is visible",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_override_property (
+ object_class, PROP_ORIENTATION, "orientation");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_SHOW_MAPS,
+ g_param_spec_boolean (
+ "preview-show-maps",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static void
+e_book_shell_content_class_finalize (EBookShellContentClass *class)
+{
+}
+
+static void
+e_book_shell_content_init (EBookShellContent *book_shell_content)
+{
+ book_shell_content->priv =
+ E_BOOK_SHELL_CONTENT_GET_PRIVATE (book_shell_content);
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+void
+e_book_shell_content_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_book_shell_content_register_type (type_module);
+}
+
+GtkWidget *
+e_book_shell_content_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_BOOK_SHELL_CONTENT,
+ "shell-view", shell_view, NULL);
+}
+
+void
+e_book_shell_content_insert_view (EBookShellContent *book_shell_content,
+ EAddressbookView *addressbook_view)
+{
+ GtkNotebook *notebook;
+ GtkWidget *child;
+
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+ g_return_if_fail (E_IS_ADDRESSBOOK_VIEW (addressbook_view));
+
+ notebook = GTK_NOTEBOOK (book_shell_content->priv->notebook);
+ child = GTK_WIDGET (addressbook_view);
+ gtk_notebook_append_page (notebook, child, NULL);
+}
+
+void
+e_book_shell_content_remove_view (EBookShellContent *book_shell_content,
+ EAddressbookView *addressbook_view)
+{
+ GtkNotebook *notebook;
+ GtkWidget *child;
+ gint page_num;
+
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+ g_return_if_fail (E_IS_ADDRESSBOOK_VIEW (addressbook_view));
+
+ notebook = GTK_NOTEBOOK (book_shell_content->priv->notebook);
+ child = GTK_WIDGET (addressbook_view);
+ page_num = gtk_notebook_page_num (notebook, child);
+ g_return_if_fail (page_num >= 0);
+
+ gtk_notebook_remove_page (notebook, page_num);
+}
+
+EAddressbookView *
+e_book_shell_content_get_current_view (EBookShellContent *book_shell_content)
+{
+ GtkNotebook *notebook;
+ GtkWidget *widget;
+ gint page_num;
+
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL);
+
+ notebook = GTK_NOTEBOOK (book_shell_content->priv->notebook);
+ page_num = gtk_notebook_get_current_page (notebook);
+ widget = gtk_notebook_get_nth_page (notebook, page_num);
+ g_return_val_if_fail (widget != NULL, NULL);
+
+ return E_ADDRESSBOOK_VIEW (widget);
+}
+
+void
+e_book_shell_content_set_current_view (EBookShellContent *book_shell_content,
+ EAddressbookView *addressbook_view)
+{
+ EShellView *shell_view;
+ EShellContent *shell_content;
+ EShellSearchbar *searchbar;
+ EBookShellView *book_shell_view;
+ GtkNotebook *notebook;
+ GtkWidget *child;
+ gint page_num, old_page_num;
+
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+ g_return_if_fail (E_IS_ADDRESSBOOK_VIEW (addressbook_view));
+
+ shell_content = E_SHELL_CONTENT (book_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+
+ book_shell_view = E_BOOK_SHELL_VIEW (shell_view);
+ searchbar = e_book_shell_content_get_searchbar (book_shell_content);
+
+ notebook = GTK_NOTEBOOK (book_shell_content->priv->notebook);
+ child = GTK_WIDGET (addressbook_view);
+ page_num = gtk_notebook_page_num (notebook, child);
+ g_return_if_fail (page_num >= 0);
+
+ old_page_num = gtk_notebook_get_current_page (notebook);
+ gtk_notebook_set_current_page (notebook, page_num);
+
+ if (old_page_num != page_num) {
+ EActionComboBox *combo_box;
+ GtkRadioAction *action;
+ gint filter_id = 0, search_id = 0;
+ gchar *search_text = NULL;
+ EFilterRule *advanced_search = NULL;
+
+ e_book_shell_view_disable_searching (book_shell_view);
+
+ e_addressbook_view_get_search (
+ addressbook_view, &filter_id, &search_id,
+ &search_text, &advanced_search);
+
+ combo_box = e_shell_searchbar_get_filter_combo_box (searchbar);
+ e_action_combo_box_set_current_value (combo_box, filter_id);
+
+ action = e_shell_searchbar_get_search_option (searchbar);
+ gtk_radio_action_set_current_value (action, search_id);
+
+ e_shell_searchbar_set_search_text (searchbar, search_text);
+
+ e_shell_view_set_search_rule (shell_view, advanced_search);
+
+ g_free (search_text);
+
+ if (advanced_search)
+ g_object_unref (advanced_search);
+
+ e_book_shell_view_enable_searching (book_shell_view);
+ }
+
+ g_object_notify (G_OBJECT (book_shell_content), "current-view");
+}
+
+EContact *
+e_book_shell_content_get_preview_contact (EBookShellContent *book_shell_content)
+{
+ EPreviewPane *preview_pane;
+ EABContactDisplay *display;
+ EWebView *web_view;
+
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL);
+
+ preview_pane = E_PREVIEW_PANE (book_shell_content->priv->preview_pane);
+ web_view = e_preview_pane_get_web_view (preview_pane);
+ display = EAB_CONTACT_DISPLAY (web_view);
+
+ return eab_contact_display_get_contact (display);
+}
+
+void
+e_book_shell_content_set_preview_contact (EBookShellContent *book_shell_content,
+ EContact *preview_contact)
+{
+ EPreviewPane *preview_pane;
+ EABContactDisplay *display;
+ EWebView *web_view;
+
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+
+ preview_pane = E_PREVIEW_PANE (book_shell_content->priv->preview_pane);
+ web_view = e_preview_pane_get_web_view (preview_pane);
+ display = EAB_CONTACT_DISPLAY (web_view);
+
+ eab_contact_display_set_contact (display, preview_contact);
+ g_object_notify (G_OBJECT (book_shell_content), "preview-contact");
+}
+
+EPreviewPane *
+e_book_shell_content_get_preview_pane (EBookShellContent *book_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL);
+
+ return E_PREVIEW_PANE (book_shell_content->priv->preview_pane);
+}
+
+gboolean
+e_book_shell_content_get_preview_visible (EBookShellContent *book_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), FALSE);
+
+ return book_shell_content->priv->preview_visible;
+}
+
+void
+e_book_shell_content_set_preview_visible (EBookShellContent *book_shell_content,
+ gboolean preview_visible)
+{
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+
+ if (book_shell_content->priv->preview_visible == preview_visible)
+ return;
+
+ book_shell_content->priv->preview_visible = preview_visible;
+
+ g_object_notify (G_OBJECT (book_shell_content), "preview-visible");
+}
+
+gboolean
+e_book_shell_content_get_preview_show_maps (EBookShellContent *book_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), FALSE);
+
+ return book_shell_content->priv->preview_show_maps;
+}
+
+void
+e_book_shell_content_set_preview_show_maps (EBookShellContent *book_shell_content,
+ gboolean show_maps)
+{
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+
+ if (book_shell_content->priv->preview_show_maps == show_maps)
+ return;
+
+ book_shell_content->priv->preview_show_maps = show_maps;
+
+ g_object_notify (G_OBJECT (book_shell_content), "preview-show-maps");
+}
+
+EShellSearchbar *
+e_book_shell_content_get_searchbar (EBookShellContent *book_shell_content)
+{
+ EShellView *shell_view;
+ EShellContent *shell_content;
+ GtkWidget *widget;
+
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL);
+
+ shell_content = E_SHELL_CONTENT (book_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ widget = e_shell_view_get_searchbar (shell_view);
+
+ return E_SHELL_SEARCHBAR (widget);
+}
diff --git a/modules/addressbook/e-book-shell-content.h b/modules/addressbook/e-book-shell-content.h
new file mode 100644
index 0000000000..b0258d82ae
--- /dev/null
+++ b/modules/addressbook/e-book-shell-content.h
@@ -0,0 +1,117 @@
+/*
+ * e-book-shell-content.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_CONTENT_H
+#define E_BOOK_SHELL_CONTENT_H
+
+#include <libebook/libebook.h>
+
+#include <shell/e-shell-content.h>
+#include <shell/e-shell-searchbar.h>
+#include <shell/e-shell-view.h>
+
+#include "addressbook/gui/widgets/e-addressbook-view.h"
+#include "eab-composer-util.h"
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_SHELL_CONTENT \
+ (e_book_shell_content_get_type ())
+#define E_BOOK_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOOK_SHELL_CONTENT, EBookShellContent))
+#define E_BOOK_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_BOOK_SHELL_CONTENT, EBookShellContentClass))
+#define E_IS_BOOK_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_BOOK_SHELL_CONTENT))
+#define E_IS_BOOK_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_BOOK_SHELL_CONTENT))
+#define E_BOOK_SHELL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_BOOK_SHELL_CONTENT, EBookShellContentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookShellContent EBookShellContent;
+typedef struct _EBookShellContentClass EBookShellContentClass;
+typedef struct _EBookShellContentPrivate EBookShellContentPrivate;
+
+enum {
+ E_BOOK_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0,
+ E_BOOK_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1,
+ E_BOOK_SHELL_CONTENT_SELECTION_HAS_EMAIL = 1 << 2,
+ E_BOOK_SHELL_CONTENT_SELECTION_IS_CONTACT_LIST = 1 << 3,
+ E_BOOK_SHELL_CONTENT_SOURCE_IS_BUSY = 1 << 4,
+ E_BOOK_SHELL_CONTENT_SOURCE_IS_EDITABLE = 1 << 5
+};
+
+struct _EBookShellContent {
+ EShellContent parent;
+ EBookShellContentPrivate *priv;
+};
+
+struct _EBookShellContentClass {
+ EShellContentClass parent_class;
+};
+
+GType e_book_shell_content_get_type (void);
+void e_book_shell_content_type_register
+ (GTypeModule *type_module);
+GtkWidget * e_book_shell_content_new
+ (EShellView *shell_view);
+void e_book_shell_content_insert_view
+ (EBookShellContent *book_shell_content,
+ EAddressbookView *addressbook_view);
+void e_book_shell_content_remove_view
+ (EBookShellContent *book_shell_content,
+ EAddressbookView *addressbook_view);
+EAddressbookView *
+ e_book_shell_content_get_current_view
+ (EBookShellContent *book_shell_content);
+void e_book_shell_content_set_current_view
+ (EBookShellContent *book_shell_content,
+ EAddressbookView *addressbook_view);
+EContact * e_book_shell_content_get_preview_contact
+ (EBookShellContent *book_shell_content);
+void e_book_shell_content_set_preview_contact
+ (EBookShellContent *book_shell_content,
+ EContact *preview_contact);
+EPreviewPane * e_book_shell_content_get_preview_pane
+ (EBookShellContent *book_shell_content);
+gboolean e_book_shell_content_get_preview_visible
+ (EBookShellContent *book_shell_content);
+void e_book_shell_content_set_preview_visible
+ (EBookShellContent *book_shell_content,
+ gboolean preview_visible);
+gboolean e_book_shell_content_get_preview_show_maps
+ (EBookShellContent *book_shell_content);
+void e_book_shell_content_set_preview_show_maps
+ (EBookShellContent *book_shell_content,
+ gboolean show_maps);
+EShellSearchbar *
+ e_book_shell_content_get_searchbar
+ (EBookShellContent *book_shell_content);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SHELL_CONTENT_H */
diff --git a/modules/addressbook/e-book-shell-migrate.c b/modules/addressbook/e-book-shell-migrate.c
new file mode 100644
index 0000000000..2e3de84b32
--- /dev/null
+++ b/modules/addressbook/e-book-shell-migrate.c
@@ -0,0 +1,41 @@
+/*
+ * e-book-shell-backend-migrate.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-shell-migrate.h"
+
+gboolean
+e_book_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error)
+{
+ g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE);
+
+ return TRUE;
+}
diff --git a/modules/addressbook/e-book-shell-migrate.h b/modules/addressbook/e-book-shell-migrate.h
new file mode 100644
index 0000000000..2bbf3f321f
--- /dev/null
+++ b/modules/addressbook/e-book-shell-migrate.h
@@ -0,0 +1,40 @@
+/*
+ * e-book-shell-migrate.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Chris Toshok (toshok@ximian.com)
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_MIGRATE_H
+#define E_BOOK_SHELL_MIGRATE_H
+
+#include <shell/e-shell-backend.h>
+
+G_BEGIN_DECLS
+
+gboolean e_book_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SHELL_MIGRATE_H */
diff --git a/modules/addressbook/e-book-shell-sidebar.c b/modules/addressbook/e-book-shell-sidebar.c
new file mode 100644
index 0000000000..b29bd9a08f
--- /dev/null
+++ b/modules/addressbook/e-book-shell-sidebar.c
@@ -0,0 +1,319 @@
+/*
+ * e-book-shell-sidebar.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-shell-sidebar.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include <e-util/e-util.h>
+
+#include "e-book-shell-view.h"
+#include "e-book-shell-backend.h"
+#include "e-addressbook-selector.h"
+
+#define E_BOOK_SHELL_SIDEBAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_BOOK_SHELL_SIDEBAR, EBookShellSidebarPrivate))
+
+struct _EBookShellSidebarPrivate {
+ GtkWidget *selector;
+};
+
+enum {
+ PROP_0,
+ PROP_SELECTOR
+};
+
+G_DEFINE_DYNAMIC_TYPE (
+ EBookShellSidebar,
+ e_book_shell_sidebar,
+ E_TYPE_SHELL_SIDEBAR)
+
+static gboolean
+book_shell_sidebar_map_uid_to_source (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ ESourceRegistry *registry;
+ ESource *source;
+ const gchar *uid;
+
+ registry = E_SOURCE_REGISTRY (user_data);
+ uid = g_variant_get_string (variant, NULL);
+ if (uid != NULL && *uid != '\0')
+ source = e_source_registry_ref_source (registry, uid);
+ else
+ source = e_source_registry_ref_default_address_book (registry);
+ g_value_take_object (value, source);
+
+ return (source != NULL);
+}
+
+static GVariant *
+book_shell_sidebar_map_source_to_uid (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ GVariant *variant = NULL;
+ ESource *source;
+
+ source = g_value_get_object (value);
+
+ if (source != NULL) {
+ const gchar *uid;
+
+ uid = e_source_get_uid (source);
+ variant = g_variant_new_string (uid);
+ }
+
+ return variant;
+}
+
+static void
+book_shell_sidebar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SELECTOR:
+ g_value_set_object (
+ value, e_book_shell_sidebar_get_selector (
+ E_BOOK_SHELL_SIDEBAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+book_shell_sidebar_dispose (GObject *object)
+{
+ EBookShellSidebarPrivate *priv;
+
+ priv = E_BOOK_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ if (priv->selector != NULL) {
+ g_object_unref (priv->selector);
+ priv->selector = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_book_shell_sidebar_parent_class)->dispose (object);
+}
+
+static void
+book_shell_sidebar_constructed (GObject *object)
+{
+ EBookShellSidebarPrivate *priv;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellSidebar *shell_sidebar;
+ EClientCache *client_cache;
+ GtkContainer *container;
+ GtkWidget *widget;
+ GSettings *settings;
+
+ priv = E_BOOK_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_book_shell_sidebar_parent_class)->constructed (object);
+
+ shell_sidebar = E_SHELL_SIDEBAR (object);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ container = GTK_CONTAINER (shell_sidebar);
+
+ 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 (container, widget);
+ gtk_widget_show (widget);
+
+ container = GTK_CONTAINER (widget);
+
+ client_cache = e_shell_get_client_cache (shell);
+ widget = e_addressbook_selector_new (client_cache);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->selector = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ settings = g_settings_new ("org.gnome.evolution.addressbook");
+
+ g_settings_bind_with_mapping (
+ settings, "primary-addressbook",
+ widget, "primary-selection",
+ G_SETTINGS_BIND_DEFAULT,
+ book_shell_sidebar_map_uid_to_source,
+ book_shell_sidebar_map_source_to_uid,
+ e_client_cache_ref_registry (client_cache),
+ (GDestroyNotify) g_object_unref);
+
+ g_object_unref (settings);
+}
+
+static guint32
+book_shell_sidebar_check_state (EShellSidebar *shell_sidebar)
+{
+ EBookShellSidebar *book_shell_sidebar;
+ ESourceSelector *selector;
+ ESourceRegistry *registry;
+ ESource *source;
+ gboolean is_writable = FALSE;
+ gboolean is_removable = FALSE;
+ gboolean is_remote_creatable = FALSE;
+ gboolean is_remote_deletable = FALSE;
+ gboolean in_collection = FALSE;
+ gboolean has_primary_source = FALSE;
+ gboolean refresh_supported = FALSE;
+ guint32 state = 0;
+
+ book_shell_sidebar = E_BOOK_SHELL_SIDEBAR (shell_sidebar);
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+ source = e_source_selector_ref_primary_selection (selector);
+ registry = e_source_selector_get_registry (selector);
+
+ if (source != NULL) {
+ EClient *client;
+ ESource *collection;
+
+ has_primary_source = TRUE;
+ is_writable = e_source_get_writable (source);
+ is_removable = e_source_get_removable (source);
+ is_remote_creatable = e_source_get_remote_creatable (source);
+ is_remote_deletable = e_source_get_remote_deletable (source);
+
+ collection = e_source_registry_find_extension (
+ registry, source, E_SOURCE_EXTENSION_COLLECTION);
+ if (collection != NULL) {
+ in_collection = TRUE;
+ g_object_unref (collection);
+ }
+
+ client = e_client_selector_ref_cached_client (
+ E_CLIENT_SELECTOR (selector), source);
+
+ if (client != NULL) {
+ refresh_supported =
+ e_client_check_refresh_supported (client);
+ g_object_unref (client);
+ }
+
+ g_object_unref (source);
+ }
+
+ if (has_primary_source)
+ state |= E_BOOK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE;
+ if (is_writable)
+ state |= E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE;
+ if (is_removable)
+ state |= E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE;
+ if (is_remote_creatable)
+ state |= E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE;
+ if (is_remote_deletable)
+ state |= E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE;
+ if (in_collection)
+ state |= E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION;
+ if (refresh_supported)
+ state |= E_BOOK_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH;
+
+ return state;
+}
+
+static void
+e_book_shell_sidebar_class_init (EBookShellSidebarClass *class)
+{
+ GObjectClass *object_class;
+ EShellSidebarClass *shell_sidebar_class;
+
+ g_type_class_add_private (class, sizeof (EBookShellSidebarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = book_shell_sidebar_get_property;
+ object_class->dispose = book_shell_sidebar_dispose;
+ object_class->constructed = book_shell_sidebar_constructed;
+
+ shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class);
+ shell_sidebar_class->check_state = book_shell_sidebar_check_state;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SELECTOR,
+ g_param_spec_object (
+ "selector",
+ "Source Selector Widget",
+ "This widget displays groups of address books",
+ E_TYPE_SOURCE_SELECTOR,
+ G_PARAM_READABLE));
+}
+
+static void
+e_book_shell_sidebar_class_finalize (EBookShellSidebarClass *class)
+{
+}
+
+static void
+e_book_shell_sidebar_init (EBookShellSidebar *book_shell_sidebar)
+{
+ book_shell_sidebar->priv =
+ E_BOOK_SHELL_SIDEBAR_GET_PRIVATE (book_shell_sidebar);
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+void
+e_book_shell_sidebar_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_book_shell_sidebar_register_type (type_module);
+}
+
+GtkWidget *
+e_book_shell_sidebar_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_BOOK_SHELL_SIDEBAR,
+ "shell-view", shell_view, NULL);
+}
+
+ESourceSelector *
+e_book_shell_sidebar_get_selector (EBookShellSidebar *book_shell_sidebar)
+{
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_SIDEBAR (book_shell_sidebar), NULL);
+
+ return E_SOURCE_SELECTOR (book_shell_sidebar->priv->selector);
+}
diff --git a/modules/addressbook/e-book-shell-sidebar.h b/modules/addressbook/e-book-shell-sidebar.h
new file mode 100644
index 0000000000..d79fb27f26
--- /dev/null
+++ b/modules/addressbook/e-book-shell-sidebar.h
@@ -0,0 +1,83 @@
+/*
+ * e-book-shell-sidebar.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_SIDEBAR_H
+#define E_BOOK_SHELL_SIDEBAR_H
+
+#include <shell/e-shell-sidebar.h>
+#include <shell/e-shell-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_SHELL_SIDEBAR \
+ (e_book_shell_sidebar_get_type ())
+#define E_BOOK_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOOK_SHELL_SIDEBAR, EBookShellSidebar))
+#define E_BOOK_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_BOOK_SHELL_SIDEBAR, EBookShellSidebarClass))
+#define E_IS_BOOK_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_BOOK_SHELL_SIDEBAR))
+#define E_IS_BOOK_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_BOOK_SHELL_SIDEBAR))
+#define E_BOOK_SHELL_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_BOOK_SHELL_SIDEBAR, EBookShellSidebarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookShellSidebar EBookShellSidebar;
+typedef struct _EBookShellSidebarClass EBookShellSidebarClass;
+typedef struct _EBookShellSidebarPrivate EBookShellSidebarPrivate;
+
+enum {
+ E_BOOK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0,
+ E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE = 1 << 1,
+ E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE = 1 << 2,
+ E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE = 1 << 3,
+ E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE = 1 << 4,
+ E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION = 1 << 5,
+ E_BOOK_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH = 1 << 6
+};
+
+struct _EBookShellSidebar {
+ EShellSidebar parent;
+ EBookShellSidebarPrivate *priv;
+};
+
+struct _EBookShellSidebarClass {
+ EShellSidebarClass parent_class;
+};
+
+GType e_book_shell_sidebar_get_type (void);
+void e_book_shell_sidebar_type_register
+ (GTypeModule *type_module);
+GtkWidget * e_book_shell_sidebar_new
+ (EShellView *shell_view);
+ESourceSelector *
+ e_book_shell_sidebar_get_selector
+ (EBookShellSidebar *book_shell_sidebar);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SHELL_SIDEBAR_H */
diff --git a/modules/addressbook/e-book-shell-view-actions.c b/modules/addressbook/e-book-shell-view-actions.c
new file mode 100644
index 0000000000..d2fe86e0ca
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-actions.c
@@ -0,0 +1,1422 @@
+/*
+ * e-book-shell-view-actions.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-shell-view-private.h"
+
+#include <e-util/e-util.h>
+
+static void
+action_address_book_copy_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_copy_to_folder (view, TRUE);
+}
+
+static void
+action_address_book_delete_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellSidebar *book_shell_sidebar;
+ ESource *source;
+ ESourceSelector *selector;
+ gint response;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+
+ source = e_source_selector_ref_primary_selection (selector);
+ g_return_if_fail (source != NULL);
+
+ if (e_source_get_remote_deletable (source)) {
+ response = e_alert_run_dialog_for_args (
+ GTK_WINDOW (shell_window),
+ "addressbook:ask-delete-remote-addressbook",
+ e_source_get_display_name (source), NULL);
+
+ if (response == GTK_RESPONSE_YES)
+ e_shell_view_remote_delete_source (shell_view, source);
+
+ } else {
+ response = e_alert_run_dialog_for_args (
+ GTK_WINDOW (shell_window),
+ "addressbook:ask-delete-addressbook",
+ e_source_get_display_name (source), NULL);
+
+ if (response == GTK_RESPONSE_YES)
+ e_shell_view_remove_source (shell_view, source);
+ }
+
+ g_object_unref (source);
+}
+
+static void
+action_address_book_move_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_move_to_folder (view, TRUE);
+}
+
+static void
+action_address_book_new_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ESourceRegistry *registry;
+ GtkWidget *config;
+ GtkWidget *dialog;
+ const gchar *icon_name;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ registry = book_shell_view->priv->registry;
+ config = e_book_source_config_new (registry, NULL);
+
+ dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config));
+
+ gtk_window_set_transient_for (
+ GTK_WINDOW (dialog), GTK_WINDOW (shell_window));
+
+ icon_name = gtk_action_get_icon_name (action);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("New Address Book"));
+
+ gtk_widget_show (dialog);
+}
+
+static void
+action_address_book_print_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ GtkPrintOperationAction print_action;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_addressbook_view_print (view, FALSE, print_action);
+}
+
+static void
+action_address_book_print_preview_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ GtkPrintOperationAction print_action;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW;
+ e_addressbook_view_print (view, FALSE, print_action);
+}
+
+static void
+action_address_book_properties_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellSidebar *book_shell_sidebar;
+ ESource *source;
+ ESourceSelector *selector;
+ ESourceRegistry *registry;
+ GtkWidget *config;
+ GtkWidget *dialog;
+ const gchar *icon_name;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+ source = e_source_selector_ref_primary_selection (selector);
+ g_return_if_fail (source != NULL);
+
+ registry = e_source_selector_get_registry (selector);
+ config = e_book_source_config_new (registry, source);
+
+ g_object_unref (source);
+
+ dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config));
+
+ gtk_window_set_transient_for (
+ GTK_WINDOW (dialog), GTK_WINDOW (shell_window));
+
+ icon_name = gtk_action_get_icon_name (action);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name);
+
+ gtk_window_set_title (
+ GTK_WINDOW (dialog), _("Address Book Properties"));
+
+ gtk_widget_show (dialog);
+}
+
+static void
+address_book_refresh_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (E_IS_CLIENT (source_object));
+
+ client = E_CLIENT (source_object);
+
+ if (!e_client_refresh_finish (client, result, &error)) {
+ ESource *source = e_client_get_source (client);
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED))
+ g_warning (
+ "%s: Failed to refresh '%s', %s",
+ G_STRFUNC, e_source_get_display_name (source),
+ error ? error->message : "Unknown error");
+
+ g_clear_error (&error);
+ }
+}
+
+static void
+action_address_book_refresh_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellSidebar *book_shell_sidebar;
+ ESourceSelector *selector;
+ EClient *client = NULL;
+ ESource *source;
+
+ book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+
+ source = e_source_selector_ref_primary_selection (selector);
+
+ if (source != NULL) {
+ client = e_client_selector_ref_cached_client (
+ E_CLIENT_SELECTOR (selector), source);
+ g_object_unref (source);
+ }
+
+ if (client == NULL)
+ return;
+
+ g_return_if_fail (e_client_check_refresh_supported (client));
+
+ e_client_refresh (client, NULL, address_book_refresh_done_cb, book_shell_view);
+
+ g_object_unref (client);
+}
+
+#ifdef WITH_CONTACT_MAPS
+static void
+contact_editor_contact_modified_cb (EABEditor *editor,
+ const GError *error,
+ EContact *contact,
+ gpointer user_data)
+{
+ EContactMapWindow *window = user_data;
+ EContactMap *map;
+ const gchar *contact_uid;
+
+ if (error) {
+ g_warning ("Error modifying contact: %s", error->message);
+ return;
+ }
+
+ map = e_contact_map_window_get_map (window);
+
+ contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+
+ e_contact_map_remove_contact (map, contact_uid);
+ e_contact_map_add_contact (map, contact);
+}
+
+static void
+map_window_show_contact_editor_cb (EContactMapWindow *window,
+ const gchar *contact_uid,
+ gpointer user_data)
+{
+ EBookShellView *book_shell_view = user_data;
+ EBookShellSidebar *book_shell_sidebar;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ ESource *source;
+ ESourceSelector *selector;
+ EClient *client;
+ EClientCache *client_cache;
+ EContact *contact;
+ EABEditor *editor;
+ GError *error = NULL;
+
+ book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+ source = e_source_selector_ref_primary_selection (selector);
+ g_return_if_fail (source != NULL);
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell = e_shell_backend_get_shell (shell_backend);
+ client_cache = e_shell_get_client_cache (shell);
+
+ /* FIXME This blocks. Needs to be asynchronous. */
+ client = e_client_cache_get_client_sync (
+ client_cache, source,
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ NULL, &error);
+
+ g_object_unref (source);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ if (error != NULL) {
+ g_warning ("Error loading addressbook: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ e_book_client_get_contact_sync (
+ E_BOOK_CLIENT (client), contact_uid, &contact, NULL, &error);
+ if (error != NULL) {
+ g_warning ("Error getting contact from addressbook: %s", error->message);
+ g_error_free (error);
+ g_object_unref (client);
+ return;
+ }
+
+ editor = e_contact_editor_new (
+ shell, E_BOOK_CLIENT (client), contact, FALSE, TRUE);
+
+ g_signal_connect (
+ editor, "contact-modified",
+ G_CALLBACK (contact_editor_contact_modified_cb), window);
+ g_signal_connect_swapped (
+ editor, "editor-closed",
+ G_CALLBACK (g_object_unref), editor);
+
+ eab_editor_show (editor);
+ g_object_unref (client);
+}
+#endif
+
+/* We need this function to he defined all the time. */
+static void
+action_address_book_map_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+#ifdef WITH_CONTACT_MAPS
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EContactMapWindow *map_window;
+ EBookShellSidebar *book_shell_sidebar;
+ ESource *source;
+ ESourceSelector *selector;
+ EClient *client;
+ EClientCache *client_cache;
+ GError *error = NULL;
+
+ book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+ source = e_source_selector_ref_primary_selection (selector);
+ g_return_if_fail (source != NULL);
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell = e_shell_backend_get_shell (shell_backend);
+ client_cache = e_shell_get_client_cache (shell);
+
+ /* FIXME This blocks. Needs to be asynchronous. */
+ client = e_client_cache_get_client_sync (
+ client_cache, source,
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ NULL, &error);
+
+ g_object_unref (source);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ if (error != NULL) {
+ g_warning ("Error loading addressbook: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ map_window = e_contact_map_window_new ();
+ e_contact_map_window_load_addressbook (
+ map_window, E_BOOK_CLIENT (client));
+
+ /* Free the map_window automatically when it is closed */
+ g_signal_connect_swapped (
+ map_window, "hide",
+ G_CALLBACK (gtk_widget_destroy), GTK_WIDGET (map_window));
+ g_signal_connect (
+ map_window, "show-contact-editor",
+ G_CALLBACK (map_window_show_contact_editor_cb), book_shell_view);
+
+ gtk_widget_show_all (GTK_WIDGET (map_window));
+
+ g_object_unref (client);
+#endif
+}
+
+static void
+action_address_book_rename_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellSidebar *book_shell_sidebar;
+ ESourceSelector *selector;
+
+ book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+ selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+
+ e_source_selector_edit_primary_selection (selector);
+}
+
+static void
+action_address_book_save_as_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EBookShellContent *book_shell_content;
+ EAddressbookModel *model;
+ EAddressbookView *view;
+ EActivity *activity;
+ EBookQuery *query;
+ EBookClient *book;
+ GSList *list = NULL;
+ GFile *file;
+ gchar *string;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_client (model);
+
+ query = e_book_query_any_field_contains ("");
+ string = e_book_query_to_string (query);
+ e_book_query_unref (query);
+
+ e_book_client_get_contacts_sync (book, string, &list, NULL, NULL);
+ g_free (string);
+
+ if (list == NULL)
+ goto exit;
+
+ string = eab_suggest_filename (list);
+ file = e_shell_run_save_dialog (
+ /* Translators: This is a save dialog title */
+ shell, _("Save as vCard"), string,
+ "*.vcf:text/x-vcard,text/directory", NULL, NULL);
+ g_free (string);
+
+ if (file == NULL)
+ goto exit;
+
+ string = eab_contact_list_to_string (list);
+ if (string == NULL) {
+ g_warning ("Could not convert contact list to a string");
+ g_object_unref (file);
+ goto exit;
+ }
+
+ /* XXX No callback means errors are discarded.
+ *
+ * There's an EAlert for this which I'm not using
+ * until I figure out a better way to display errors:
+ *
+ * "addressbook:save-error"
+ */
+ activity = e_file_replace_contents_async (
+ file, string, strlen (string), NULL, FALSE,
+ G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ /* Free the string when the activity is finalized. */
+ g_object_set_data_full (
+ G_OBJECT (activity),
+ "file-content", string,
+ (GDestroyNotify) g_free);
+
+ g_object_unref (file);
+
+exit:
+ g_slist_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+action_address_book_stop_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_stop (view);
+}
+
+static void
+action_contact_copy_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_copy_to_folder (view, FALSE);
+}
+
+static void
+action_contact_delete_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_selectable_delete_selection (E_SELECTABLE (view));
+}
+
+static void
+action_contact_find_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EPreviewPane *preview_pane;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ preview_pane = e_book_shell_content_get_preview_pane (book_shell_content);
+
+ e_preview_pane_show_search_bar (preview_pane);
+}
+
+static void
+action_contact_forward_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ GSList *list, *iter;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ list = e_addressbook_view_get_selected (view);
+ g_return_if_fail (list != NULL);
+
+ /* Convert the list of contacts to a list of destinations. */
+ for (iter = list; iter != NULL; iter = iter->next) {
+ EContact *contact = iter->data;
+ EDestination *destination;
+
+ destination = e_destination_new ();
+ e_destination_set_contact (destination, contact, 0);
+ g_object_unref (contact);
+
+ iter->data = destination;
+ }
+
+ eab_send_as_attachment (shell, list);
+
+ g_slist_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+action_contact_move_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_move_to_folder (view, FALSE);
+}
+
+static void
+action_contact_new_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ EContact *contact;
+ EABEditor *editor;
+ EBookClient *book;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_client (model);
+ g_return_if_fail (book != NULL);
+
+ contact = e_contact_new ();
+ editor = e_contact_editor_new (shell, book, contact, TRUE, TRUE);
+ eab_editor_show (editor);
+ g_object_unref (contact);
+}
+
+static void
+action_contact_new_list_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ EContact *contact;
+ EABEditor *editor;
+ EBookClient *book;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_client (model);
+ g_return_if_fail (book != NULL);
+
+ contact = e_contact_new ();
+ editor = e_contact_list_editor_new (shell, book, contact, TRUE, TRUE);
+ eab_editor_show (editor);
+ g_object_unref (contact);
+}
+
+static void
+action_contact_open_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_view (view);
+}
+
+static void
+action_contact_preview_cb (GtkToggleAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ gboolean visible;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ visible = gtk_toggle_action_get_active (action);
+ e_book_shell_content_set_preview_visible (book_shell_content, visible);
+}
+
+static void
+action_contact_preview_show_maps_cb (GtkToggleAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ gboolean show_maps;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ show_maps = gtk_toggle_action_get_active (action);
+ e_book_shell_content_set_preview_show_maps (book_shell_content, show_maps);
+}
+
+static void
+action_contact_print_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ GtkPrintOperationAction print_action;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_addressbook_view_print (view, TRUE, print_action);
+}
+
+static void
+action_contact_save_as_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EActivity *activity;
+ GSList *list;
+ GFile *file;
+ gchar *string;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ list = e_addressbook_view_get_selected (view);
+
+ if (list == NULL)
+ goto exit;
+
+ string = eab_suggest_filename (list);
+ file = e_shell_run_save_dialog (
+ /* Translators: This is a save dialog title */
+ shell, _("Save as vCard"), string,
+ "*.vcf:text/x-vcard,text/directory", NULL, NULL);
+ g_free (string);
+
+ if (file == NULL)
+ goto exit;
+
+ string = eab_contact_list_to_string (list);
+ if (string == NULL) {
+ g_warning ("Could not convert contact list to a string");
+ g_object_unref (file);
+ goto exit;
+ }
+
+ /* XXX No callback means errors are discarded.
+ *
+ * There's an EAlert for this which I'm not using
+ * until I figure out a better way to display errors:
+ *
+ * "addressbook:save-error"
+ */
+ activity = e_file_replace_contents_async (
+ file, string, strlen (string), NULL, FALSE,
+ G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ /* Free the string when the activity is finalized. */
+ g_object_set_data_full (
+ G_OBJECT (activity),
+ "file-content", string,
+ (GDestroyNotify) g_free);
+
+ g_object_unref (file);
+
+ exit:
+ g_slist_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+action_contact_send_message_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ GSList *list, *iter;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ list = e_addressbook_view_get_selected (view);
+ g_return_if_fail (list != NULL);
+
+ /* Convert the list of contacts to a list of destinations. */
+ for (iter = list; iter != NULL; iter = iter->next) {
+ EContact *contact = iter->data;
+ EDestination *destination;
+
+ destination = e_destination_new ();
+ e_destination_set_contact (destination, contact, 0);
+ g_object_unref (contact);
+
+ iter->data = destination;
+ }
+
+ eab_send_as_to (shell, list);
+
+ g_slist_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+action_contact_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ orientable = GTK_ORIENTABLE (book_shell_content);
+
+ switch (gtk_radio_action_get_current_value (action)) {
+ case 0:
+ orientation = GTK_ORIENTATION_VERTICAL;
+ break;
+ case 1:
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ gtk_orientable_set_orientation (orientable, orientation);
+}
+
+static GtkActionEntry contact_entries[] = {
+
+ { "address-book-copy",
+ GTK_STOCK_COPY,
+ N_("Co_py All Contacts To..."),
+ NULL,
+ N_("Copy the contacts of the selected address book to another"),
+ G_CALLBACK (action_address_book_copy_cb) },
+
+ { "address-book-delete",
+ GTK_STOCK_DELETE,
+ N_("D_elete Address Book"),
+ NULL,
+ N_("Delete the selected address book"),
+ G_CALLBACK (action_address_book_delete_cb) },
+
+ { "address-book-move",
+ "folder-move",
+ N_("Mo_ve All Contacts To..."),
+ NULL,
+ N_("Move the contacts of the selected address book to another"),
+ G_CALLBACK (action_address_book_move_cb) },
+
+ { "address-book-new",
+ "address-book-new",
+ N_("_New Address Book"),
+ NULL,
+ N_("Create a new address book"),
+ G_CALLBACK (action_address_book_new_cb) },
+
+ { "address-book-properties",
+ GTK_STOCK_PROPERTIES,
+ N_("Address _Book Properties"),
+ NULL,
+ N_("Show properties of the selected address book"),
+ G_CALLBACK (action_address_book_properties_cb) },
+
+ { "address-book-refresh",
+ GTK_STOCK_REFRESH,
+ N_("Re_fresh"),
+ NULL,
+ N_("Refresh the selected address book"),
+ G_CALLBACK (action_address_book_refresh_cb) },
+
+ { "address-book-map",
+ NULL,
+ N_("Address Book _Map"),
+ NULL,
+ N_("Show map with all contacts from selected address book"),
+ G_CALLBACK (action_address_book_map_cb) },
+
+ { "address-book-rename",
+ NULL,
+ N_("_Rename..."),
+ "F2",
+ N_("Rename the selected address book"),
+ G_CALLBACK (action_address_book_rename_cb) },
+
+ { "address-book-stop",
+ GTK_STOCK_STOP,
+ NULL,
+ NULL,
+ N_("Stop loading"),
+ G_CALLBACK (action_address_book_stop_cb) },
+
+ { "contact-copy",
+ NULL,
+ N_("_Copy Contact To..."),
+ "<Control><Shift>y",
+ N_("Copy selected contacts to another address book"),
+ G_CALLBACK (action_contact_copy_cb) },
+
+ { "contact-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete Contact"),
+ "<Control>d",
+ N_("Delete selected contacts"),
+ G_CALLBACK (action_contact_delete_cb) },
+
+ { "contact-find",
+ GTK_STOCK_FIND,
+ N_("_Find in Contact..."),
+ "<Shift><Control>f",
+ N_("Search for text in the displayed contact"),
+ G_CALLBACK (action_contact_find_cb) },
+
+ { "contact-forward",
+ "mail-forward",
+ N_("_Forward Contact..."),
+ NULL,
+ N_("Send selected contacts to another person"),
+ G_CALLBACK (action_contact_forward_cb) },
+
+ { "contact-move",
+ NULL,
+ N_("_Move Contact To..."),
+ "<Control><Shift>v",
+ N_("Move selected contacts to another address book"),
+ G_CALLBACK (action_contact_move_cb) },
+
+ { "contact-new",
+ "contact-new",
+ N_("_New Contact..."),
+ NULL,
+ N_("Create a new contact"),
+ G_CALLBACK (action_contact_new_cb) },
+
+ { "contact-new-list",
+ "stock_contact-list",
+ N_("New Contact _List..."),
+ NULL,
+ N_("Create a new contact list"),
+ G_CALLBACK (action_contact_new_list_cb) },
+
+ { "contact-open",
+ NULL,
+ N_("_Open Contact"),
+ "<Control>o",
+ N_("View the current contact"),
+ G_CALLBACK (action_contact_open_cb) },
+
+ { "contact-send-message",
+ "mail-message-new",
+ N_("_Send Message to Contact..."),
+ NULL,
+ N_("Send a message to the selected contacts"),
+ G_CALLBACK (action_contact_send_message_cb) },
+
+ /*** Menus ***/
+
+ { "contact-actions-menu",
+ NULL,
+ N_("_Actions"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "contact-preview-menu",
+ NULL,
+ N_("_Preview"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static EPopupActionEntry contact_popup_entries[] = {
+
+ { "address-book-popup-delete",
+ N_("_Delete"),
+ "address-book-delete" },
+
+ { "address-book-popup-properties",
+ N_("_Properties"),
+ "address-book-properties" },
+
+ { "address-book-popup-refresh",
+ NULL,
+ "address-book-refresh" },
+
+ { "address-book-popup-map",
+ N_("Address Book Map"),
+ "address-book-map" },
+
+ { "address-book-popup-rename",
+ NULL,
+ "address-book-rename" },
+
+ { "contact-popup-copy",
+ NULL,
+ "contact-copy" },
+
+ { "contact-popup-forward",
+ NULL,
+ "contact-forward" },
+
+ { "contact-popup-move",
+ NULL,
+ "contact-move" },
+
+ { "contact-popup-open",
+ NULL,
+ "contact-open" },
+
+ { "contact-popup-send-message",
+ NULL,
+ "contact-send-message" },
+};
+
+static GtkToggleActionEntry contact_toggle_entries[] = {
+
+ { "contact-preview",
+ NULL,
+ N_("Contact _Preview"),
+ "<Control>m",
+ N_("Show contact preview window"),
+ G_CALLBACK (action_contact_preview_cb),
+ TRUE },
+
+ { "contact-preview-show-maps",
+ NULL,
+ N_("Show _Maps"),
+ NULL,
+ N_("Show maps in contact preview window"),
+ G_CALLBACK (action_contact_preview_show_maps_cb),
+ FALSE }
+};
+
+static GtkRadioActionEntry contact_view_entries[] = {
+
+ /* This action represents the initial active contact view.
+ * It should not be visible in the UI, nor should it be
+ * possible to switch to it from another shell view. */
+ { "contact-view-initial",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ -1 },
+
+ { "contact-view-classic",
+ NULL,
+ N_("_Classic View"),
+ NULL,
+ N_("Show contact preview below the contact list"),
+ 0 },
+
+ { "contact-view-vertical",
+ NULL,
+ N_("_Vertical View"),
+ NULL,
+ N_("Show contact preview alongside the contact list"),
+ 1 }
+};
+
+static GtkRadioActionEntry contact_filter_entries[] = {
+
+ { "contact-filter-any-category",
+ NULL,
+ N_("Any Category"),
+ NULL,
+ NULL,
+ CONTACT_FILTER_ANY_CATEGORY },
+
+ { "contact-filter-unmatched",
+ NULL,
+ N_("Unmatched"),
+ NULL,
+ NULL,
+ CONTACT_FILTER_UNMATCHED }
+};
+
+static GtkRadioActionEntry contact_search_entries[] = {
+
+ { "contact-search-advanced-hidden",
+ NULL,
+ N_("Advanced Search"),
+ NULL,
+ NULL,
+ CONTACT_SEARCH_ADVANCED },
+
+ { "contact-search-any-field-contains",
+ NULL,
+ N_("Any field contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CONTACT_SEARCH_ANY_FIELD_CONTAINS },
+
+ { "contact-search-email-begins-with",
+ NULL,
+ N_("Email begins with"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CONTACT_SEARCH_EMAIL_BEGINS_WITH },
+
+ { "contact-search-name-contains",
+ NULL,
+ N_("Name contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CONTACT_SEARCH_NAME_CONTAINS }
+};
+
+static GtkActionEntry lockdown_printing_entries[] = {
+
+ { "address-book-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ "<Control>p",
+ N_("Print all shown contacts"),
+ G_CALLBACK (action_address_book_print_cb) },
+
+ { "address-book-print-preview",
+ GTK_STOCK_PRINT_PREVIEW,
+ NULL,
+ NULL,
+ N_("Preview the contacts to be printed"),
+ G_CALLBACK (action_address_book_print_preview_cb) },
+
+ { "contact-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ NULL,
+ N_("Print selected contacts"),
+ G_CALLBACK (action_contact_print_cb) }
+};
+
+static EPopupActionEntry lockdown_printing_popup_entries[] = {
+
+ { "contact-popup-print",
+ NULL,
+ "contact-print" }
+};
+
+static GtkActionEntry lockdown_save_to_disk_entries[] = {
+
+ { "address-book-save-as",
+ GTK_STOCK_SAVE_AS,
+ N_("S_ave Address Book as vCard"),
+ NULL,
+ N_("Save the contacts of the selected address book as a vCard"),
+ G_CALLBACK (action_address_book_save_as_cb) },
+
+ { "contact-save-as",
+ GTK_STOCK_SAVE_AS,
+ /* Translators: This is an action label */
+ N_("_Save as vCard..."),
+ NULL,
+ N_("Save selected contacts as a vCard"),
+ G_CALLBACK (action_contact_save_as_cb) }
+};
+
+static EPopupActionEntry lockdown_save_to_disk_popup_entries[] = {
+
+ { "address-book-popup-save-as",
+ /* Translators: This is an action label */
+ N_("_Save as vCard..."),
+ "address-book-save-as" },
+
+ { "contact-popup-save-as",
+ NULL,
+ "contact-save-as" }
+};
+
+void
+e_book_shell_view_actions_init (EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSearchbar *searchbar;
+ EPreviewPane *preview_pane;
+ EWebView *web_view;
+ GtkActionGroup *action_group;
+ GSettings *settings;
+ GtkAction *action;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ searchbar = e_book_shell_content_get_searchbar (book_shell_content);
+ preview_pane = e_book_shell_content_get_preview_pane (book_shell_content);
+ web_view = e_preview_pane_get_web_view (preview_pane);
+
+ /* Contact Actions */
+ action_group = ACTION_GROUP (CONTACTS);
+ gtk_action_group_add_actions (
+ action_group, contact_entries,
+ G_N_ELEMENTS (contact_entries), book_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, contact_popup_entries,
+ G_N_ELEMENTS (contact_popup_entries));
+ gtk_action_group_add_toggle_actions (
+ action_group, contact_toggle_entries,
+ G_N_ELEMENTS (contact_toggle_entries), book_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, contact_view_entries,
+ G_N_ELEMENTS (contact_view_entries), -1,
+ G_CALLBACK (action_contact_view_cb), book_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, contact_search_entries,
+ G_N_ELEMENTS (contact_search_entries),
+ -1, NULL, NULL);
+
+ /* Advanced Search Action */
+ action = ACTION (CONTACT_SEARCH_ADVANCED_HIDDEN);
+ gtk_action_set_visible (action, FALSE);
+ e_shell_searchbar_set_search_option (
+ searchbar, GTK_RADIO_ACTION (action));
+
+ /* Lockdown Printing Actions */
+ action_group = ACTION_GROUP (LOCKDOWN_PRINTING);
+ gtk_action_group_add_actions (
+ action_group, lockdown_printing_entries,
+ G_N_ELEMENTS (lockdown_printing_entries),
+ book_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, lockdown_printing_popup_entries,
+ G_N_ELEMENTS (lockdown_printing_popup_entries));
+
+ /* Lockdown Save-to-Disk Actions */
+ action_group = ACTION_GROUP (LOCKDOWN_SAVE_TO_DISK);
+ gtk_action_group_add_actions (
+ action_group, lockdown_save_to_disk_entries,
+ G_N_ELEMENTS (lockdown_save_to_disk_entries),
+ book_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, lockdown_save_to_disk_popup_entries,
+ G_N_ELEMENTS (lockdown_save_to_disk_popup_entries));
+
+ /* Bind GObject properties to GSettings keys. */
+
+ settings = g_settings_new ("org.gnome.evolution.addressbook");
+
+ g_settings_bind (
+ settings, "show-preview",
+ ACTION (CONTACT_PREVIEW), "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "layout",
+ ACTION (CONTACT_VIEW_VERTICAL), "current-value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "preview-show-maps",
+ ACTION (CONTACT_PREVIEW_SHOW_MAPS), "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_object_unref (settings);
+
+ /* Fine tuning. */
+
+ g_object_bind_property (
+ ACTION (CONTACT_PREVIEW), "active",
+ ACTION (CONTACT_VIEW_CLASSIC), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ ACTION (CONTACT_PREVIEW), "active",
+ ACTION (CONTACT_VIEW_VERTICAL), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ ACTION (CONTACT_PREVIEW), "active",
+ ACTION (CONTACT_PREVIEW_SHOW_MAPS), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_web_view_set_open_proxy (web_view, ACTION (CONTACT_OPEN));
+ e_web_view_set_print_proxy (web_view, ACTION (CONTACT_PRINT));
+ e_web_view_set_save_as_proxy (web_view, ACTION (CONTACT_SAVE_AS));
+
+#ifndef WITH_CONTACT_MAPS
+ gtk_action_set_visible (ACTION (CONTACT_PREVIEW_SHOW_MAPS), FALSE);
+ gtk_action_set_visible (ACTION (ADDRESS_BOOK_MAP), FALSE);
+ gtk_action_set_visible (ACTION (ADDRESS_BOOK_POPUP_MAP), FALSE);
+#endif
+}
+
+void
+e_book_shell_view_update_search_filter (EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSearchbar *searchbar;
+ EActionComboBox *combo_box;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GList *list, *iter;
+ GSList *group;
+ gint ii;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ action_group = ACTION_GROUP (CONTACTS_FILTER);
+ e_action_group_remove_all_actions (action_group);
+
+ /* Add the standard filter actions. No callback is needed
+ * because changes in the EActionComboBox are detected and
+ * handled by EShellSearchbar. */
+ gtk_action_group_add_radio_actions (
+ action_group, contact_filter_entries,
+ G_N_ELEMENTS (contact_filter_entries),
+ CONTACT_FILTER_ANY_CATEGORY, NULL, NULL);
+
+ /* Retrieve the radio group from an action we just added. */
+ list = gtk_action_group_list_actions (action_group);
+ radio_action = GTK_RADIO_ACTION (list->data);
+ group = gtk_radio_action_get_group (radio_action);
+ g_list_free (list);
+
+ /* Build the category actions. */
+
+ list = e_util_get_searchable_categories ();
+ for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) {
+ const gchar *category_name = iter->data;
+ const gchar *filename;
+ GtkAction *action;
+ gchar *action_name;
+
+ action_name = g_strdup_printf (
+ "contact-filter-category-%d", ii);
+ radio_action = gtk_radio_action_new (
+ action_name, category_name, NULL, NULL, ii);
+ g_free (action_name);
+
+ /* Convert the category icon file to a themed icon name. */
+ filename = e_categories_get_icon_file_for (category_name);
+ if (filename != NULL && *filename != '\0') {
+ gchar *basename;
+ gchar *cp;
+
+ basename = g_path_get_basename (filename);
+
+ /* Lose the file extension. */
+ if ((cp = strrchr (basename, '.')) != NULL)
+ *cp = '\0';
+
+ g_object_set (
+ radio_action, "icon-name", basename, NULL);
+
+ g_free (basename);
+ }
+
+ gtk_radio_action_set_group (radio_action, group);
+ group = gtk_radio_action_get_group (radio_action);
+
+ /* The action group takes ownership of the action. */
+ action = GTK_ACTION (radio_action);
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (radio_action);
+ }
+ g_list_free (list);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ searchbar = e_book_shell_content_get_searchbar (book_shell_content);
+ combo_box = e_shell_searchbar_get_filter_combo_box (searchbar);
+
+ e_shell_view_block_execute_search (shell_view);
+
+ /* Use any action in the group; doesn't matter which. */
+ e_action_combo_box_set_action (combo_box, radio_action);
+
+ ii = CONTACT_FILTER_UNMATCHED;
+ e_action_combo_box_add_separator_after (combo_box, ii);
+
+ e_shell_view_unblock_execute_search (shell_view);
+}
diff --git a/modules/addressbook/e-book-shell-view-actions.h b/modules/addressbook/e-book-shell-view-actions.h
new file mode 100644
index 0000000000..32bf261a7c
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-actions.h
@@ -0,0 +1,101 @@
+/*
+ * e-book-shell-view-actions.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_VIEW_ACTIONS_H
+#define E_BOOK_SHELL_VIEW_ACTIONS_H
+
+#include <shell/e-shell-window-actions.h>
+
+/* Address Book Actions */
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-copy")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-delete")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_MOVE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-move")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-print")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_PRINT_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-print-preview")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_PROPERTIES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-properties")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_REFRESH(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-refresh")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_RENAME(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-rename")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_SAVE_AS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-save-as")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_STOP(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-stop")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_MAP(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-map")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_POPUP_MAP(window) \
+ E_SHELL_WINDOW_ACTION ((window), "address-book-popup-map")
+
+/* Contact Actions */
+#define E_SHELL_WINDOW_ACTION_CONTACT_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-copy")
+#define E_SHELL_WINDOW_ACTION_CONTACT_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-delete")
+#define E_SHELL_WINDOW_ACTION_CONTACT_FIND(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-find")
+#define E_SHELL_WINDOW_ACTION_CONTACT_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-forward")
+#define E_SHELL_WINDOW_ACTION_CONTACT_MOVE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-move")
+#define E_SHELL_WINDOW_ACTION_CONTACT_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-new")
+#define E_SHELL_WINDOW_ACTION_CONTACT_NEW_LIST(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-new-list")
+#define E_SHELL_WINDOW_ACTION_CONTACT_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-open")
+#define E_SHELL_WINDOW_ACTION_CONTACT_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-preview")
+#define E_SHELL_WINDOW_ACTION_CONTACT_PREVIEW_SHOW_MAPS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-preview-show-maps")
+#define E_SHELL_WINDOW_ACTION_CONTACT_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-print")
+#define E_SHELL_WINDOW_ACTION_CONTACT_SAVE_AS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-save-as")
+#define E_SHELL_WINDOW_ACTION_CONTACT_SEND_MESSAGE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-send-message")
+#define E_SHELL_WINDOW_ACTION_CONTACT_VIEW_CLASSIC(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-view-classic")
+#define E_SHELL_WINDOW_ACTION_CONTACT_VIEW_VERTICAL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-view-vertical")
+
+/* Search Actions */
+#define E_SHELL_WINDOW_ACTION_CONTACT_SEARCH_ADVANCED_HIDDEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-search-advanced-hidden")
+#define E_SHELL_WINDOW_ACTION_CONTACT_SEARCH_ANY_FIELD_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-search-any-field-contains")
+#define E_SHELL_WINDOW_ACTION_CONTACT_SEARCH_EMAIL_BEGINS_WITH(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-search-email-begins-with")
+#define E_SHELL_WINDOW_ACTION_CONTACT_SEARCH_NAME_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-search-name-contains")
+
+/* Action Groups */
+#define E_SHELL_WINDOW_ACTION_GROUP_CONTACTS(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "contacts")
+#define E_SHELL_WINDOW_ACTION_GROUP_CONTACTS_FILTER(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "contacts-filter")
+
+#endif /* E_BOOK_SHELL_VIEW_ACTIONS_H */
diff --git a/modules/addressbook/e-book-shell-view-private.c b/modules/addressbook/e-book-shell-view-private.c
new file mode 100644
index 0000000000..b30ca5f04d
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-private.c
@@ -0,0 +1,609 @@
+/*
+ * e-book-shell-view-private.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-util/e-util-private.h"
+
+#include "e-book-shell-view-private.h"
+
+static void
+open_contact (EBookShellView *book_shell_view,
+ EContact *contact,
+ gboolean is_new_contact,
+ EAddressbookView *view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EAddressbookModel *model;
+ EABEditor *editor;
+ EBookClient *book;
+ gboolean editable;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_client (model);
+ editable = e_addressbook_model_get_editable (model);
+
+ if (e_contact_get (contact, E_CONTACT_IS_LIST))
+ editor = e_contact_list_editor_new (
+ shell, book, contact, is_new_contact, editable);
+ else
+ editor = e_contact_editor_new (
+ shell, book, contact, is_new_contact, editable);
+
+ eab_editor_show (editor);
+}
+
+static void
+popup_event (EBookShellView *book_shell_view,
+ GdkEvent *button_event)
+{
+ EShellView *shell_view;
+ const gchar *widget_path;
+
+ widget_path = "/contact-popup";
+ shell_view = E_SHELL_VIEW (book_shell_view);
+
+ e_shell_view_show_popup_menu (shell_view, widget_path, button_event);
+}
+
+static void
+book_shell_view_selection_change_foreach (gint row,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ EContact *contact;
+
+ /* XXX A "foreach" function is kind of a silly way to retrieve
+ * the one and only selected contact, but this is the only
+ * means that ESelectionModel provides. */
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ model = e_addressbook_view_get_model (view);
+ contact = e_addressbook_model_get_contact (model, row);
+
+ e_book_shell_content_set_preview_contact (book_shell_content, contact);
+ book_shell_view->priv->preview_index = row;
+
+ if (contact)
+ g_object_unref (contact);
+}
+
+static void
+selection_change (EBookShellView *book_shell_view,
+ EAddressbookView *view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *current_view;
+ ESelectionModel *selection_model;
+ EShellView *shell_view;
+ gint n_selected;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ current_view = e_book_shell_content_get_current_view (book_shell_content);
+
+ if (view != current_view)
+ return;
+
+ e_shell_view_update_actions (shell_view);
+
+ selection_model = e_addressbook_view_get_selection_model (view);
+
+ n_selected = (selection_model != NULL) ?
+ e_selection_model_selected_count (selection_model) : 0;
+
+ if (n_selected == 1)
+ e_selection_model_foreach (
+ selection_model, (EForeachFunc)
+ book_shell_view_selection_change_foreach,
+ book_shell_view);
+ else {
+ e_book_shell_content_set_preview_contact (
+ book_shell_content, NULL);
+ book_shell_view->priv->preview_index = -1;
+ }
+}
+
+static void
+contact_changed (EBookShellView *book_shell_view,
+ gint index,
+ EAddressbookModel *model)
+{
+ EBookShellContent *book_shell_content;
+ EContact *contact;
+
+ g_return_if_fail (E_IS_SHELL_VIEW (book_shell_view));
+ g_return_if_fail (book_shell_view->priv != NULL);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+
+ contact = e_addressbook_model_contact_at (model, index);
+
+ if (book_shell_view->priv->preview_index != index)
+ return;
+
+ /* Re-render the same contact. */
+ e_book_shell_content_set_preview_contact (book_shell_content, contact);
+}
+
+static void
+contacts_removed (EBookShellView *book_shell_view,
+ GArray *removed_indices,
+ EAddressbookModel *model)
+{
+ EBookShellContent *book_shell_content;
+ EContact *preview_contact;
+
+ g_return_if_fail (E_IS_SHELL_VIEW (book_shell_view));
+ g_return_if_fail (book_shell_view->priv != NULL);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+
+ preview_contact =
+ e_book_shell_content_get_preview_contact (book_shell_content);
+
+ if (preview_contact == NULL)
+ return;
+
+ /* Is the displayed contact still in the model? */
+ if (e_addressbook_model_find (model, preview_contact) < 0)
+ return;
+
+ /* If not, clear the contact display. */
+ e_book_shell_content_set_preview_contact (book_shell_content, NULL);
+ book_shell_view->priv->preview_index = -1;
+}
+
+static void
+model_query_changed_cb (EBookShellView *book_shell_view,
+ GParamSpec *param,
+ EAddressbookModel *model)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *current_view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ current_view = e_book_shell_content_get_current_view (book_shell_content);
+
+ if (!current_view || e_addressbook_view_get_model (current_view) != model)
+ return;
+
+ /* clear the contact preview when model's query changed */
+ e_book_shell_content_set_preview_contact (book_shell_content, NULL);
+ book_shell_view->priv->preview_index = -1;
+}
+
+static void
+book_shell_view_client_connect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EAddressbookView *view = user_data;
+ EClient *client;
+ EAddressbookModel *model;
+ GError *error = NULL;
+
+ client = e_client_selector_get_client_finish (
+ E_CLIENT_SELECTOR (source_object), result, &error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free (error);
+ goto exit;
+
+ } else if (error != NULL) {
+ EShellView *shell_view;
+ EShellContent *shell_content;
+ EAlertSink *alert_sink;
+ ESource *source;
+
+ source = e_addressbook_view_get_source (view);
+ shell_view = e_addressbook_view_get_shell_view (view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ alert_sink = E_ALERT_SINK (shell_content);
+
+ eab_load_error_dialog (NULL, alert_sink, source, error);
+
+ g_error_free (error);
+ goto exit;
+ }
+
+ model = e_addressbook_view_get_model (view);
+ e_addressbook_model_set_client (model, E_BOOK_CLIENT (client));
+ e_addressbook_model_force_folder_bar_message (model);
+
+exit:
+ g_object_unref (view);
+}
+
+static void
+book_shell_view_activate_selected_source (EBookShellView *book_shell_view,
+ ESourceSelector *selector)
+{
+ EShellView *shell_view;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ ESource *source;
+ GalViewInstance *view_instance;
+ GHashTable *hash_table;
+ GtkWidget *widget;
+ const gchar *uid;
+ gchar *view_id;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ source = e_source_selector_ref_primary_selection (selector);
+
+ if (source == NULL)
+ return;
+
+ uid = e_source_get_uid (source);
+ hash_table = book_shell_view->priv->uid_to_view;
+ widget = g_hash_table_lookup (hash_table, uid);
+
+ if (widget != NULL) {
+ view = E_ADDRESSBOOK_VIEW (widget);
+ model = e_addressbook_view_get_model (view);
+
+ } else {
+ /* Create a view for this UID. */
+ widget = e_addressbook_view_new (shell_view, source);
+ gtk_widget_show (widget);
+
+ /* Default searching options for a new view. */
+ e_addressbook_view_set_search (
+ E_ADDRESSBOOK_VIEW (widget),
+ CONTACT_FILTER_ANY_CATEGORY,
+ CONTACT_SEARCH_NAME_CONTAINS,
+ NULL, NULL);
+
+ e_book_shell_content_insert_view (
+ book_shell_content,
+ E_ADDRESSBOOK_VIEW (widget));
+
+ g_hash_table_insert (
+ hash_table, g_strdup (uid),
+ g_object_ref (widget));
+
+ g_signal_connect_object (
+ widget, "open-contact", G_CALLBACK (open_contact),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ widget, "popup-event", G_CALLBACK (popup_event),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ widget, "command-state-change",
+ G_CALLBACK (e_shell_view_update_actions),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ widget, "selection-change", G_CALLBACK (selection_change),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ view = E_ADDRESSBOOK_VIEW (widget);
+ model = e_addressbook_view_get_model (view);
+
+ g_signal_connect_object (
+ model, "contact-changed",
+ G_CALLBACK (contact_changed),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ model, "contacts-removed",
+ G_CALLBACK (contacts_removed),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ model, "notify::query",
+ G_CALLBACK (model_query_changed_cb),
+ book_shell_view, G_CONNECT_SWAPPED);
+ }
+
+ /* XXX No way to cancel this? */
+ e_client_selector_get_client (
+ E_CLIENT_SELECTOR (selector),
+ source, NULL,
+ book_shell_view_client_connect_cb,
+ g_object_ref (view));
+
+ e_book_shell_content_set_current_view (
+ book_shell_content, E_ADDRESSBOOK_VIEW (widget));
+
+ /* XXX We have to keep the addressbook selector informed of the
+ * current view so it can move contacts via drag-and-drop. */
+ e_addressbook_selector_set_current_view (
+ E_ADDRESSBOOK_SELECTOR (selector),
+ E_ADDRESSBOOK_VIEW (widget));
+
+ view_instance = e_addressbook_view_get_view_instance (view);
+
+ /* This must come after e_book_shell_content_set_current_view()
+ * because book_shell_view_notify_view_id_cb() relies on it. */
+ gal_view_instance_load (view_instance);
+
+ view_id = gal_view_instance_get_current_view_id (view_instance);
+ e_shell_view_set_view_id (shell_view, view_id);
+ g_free (view_id);
+
+ e_addressbook_model_force_folder_bar_message (model);
+ selection_change (book_shell_view, view);
+
+ g_object_unref (source);
+}
+
+static gboolean
+book_shell_view_show_popup_menu (GdkEvent *button_event,
+ EShellView *shell_view)
+{
+ const gchar *widget_path;
+
+ widget_path = "/address-book-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, button_event);
+
+ return TRUE;
+}
+
+static gboolean
+book_shell_view_selector_button_press_event_cb (EShellView *shell_view,
+ GdkEvent *button_event)
+{
+ guint event_button = 0;
+
+ /* XXX Use ESourceSelector's "popup-event" signal instead. */
+
+ gdk_event_get_button (button_event, &event_button);
+
+ if (button_event->type != GDK_BUTTON_PRESS || event_button != 3)
+ return FALSE;
+
+ return book_shell_view_show_popup_menu (button_event, shell_view);
+}
+
+static gboolean
+book_shell_view_selector_popup_menu_cb (EShellView *shell_view)
+{
+ /* XXX Use ESourceSelector's "popup-event" signal instead. */
+
+ return book_shell_view_show_popup_menu (NULL, shell_view);
+}
+
+static void
+book_shell_view_backend_error_cb (EClientCache *client_cache,
+ EClient *client,
+ EAlert *alert,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ ESource *source;
+ const gchar *extension_name;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+
+ source = e_client_get_source (client);
+ extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+
+ /* Only submit alerts from address book backends. */
+ if (e_source_has_extension (source, extension_name)) {
+ EAlertSink *alert_sink;
+
+ alert_sink = E_ALERT_SINK (book_shell_content);
+ e_alert_sink_submit_alert (alert_sink, alert);
+ }
+}
+
+static void
+book_shell_view_source_removed_cb (ESourceRegistry *registry,
+ ESource *source,
+ EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv = book_shell_view->priv;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ const gchar *uid;
+
+ uid = e_source_get_uid (source);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+
+ /* Remove the EAddressbookView for the deleted source. */
+ view = g_hash_table_lookup (priv->uid_to_view, uid);
+ if (view != NULL) {
+ e_book_shell_content_remove_view (book_shell_content, view);
+ g_hash_table_remove (priv->uid_to_view, uid);
+ }
+
+ e_shell_view_update_actions (E_SHELL_VIEW (book_shell_view));
+}
+
+static void
+book_shell_view_notify_view_id_cb (EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *address_view;
+ GalViewInstance *view_instance;
+ const gchar *view_id;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ address_view = e_book_shell_content_get_current_view (book_shell_content);
+ view_instance = e_addressbook_view_get_view_instance (address_view);
+ view_id = e_shell_view_get_view_id (E_SHELL_VIEW (book_shell_view));
+
+ /* A NULL view ID implies we're in a custom view. But you can
+ * only get to a custom view via the "Define Views" dialog, which
+ * would have already modified the view instance appropriately.
+ * Furthermore, there's no way to refer to a custom view by ID
+ * anyway, since custom views have no IDs. */
+ if (view_id == NULL)
+ return;
+
+ gal_view_instance_set_current_view_id (view_instance, view_id);
+}
+
+void
+e_book_shell_view_private_init (EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv = book_shell_view->priv;
+ GHashTable *uid_to_view;
+
+ uid_to_view = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ priv->uid_to_view = uid_to_view;
+ priv->preview_index = -1;
+
+ g_signal_connect (
+ book_shell_view, "notify::view-id",
+ G_CALLBACK (book_shell_view_notify_view_id_cb), NULL);
+}
+
+void
+e_book_shell_view_private_constructed (EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv = book_shell_view->priv;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellBackend *shell_backend;
+ ESourceSelector *selector;
+ gulong handler_id;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ e_shell_window_add_action_group (shell_window, "contacts");
+ e_shell_window_add_action_group (shell_window, "contacts-filter");
+
+ /* Cache these to avoid lots of awkward casting. */
+ priv->book_shell_backend = g_object_ref (shell_backend);
+ priv->book_shell_content = g_object_ref (shell_content);
+ priv->book_shell_sidebar = g_object_ref (shell_sidebar);
+
+ /* Keep our own reference to this so we can
+ * disconnect our signal handler in dispose(). */
+ priv->client_cache = g_object_ref (e_shell_get_client_cache (shell));
+
+ /* Keep our own reference to this so we can
+ * disconnect our signal handler in dispose(). */
+ priv->registry = g_object_ref (e_shell_get_registry (shell));
+
+ selector = e_book_shell_sidebar_get_selector (
+ E_BOOK_SHELL_SIDEBAR (shell_sidebar));
+
+ handler_id = g_signal_connect (
+ priv->client_cache, "backend-error",
+ G_CALLBACK (book_shell_view_backend_error_cb),
+ book_shell_view);
+ priv->backend_error_handler_id = handler_id;
+
+ handler_id = g_signal_connect (
+ priv->registry, "source-removed",
+ G_CALLBACK (book_shell_view_source_removed_cb),
+ book_shell_view);
+ priv->source_removed_handler_id = handler_id;
+
+ g_signal_connect_object (
+ selector, "button-press-event",
+ G_CALLBACK (book_shell_view_selector_button_press_event_cb),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ selector, "popup-menu",
+ G_CALLBACK (book_shell_view_selector_popup_menu_cb),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ selector, "primary-selection-changed",
+ G_CALLBACK (book_shell_view_activate_selected_source),
+ book_shell_view, G_CONNECT_SWAPPED);
+
+ e_categories_add_change_hook (
+ (GHookFunc) e_book_shell_view_update_search_filter,
+ book_shell_view);
+
+ e_book_shell_view_actions_init (book_shell_view);
+ book_shell_view_activate_selected_source (book_shell_view, selector);
+ e_book_shell_view_update_search_filter (book_shell_view);
+}
+
+void
+e_book_shell_view_private_dispose (EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv = book_shell_view->priv;
+
+ if (priv->backend_error_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->client_cache,
+ priv->backend_error_handler_id);
+ priv->backend_error_handler_id = 0;
+ }
+
+ if (priv->source_removed_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->registry,
+ priv->source_removed_handler_id);
+ priv->source_removed_handler_id = 0;
+ }
+
+ g_clear_object (&priv->book_shell_backend);
+ g_clear_object (&priv->book_shell_content);
+ g_clear_object (&priv->book_shell_sidebar);
+
+ g_clear_object (&priv->client_cache);
+ g_clear_object (&priv->registry);
+
+ g_hash_table_remove_all (priv->uid_to_view);
+}
+
+void
+e_book_shell_view_private_finalize (EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv = book_shell_view->priv;
+
+ g_hash_table_destroy (priv->uid_to_view);
+}
diff --git a/modules/addressbook/e-book-shell-view-private.h b/modules/addressbook/e-book-shell-view-private.h
new file mode 100644
index 0000000000..d5b6cc5c94
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-private.h
@@ -0,0 +1,120 @@
+/*
+ * e-book-shell-view-private.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_VIEW_PRIVATE_H
+#define E_BOOK_SHELL_VIEW_PRIVATE_H
+
+#include "e-book-shell-view.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <libebook/libebook.h>
+
+#include "shell/e-shell-content.h"
+#include "shell/e-shell-searchbar.h"
+#include "shell/e-shell-sidebar.h"
+#include "shell/e-shell-utils.h"
+
+#include "addressbook/util/eab-book-util.h"
+#include "addressbook/gui/contact-editor/e-contact-editor.h"
+#include "addressbook/gui/contact-list-editor/e-contact-list-editor.h"
+#include "addressbook/gui/widgets/eab-gui-util.h"
+#include "addressbook/gui/widgets/e-addressbook-view.h"
+#include "addressbook/gui/widgets/e-addressbook-selector.h"
+#include "addressbook/gui/widgets/e-contact-map-window.h"
+
+#include "e-book-shell-backend.h"
+#include "e-book-shell-content.h"
+#include "e-book-shell-sidebar.h"
+#include "e-book-shell-view-actions.h"
+
+#define E_BOOK_SHELL_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_BOOK_SHELL_VIEW, EBookShellViewPrivate))
+
+/* Shorthand, requires a variable named "shell_window". */
+#define ACTION(name) \
+ (E_SHELL_WINDOW_ACTION_##name (shell_window))
+#define ACTION_GROUP(name) \
+ (E_SHELL_WINDOW_ACTION_GROUP_##name (shell_window))
+
+/* ETable Specifications */
+#define ETSPEC_FILENAME "e-addressbook-view.etspec"
+
+G_BEGIN_DECLS
+
+/* List these in the order to be displayed.
+ * Positive values are reserved for categories. */
+enum {
+ CONTACT_FILTER_ANY_CATEGORY = -2,
+ CONTACT_FILTER_UNMATCHED = -1
+};
+
+/* List these in the order to be displayed. */
+enum {
+ CONTACT_SEARCH_ADVANCED = -1,
+ CONTACT_SEARCH_NAME_CONTAINS,
+ CONTACT_SEARCH_EMAIL_BEGINS_WITH,
+ CONTACT_SEARCH_ANY_FIELD_CONTAINS
+};
+
+struct _EBookShellViewPrivate {
+
+ /* These are just for convenience. */
+ EBookShellBackend *book_shell_backend;
+ EBookShellContent *book_shell_content;
+ EBookShellSidebar *book_shell_sidebar;
+
+ EClientCache *client_cache;
+ gulong backend_error_handler_id;
+
+ ESourceRegistry *registry;
+ gulong source_removed_handler_id;
+
+ GHashTable *uid_to_view;
+
+ gint preview_index;
+
+ /* Can track whether search changed while locked,
+ * but it is not usable at the moment. */
+ gint search_locked;
+};
+
+void e_book_shell_view_private_init
+ (EBookShellView *book_shell_view);
+void e_book_shell_view_private_constructed
+ (EBookShellView *book_shell_view);
+void e_book_shell_view_private_dispose
+ (EBookShellView *book_shell_view);
+void e_book_shell_view_private_finalize
+ (EBookShellView *book_shell_view);
+
+/* Private Utilities */
+
+void e_book_shell_view_actions_init
+ (EBookShellView *book_shell_view);
+void e_book_shell_view_update_search_filter
+ (EBookShellView *book_shell_view);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SHELL_VIEW_PRIVATE_H */
diff --git a/modules/addressbook/e-book-shell-view.c b/modules/addressbook/e-book-shell-view.c
new file mode 100644
index 0000000000..0c812413e2
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view.c
@@ -0,0 +1,430 @@
+/*
+ * e-book-shell-view.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-shell-view-private.h"
+
+#include "addressbook/gui/widgets/gal-view-minicard.h"
+
+G_DEFINE_DYNAMIC_TYPE (
+ EBookShellView,
+ e_book_shell_view,
+ E_TYPE_SHELL_VIEW);
+
+static void
+book_shell_view_dispose (GObject *object)
+{
+ EBookShellView *book_shell_view;
+
+ book_shell_view = E_BOOK_SHELL_VIEW (object);
+ e_book_shell_view_private_dispose (book_shell_view);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_book_shell_view_parent_class)->dispose (object);
+}
+
+static void
+book_shell_view_finalize (GObject *object)
+{
+ EBookShellView *book_shell_view;
+
+ book_shell_view = E_BOOK_SHELL_VIEW (object);
+ e_book_shell_view_private_finalize (book_shell_view);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_book_shell_view_parent_class)->finalize (object);
+}
+
+static void
+book_shell_view_constructed (GObject *object)
+{
+ EBookShellView *book_shell_view;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_book_shell_view_parent_class)->constructed (object);
+
+ book_shell_view = E_BOOK_SHELL_VIEW (object);
+ e_book_shell_view_private_constructed (book_shell_view);
+}
+
+static void
+book_shell_view_execute_search (EShellView *shell_view)
+{
+ EBookShellViewPrivate *priv;
+ EBookShellContent *book_shell_content;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ EShellSearchbar *searchbar;
+ EActionComboBox *combo_box;
+ GtkRadioAction *action;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ gchar *query;
+ gchar *temp;
+ gint filter_id, search_id;
+ gchar *search_text = NULL;
+ EFilterRule *advanced_search = NULL;
+
+ priv = E_BOOK_SHELL_VIEW_GET_PRIVATE (shell_view);
+
+ if (priv->search_locked)
+ return;
+
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ book_shell_content = E_BOOK_SHELL_CONTENT (shell_content);
+ searchbar = e_book_shell_content_get_searchbar (book_shell_content);
+
+ action = GTK_RADIO_ACTION (ACTION (CONTACT_SEARCH_ANY_FIELD_CONTAINS));
+ search_id = gtk_radio_action_get_current_value (action);
+
+ if (search_id == CONTACT_SEARCH_ADVANCED) {
+ query = e_shell_view_get_search_query (shell_view);
+
+ if (query == NULL)
+ query = g_strdup ("");
+
+ /* internal pointer, no need to free it */
+ advanced_search = e_shell_view_get_search_rule (shell_view);
+ } else {
+ const gchar *text;
+ const gchar *format;
+ GString *string;
+
+ text = e_shell_searchbar_get_search_text (searchbar);
+
+ if (text == NULL || *text == '\0') {
+ text = "";
+ search_id = CONTACT_SEARCH_ANY_FIELD_CONTAINS;
+ }
+
+ search_text = text && *text ? g_strdup (text) : NULL;
+
+ switch (search_id) {
+ case CONTACT_SEARCH_NAME_CONTAINS:
+ format = "(contains \"full_name\" %s)";
+ break;
+
+ case CONTACT_SEARCH_EMAIL_BEGINS_WITH:
+ format = "(beginswith \"email\" %s)";
+ break;
+
+ default:
+ text = "";
+ /* fall through */
+
+ case CONTACT_SEARCH_ANY_FIELD_CONTAINS:
+ format = "(contains \"x-evolution-any-field\" %s)";
+ break;
+ }
+
+ /* Build the query. */
+ string = g_string_new ("");
+ e_sexp_encode_string (string, text);
+ query = g_strdup_printf (format, string->str);
+ g_string_free (string, TRUE);
+ }
+
+ /* Apply selected filter. */
+ combo_box = e_shell_searchbar_get_filter_combo_box (searchbar);
+ filter_id = e_action_combo_box_get_current_value (combo_box);
+ switch (filter_id) {
+ case CONTACT_FILTER_ANY_CATEGORY:
+ break;
+
+ case CONTACT_FILTER_UNMATCHED:
+ temp = g_strdup_printf (
+ "(and (not (and (exists \"CATEGORIES\") "
+ "(not (is \"CATEGORIES\" \"\")))) %s)",
+ query);
+ g_free (query);
+ query = temp;
+ break;
+
+ default:
+ {
+ GList *categories;
+ const gchar *category_name;
+
+ categories = e_util_get_searchable_categories ();
+ category_name = g_list_nth_data (categories, filter_id);
+ g_list_free (categories);
+
+ temp = g_strdup_printf (
+ "(and (is \"category_list\" \"%s\") %s)",
+ category_name, query);
+ g_free (query);
+ query = temp;
+ }
+ }
+
+ /* Submit the query. */
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ model = e_addressbook_view_get_model (view);
+ e_addressbook_model_set_query (model, query);
+ e_addressbook_view_set_search (
+ view, filter_id, search_id, search_text, advanced_search);
+ g_free (query);
+ g_free (search_text);
+}
+
+static void
+book_shell_view_update_actions (EShellView *shell_view)
+{
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ GtkAction *action;
+ const gchar *label;
+ gboolean sensitive;
+ guint32 state;
+
+ /* Be descriptive. */
+ gboolean any_contacts_selected;
+ gboolean has_primary_source;
+ gboolean multiple_contacts_selected;
+ gboolean primary_source_is_writable;
+ gboolean primary_source_is_removable;
+ gboolean primary_source_is_remote_deletable;
+ gboolean primary_source_in_collection;
+ gboolean refresh_supported;
+ gboolean single_contact_selected;
+ gboolean selection_is_contact_list;
+ gboolean selection_has_email;
+ gboolean source_is_busy;
+ gboolean source_is_editable;
+
+ /* Chain up to parent's update_actions() method. */
+ E_SHELL_VIEW_CLASS (e_book_shell_view_parent_class)->
+ update_actions (shell_view);
+
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ state = e_shell_content_check_state (shell_content);
+
+ single_contact_selected =
+ (state & E_BOOK_SHELL_CONTENT_SELECTION_SINGLE);
+ multiple_contacts_selected =
+ (state & E_BOOK_SHELL_CONTENT_SELECTION_MULTIPLE);
+ selection_has_email =
+ (state & E_BOOK_SHELL_CONTENT_SELECTION_HAS_EMAIL);
+ selection_is_contact_list =
+ (state & E_BOOK_SHELL_CONTENT_SELECTION_IS_CONTACT_LIST);
+ source_is_busy =
+ (state & E_BOOK_SHELL_CONTENT_SOURCE_IS_BUSY);
+ source_is_editable =
+ (state & E_BOOK_SHELL_CONTENT_SOURCE_IS_EDITABLE);
+
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ state = e_shell_sidebar_check_state (shell_sidebar);
+
+ has_primary_source =
+ (state & E_BOOK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE);
+ primary_source_is_writable =
+ (state & E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE);
+ primary_source_is_removable =
+ (state & E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE);
+ primary_source_is_remote_deletable =
+ (state & E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE);
+ primary_source_in_collection =
+ (state & E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION);
+ refresh_supported =
+ (state & E_BOOK_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH);
+
+ any_contacts_selected =
+ (single_contact_selected || multiple_contacts_selected);
+
+ action = ACTION (ADDRESS_BOOK_MOVE);
+ sensitive = source_is_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_DELETE);
+ sensitive =
+ primary_source_is_removable ||
+ primary_source_is_remote_deletable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_PRINT);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_PRINT_PREVIEW);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_PROPERTIES);
+ sensitive = primary_source_is_writable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_REFRESH);
+ sensitive = refresh_supported;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_RENAME);
+ sensitive =
+ primary_source_is_writable &&
+ !primary_source_in_collection;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (ADDRESS_BOOK_STOP);
+ sensitive = source_is_busy;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_COPY);
+ sensitive = any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_DELETE);
+ sensitive = source_is_editable && any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_FIND);
+ sensitive = single_contact_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_FORWARD);
+ sensitive = any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+ if (multiple_contacts_selected)
+ label = _("_Forward Contacts");
+ else
+ label = _("_Forward Contact");
+ gtk_action_set_label (action, label);
+
+ action = ACTION (CONTACT_MOVE);
+ sensitive = source_is_editable && any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_NEW);
+ sensitive = source_is_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_NEW_LIST);
+ sensitive = source_is_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_OPEN);
+ sensitive = any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_PRINT);
+ sensitive = any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_SAVE_AS);
+ sensitive = any_contacts_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CONTACT_SEND_MESSAGE);
+ sensitive = any_contacts_selected && selection_has_email;
+ gtk_action_set_sensitive (action, sensitive);
+ if (multiple_contacts_selected)
+ label = _("_Send Message to Contacts");
+ else if (selection_is_contact_list)
+ label = _("_Send Message to List");
+ else
+ label = _("_Send Message to Contact");
+ gtk_action_set_label (action, label);
+}
+
+static void
+e_book_shell_view_class_finalize (EBookShellViewClass *class)
+{
+}
+
+static void
+e_book_shell_view_class_init (EBookShellViewClass *class)
+{
+ GObjectClass *object_class;
+ EShellViewClass *shell_view_class;
+
+ g_type_class_add_private (class, sizeof (EBookShellViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = book_shell_view_dispose;
+ object_class->finalize = book_shell_view_finalize;
+ object_class->constructed = book_shell_view_constructed;
+
+ shell_view_class = E_SHELL_VIEW_CLASS (class);
+ shell_view_class->label = _("Contacts");
+ shell_view_class->icon_name = "x-office-address-book";
+ shell_view_class->ui_definition = "evolution-contacts.ui";
+ shell_view_class->ui_manager_id = "org.gnome.evolution.contacts";
+ shell_view_class->search_options = "/contact-search-options";
+ shell_view_class->search_rules = "addresstypes.xml";
+ shell_view_class->new_shell_content = e_book_shell_content_new;
+ shell_view_class->new_shell_sidebar = e_book_shell_sidebar_new;
+ shell_view_class->execute_search = book_shell_view_execute_search;
+ shell_view_class->update_actions = book_shell_view_update_actions;
+
+ /* Ensure the GalView types we need are registered. */
+ g_type_ensure (GAL_TYPE_VIEW_ETABLE);
+ g_type_ensure (GAL_TYPE_VIEW_MINICARD);
+}
+
+static void
+e_book_shell_view_init (EBookShellView *book_shell_view)
+{
+ book_shell_view->priv =
+ E_BOOK_SHELL_VIEW_GET_PRIVATE (book_shell_view);
+
+ e_book_shell_view_private_init (book_shell_view);
+}
+
+void
+e_book_shell_view_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_book_shell_view_register_type (type_module);
+}
+
+void
+e_book_shell_view_disable_searching (EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv;
+
+ g_return_if_fail (book_shell_view != NULL);
+ g_return_if_fail (E_IS_BOOK_SHELL_VIEW (book_shell_view));
+
+ priv = book_shell_view->priv;
+ priv->search_locked++;
+}
+
+void
+e_book_shell_view_enable_searching (EBookShellView *book_shell_view)
+{
+ EBookShellViewPrivate *priv;
+
+ g_return_if_fail (book_shell_view != NULL);
+ g_return_if_fail (E_IS_BOOK_SHELL_VIEW (book_shell_view));
+
+ priv = book_shell_view->priv;
+ g_return_if_fail (priv->search_locked > 0);
+
+ priv->search_locked--;
+}
diff --git a/modules/addressbook/e-book-shell-view.h b/modules/addressbook/e-book-shell-view.h
new file mode 100644
index 0000000000..144effebba
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view.h
@@ -0,0 +1,69 @@
+/*
+ * e-book-shell-view.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_SHELL_VIEW_H
+#define E_BOOK_SHELL_VIEW_H
+
+#include <shell/e-shell-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_SHELL_VIEW \
+ (e_book_shell_view_get_type ())
+#define E_BOOK_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOOK_SHELL_VIEW, EBookShellView))
+#define E_BOOK_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_BOOK_SHELL_VIEW, EBookShellViewClass))
+#define E_IS_BOOK_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_BOOK_SHELL_VIEW))
+#define E_IS_BOOK_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_BOOK_SHELL_VIEW))
+#define E_BOOK_SHELL_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_BOOK_SHELL_VIEW, EBookShellViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookShellView EBookShellView;
+typedef struct _EBookShellViewClass EBookShellViewClass;
+typedef struct _EBookShellViewPrivate EBookShellViewPrivate;
+
+struct _EBookShellView {
+ EShellView parent;
+ EBookShellViewPrivate *priv;
+};
+
+struct _EBookShellViewClass {
+ EShellViewClass parent_class;
+};
+
+GType e_book_shell_view_get_type (void);
+void e_book_shell_view_type_register (GTypeModule *type_module);
+
+void e_book_shell_view_disable_searching (EBookShellView *book_shell_view);
+void e_book_shell_view_enable_searching (EBookShellView *book_shell_view);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SHELL_VIEW_H */
diff --git a/modules/addressbook/eab-composer-util.c b/modules/addressbook/eab-composer-util.c
new file mode 100644
index 0000000000..90af295ded
--- /dev/null
+++ b/modules/addressbook/eab-composer-util.c
@@ -0,0 +1,206 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "eab-composer-util.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libebook/libebook.h>
+
+#include "composer/e-msg-composer.h"
+#include "addressbook/util/eab-book-util.h"
+#include "addressbook/gui/widgets/eab-gui-util.h"
+
+void
+eab_send_as_to (EShell *shell,
+ GSList *destinations)
+{
+ EMsgComposer *composer;
+ EComposerHeaderTable *table;
+ GPtrArray *to_array;
+ GPtrArray *bcc_array;
+
+ union {
+ gpointer *pdata;
+ EDestination **destinations;
+ } convert;
+
+ g_return_if_fail (E_IS_SHELL (shell));
+
+ if (destinations == NULL)
+ return;
+
+ composer = e_msg_composer_new (shell);
+ table = e_msg_composer_get_header_table (composer);
+
+ to_array = g_ptr_array_new ();
+ bcc_array = g_ptr_array_new ();
+
+ /* Sort contacts into "To" and "Bcc" destinations. */
+ while (destinations != NULL) {
+ EDestination *destination = destinations->data;
+
+ if (e_destination_is_evolution_list (destination)) {
+ if (e_destination_list_show_addresses (destination))
+ g_ptr_array_add (to_array, destination);
+ else
+ g_ptr_array_add (bcc_array, destination);
+ } else
+ g_ptr_array_add (to_array, destination);
+
+ destinations = g_slist_next (destinations);
+ }
+
+ /* Add sentinels to each array. */
+ g_ptr_array_add (to_array, NULL);
+ g_ptr_array_add (bcc_array, NULL);
+
+ /* XXX Acrobatics like this make me question whether NULL-terminated
+ * arrays are really the best argument type for passing a list of
+ * destinations to the header table. */
+
+ /* Set "To" destinations. */
+ convert.pdata = to_array->pdata;
+ e_composer_header_table_set_destinations_to (
+ table, convert.destinations);
+ g_ptr_array_free (to_array, FALSE);
+
+ /* Add "Bcc" destinations. */
+ convert.pdata = bcc_array->pdata;
+ e_composer_header_table_add_destinations_bcc (
+ table, convert.destinations);
+ g_ptr_array_free (bcc_array, FALSE);
+
+ gtk_widget_show (GTK_WIDGET (composer));
+}
+
+static const gchar *
+get_email (EContact *contact,
+ EContactField field_id,
+ gchar **to_free)
+{
+ gchar *name = NULL, *mail = NULL;
+ const gchar *value = e_contact_get_const (contact, field_id);
+
+ *to_free = NULL;
+
+ if (eab_parse_qp_email (value, &name, &mail)) {
+ *to_free = g_strdup_printf ("%s <%s>", name, mail);
+ value = *to_free;
+ }
+
+ g_free (name);
+ g_free (mail);
+
+ return value;
+}
+
+void
+eab_send_as_attachment (EShell *shell,
+ GSList *destinations)
+{
+ EMsgComposer *composer;
+ EComposerHeaderTable *table;
+ CamelMimePart *attachment;
+ GSList *contacts, *iter;
+ gchar *data;
+
+ g_return_if_fail (E_IS_SHELL (shell));
+
+ if (destinations == NULL)
+ return;
+
+ composer = e_msg_composer_new (shell);
+ table = e_msg_composer_get_header_table (composer);
+
+ attachment = camel_mime_part_new ();
+
+ contacts = g_slist_copy (destinations);
+ for (iter = contacts; iter != NULL; iter = iter->next)
+ iter->data = e_destination_get_contact (iter->data);
+ data = eab_contact_list_to_string (contacts);
+ g_slist_free (contacts);
+
+ camel_mime_part_set_content (
+ attachment, data, strlen (data), "text/x-vcard");
+
+ if (destinations->next != NULL)
+ camel_mime_part_set_description (
+ attachment, _("Multiple vCards"));
+ else {
+ EContact *contact;
+ const gchar *file_as;
+ gchar *description;
+
+ contact = e_destination_get_contact (destinations->data);
+ file_as = e_contact_get_const (contact, E_CONTACT_FILE_AS);
+ description = g_strdup_printf (_("vCard for %s"), file_as);
+ camel_mime_part_set_description (attachment, description);
+ g_free (description);
+ }
+
+ camel_mime_part_set_disposition (attachment, "attachment");
+
+ e_msg_composer_attach (composer, attachment);
+ g_object_unref (attachment);
+
+ if (destinations->next != NULL)
+ e_composer_header_table_set_subject (
+ table, _("Contact information"));
+ else {
+ EContact *contact;
+ gchar *tempstr;
+ const gchar *tempstr2;
+ gchar *tempfree = NULL;
+
+ contact = e_destination_get_contact (destinations->data);
+ tempstr2 = e_contact_get_const (contact, E_CONTACT_FILE_AS);
+ if (!tempstr2 || !*tempstr2)
+ tempstr2 = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
+ if (!tempstr2 || !*tempstr2)
+ tempstr2 = e_contact_get_const (contact, E_CONTACT_ORG);
+ if (!tempstr2 || !*tempstr2) {
+ g_free (tempfree);
+ tempstr2 = get_email (contact, E_CONTACT_EMAIL_1, &tempfree);
+ }
+ if (!tempstr2 || !*tempstr2) {
+ g_free (tempfree);
+ tempstr2 = get_email (contact, E_CONTACT_EMAIL_2, &tempfree);
+ }
+ if (!tempstr2 || !*tempstr2) {
+ g_free (tempfree);
+ tempstr2 = get_email (contact, E_CONTACT_EMAIL_3, &tempfree);
+ }
+
+ if (!tempstr2 || !*tempstr2)
+ tempstr = g_strdup_printf (_("Contact information"));
+ else
+ tempstr = g_strdup_printf (_("Contact information for %s"), tempstr2);
+
+ e_composer_header_table_set_subject (table, tempstr);
+
+ g_free (tempstr);
+ g_free (tempfree);
+ }
+
+ gtk_widget_show (GTK_WIDGET (composer));
+}
diff --git a/modules/addressbook/eab-composer-util.h b/modules/addressbook/eab-composer-util.h
new file mode 100644
index 0000000000..b8644ab187
--- /dev/null
+++ b/modules/addressbook/eab-composer-util.h
@@ -0,0 +1,33 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EAB_COMPOSER_UTIL_H
+#define EAB_COMPOSER_UTIL_H
+
+#include <shell/e-shell.h>
+
+G_BEGIN_DECLS
+
+void eab_send_as_to (EShell *shell,
+ GSList *destinations);
+void eab_send_as_attachment (EShell *shell,
+ GSList *destinations);
+
+G_END_DECLS
+
+#endif /* EAB_COMPOSER_UTIL_H */
diff --git a/modules/addressbook/evolution-module-addressbook.c b/modules/addressbook/evolution-module-addressbook.c
new file mode 100644
index 0000000000..e8a6dafa39
--- /dev/null
+++ b/modules/addressbook/evolution-module-addressbook.c
@@ -0,0 +1,53 @@
+/*
+ * evolution-module-addressbook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-book-config-hook.h"
+
+#include "e-book-shell-view.h"
+#include "e-book-shell-backend.h"
+#include "e-book-shell-content.h"
+#include "e-book-shell-sidebar.h"
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ /* Register dynamically loaded types. */
+
+ e_book_config_hook_register_type (type_module);
+
+ e_book_shell_view_type_register (type_module);
+ e_book_shell_backend_type_register (type_module);
+ e_book_shell_content_type_register (type_module);
+ e_book_shell_sidebar_type_register (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/addressbook/openldap-extract.h b/modules/addressbook/openldap-extract.h
new file mode 100644
index 0000000000..d838e8b49b
--- /dev/null
+++ b/modules/addressbook/openldap-extract.h
@@ -0,0 +1,1449 @@
+/* This is extracted from the OpenLDAP sources.
+ *
+ * Stuff that isn't used in e-book-backend-ldap.c was dropped, like
+ * the LDAPSchemaExtensionItem stuff.
+ *
+ * This file basically has three parts:
+ *
+ * - some general macros from OpenLDAP that work as such on all
+ * implementations.
+ *
+ * - ldap_str2objectclass()
+ *
+ * - ldap_url_parse()
+ */
+
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2005 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file COPYING.OPENLDAP in
+ * the top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include <string.h>
+#include <assert.h>
+
+/* from various header files */
+
+#define LDAP_CONST const
+
+#define LDAP_PORT 389 /* ldap:/// default LDAP port */
+#define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
+
+#define LDAP_ROOT_DSE ""
+
+#define LDAP_SPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
+#define LDAP_DIGIT(c) ((c) >= '0' && (c) <= '9')
+
+#define LDAP_EXOP_START_TLS "1.3.6.1.4.1.1466.20037" /* RFC 2830 */
+
+#define LDAP_MALLOC(n) malloc((n))
+#define LDAP_CALLOC(n,s) calloc((n),(s))
+#define LDAP_REALLOC(p,s) realloc((p),(s))
+#define LDAP_FREE(p) free((p))
+#define LDAP_VFREE(p) vfree((gpointer *)(p))
+#define LDAP_STRDUP(s) strdup((s))
+
+#define LDAP_RANGE(n,x,y) (((x) <= (n)) && ((n) <= (y)))
+#define LDAP_NAME_ERROR(n) LDAP_RANGE((n),0x20,0x24) /* 32-34,36 */
+
+#define ldap_msgtype(lm) (lm)->lm_msgtype
+#define ldap_msgid(lm) (lm)->lm_msgid
+#ifndef LDAP_TYPE_OR_VALUE_EXISTS
+#define LDAP_TYPE_OR_VALUE_EXISTS 0x14
+#endif
+#ifndef LDAP_SCOPE_DEFAULT
+#define LDAP_SCOPE_DEFAULT -1
+#endif
+#ifndef LDAP_OPT_SUCCESS
+#define LDAP_OPT_SUCCESS 0x00
+#endif
+#ifndef LDAP_INSUFFICIENT_ACCESS
+#define LDAP_INSUFFICIENT_ACCESS 0x32
+#endif
+
+#define LDAP_SCHERR_OUTOFMEM 1
+#define LDAP_SCHERR_UNEXPTOKEN 2
+#define LDAP_SCHERR_NOLEFTPAREN 3
+#define LDAP_SCHERR_NORIGHTPAREN 4
+#define LDAP_SCHERR_NODIGIT 5
+#define LDAP_SCHERR_BADNAME 6
+#define LDAP_SCHERR_BADDESC 7
+#define LDAP_SCHERR_BADSUP 8
+#define LDAP_SCHERR_DUPOPT 9
+#define LDAP_SCHERR_EMPTY 10
+#define LDAP_SCHERR_MISSING 11
+#define LDAP_SCHERR_OUT_OF_ORDER 12
+
+#define LDAP_SCHEMA_YES 1
+
+#define LDAP_SCHEMA_ABSTRACT 0
+#define LDAP_SCHEMA_STRUCTURAL 1
+#define LDAP_SCHEMA_AUXILIARY 2
+
+#define LDAP_SCHEMA_ALLOW_NONE 0x00U /* Strict parsing */
+#define LDAP_SCHEMA_ALLOW_NO_OID 0x01U /* Allow missing oid */
+#define LDAP_SCHEMA_ALLOW_QUOTED 0x02U /* Allow bogus extra quotes */
+#define LDAP_SCHEMA_ALLOW_DESCR 0x04U /* Allow descr instead of OID */
+#define LDAP_SCHEMA_ALLOW_DESCR_PREFIX 0x08U /* Allow descr as OID prefix */
+#define LDAP_SCHEMA_ALLOW_OID_MACRO 0x10U /* Allow OID macros in slapd */
+#define LDAP_SCHEMA_ALLOW_OUT_OF_ORDER_FIELDS 0x20U /* Allow fields in most any order */
+#define LDAP_SCHEMA_ALLOW_ALL 0x3fU /* Be very liberal in parsing */
+#define LDAP_SCHEMA_SKIP 0x80U /* Don't malloc any result */
+
+typedef struct ldap_objectclass {
+ gchar *oc_oid; /* REQUIRED */
+ gchar **oc_names; /* OPTIONAL */
+ gchar *oc_desc; /* OPTIONAL */
+ gint oc_obsolete; /* 0=no, 1=yes */
+ gchar **oc_sup_oids; /* OPTIONAL */
+ gint oc_kind; /* 0=ABSTRACT, 1=STRUCTURAL, 2=AUXILIARY */
+ gchar **oc_at_oids_must; /* OPTIONAL */
+ gchar **oc_at_oids_may; /* OPTIONAL */
+} LDAPObjectClass;
+
+static void
+vfree (gpointer *vec)
+{
+ gint i;
+
+ for (i = 0; vec[i] != NULL; i++)
+ free (vec[i]);
+}
+
+/* from schema.c */
+
+/*
+ * Now come the parsers. There is one parser for each entity type:
+ * objectclasses, attributetypes, etc.
+ *
+ * Each of them is written as a recursive-descent parser, except that
+ * none of them is really recursive. But the idea is kept: there
+ * is one routine per non-terminal that eithers gobbles lexical tokens
+ * or calls lower-level routines, etc.
+ *
+ * The scanner is implemented in the routine get_token. Actually,
+ * get_token is more than a scanner and will return tokens that are
+ * in fact non-terminals in the grammar. So you can see the whole
+ * approach as the combination of a low-level bottom-up recognizer
+ * combined with a scanner and a number of top-down parsers. Or just
+ * consider that the real grammars recognized by the parsers are not
+ * those of the standards. As a matter of fact, our parsers are more
+ * liberal than the spec when there is no ambiguity.
+ *
+ * The difference is pretty academic (modulo bugs or incorrect
+ * interpretation of the specs).
+ */
+
+#define TK_NOENDQUOTE -2
+#define TK_OUTOFMEM -1
+#define TK_EOS 0
+#define TK_UNEXPCHAR 1
+#define TK_BAREWORD 2
+#define TK_QDSTRING 3
+#define TK_LEFTPAREN 4
+#define TK_RIGHTPAREN 5
+#define TK_DOLLAR 6
+#define TK_QDESCR TK_QDSTRING
+
+struct token {
+ gint type;
+ gchar *sval;
+};
+
+static gint
+get_token (const gchar **sp,
+ gchar **token_val)
+{
+ gint kind;
+ const gchar *p;
+ const gchar *q;
+ gchar *res;
+
+ *token_val = NULL;
+ switch (**sp) {
+ case '\0':
+ kind = TK_EOS;
+ (*sp)++;
+ break;
+ case '(':
+ kind = TK_LEFTPAREN;
+ (*sp)++;
+ break;
+ case ')':
+ kind = TK_RIGHTPAREN;
+ (*sp)++;
+ break;
+ case '$':
+ kind = TK_DOLLAR;
+ (*sp)++;
+ break;
+ case '\'':
+ kind = TK_QDSTRING;
+ (*sp)++;
+ p = *sp;
+ while (**sp != '\'' && **sp != '\0')
+ (*sp)++;
+ if (**sp == '\'') {
+ q = *sp;
+ res = LDAP_MALLOC (q - p + 1);
+ if (!res) {
+ kind = TK_OUTOFMEM;
+ } else {
+ strncpy (res,p,q - p);
+ res[q - p] = '\0';
+ *token_val = res;
+ }
+ (*sp)++;
+ } else {
+ kind = TK_NOENDQUOTE;
+ }
+ break;
+ default:
+ kind = TK_BAREWORD;
+ p = *sp;
+ while (!LDAP_SPACE (**sp) &&
+ **sp != '(' &&
+ **sp != ')' &&
+ **sp != '$' &&
+ **sp != '\'' &&
+ **sp != '\0')
+ (*sp)++;
+ q = *sp;
+ res = LDAP_MALLOC (q - p + 1);
+ if (!res) {
+ kind = TK_OUTOFMEM;
+ } else {
+ strncpy (res,p,q - p);
+ res[q - p] = '\0';
+ *token_val = res;
+ }
+ break;
+/* kind = TK_UNEXPCHAR; */
+/* break; */
+ }
+
+ return kind;
+}
+
+/* Gobble optional whitespace */
+static void
+parse_whsp (const gchar **sp)
+{
+ while (LDAP_SPACE (**sp))
+ (*sp)++;
+}
+
+/* Parse a sequence of dot-separated decimal strings */
+static gchar *
+ldap_int_parse_numericoid (const gchar **sp, gint *code, const gint flags)
+{
+ gchar *res = NULL;
+ const gchar *start = *sp;
+ gint len;
+ gint quoted = 0;
+
+ /* Netscape puts the SYNTAX value in quotes (incorrectly) */
+ if (flags & LDAP_SCHEMA_ALLOW_QUOTED && **sp == '\'') {
+ quoted = 1;
+ (*sp)++;
+ start++;
+ }
+ /* Each iteration of this loop gets one decimal string */
+ while (**sp) {
+ if (!LDAP_DIGIT (**sp)) {
+ /*
+ * Initial gchar is not a digit or gchar after dot is
+ * not a digit
+ */
+ *code = LDAP_SCHERR_NODIGIT;
+ return NULL;
+ }
+ (*sp)++;
+ while (LDAP_DIGIT (**sp))
+ (*sp)++;
+ if (**sp != '.')
+ break;
+ /* Otherwise, gobble the dot and loop again */
+ (*sp)++;
+ }
+ /* Now *sp points at the gchar past the numericoid. Perfect. */
+ len = *sp - start;
+ if (flags & LDAP_SCHEMA_ALLOW_QUOTED && quoted) {
+ if (**sp == '\'') {
+ (*sp)++;
+ } else {
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ return NULL;
+ }
+ }
+ if (flags & LDAP_SCHEMA_SKIP) {
+ res = (gchar *) start;
+ } else {
+ res = LDAP_MALLOC (len + 1);
+ if (!res) {
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return (NULL);
+ }
+ strncpy (res,start,len);
+ res[len] = '\0';
+ }
+ return (res);
+}
+
+/* Parse a qdescr or a list of them enclosed in () */
+static gchar **
+parse_qdescrs (const gchar **sp, gint *code)
+{
+ gchar ** res;
+ gchar ** res1;
+ gint kind;
+ gchar *sval;
+ gint size;
+ gint pos;
+
+ parse_whsp (sp);
+ kind = get_token (sp,&sval);
+ if (kind == TK_LEFTPAREN) {
+ /* Let's presume there will be at least 2 entries */
+ size = 3;
+ res = LDAP_CALLOC (3,sizeof (gchar *));
+ if (!res) {
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return NULL;
+ }
+ pos = 0;
+ while (1) {
+ parse_whsp (sp);
+ kind = get_token (sp,&sval);
+ if (kind == TK_RIGHTPAREN)
+ break;
+ if (kind == TK_QDESCR) {
+ if (pos == size - 2) {
+ size++;
+ res1 = LDAP_REALLOC (res,size *sizeof (gchar *));
+ if (!res1) {
+ LDAP_VFREE (res);
+ LDAP_FREE (sval);
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return (NULL);
+ }
+ res = res1;
+ }
+ res[pos++] = sval;
+ res[pos] = NULL;
+ parse_whsp (sp);
+ } else {
+ LDAP_VFREE (res);
+ LDAP_FREE (sval);
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ return (NULL);
+ }
+ }
+ parse_whsp (sp);
+ return (res);
+ } else if (kind == TK_QDESCR) {
+ res = LDAP_CALLOC (2,sizeof (gchar *));
+ if (!res) {
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return NULL;
+ }
+ res[0] = sval;
+ res[1] = NULL;
+ parse_whsp (sp);
+ return res;
+ } else {
+ LDAP_FREE (sval);
+ *code = LDAP_SCHERR_BADNAME;
+ return NULL;
+ }
+}
+
+/* Parse a woid or a $-separated list of them enclosed in () */
+static gchar **
+parse_oids (const gchar **sp, gint *code, const gint allow_quoted)
+{
+ gchar ** res;
+ gchar ** res1;
+ gint kind;
+ gchar *sval;
+ gint size;
+ gint pos;
+
+ /*
+ * Strictly speaking, doing this here accepts whsp before the
+ * ( at the begining of an oidlist, but this is harmless. Also,
+ * we are very liberal in what we accept as an OID. Maybe
+ * refine later.
+ */
+ parse_whsp (sp);
+ kind = get_token (sp,&sval);
+ if (kind == TK_LEFTPAREN) {
+ /* Let's presume there will be at least 2 entries */
+ size = 3;
+ res = LDAP_CALLOC (3,sizeof (gchar *));
+ if (!res) {
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return NULL;
+ }
+ pos = 0;
+ parse_whsp (sp);
+ kind = get_token (sp,&sval);
+ if (kind == TK_BAREWORD ||
+ (allow_quoted && kind == TK_QDSTRING)) {
+ res[pos++] = sval;
+ res[pos] = NULL;
+ } else {
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ LDAP_FREE (sval);
+ LDAP_VFREE (res);
+ return NULL;
+ }
+ parse_whsp (sp);
+ while (1) {
+ kind = get_token (sp,&sval);
+ if (kind == TK_RIGHTPAREN)
+ break;
+ if (kind == TK_DOLLAR) {
+ parse_whsp (sp);
+ kind = get_token (sp,&sval);
+ if (kind == TK_BAREWORD ||
+ (allow_quoted &&
+ kind == TK_QDSTRING)) {
+ if (pos == size - 2) {
+ size++;
+ res1 = LDAP_REALLOC (res,size *sizeof (gchar *));
+ if (!res1) {
+ LDAP_FREE (sval);
+ LDAP_VFREE (res);
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return (NULL);
+ }
+ res = res1;
+ }
+ res[pos++] = sval;
+ res[pos] = NULL;
+ } else {
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ LDAP_FREE (sval);
+ LDAP_VFREE (res);
+ return NULL;
+ }
+ parse_whsp (sp);
+ } else {
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ LDAP_FREE (sval);
+ LDAP_VFREE (res);
+ return NULL;
+ }
+ }
+ parse_whsp (sp);
+ return (res);
+ } else if (kind == TK_BAREWORD ||
+ (allow_quoted && kind == TK_QDSTRING)) {
+ res = LDAP_CALLOC (2,sizeof (gchar *));
+ if (!res) {
+ LDAP_FREE (sval);
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return NULL;
+ }
+ res[0] = sval;
+ res[1] = NULL;
+ parse_whsp (sp);
+ return res;
+ } else {
+ LDAP_FREE (sval);
+ *code = LDAP_SCHERR_BADNAME;
+ return NULL;
+ }
+}
+
+static void
+ldap_objectclass_free (LDAPObjectClass *oc)
+{
+ LDAP_FREE (oc->oc_oid);
+ if (oc->oc_names) LDAP_VFREE (oc->oc_names);
+ if (oc->oc_desc) LDAP_FREE (oc->oc_desc);
+ if (oc->oc_sup_oids) LDAP_VFREE (oc->oc_sup_oids);
+ if (oc->oc_at_oids_must) LDAP_VFREE (oc->oc_at_oids_must);
+ if (oc->oc_at_oids_may) LDAP_VFREE (oc->oc_at_oids_may);
+ LDAP_FREE (oc);
+}
+
+static LDAPObjectClass *
+ldap_str2objectclass (LDAP_CONST gchar *s,
+ gint *code,
+ LDAP_CONST gchar **errp,
+ LDAP_CONST unsigned flags)
+{
+ gint kind;
+ const gchar *ss = s;
+ gchar *sval;
+ gint seen_name = 0;
+ gint seen_desc = 0;
+ gint seen_obsolete = 0;
+ gint seen_sup = 0;
+ gint seen_kind = 0;
+ gint seen_must = 0;
+ gint seen_may = 0;
+ LDAPObjectClass *oc;
+ gchar ** ext_vals;
+ const gchar *savepos;
+
+ if (!s) {
+ *code = LDAP_SCHERR_EMPTY;
+ *errp = "";
+ return NULL;
+ }
+
+ *errp = s;
+ oc = LDAP_CALLOC (1,sizeof (LDAPObjectClass));
+
+ if (!oc) {
+ *code = LDAP_SCHERR_OUTOFMEM;
+ return NULL;
+ }
+ oc->oc_kind = LDAP_SCHEMA_STRUCTURAL;
+
+ kind = get_token (&ss,&sval);
+ if (kind != TK_LEFTPAREN) {
+ *code = LDAP_SCHERR_NOLEFTPAREN;
+ LDAP_FREE (sval);
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+
+ /*
+ * Definitions MUST begin with an OID in the numericoid format.
+ * However, this routine is used by clients to parse the response
+ * from servers and very well known servers will provide an OID
+ * in the wrong format or even no OID at all. We do our best to
+ * extract info from those servers.
+ */
+ parse_whsp (&ss);
+ savepos = ss;
+ oc->oc_oid = ldap_int_parse_numericoid (&ss,code,0);
+ if (!oc->oc_oid) {
+ if ((flags & LDAP_SCHEMA_ALLOW_ALL) && (ss == savepos)) {
+ /* Backtracking */
+ ss = savepos;
+ kind = get_token (&ss,&sval);
+ if (kind == TK_BAREWORD) {
+ if (!strcasecmp (sval, "NAME") ||
+ !strcasecmp (sval, "DESC") ||
+ !strcasecmp (sval, "OBSOLETE") ||
+ !strcasecmp (sval, "SUP") ||
+ !strcasecmp (sval, "ABSTRACT") ||
+ !strcasecmp (sval, "STRUCTURAL") ||
+ !strcasecmp (sval, "AUXILIARY") ||
+ !strcasecmp (sval, "MUST") ||
+ !strcasecmp (sval, "MAY") ||
+ !strncasecmp (sval, "X-", 2)) {
+ /* Missing OID, backtrack */
+ ss = savepos;
+ } else if (flags &
+ LDAP_SCHEMA_ALLOW_OID_MACRO) {
+ /* Non-numerical OID, ignore */
+ gint len = ss - savepos;
+ oc->oc_oid = LDAP_MALLOC (len + 1);
+ strncpy (oc->oc_oid, savepos, len);
+ oc->oc_oid[len] = 0;
+ }
+ }
+ LDAP_FREE (sval);
+ } else {
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ }
+ parse_whsp (&ss);
+
+ /*
+ * Beyond this point we will be liberal an accept the items
+ * in any order.
+ */
+ while (1) {
+ kind = get_token (&ss,&sval);
+ switch (kind) {
+ case TK_EOS:
+ *code = LDAP_SCHERR_NORIGHTPAREN;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ case TK_RIGHTPAREN:
+ return oc;
+ case TK_BAREWORD:
+ if (!strcasecmp (sval,"NAME")) {
+ LDAP_FREE (sval);
+ if (seen_name) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_name = 1;
+ oc->oc_names = parse_qdescrs (&ss,code);
+ if (!oc->oc_names) {
+ if (*code != LDAP_SCHERR_OUTOFMEM)
+ *code = LDAP_SCHERR_BADNAME;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ } else if (!strcasecmp (sval,"DESC")) {
+ LDAP_FREE (sval);
+ if (seen_desc) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_desc = 1;
+ parse_whsp (&ss);
+ kind = get_token (&ss,&sval);
+ if (kind != TK_QDSTRING) {
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ *errp = ss;
+ LDAP_FREE (sval);
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ oc->oc_desc = sval;
+ parse_whsp (&ss);
+ } else if (!strcasecmp (sval,"OBSOLETE")) {
+ LDAP_FREE (sval);
+ if (seen_obsolete) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_obsolete = 1;
+ oc->oc_obsolete = LDAP_SCHEMA_YES;
+ parse_whsp (&ss);
+ } else if (!strcasecmp (sval,"SUP")) {
+ LDAP_FREE (sval);
+ if (seen_sup) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_sup = 1;
+ oc->oc_sup_oids = parse_oids (&ss,
+ code,
+ flags);
+ if (!oc->oc_sup_oids) {
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ } else if (!strcasecmp (sval,"ABSTRACT")) {
+ LDAP_FREE (sval);
+ if (seen_kind) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_kind = 1;
+ oc->oc_kind = LDAP_SCHEMA_ABSTRACT;
+ parse_whsp (&ss);
+ } else if (!strcasecmp (sval,"STRUCTURAL")) {
+ LDAP_FREE (sval);
+ if (seen_kind) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_kind = 1;
+ oc->oc_kind = LDAP_SCHEMA_STRUCTURAL;
+ parse_whsp (&ss);
+ } else if (!strcasecmp (sval,"AUXILIARY")) {
+ LDAP_FREE (sval);
+ if (seen_kind) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_kind = 1;
+ oc->oc_kind = LDAP_SCHEMA_AUXILIARY;
+ parse_whsp (&ss);
+ } else if (!strcasecmp (sval,"MUST")) {
+ LDAP_FREE (sval);
+ if (seen_must) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_must = 1;
+ oc->oc_at_oids_must = parse_oids (&ss,code,0);
+ if (!oc->oc_at_oids_must) {
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ parse_whsp (&ss);
+ } else if (!strcasecmp (sval,"MAY")) {
+ LDAP_FREE (sval);
+ if (seen_may) {
+ *code = LDAP_SCHERR_DUPOPT;
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return (NULL);
+ }
+ seen_may = 1;
+ oc->oc_at_oids_may = parse_oids (&ss,code,0);
+ if (!oc->oc_at_oids_may) {
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ parse_whsp (&ss);
+ } else if (sval[0] == 'X' && sval[1] == '-') {
+ /* Should be parse_qdstrings */
+ ext_vals = parse_qdescrs (&ss, code);
+ if (!ext_vals) {
+ *errp = ss;
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+#if 0
+ if (add_extension (&oc->oc_extensions,
+ sval, ext_vals)) {
+ *code = LDAP_SCHERR_OUTOFMEM;
+ *errp = ss;
+ LDAP_FREE (sval);
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+#endif
+ } else {
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ *errp = ss;
+ LDAP_FREE (sval);
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ break;
+ default:
+ *code = LDAP_SCHERR_UNEXPTOKEN;
+ *errp = ss;
+ LDAP_FREE (sval);
+ ldap_objectclass_free (oc);
+ return NULL;
+ }
+ }
+}
+
+/* from utf-8.c */
+
+#define LDAP_UTF8_NEXT(p) g_utf8_next_char((p))
+#define LDAP_UTF8_INCR(p) ((p)=LDAP_UTF8_NEXT((p)))
+#define ldap_x_utf8_to_ucs4(str) g_utf8_get_char(str)
+
+static gchar *
+ldap_utf8_strchr (const gchar *str,
+ const gchar *chr)
+{
+ for (; *str != '\0'; LDAP_UTF8_INCR (str)) {
+ if (ldap_x_utf8_to_ucs4 (str) == ldap_x_utf8_to_ucs4 (chr)) {
+ return (gchar *) str;
+ }
+ }
+
+ return NULL;
+}
+
+static gsize
+ldap_utf8_strcspn (const gchar *str,
+ const gchar *set)
+{
+ const gchar *cstr;
+ const gchar *cset;
+
+ for (cstr = str; *cstr != '\0'; LDAP_UTF8_INCR (cstr)) {
+ for (cset = set; *cset != '\0'; LDAP_UTF8_INCR (cset)) {
+ if (ldap_x_utf8_to_ucs4 (cstr) == ldap_x_utf8_to_ucs4 (cset)) {
+ return cstr - str;
+ }
+ }
+ }
+
+ return cstr - str;
+}
+
+static gsize
+ldap_utf8_strspn (const gchar *str,
+ const gchar *set)
+{
+ const gchar *cstr;
+ const gchar *cset;
+
+ for (cstr = str; *cstr != '\0'; LDAP_UTF8_INCR (cstr)) {
+ for (cset = set; ; LDAP_UTF8_INCR (cset)) {
+ if (*cset == '\0') {
+ return cstr - str;
+ }
+
+ if (ldap_x_utf8_to_ucs4 (cstr) == ldap_x_utf8_to_ucs4 (cset)) {
+ break;
+ }
+ }
+ }
+
+ return cstr - str;
+}
+
+static gchar *ldap_utf8_strtok (gchar *str, const gchar *sep, gchar **last)
+{
+ gchar *begin;
+ gchar *end;
+
+ if (last == NULL) return NULL;
+
+ begin = str ? str : *last;
+
+ begin += ldap_utf8_strspn (begin, sep);
+
+ if (*begin == '\0') {
+ *last = NULL;
+ return NULL;
+ }
+
+ end = &begin[ ldap_utf8_strcspn (begin, sep) ];
+
+ if (*end != '\0') {
+ gchar *next = LDAP_UTF8_NEXT (end);
+ *end = '\0';
+ end = next;
+ }
+
+ *last = end;
+ return begin;
+}
+
+/* from ldap.h */
+
+#define LDAP_URL_SUCCESS 0x00 /* Success */
+#define LDAP_URL_ERR_MEM 0x01 /* can't allocate memory space */
+#define LDAP_URL_ERR_PARAM 0x02 /* parameter is bad */
+
+#define LDAP_URL_ERR_BADSCHEME 0x03 /* URL doesn't begin with "ldap[si]://" */
+#define LDAP_URL_ERR_BADENCLOSURE 0x04 /* URL is missing trailing ">" */
+#define LDAP_URL_ERR_BADURL 0x05 /* URL is bad */
+#define LDAP_URL_ERR_BADHOST 0x06 /* host port is bad */
+#define LDAP_URL_ERR_BADATTRS 0x07 /* bad (or missing) attributes */
+#define LDAP_URL_ERR_BADSCOPE 0x08 /* scope string is invalid (or missing) */
+#define LDAP_URL_ERR_BADFILTER 0x09 /* bad or missing filter */
+#define LDAP_URL_ERR_BADEXTS 0x0a /* bad or missing extensions */
+
+#define LDAP_URL_PREFIX "ldap://"
+#define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1)
+#define LDAPS_URL_PREFIX "ldaps://"
+#define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1)
+#define LDAPI_URL_PREFIX "ldapi://"
+#define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1)
+
+#define LDAP_URL_URLCOLON "URL:"
+#define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1)
+
+typedef struct ldap_url_desc {
+ struct ldap_url_desc *lud_next;
+ gchar *lud_scheme;
+ gchar *lud_host;
+ gint lud_port;
+ gchar *lud_dn;
+ gchar **lud_attrs;
+ gint lud_scope;
+ gchar *lud_filter;
+ gchar **lud_exts;
+ gint lud_crit_exts;
+} LDAPURLDesc;
+
+/* from url.c */
+
+static const gchar *
+skip_url_prefix (
+ const gchar *url,
+ gint *enclosedp,
+ const gchar **scheme)
+{
+ /*
+ * return non-zero if this looks like a LDAP URL; zero if not
+ * if non-zero returned, *urlp will be moved past "ldap://" part of URL
+ */
+ const gchar *p;
+
+ if (url == NULL) {
+ return (NULL);
+ }
+
+ p = url;
+
+ /* skip leading '<' (if any) */
+ if (*p == '<') {
+ *enclosedp = 1;
+ ++p;
+ } else {
+ *enclosedp = 0;
+ }
+
+ /* skip leading "URL:" (if any) */
+ if (strncasecmp (p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN) == 0) {
+ p += LDAP_URL_URLCOLON_LEN;
+ }
+
+ /* check for "ldap://" prefix */
+ if (strncasecmp (p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN) == 0) {
+ /* skip over "ldap://" prefix and return success */
+ p += LDAP_URL_PREFIX_LEN;
+ *scheme = "ldap";
+ return (p);
+ }
+
+ /* check for "ldaps://" prefix */
+ if (strncasecmp (p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN) == 0) {
+ /* skip over "ldaps://" prefix and return success */
+ p += LDAPS_URL_PREFIX_LEN;
+ *scheme = "ldaps";
+ return (p);
+ }
+
+ /* check for "ldapi://" prefix */
+ if (strncasecmp (p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN) == 0) {
+ /* skip over "ldapi://" prefix and return success */
+ p += LDAPI_URL_PREFIX_LEN;
+ *scheme = "ldapi";
+ return (p);
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ /* check for "cldap://" prefix */
+ if (strncasecmp (p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN) == 0) {
+ /* skip over "cldap://" prefix and return success */
+ p += LDAPC_URL_PREFIX_LEN;
+ *scheme = "cldap";
+ return (p);
+ }
+#endif
+
+ return (NULL);
+}
+
+static gint
+str2scope (const gchar *p)
+{
+ if (strcasecmp (p, "one") == 0) {
+ return LDAP_SCOPE_ONELEVEL;
+
+ } else if (strcasecmp (p, "onelevel") == 0) {
+ return LDAP_SCOPE_ONELEVEL;
+
+ } else if (strcasecmp (p, "base") == 0) {
+ return LDAP_SCOPE_BASE;
+
+ } else if (strcasecmp (p, "sub") == 0) {
+ return LDAP_SCOPE_SUBTREE;
+
+ } else if (strcasecmp (p, "subtree") == 0) {
+ return LDAP_SCOPE_SUBTREE;
+ }
+
+ return (-1);
+}
+
+static void
+ldap_free_urldesc (LDAPURLDesc *ludp)
+{
+ if (ludp == NULL) {
+ return;
+ }
+
+ if (ludp->lud_scheme != NULL) {
+ LDAP_FREE (ludp->lud_scheme);
+ }
+
+ if (ludp->lud_host != NULL) {
+ LDAP_FREE (ludp->lud_host);
+ }
+
+ if (ludp->lud_dn != NULL) {
+ LDAP_FREE (ludp->lud_dn);
+ }
+
+ if (ludp->lud_filter != NULL) {
+ LDAP_FREE (ludp->lud_filter);
+ }
+
+ if (ludp->lud_attrs != NULL) {
+ LDAP_VFREE (ludp->lud_attrs);
+ }
+
+ if (ludp->lud_exts != NULL) {
+ LDAP_VFREE (ludp->lud_exts);
+ }
+
+ LDAP_FREE (ludp);
+}
+
+static gint
+ldap_int_unhex (gint c)
+{
+ return (c >= '0' && c <= '9' ? c - '0'
+ : c >= 'A' && c <= 'F' ? c - 'A' + 10
+ : c - 'a' + 10);
+}
+
+static void
+ldap_pvt_hex_unescape (gchar *s)
+{
+ /*
+ * Remove URL hex escapes from s... done in place. The basic concept for
+ * this routine is borrowed from the WWW library HTUnEscape() routine.
+ */
+ gchar *p;
+
+ for (p = s; *s != '\0'; ++s) {
+ if (*s == '%') {
+ if (*++s == '\0') {
+ break;
+ }
+ *p = ldap_int_unhex(*s) << 4;
+ if (*++s == '\0') {
+ break;
+ }
+ *p++ += ldap_int_unhex(*s);
+ } else {
+ *p++ = *s;
+ }
+ }
+
+ *p = '\0';
+}
+
+static gchar **
+ldap_str2charray (const gchar *str_in,
+ const gchar *brkstr)
+{
+ gchar **res;
+ gchar *str, *s;
+ gchar *lasts;
+ gint i;
+
+ /* protect the input string from strtok */
+ str = LDAP_STRDUP (str_in);
+ if (str == NULL) {
+ return NULL;
+ }
+
+ i = 1;
+ for (s = str; *s; s++) {
+ if (ldap_utf8_strchr (brkstr, s) != NULL) {
+ i++;
+ }
+ }
+
+ res = (gchar **) LDAP_MALLOC ((i + 1) * sizeof (gchar *));
+
+ if (res == NULL) {
+ LDAP_FREE (str);
+ return NULL;
+ }
+
+ i = 0;
+
+ for (s = ldap_utf8_strtok (str, brkstr, &lasts);
+ s != NULL;
+ s = ldap_utf8_strtok (NULL, brkstr, &lasts))
+ {
+ res[i] = LDAP_STRDUP (s);
+
+ if (res[i] == NULL) {
+ for (--i; i >= 0; i--) {
+ LDAP_FREE (res[i]);
+ }
+ LDAP_FREE (res);
+ LDAP_FREE (str);
+ return NULL;
+ }
+
+ i++;
+ }
+
+ res[i] = NULL;
+
+ LDAP_FREE (str);
+ return (res);
+}
+
+static gint
+ldap_url_parse_ext (LDAP_CONST gchar *url_in,
+ LDAPURLDesc **ludpp)
+{
+/*
+ * Pick apart the pieces of an LDAP URL.
+ */
+
+ LDAPURLDesc *ludp;
+ gchar *p, *q, *r;
+ gint i, enclosed;
+ const gchar *scheme = NULL;
+ const gchar *url_tmp;
+ gchar *url;
+
+ if (url_in == NULL || ludpp == NULL) {
+ return LDAP_URL_ERR_PARAM;
+ }
+
+ *ludpp = NULL; /* pessimistic */
+
+ url_tmp = skip_url_prefix (url_in, &enclosed, &scheme);
+
+ if (url_tmp == NULL) {
+ return LDAP_URL_ERR_BADSCHEME;
+ }
+
+ assert (scheme);
+
+ /* make working copy of the remainder of the URL */
+ url = LDAP_STRDUP (url_tmp);
+ if (url == NULL) {
+ return LDAP_URL_ERR_MEM;
+ }
+
+ if (enclosed) {
+ p = &url[strlen (url) - 1];
+
+ if (*p != '>') {
+ LDAP_FREE (url);
+ return LDAP_URL_ERR_BADENCLOSURE;
+ }
+
+ *p = '\0';
+ }
+
+ /* allocate return struct */
+ ludp = (LDAPURLDesc *) LDAP_CALLOC (1, sizeof (LDAPURLDesc));
+
+ if (ludp == NULL) {
+ LDAP_FREE (url);
+ return LDAP_URL_ERR_MEM;
+ }
+
+ ludp->lud_next = NULL;
+ ludp->lud_host = NULL;
+ ludp->lud_port = 0;
+ ludp->lud_dn = NULL;
+ ludp->lud_attrs = NULL;
+ ludp->lud_filter = NULL;
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ ludp->lud_filter = NULL;
+ ludp->lud_exts = NULL;
+
+ ludp->lud_scheme = LDAP_STRDUP (scheme);
+
+ if (ludp->lud_scheme == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_MEM;
+ }
+
+ /* scan forward for '/' that marks end of hostport and begin. of dn */
+ p = strchr (url, '/');
+
+ if (p != NULL) {
+ /* terminate hostport; point to start of dn */
+ *p++ = '\0';
+ }
+
+ /* IPv6 syntax with [ip address]:port */
+ if (*url == '[') {
+ r = strchr (url, ']');
+ if (r == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADURL;
+ }
+ *r++ = '\0';
+ q = strchr (r, ':');
+ } else {
+ q = strchr (url, ':');
+ }
+
+ if (q != NULL) {
+ gchar *next;
+
+ *q++ = '\0';
+ ldap_pvt_hex_unescape (q);
+
+ if (*q == '\0') {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADURL;
+ }
+
+ ludp->lud_port = strtol (q, &next, 10);
+ if (next == NULL || next[0] != '\0') {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADURL;
+ }
+ }
+
+ ldap_pvt_hex_unescape (url);
+
+ /* If [ip address]:port syntax, url is [ip and we skip the [ */
+ ludp->lud_host = LDAP_STRDUP (url + (*url == '['));
+
+ if (ludp->lud_host == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_MEM;
+ }
+
+ /*
+ * Kludge. ldap://111.222.333.444:389??cn=abc,o=company
+ *
+ * On early Novell releases, search references/referrals were returned
+ * in this format, i.e., the dn was kind of in the scope position,
+ * but the required slash is missing. The whole thing is illegal syntax,
+ * but we need to account for it. Fortunately it can't be confused with
+ * anything real.
+ */
+ if ((p == NULL) && (q != NULL) && ((q = strchr (q, '?')) != NULL)) {
+ q++;
+ /* ? immediately followed by question */
+ if (*q == '?') {
+ q++;
+ if (*q != '\0') {
+ /* parse dn part */
+ ldap_pvt_hex_unescape (q);
+ ludp->lud_dn = LDAP_STRDUP (q);
+ } else {
+ ludp->lud_dn = LDAP_STRDUP ("");
+ }
+
+ if (ludp->lud_dn == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_MEM;
+ }
+ }
+ }
+
+ if (p == NULL) {
+ LDAP_FREE (url);
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of dn */
+ q = strchr (p, '?');
+
+ if (q != NULL) {
+ /* terminate dn part */
+ *q++ = '\0';
+ }
+
+ if (*p != '\0') {
+ /* parse dn part */
+ ldap_pvt_hex_unescape (p);
+ ludp->lud_dn = LDAP_STRDUP (p);
+ } else {
+ ludp->lud_dn = LDAP_STRDUP ("");
+ }
+
+ if (ludp->lud_dn == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_MEM;
+ }
+
+ if (q == NULL) {
+ /* no more */
+ LDAP_FREE (url);
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of attributes */
+ p = q;
+ q = strchr (p, '?');
+
+ if (q != NULL) {
+ /* terminate attributes part */
+ *q++ = '\0';
+ }
+
+ if (*p != '\0') {
+ /* parse attributes */
+ ldap_pvt_hex_unescape (p);
+ ludp->lud_attrs = ldap_str2charray (p, ",");
+
+ if (ludp->lud_attrs == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADATTRS;
+ }
+ }
+
+ if (q == NULL) {
+ /* no more */
+ LDAP_FREE (url);
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of scope */
+ p = q;
+ q = strchr (p, '?');
+
+ if (q != NULL) {
+ /* terminate the scope part */
+ *q++ = '\0';
+ }
+
+ if (*p != '\0') {
+ /* parse the scope */
+ ldap_pvt_hex_unescape (p);
+ ludp->lud_scope = str2scope (p);
+
+ if (ludp->lud_scope == -1) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADSCOPE;
+ }
+ }
+
+ if (q == NULL) {
+ /* no more */
+ LDAP_FREE (url);
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of filter */
+ p = q;
+ q = strchr (p, '?');
+
+ if (q != NULL) {
+ /* terminate the filter part */
+ *q++ = '\0';
+ }
+
+ if (*p != '\0') {
+ /* parse the filter */
+ ldap_pvt_hex_unescape (p);
+
+ if (!*p) {
+ /* missing filter */
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADFILTER;
+ }
+
+ LDAP_FREE (ludp->lud_filter);
+ ludp->lud_filter = LDAP_STRDUP (p);
+
+ if (ludp->lud_filter == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_MEM;
+ }
+ }
+
+ if (q == NULL) {
+ /* no more */
+ LDAP_FREE (url);
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of extensions */
+ p = q;
+ q = strchr (p, '?');
+
+ if (q != NULL) {
+ /* extra '?' */
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADURL;
+ }
+
+ /* parse the extensions */
+ ludp->lud_exts = ldap_str2charray (p, ",");
+
+ if (ludp->lud_exts == NULL) {
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADEXTS;
+ }
+
+ for (i = 0; ludp->lud_exts[i] != NULL; i++) {
+ ldap_pvt_hex_unescape (ludp->lud_exts[i]);
+
+ if (*ludp->lud_exts[i] == '!') {
+ /* count the number of critical extensions */
+ ludp->lud_crit_exts++;
+ }
+ }
+
+ if (i == 0) {
+ /* must have 1 or more */
+ LDAP_FREE (url);
+ ldap_free_urldesc (ludp);
+ return LDAP_URL_ERR_BADEXTS;
+ }
+
+ /* no more */
+ *ludpp = ludp;
+ LDAP_FREE (url);
+ return LDAP_URL_SUCCESS;
+}
+
+static gint
+ldap_url_parse (LDAP_CONST gchar *url_in,
+ LDAPURLDesc **ludpp)
+{
+ gint rc = ldap_url_parse_ext (url_in, ludpp);
+
+ if (rc != LDAP_URL_SUCCESS) {
+ return rc;
+ }
+
+ if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) {
+ (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
+ }
+
+ if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
+ LDAP_FREE ((*ludpp)->lud_host);
+ (*ludpp)->lud_host = NULL;
+ }
+
+ if ((*ludpp)->lud_port == 0) {
+ if (strcmp ((*ludpp)->lud_scheme, "ldap") == 0) {
+ (*ludpp)->lud_port = LDAP_PORT;
+#ifdef LDAP_CONNECTIONLESS
+ } else if (strcmp ((*ludpp)->lud_scheme, "cldap") == 0) {
+ (*ludpp)->lud_port = LDAP_PORT;
+#endif
+ } else if (strcmp ((*ludpp)->lud_scheme, "ldaps") == 0) {
+ (*ludpp)->lud_port = LDAPS_PORT;
+ }
+ }
+
+ return rc;
+}
+