diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2009-06-25 00:59:33 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2009-06-25 06:29:22 +0800 |
commit | f0d3f3afdfa314e1e8cd7d8da790878008a46aad (patch) | |
tree | 7116e2a18c7bd50633b2f8de42b5377af1e8172a /modules/addressbook | |
parent | 94302ea73cde0b470faad653f752406f19f202d7 (diff) | |
download | gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.tar gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.tar.gz gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.tar.bz2 gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.tar.lz gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.tar.xz gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.tar.zst gsoc2013-evolution-f0d3f3afdfa314e1e8cd7d8da790878008a46aad.zip |
Radically reorganize source code.
- Collect all shell modules into a new top-level 'modules' directory:
$(top_srcdir)/modules/addressbook
$(top_srcdir)/modules/calendar
$(top_srcdir)/modules/mail
Nothing is allowed to link to these, not plugins nor other modules.
THIS SOLVES BUG #571275 AND OPENS THE DOOR TO PORTING TO MAC OS X.
- Mimic the libevolution-mail-shared library from master (except drop
the "shared" suffix) and have libevolution-mail-importers and all
mail-related plugins link to it.
- Discard the a11y subdirectories and have the files live alongside
their counterpart widgets.
Diffstat (limited to 'modules/addressbook')
25 files changed, 9907 insertions, 0 deletions
diff --git a/modules/addressbook/Makefile.am b/modules/addressbook/Makefile.am new file mode 100644 index 0000000000..960df59e09 --- /dev/null +++ b/modules/addressbook/Makefile.am @@ -0,0 +1,129 @@ +if OS_WIN32 +WIN32_BOOTSTRAP_LIBS = $(top_builddir)/win32/libevolution-mail.la +endif + +INCLUDES = \ + -DG_LOG_DOMAIN=\"evolution-addressbook\" \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -I$(top_srcdir)/shell \ + -I$(top_builddir)/shell \ + -I$(top_srcdir)/widgets/menus \ + -I$(top_srcdir)/widgets/misc \ + -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 \ + -I$(top_srcdir)/a11y/addressbook \ + -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \ + -DEVOLUTION_GALVIEWSDIR=\""$(viewsdir)"\" \ + -DEVOLUTION_GLADEDIR=\""$(gladedir)"\" \ + -DEVOLUTION_LOCALEDIR=\""$(localedir)"\" \ + -DEVOLUTION_UIDIR=\""$(evolutionuidir)"\" \ + -DPREFIX=\""$(prefix)"\" \ + $(LDAP_CFLAGS) \ + $(EVOLUTION_ADDRESSBOOK_CFLAGS) + +module_LTLIBRARIES = libevolution-module-addressbook.la + +libevolution_module_addressbook_la_SOURCES = \ + evolution-module-addressbook.c \ + addressbook-config.c \ + addressbook-config.h \ + autocompletion-config.c \ + autocompletion-config.h \ + eab-composer-util.c \ + eab-composer-util.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 + +libevolution_module_addressbook_la_LIBADD = \ + $(SMIME_LIB) \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/composer/libcomposer.la \ + $(top_builddir)/addressbook/printing/libecontactprint.la \ + $(top_builddir)/shell/libeshell.la \ + $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \ + $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \ + $(top_builddir)/filter/libfilter.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)/widgets/table/libetable.la \ + $(top_builddir)/widgets/text/libetext.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ + $(top_builddir)/widgets/menus/libmenus.la \ + $(top_builddir)/addressbook/importers/libevolution-addressbook-importers.la \ + $(EVOLUTION_ADDRESSBOOK_LIBS) $(LDAP_LIBS) + + +libevolution_module_addressbook_la_LDFLAGS = \ + -module -avoid-version $(NO_UNDEFINED) + +# GConf schemas + +schemadir = $(GCONF_SCHEMA_FILE_DIR) +schema_in_files = apps_evolution_addressbook.schemas.in +schema_DATA = $(schema_in_files:.schemas.in=.schemas) + +@INTLTOOL_SCHEMAS_RULE@ + +if OS_WIN32 +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + (echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE); \ + echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat; \ + cmd /c _temp.bat; \ + done \ + fi +else +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $$p; \ + done \ + fi +endif + +glade_DATA = \ + ldap-config.glade + +DISTCLEANFILES = $(schema_DATA) + +EXTRA_DIST = \ + $(glade_DATA) \ + $(schema_in_files) \ + 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/addressbook-config.c b/modules/addressbook/addressbook-config.c new file mode 100644 index 0000000000..e663de03da --- /dev/null +++ b/modules/addressbook/addressbook-config.c @@ -0,0 +1,1250 @@ +/* + * 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> + * Chris Lahey <clahey@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * And no doubt others ... + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +/*#define STANDALONE*/ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> +#include <sys/time.h> + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#ifdef G_OS_WIN32 +/* Include <windows.h> early and work around DATADIR lossage */ +#define DATADIR crap_DATADIR +#include <windows.h> +#undef DATADIR +#endif + +#include <glade/glade.h> + +#include "addressbook.h" +#include "addressbook-config.h" + +#include "e-util/e-error.h" +#include "e-util/e-util-private.h" + +#include "addressbook/gui/widgets/eab-config.h" + +#define d(x) + +#ifdef HAVE_LDAP +#ifndef G_OS_WIN32 +#include <ldap.h> +#ifndef SUNLDAP +#include <ldap_schema.h> +#endif +#else +#include <winldap.h> +#include "openldap-extract.h" +#endif +#endif + +#define LDAP_PORT_STRING "389" +#define LDAPS_PORT_STRING "636" + +#define GLADE_FILE_NAME "ldap-config.glade" + +GtkWidget* supported_bases_create_table (gchar *name, gchar *string1, gchar *string2, + gint num1, gint num2); + +/* default objectclasses */ +#define TOP "top" +#define PERSON "person" +#define ORGANIZATIONALPERSON "organizationalPerson" +#define INETORGPERSON "inetOrgPerson" +#define EVOLUTIONPERSON "evolutionPerson" +#define CALENTRY "calEntry" + + +typedef struct _AddressbookSourceDialog AddressbookSourceDialog; + +struct _AddressbookSourceDialog { + GladeXML *gui; + + EABConfig *config; /* the config manager */ + + GtkWidget *window; + + /* Source selection (druid only) */ + ESourceList *source_list; + GSList *menu_source_groups; + + /* ESource we're currently editing */ + ESource *source; + /* The original source in edit mode. Also used to flag when we are in edit mode. */ + ESource *original_source; + + /* Source group we're creating/editing a source in */ + ESourceGroup *source_group; + + /* info page fields */ + GtkWidget *host; + GtkWidget *auth_combobox; + AddressbookLDAPAuthType auth; + GtkWidget *auth_principal; + + /* connecting page fields */ + GtkWidget *port_comboentry; + GtkWidget *ssl_combobox; + AddressbookLDAPSSLType ssl; + + /* searching page fields */ + GtkWidget *rootdn; + AddressbookLDAPScopeType scope; + GtkWidget *scope_combobox; + GtkWidget *search_filter; + GtkWidget *timeout_scale; + GtkWidget *limit_spinbutton; + GtkWidget *canbrowsecheck; + + /* display name page fields */ + GtkWidget *display_name; +}; + + + +#ifdef HAVE_LDAP + +static const gchar * +ldap_unparse_auth (AddressbookLDAPAuthType auth_type) +{ + switch (auth_type) { + case ADDRESSBOOK_LDAP_AUTH_NONE: + return "none"; + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_EMAIL: + return "ldap/simple-email"; + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_BINDDN: + return "ldap/simple-binddn"; + default: + g_return_val_if_reached ("none"); + } +} + +static AddressbookLDAPAuthType +ldap_parse_auth (const gchar *auth) +{ + if (!auth) + return ADDRESSBOOK_LDAP_AUTH_NONE; + + if (!strcmp (auth, "ldap/simple-email") || !strcmp (auth, "simple")) + return ADDRESSBOOK_LDAP_AUTH_SIMPLE_EMAIL; + else if (!strcmp (auth, "ldap/simple-binddn")) + return ADDRESSBOOK_LDAP_AUTH_SIMPLE_BINDDN; + else + return ADDRESSBOOK_LDAP_AUTH_NONE; +} + +static const gchar * +ldap_unparse_scope (AddressbookLDAPScopeType scope_type) +{ + switch (scope_type) { + case ADDRESSBOOK_LDAP_SCOPE_BASE: + return "base"; + case ADDRESSBOOK_LDAP_SCOPE_ONELEVEL: + return "one"; + case ADDRESSBOOK_LDAP_SCOPE_SUBTREE: + return "sub"; + default: + g_return_val_if_reached (""); + } +} + +static const gchar * +ldap_unparse_ssl (AddressbookLDAPSSLType ssl_type) +{ + switch (ssl_type) { + case ADDRESSBOOK_LDAP_SSL_NEVER: + return "never"; + case ADDRESSBOOK_LDAP_SSL_WHENEVER_POSSIBLE: + return "whenever_possible"; + case ADDRESSBOOK_LDAP_SSL_ALWAYS: + return "always"; + default: + g_return_val_if_reached (""); + } +} + +static AddressbookLDAPSSLType +ldap_parse_ssl (const gchar *ssl) +{ + if (!ssl) + return ADDRESSBOOK_LDAP_SSL_WHENEVER_POSSIBLE; /* XXX good default? */ + + if (!strcmp (ssl, "always")) + return ADDRESSBOOK_LDAP_SSL_ALWAYS; + else if (!strcmp (ssl, "never")) + return ADDRESSBOOK_LDAP_SSL_NEVER; + else + return ADDRESSBOOK_LDAP_SSL_WHENEVER_POSSIBLE; +} + +static const gchar * +ldap_get_ssl_tooltip (AddressbookLDAPSSLType ssl_type) +{ + switch (ssl_type) { + case ADDRESSBOOK_LDAP_SSL_ALWAYS: + return _("Selecting this option means that Evolution will only connect to your LDAP server if your LDAP server supports SSL."); + case ADDRESSBOOK_LDAP_SSL_WHENEVER_POSSIBLE: + return _("Selecting this option means that Evolution will only connect to your LDAP server if your LDAP server supports TLS."); + case ADDRESSBOOK_LDAP_SSL_NEVER: + return _("Selecting this option means that your server does not support either SSL or TLS. This means that your connection will be insecure, and that you will be vulnerable to security exploits."); + } + + return NULL; +} + +static gboolean +source_to_uri_parts (ESource *source, gchar **host, gchar **rootdn, AddressbookLDAPScopeType *scope, gchar **search_filter, gint *port) +{ + gchar *uri; + LDAPURLDesc *lud; + gint ldap_error; + + g_return_val_if_fail (source, FALSE); + + uri = e_source_get_uri (source); + ldap_error = ldap_url_parse ((gchar *) uri, &lud); + g_free (uri); + + if (ldap_error != LDAP_SUCCESS) + return FALSE; + + if (host) + *host = g_strdup (lud->lud_host ? lud->lud_host : ""); + if (rootdn) + *rootdn = g_strdup (lud->lud_dn ? lud->lud_dn : ""); + if (port) + *port = lud->lud_port ? lud->lud_port : LDAP_PORT; + if (scope) + *scope = lud->lud_scope == LDAP_SCOPE_BASE ? ADDRESSBOOK_LDAP_SCOPE_BASE : + lud->lud_scope == LDAP_SCOPE_ONELEVEL ? ADDRESSBOOK_LDAP_SCOPE_ONELEVEL : + lud->lud_scope == LDAP_SCOPE_SUBTREE ? ADDRESSBOOK_LDAP_SCOPE_SUBTREE : + ADDRESSBOOK_LDAP_SCOPE_ONELEVEL; + if (search_filter && lud->lud_filter) + *search_filter = g_strdup (lud->lud_filter); + + ldap_free_urldesc (lud); + return TRUE; +} + +static gboolean +source_group_is_remote (ESourceGroup *group) +{ + return strncmp ("ldap:", e_source_group_peek_base_uri (group), 5) == 0; +} + +/* ldap api foo */ +static LDAP * +addressbook_ldap_init (GtkWidget *window, ESource *source) +{ + LDAP *ldap; + gchar *host; + gint port; + gint ldap_error; + gint protocol_version = LDAP_VERSION3; + + if (!source_to_uri_parts (source, &host, NULL, NULL, NULL, &port)) + return NULL; + + if (!(ldap = ldap_init (host, port))) { + e_error_run ((GtkWindow *) window, "addressbook:ldap-init", NULL); + goto done; + } + + ldap_error = ldap_set_option (ldap, LDAP_OPT_PROTOCOL_VERSION, &protocol_version); + if (LDAP_SUCCESS != ldap_error) + g_warning ("failed to set protocol version to LDAPv3"); + + /* XXX do TLS if it's configured in */ + + done: + g_free (host); + return ldap; +} + +static gint +addressbook_ldap_auth (GtkWidget *window, LDAP *ldap) +{ + gint ldap_error; + + /* XXX use auth info from source */ + ldap_error = ldap_simple_bind_s (ldap, NULL, NULL); + if (LDAP_SUCCESS != ldap_error) + e_error_run ((GtkWindow *) window, "addressbook:ldap-auth", NULL); + + return ldap_error; +} + +static gint +addressbook_root_dse_query (AddressbookSourceDialog *dialog, LDAP *ldap, + const gchar **attrs, LDAPMessage **resp) +{ + gint ldap_error; + struct timeval timeout; + + timeout.tv_sec = (gint) gtk_adjustment_get_value (GTK_RANGE(dialog->timeout_scale)->adjustment); + timeout.tv_usec = 0; + + ldap_error = ldap_search_ext_s (ldap, + LDAP_ROOT_DSE, LDAP_SCOPE_BASE, + "(objectclass=*)", + (gchar **) attrs, 0, NULL, NULL, &timeout, LDAP_NO_LIMIT, resp); + if (LDAP_SUCCESS != ldap_error) + e_error_run (GTK_WINDOW (dialog->window), "addressbook:ldap-search-base", NULL); + + return ldap_error; +} + +/* searching page */ +GtkWidget* +supported_bases_create_table (gchar *name, gchar *string1, gchar *string2, gint num1, gint num2) +{ + GtkWidget *table, *scrolled; + GtkTreeSelection *selection; + GtkCellRenderer *renderer; + GtkListStore *model; + + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN); + + model = gtk_list_store_new (1, G_TYPE_STRING); + table = gtk_tree_view_new_with_model ((GtkTreeModel *) model); + g_object_unref (model); + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes ((GtkTreeView *) table, -1, _("Base"), renderer, "text", 0, NULL); + gtk_tree_view_set_headers_visible ((GtkTreeView *) table, FALSE); + selection = gtk_tree_view_get_selection ((GtkTreeView *) table); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + gtk_container_add (GTK_CONTAINER (scrolled), table); + g_object_set_data((GObject *)scrolled, "table", table); + + return scrolled; +} + +static gboolean +do_ldap_root_dse_query (AddressbookSourceDialog *sdialog, GtkListStore *model, ESource *source) +{ + LDAP *ldap; + const gchar *attrs[2]; + gint ldap_error; + gchar **values; + LDAPMessage *resp; + gint i; + + ldap = addressbook_ldap_init (sdialog->window, source); + if (!ldap) + return FALSE; + + if (LDAP_SUCCESS != addressbook_ldap_auth (sdialog->window, ldap)) + goto fail; + + attrs[0] = "namingContexts"; + attrs[1] = NULL; + + ldap_error = addressbook_root_dse_query (sdialog, ldap, attrs, &resp); + + if (ldap_error != LDAP_SUCCESS) + goto fail; + + values = ldap_get_values (ldap, resp, "namingContexts"); + if (!values || values[0] == NULL || strlen (values[0]) == 0) { + e_error_run (GTK_WINDOW (sdialog->window), "addressbook:ldap-search-base", NULL); + goto fail; + } + + for (i = 0; values[i]; i++) { + GtkTreeIter iter; + + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, 0, values[i], -1); + } + + ldap_value_free (values); + ldap_unbind_s (ldap); + return TRUE; + + fail: + ldap_unbind_s (ldap); + return FALSE; +} + +static void +search_base_selection_model_changed (GtkTreeSelection *selection, GtkWidget *dialog) +{ + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + gtk_tree_selection_get_selected(selection, NULL, NULL)); +} + +static void +query_for_supported_bases (GtkWidget *button, AddressbookSourceDialog *sdialog) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeView *table; + GtkWidget *dialog; + GtkWidget *supported_bases_table; + GladeXML *gui; + GtkTreeIter iter; + gchar *gladefile; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + gui = glade_xml_new (gladefile, "supported-bases-dialog", NULL); + g_free (gladefile); + + dialog = glade_xml_get_widget (gui, "supported-bases-dialog"); + + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (sdialog->window)); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + gtk_widget_ensure_style (dialog); + gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), 0); + gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)->action_area), 12); + + supported_bases_table = glade_xml_get_widget (gui, "supported-bases-table"); + gtk_widget_show_all (supported_bases_table); + + table = g_object_get_data (G_OBJECT (supported_bases_table), "table"); + model = gtk_tree_view_get_model (table); + selection = gtk_tree_view_get_selection (table); + g_signal_connect (selection, "changed", G_CALLBACK (search_base_selection_model_changed), dialog); + search_base_selection_model_changed (selection, dialog); + + if (do_ldap_root_dse_query (sdialog, GTK_LIST_STORE (model), sdialog->source)) { + gtk_widget_show (dialog); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK + && gtk_tree_selection_get_selected (selection, &model, &iter)) { + gchar *dn; + + gtk_tree_model_get (model, &iter, 0, &dn, -1); + gtk_entry_set_text((GtkEntry *)sdialog->rootdn, dn); + g_free(dn); + } + } + + gtk_widget_destroy (dialog); +} + +#endif /* HAVE_LDAP */ + +GtkWidget* +addressbook_config_create_new_source (GtkWidget *parent) +{ + return addressbook_config_edit_source(parent, NULL); +} + +/* ********************************************************************** */ + +static void +eabc_type_changed(GtkComboBox *dropdown, AddressbookSourceDialog *sdialog) +{ + gint id = gtk_combo_box_get_active(dropdown); + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_combo_box_get_model(dropdown); + if (id == -1 || !gtk_tree_model_iter_nth_child(model, &iter, NULL, id)) + return; + + /* TODO: when we change the group type, we lose all of the pre-filled dialog info */ + + gtk_tree_model_get(model, &iter, 1, &sdialog->source_group, -1); + /* HACK: doesn't work if you don't do this */ + e_source_set_absolute_uri(sdialog->source, NULL); + e_source_set_group(sdialog->source, sdialog->source_group); + + /* BIG HACK: We load the defaults for each type here. + I guess plugins will have to use the do it in their factory callbacks */ + if (!strncmp(e_source_group_peek_base_uri(sdialog->source_group), "groupwise:", 10)) { + GSList *l; + ESource *source; + gchar *tmp; + + l = e_source_group_peek_sources(sdialog->source_group); + if (l && l->data ) { + source = l->data; + e_source_set_property(sdialog->source, "auth", e_source_get_property(source, "auth")); + e_source_set_property(sdialog->source, "user", e_source_get_property(source, "user")); + e_source_set_property(sdialog->source, "user_ssl", e_source_get_property(source, "use_ssl")); + } + + e_source_set_property(sdialog->source, "auth-domain", "Groupwise"); + tmp = g_strconcat (";", e_source_peek_name(sdialog->source), NULL); + e_source_set_relative_uri (sdialog->source, tmp); + g_free (tmp); +#ifdef HAVE_LDAP + } else if (!strncmp(e_source_group_peek_base_uri(sdialog->source_group), "ldap:", 5)) { + gchar *tmp; + + tmp = g_strdup_printf ("%s:%s/%s?" /* trigraph prevention */ "?%s", + "", LDAP_PORT_STRING, + "", + "one"); + e_source_set_relative_uri (sdialog->source, tmp); + g_free (tmp); + e_source_set_property(sdialog->source, "timeout", "3"); + e_source_set_property(sdialog->source, "limit", "100"); +#endif + } else { + e_source_set_relative_uri (sdialog->source, e_source_peek_uid (sdialog->source)); + } + + e_config_target_changed((EConfig *)sdialog->config, E_CONFIG_TARGET_CHANGED_REBUILD); +} + +static GtkWidget * +eabc_general_type(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + GtkComboBox *dropdown; + GtkCellRenderer *cell; + GtkListStore *store; + GtkTreeIter iter; + GSList *l; + GtkWidget *w, *label; + gint i, row = 0; + + if (old) + return old; + + w = gtk_hbox_new(FALSE, 6); + label = gtk_label_new_with_mnemonic(_("_Type:")); + gtk_box_pack_start((GtkBox *)w, label, FALSE, FALSE, 0); + + dropdown = (GtkComboBox *)gtk_combo_box_new(); + cell = gtk_cell_renderer_text_new(); + store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER); + i = 0; + for (l=sdialog->menu_source_groups;l;l=g_slist_next(l)) { + ESourceGroup *group = l->data; + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, e_source_group_peek_name(group), 1, group, -1); + if (e_source_peek_group(sdialog->source) == group) + row = i; + i++; + } + + gtk_cell_layout_pack_start((GtkCellLayout *)dropdown, cell, TRUE); + gtk_cell_layout_set_attributes((GtkCellLayout *)dropdown, cell, "text", 0, NULL); + gtk_combo_box_set_model(dropdown, (GtkTreeModel *)store); + gtk_combo_box_set_active(dropdown, -1); + gtk_combo_box_set_active(dropdown, row); + g_signal_connect(dropdown, "changed", G_CALLBACK(eabc_type_changed), sdialog); + gtk_widget_show((GtkWidget *)dropdown); + gtk_box_pack_start((GtkBox *)w, (GtkWidget *)dropdown, TRUE, TRUE, 0); + gtk_label_set_mnemonic_widget((GtkLabel *)label, (GtkWidget *)dropdown); + + gtk_box_pack_start((GtkBox *)parent, (GtkWidget *)w, FALSE, FALSE, 0); + + gtk_widget_show_all(w); + + return (GtkWidget *)w; +} + +static void +name_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + e_source_set_name (sdialog->source, gtk_entry_get_text (GTK_ENTRY (sdialog->display_name))); +} + +static void +offline_status_changed_cb (GtkWidget *widget, AddressbookSourceDialog *sdialog) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + e_source_set_property (sdialog->source, "offline_sync", "1"); + else + e_source_set_property (sdialog->source, "offline_sync", "0"); + +} + +static GtkWidget * +eabc_general_name(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + const gchar *uri; + GtkWidget *w; + GladeXML *gui; + gchar *gladefile; + + if (old) + return old; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + gui = glade_xml_new (gladefile, item->label, NULL); + g_free (gladefile); + + w = glade_xml_get_widget(gui, item->label); + gtk_box_pack_start((GtkBox *)parent, w, FALSE, FALSE, 0); + + sdialog->display_name = glade_xml_get_widget (gui, "account-editor-display-name-entry"); + g_signal_connect(sdialog->display_name, "changed", G_CALLBACK(name_changed_cb), sdialog); + gtk_entry_set_text((GtkEntry *)sdialog->display_name, e_source_peek_name(sdialog->source)); + + /* Hardcoded: groupwise can't edit the name (or anything else) */ + if (sdialog->original_source) { + uri = e_source_group_peek_base_uri (sdialog->source_group); + if (uri && strncmp(uri, "groupwise:", 10) == 0) { + gtk_widget_set_sensitive (GTK_WIDGET(sdialog->display_name), FALSE); + } + } + + g_object_unref(gui); + + return w; +} + + +static GtkWidget * +eabc_general_offline(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + GtkWidget *offline_setting; + const gchar *offline_sync; + gboolean is_local_book; + + is_local_book = g_str_has_prefix (e_source_group_peek_base_uri (sdialog->source_group), "file:"); + offline_sync = e_source_get_property (sdialog->source, "offline_sync"); + if (old) + return old; + else { + offline_setting = gtk_check_button_new_with_mnemonic (_("Copy _book content locally for offline operation")); + gtk_widget_show (offline_setting); + gtk_container_add (GTK_CONTAINER (parent), offline_setting); + g_signal_connect (offline_setting, "toggled", G_CALLBACK (offline_status_changed_cb), sdialog); + + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (offline_setting), (offline_sync && g_str_equal (offline_sync, "1")) ? TRUE : FALSE); + if (is_local_book) + gtk_widget_hide (offline_setting); + return offline_setting; + +} + +#ifdef HAVE_LDAP +static gchar * +form_ldap_search_filter (GtkWidget *w) +{ + gchar *filter; + const gchar *search_filter = gtk_entry_get_text ((GtkEntry *) w); + + /* this function can be used to format the search filter entered */ + if ((strlen (search_filter) !=0) && *search_filter != '(' && *(search_filter + (strlen (search_filter-1))) != ')') + filter = g_strdup_printf ("(%s)", search_filter); + else + filter = g_strdup_printf ("%s", search_filter); + + return filter; +} + +static void +url_changed(AddressbookSourceDialog *sdialog) +{ + gchar *str, *search_filter; + + search_filter = form_ldap_search_filter (sdialog->search_filter); + str = g_strdup_printf ("%s:%s/%s?" /* trigraph prevention */ "?%s?%s", + gtk_entry_get_text (GTK_ENTRY (sdialog->host)), + gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (sdialog->port_comboentry)))), + gtk_entry_get_text (GTK_ENTRY (sdialog->rootdn)), + ldap_unparse_scope (sdialog->scope), + search_filter); + e_source_set_relative_uri (sdialog->source, str); + g_free (search_filter); + g_free (str); +} + +static void +host_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + url_changed(sdialog); +} + +static void +port_entry_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + const gchar *port = gtk_entry_get_text((GtkEntry *)w); + + if (!strcmp (port, LDAPS_PORT_STRING)) { + sdialog->ssl = ADDRESSBOOK_LDAP_SSL_ALWAYS; + gtk_combo_box_set_active (GTK_COMBO_BOX (sdialog->ssl_combobox), sdialog->ssl); + gtk_widget_set_sensitive (sdialog->ssl_combobox, FALSE); + } else { + gtk_widget_set_sensitive (sdialog->ssl_combobox, TRUE); + } + + url_changed(sdialog); +} + +static void +ssl_combobox_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + sdialog->ssl = gtk_combo_box_get_active (GTK_COMBO_BOX (w)); + e_source_set_property (sdialog->source, "ssl", ldap_unparse_ssl (sdialog->ssl)); + + gtk_widget_set_tooltip_text (sdialog->ssl_combobox, ldap_get_ssl_tooltip (sdialog->ssl)); +} + + +static GtkWidget * +eabc_general_host(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + const gchar *tmp; + GtkWidget *w; + gchar *uri, port[16]; + LDAPURLDesc *lud; + GladeXML *gui; + gchar *gladefile; + + if (!source_group_is_remote(sdialog->source_group)) + return NULL; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + gui = glade_xml_new (gladefile, item->label, NULL); + g_free (gladefile); + + w = glade_xml_get_widget(gui, item->label); + gtk_box_pack_start((GtkBox *)parent, w, FALSE, FALSE, 0); + + uri = e_source_get_uri(sdialog->source); + if (ldap_url_parse(uri, &lud) != LDAP_SUCCESS) + lud = NULL; + g_free(uri); + + sdialog->host = glade_xml_get_widget (gui, "server-name-entry"); + gtk_entry_set_text((GtkEntry *)sdialog->host, lud && lud->lud_host ? lud->lud_host : ""); + g_signal_connect (sdialog->host, "changed", G_CALLBACK (host_changed_cb), sdialog); + + sdialog->port_comboentry = glade_xml_get_widget (gui, "port-comboentry"); + gtk_widget_set_has_tooltip (sdialog->port_comboentry, TRUE); + gtk_widget_set_tooltip_text (sdialog->port_comboentry, _("This is the port on the LDAP server that Evolution will try to connect to. A list of standard ports has been provided. Ask your system administrator what port you should specify.")); + sprintf(port, "%u", lud && lud->lud_port? lud->lud_port : LDAP_PORT); + gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (sdialog->port_comboentry))), port); + g_signal_connect (gtk_bin_get_child (GTK_BIN (sdialog->port_comboentry)), "changed", G_CALLBACK (port_entry_changed_cb), sdialog); + + if (lud) + ldap_free_urldesc (lud); + + sdialog->ssl_combobox = glade_xml_get_widget (gui, "ssl-combobox"); + gtk_widget_set_has_tooltip (sdialog->ssl_combobox, TRUE); + tmp = e_source_get_property (sdialog->source, "ssl"); + sdialog->ssl = tmp ? ldap_parse_ssl (tmp) : ADDRESSBOOK_LDAP_SSL_WHENEVER_POSSIBLE; + gtk_combo_box_set_active (GTK_COMBO_BOX (sdialog->ssl_combobox), sdialog->ssl); + gtk_widget_set_tooltip_text (sdialog->ssl_combobox, ldap_get_ssl_tooltip (sdialog->ssl)); + gtk_widget_set_sensitive (sdialog->ssl_combobox, strcmp (port, LDAPS_PORT_STRING) != 0); + g_signal_connect (sdialog->ssl_combobox, "changed", G_CALLBACK (ssl_combobox_changed_cb), sdialog); + + g_object_unref(gui); + + return w; +} + +static void +auth_entry_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + const gchar *principal = gtk_entry_get_text((GtkEntry *)w); + + /* seems messy ... but the api is */ + switch (sdialog->auth) { + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_BINDDN: + e_source_set_property(sdialog->source, "email_addr", NULL); + e_source_set_property(sdialog->source, "binddn", principal); + break; + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_EMAIL: + e_source_set_property(sdialog->source, "binddn", NULL); + e_source_set_property(sdialog->source, "email_addr", principal); + break; + case ADDRESSBOOK_LDAP_AUTH_NONE: + default: + e_source_set_property(sdialog->source, "email_addr", NULL); + e_source_set_property(sdialog->source, "binddn", NULL); + break; + } +} + +static void +auth_combobox_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + sdialog->auth = gtk_combo_box_get_active (GTK_COMBO_BOX (w)); + e_source_set_property (sdialog->source, "auth", ldap_unparse_auth (sdialog->auth)); + + /* make sure the right property is set for the auth - ugh, funny api */ + auth_entry_changed_cb(sdialog->auth_principal, sdialog); +} + +static GtkWidget * +eabc_general_auth(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + GtkWidget *w; + const gchar *tmp; + GladeXML *gui; + gchar *gladefile; + + if (!source_group_is_remote(sdialog->source_group)) + return NULL; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + gui = glade_xml_new (gladefile, item->label, NULL); + g_free (gladefile); + + w = glade_xml_get_widget(gui, item->label); + gtk_box_pack_start((GtkBox *)parent, w, FALSE, FALSE, 0); + + sdialog->auth_combobox = glade_xml_get_widget (gui, "auth-combobox"); + gtk_widget_set_has_tooltip (sdialog->auth_combobox, TRUE); + gtk_widget_set_tooltip_text (sdialog->auth_combobox, _("This is the method Evolution will use to authenticate you. Note that setting this to \"Email Address\" requires anonymous access to your LDAP server.")); + tmp = e_source_get_property(sdialog->source, "auth"); + sdialog->auth = tmp ? ldap_parse_auth(tmp) : ADDRESSBOOK_LDAP_AUTH_NONE; + gtk_combo_box_set_active (GTK_COMBO_BOX (sdialog->auth_combobox), sdialog->auth); + g_signal_connect (sdialog->auth_combobox, "changed", G_CALLBACK(auth_combobox_changed_cb), sdialog); + + sdialog->auth_principal = glade_xml_get_widget (gui, "auth-entry"); + switch (sdialog->auth) { + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_EMAIL: + tmp = e_source_get_property(sdialog->source, "email_addr"); + break; + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_BINDDN: + tmp = e_source_get_property(sdialog->source, "binddn"); + break; + case ADDRESSBOOK_LDAP_AUTH_NONE: + default: + tmp = ""; + break; + } + gtk_entry_set_text((GtkEntry *)sdialog->auth_principal, tmp?tmp:""); + g_signal_connect (sdialog->auth_principal, "changed", G_CALLBACK (auth_entry_changed_cb), sdialog); + + g_object_unref(gui); + + return w; +} + +static void +rootdn_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + url_changed(sdialog); +} + +static void +search_filter_changed_cb (GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + url_changed (sdialog); +} + +static void +scope_combobox_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + sdialog->scope = gtk_combo_box_get_active (GTK_COMBO_BOX (w)); + url_changed(sdialog); +} + +static GtkWidget * +eabc_details_search(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + GtkWidget *w; + LDAPURLDesc *lud; + gchar *uri; + GladeXML *gui; + gchar *gladefile; + + if (!source_group_is_remote(sdialog->source_group)) + return NULL; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + gui = glade_xml_new (gladefile, item->label, NULL); + g_free (gladefile); + + w = glade_xml_get_widget(gui, item->label); + gtk_box_pack_start((GtkBox *)parent, w, FALSE, FALSE, 0); + + uri = e_source_get_uri(sdialog->source); + if (ldap_url_parse(uri, &lud) != LDAP_SUCCESS) + lud = NULL; + g_free(uri); + + sdialog->rootdn = glade_xml_get_widget (gui, "rootdn-entry"); + gtk_entry_set_text((GtkEntry *)sdialog->rootdn, lud && lud->lud_dn ? lud->lud_dn : ""); + g_signal_connect (sdialog->rootdn, "changed", G_CALLBACK (rootdn_changed_cb), sdialog); + + sdialog->scope_combobox = glade_xml_get_widget (gui, "scope-combobox"); + gtk_widget_set_has_tooltip (sdialog->scope_combobox, TRUE); + gtk_widget_set_tooltip_text (sdialog->scope_combobox, _("The search scope defines how deep you would like the search to extend down the directory tree. A search scope of \"sub\" will include all entries below your search base. A search scope of \"one\" will only include the entries one level beneath your base.")); + if (lud) { + switch (lud->lud_scope) { + case LDAP_SCOPE_BASE: + sdialog->scope = ADDRESSBOOK_LDAP_SCOPE_BASE; + break; + default: + case LDAP_SCOPE_ONELEVEL: + sdialog->scope = ADDRESSBOOK_LDAP_SCOPE_ONELEVEL; + break; + case LDAP_SCOPE_SUBTREE: + sdialog->scope = ADDRESSBOOK_LDAP_SCOPE_SUBTREE; + break; + } + } + gtk_combo_box_set_active (GTK_COMBO_BOX (sdialog->scope_combobox), sdialog->scope); + g_signal_connect (sdialog->scope_combobox, "changed", G_CALLBACK(scope_combobox_changed_cb), sdialog); + + sdialog->search_filter = glade_xml_get_widget (gui, "search-filter-entry"); + gtk_entry_set_text((GtkEntry *)sdialog->search_filter, lud && lud->lud_filter ? lud->lud_filter : ""); + g_signal_connect (sdialog->search_filter, "changed", G_CALLBACK (search_filter_changed_cb), sdialog); + + g_signal_connect (glade_xml_get_widget(gui, "rootdn-button"), "clicked", + G_CALLBACK(query_for_supported_bases), sdialog); + + if (lud) + ldap_free_urldesc (lud); + + g_object_unref(gui); + + return w; +} + +static void +timeout_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + gchar *timeout; + + timeout = g_strdup_printf("%f", gtk_adjustment_get_value(((GtkRange *)sdialog->timeout_scale)->adjustment)); + e_source_set_property(sdialog->source, "timeout", timeout); + g_free(timeout); +} + +static void +limit_changed_cb(GtkWidget *w, AddressbookSourceDialog *sdialog) +{ + gchar limit[16]; + + sprintf(limit, "%d", gtk_spin_button_get_value_as_int((GtkSpinButton *)sdialog->limit_spinbutton)); + e_source_set_property(sdialog->source, "limit", limit); +} + +static void +canbrowse_toggled_cb (GtkWidget *toggle_button, ESource *source) +{ + if (!source || !toggle_button) + return; + + e_source_set_property (source, "can-browse", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle_button)) ? "1" : NULL); +} + +static GtkWidget * +eabc_details_limit(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + GtkWidget *w; + const gchar *tmp; + GladeXML *gui; + gchar *gladefile; + + if (!source_group_is_remote(sdialog->source_group)) + return NULL; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + gui = glade_xml_new (gladefile, item->label, NULL); + g_free (gladefile); + + w = glade_xml_get_widget(gui, item->label); + gtk_box_pack_start((GtkBox *)parent, w, FALSE, FALSE, 0); + + sdialog->timeout_scale = glade_xml_get_widget (gui, "timeout-scale"); + tmp = e_source_get_property(sdialog->source, "timeout"); + gtk_adjustment_set_value(((GtkRange *)sdialog->timeout_scale)->adjustment, tmp?g_strtod(tmp, NULL):3.0); + g_signal_connect (GTK_RANGE(sdialog->timeout_scale)->adjustment, "value_changed", G_CALLBACK (timeout_changed_cb), sdialog); + + sdialog->limit_spinbutton = glade_xml_get_widget (gui, "download-limit-spinbutton"); + tmp = e_source_get_property(sdialog->source, "limit"); + gtk_spin_button_set_value((GtkSpinButton *)sdialog->limit_spinbutton, tmp?g_strtod(tmp, NULL):100.0); + g_signal_connect (sdialog->limit_spinbutton, "value_changed", G_CALLBACK (limit_changed_cb), sdialog); + + sdialog->canbrowsecheck = glade_xml_get_widget (gui, "canbrowsecheck"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sdialog->canbrowsecheck), e_source_get_property (sdialog->source, "can-browse") && strcmp (e_source_get_property (sdialog->source, "can-browse"), "1") == 0); + g_signal_connect (sdialog->canbrowsecheck, "toggled", G_CALLBACK (canbrowse_toggled_cb), sdialog->source); + + g_object_unref(gui); + + return w; +} +#endif + +static EConfigItem eabc_items[] = { + { E_CONFIG_BOOK, (gchar *) (gchar *) "", }, + { E_CONFIG_PAGE, (gchar *) "00.general", (gchar *) N_("General") }, + { E_CONFIG_SECTION, (gchar *) "00.general/10.display", (gchar *) N_("Address Book") }, + { E_CONFIG_ITEM, (gchar *) "00.general/10.display/10.name", (gchar *) "hbox122", eabc_general_name }, + { E_CONFIG_ITEM, (gchar *) "00.general/10.display/20.offline", NULL, eabc_general_offline }, +#ifdef HAVE_LDAP + { E_CONFIG_SECTION, (gchar *) "00.general/20.server", (gchar *) N_("Server Information") }, + { E_CONFIG_ITEM, (gchar *) "00.general/20.server/00.host", (gchar *) "table31", eabc_general_host }, + { E_CONFIG_SECTION, (gchar *) "00.general/30.auth", (gchar *) N_("Authentication") }, + { E_CONFIG_ITEM, (gchar *) "00.general/30.auth/00.auth", (gchar *) "table32", eabc_general_auth }, + + { E_CONFIG_PAGE, (gchar *) "10.details", (gchar *) N_("Details") }, + { E_CONFIG_SECTION, (gchar *) "10.details/00.search", (gchar *) N_("Searching") }, + { E_CONFIG_ITEM, (gchar *) "10.details/00.search/00.search", (gchar *) "table33", eabc_details_search }, + { E_CONFIG_SECTION, (gchar *) "10.details/10.limit", (gchar *) N_("Downloading") }, + { E_CONFIG_ITEM, (gchar *) "10.details/10.limit/00.limit", (gchar *) "table34", eabc_details_limit }, +#endif + { 0 }, +}; + +/* items needed for the 'new addressbook' window */ +static EConfigItem eabc_new_items[] = { + { E_CONFIG_ITEM, (gchar *) "00.general/10.display/00.type", NULL, eabc_general_type }, + { 0 }, +}; + +static void +eabc_commit(EConfig *ec, GSList *items, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + xmlNodePtr xml; +#if d(!)0 + gchar *txt; +#endif + if (sdialog->original_source) { + d(printf("committing addressbook changes\n")); + + /* these api's kinda suck */ + xml = xmlNewNode(NULL, (const guchar *)"dummy"); + e_source_dump_to_xml_node(sdialog->source, xml); + e_source_update_from_xml_node(sdialog->original_source, xml->children, NULL); + xmlFreeNode(xml); +#if d(!)0 + txt = e_source_to_standalone_xml(sdialog->original_source); + printf("source is now:\n%s\n", txt); + g_free(txt); +#endif + } else { + d(printf("committing new source\n")); + e_source_group_add_source(sdialog->source_group, sdialog->source, -1); + e_source_list_sync(sdialog->source_list, NULL); + } + +#if d(!)0 + txt = e_source_to_standalone_xml(sdialog->source); + printf("running source is now:\n%s\n", txt); + g_free(txt); +#endif +} + +static void +eabc_free(EConfig *ec, GSList *items, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + + g_slist_free(items); + + g_object_unref(sdialog->source); + if (sdialog->original_source) + g_object_unref(sdialog->original_source); + if (sdialog->source_list) + g_object_unref(sdialog->source_list); + g_slist_free(sdialog->menu_source_groups); + + g_object_unref(sdialog->gui); + + g_free(sdialog); +} + +static gboolean +eabc_check_complete(EConfig *ec, const gchar *pageid, gpointer data) +{ + AddressbookSourceDialog *sdialog = data; + gint valid = TRUE; + const gchar *tmp; + ESource *source; + + d(printf("check complete, pageid = '%s'\n", pageid?pageid:"<all>")); + /* have name, and unique */ + tmp = e_source_peek_name(sdialog->source); + valid = tmp && tmp[0] != 0 + && ((source = e_source_group_peek_source_by_name(sdialog->source_group, tmp)) == NULL + || source == sdialog->original_source); + +#ifdef HAVE_LDAP + if (valid && source_group_is_remote(sdialog->source_group)) { + gchar *uri = e_source_get_uri(sdialog->source); + LDAPURLDesc *lud; + + /* check host and port set */ + if (ldap_url_parse(uri, &lud) == LDAP_SUCCESS) { + valid = lud->lud_host != NULL + && lud->lud_host[0] != 0 + && lud->lud_port != 0; + ldap_free_urldesc (lud); + } else + valid = FALSE; + g_free(uri); + + /* check auth name provided if auth set */ + if (valid && (tmp = e_source_get_property(sdialog->source, "auth"))) { + switch (ldap_parse_auth(tmp)) { + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_EMAIL: + tmp = e_source_get_property(sdialog->source, "email_addr"); + break; + case ADDRESSBOOK_LDAP_AUTH_SIMPLE_BINDDN: + tmp = e_source_get_property(sdialog->source, "binddn"); + break; + default: + tmp = "dummy"; + break; + } + valid = tmp && tmp[0]; + } + + /* check timeout isn't too short (why don't we just force it?) */ + if (valid) { + tmp = e_source_get_property(sdialog->source, "timeout"); + valid = tmp && g_strtod(tmp, NULL) > 0.0; + } + } +#endif + return valid; +} + +/* debug only: */ +#if d(!)0 +static void +source_changed(ESource *source, AddressbookSourceDialog *sdialog) +{ + gchar *xml; + + xml = e_source_to_standalone_xml(source); + printf("source changed:\n%s\n", xml); + g_free(xml); +} +#endif + +GtkWidget* +addressbook_config_edit_source (GtkWidget *parent, ESource *source) +{ + AddressbookSourceDialog *sdialog = g_new0 (AddressbookSourceDialog, 1); + EABConfig *ec; + gint i; + GSList *items = NULL; + EABConfigTargetSource *target; + gchar *xml; + gchar *gladefile; + + gladefile = g_build_filename (EVOLUTION_GLADEDIR, + GLADE_FILE_NAME, + NULL); + sdialog->gui = glade_xml_new (gladefile, "account-editor-notebook", NULL); + g_free (gladefile); + + if (source) { + sdialog->original_source = source; + g_object_ref(source); + sdialog->source_group = e_source_peek_group (source); + xml = e_source_to_standalone_xml(source); + sdialog->source = e_source_new_from_standalone_xml(xml); + g_free(xml); + } else { + GConfClient *gconf; + GSList *l; + + sdialog->source = e_source_new("", ""); + gconf = gconf_client_get_default(); + sdialog->source_list = e_source_list_new_for_gconf(gconf, "/apps/evolution/addressbook/sources"); + l = e_source_list_peek_groups(sdialog->source_list); + if (!l) { + g_warning ("Address Book source groups are missing! Check your GConf setup."); + g_object_unref (gconf); + g_free (sdialog); + return NULL; + } + + sdialog->menu_source_groups = g_slist_copy(l); +#ifndef HAVE_LDAP + for (;l;l = g_slist_next(l)) + if (!strncmp("ldap:", e_source_group_peek_base_uri(l->data), 5)) + sdialog->menu_source_groups = g_slist_remove (sdialog->menu_source_groups, l->data); +#endif + sdialog->source_group = (ESourceGroup *)sdialog->menu_source_groups->data; + for (i=0;eabc_new_items[i].path;i++) + items = g_slist_prepend(items, &eabc_new_items[i]); + g_object_unref(gconf); + } + + /* HACK: doesn't work if you don't do this */ + e_source_set_group(sdialog->source, sdialog->source_group); + +#if d(!)0 + xml = e_source_to_standalone_xml(sdialog->source); + printf("but working standalone xml: %s\n", xml); + g_free(xml); + g_signal_connect(sdialog->source, "changed", source_changed, sdialog); +#endif + + sdialog->config = ec = eab_config_new(E_CONFIG_BOOK, "com.novell.evolution.addressbook.config.accountEditor"); + + for (i=0;eabc_items[i].path;i++) { + if (eabc_items[i].label) + eabc_items[i].label = gettext(eabc_items[i].label); + items = g_slist_prepend(items, &eabc_items[i]); + } + + e_config_add_items((EConfig *)ec, items, eabc_commit, NULL, eabc_free, sdialog); + e_config_add_page_check((EConfig *)ec, NULL, eabc_check_complete, sdialog); + + target = eab_config_target_new_source(ec, sdialog->source); + e_config_set_target((EConfig *)ec, (EConfigTarget *)target); + + if(source) + sdialog->window = e_config_create_window((EConfig *)ec, NULL, _("Address Book Properties")); + else + sdialog->window = e_config_create_window((EConfig *)ec, NULL, _("New Address Book")); + + + /* forces initial validation */ + if (!sdialog->original_source) + e_config_target_changed((EConfig *)ec, E_CONFIG_TARGET_CHANGED_STATE); + + return sdialog->window; +} diff --git a/modules/addressbook/addressbook-config.h b/modules/addressbook/addressbook-config.h new file mode 100644 index 0000000000..26c1788e51 --- /dev/null +++ b/modules/addressbook/addressbook-config.h @@ -0,0 +1,52 @@ +/* + * 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> + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __ADDRESSBOOK_CONFIG_H__ +#define __ADDRESSBOOK_CONFIG_H__ + +#include <gtk/gtk.h> +#include <libedataserver/e-source.h> + +typedef enum { + ADDRESSBOOK_LDAP_AUTH_NONE, + ADDRESSBOOK_LDAP_AUTH_SIMPLE_EMAIL, + ADDRESSBOOK_LDAP_AUTH_SIMPLE_BINDDN +} AddressbookLDAPAuthType; + +typedef enum { + ADDRESSBOOK_LDAP_SCOPE_ONELEVEL, + ADDRESSBOOK_LDAP_SCOPE_SUBTREE, + ADDRESSBOOK_LDAP_SCOPE_BASE, + ADDRESSBOOK_LDAP_SCOPE_LAST +} AddressbookLDAPScopeType; + +typedef enum { + ADDRESSBOOK_LDAP_SSL_ALWAYS, + ADDRESSBOOK_LDAP_SSL_WHENEVER_POSSIBLE, + ADDRESSBOOK_LDAP_SSL_NEVER +} AddressbookLDAPSSLType; + +GtkWidget* addressbook_config_edit_source (GtkWidget *parent, ESource *source); +GtkWidget* addressbook_config_create_new_source (GtkWidget *parent); + +#endif /* __ADDRESSBOOK_CONFIG_H__ */ diff --git a/modules/addressbook/apps_evolution_addressbook.schemas.in b/modules/addressbook/apps_evolution_addressbook.schemas.in new file mode 100644 index 0000000000..4a336409df --- /dev/null +++ b/modules/addressbook/apps_evolution_addressbook.schemas.in @@ -0,0 +1,82 @@ +<gconfschemafile> + <schemalist> + + <!-- Completion uris --> + + <schema> + <key>/schemas/apps/evolution/addressbook/completion/uris</key> + <applyto>/apps/evolution/addressbook/completion/uris</applyto> + <owner>evolution-addressbook</owner> + <type>string</type> + <default></default> + <locale name="C"> + <short>EFolderList XML for the list of completion URIs</short> + <long>EFolderList XML for the list of completion URIs.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/evolution/addressbook/completion/minimum_query_length</key> + <applyto>/apps/evolution/addressbook/completion/minimum_query_length</applyto> + <owner>evolution-addressbook</owner> + <type>int</type> + <default>3</default> + <locale name="C"> + <short>Autocomplete length</short> + <long>The number of characters that must be typed before Evolution will attempt to autocomplete.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/evolution/addressbook/completion/show_address</key> + <applyto>/apps/evolution/addressbook/completion/show_address</applyto> + <owner>evolution-addressbook</owner> + <type>bool</type> + <default>false</default> + <locale name="C"> + <short>Show autocompleted name with an address</short> + <long>Whether force showing the mail address with the name of the autocompleted contact in the entry.</long> + </locale> + </schema> + + <!-- Select names last used uri --> + + <schema> + <key>/schemas/apps/evolution/addressbook/select_names/last_used_uri</key> + <applyto>/apps/evolution/addressbook/select_names/last_used_uri</applyto> + <owner>evolution-addressbook</owner> + <type>string</type> + <default></default> + <locale name="C"> + <short>URI for the folder last used in the select names dialog</short> + <long>URI for the folder last used in the select names dialog.</long> + </locale> + </schema> + + <!-- View state --> + + <schema> + <key>/schemas/apps/evolution/addressbook/display/vpane_position</key> + <applyto>/apps/evolution/addressbook/display/vpane_position</applyto> + <owner>evolution-addressbook</owner> + <type>int</type> + <locale name="C"> + <short>Vertical pane position</short> + <long>Position of the vertical pane, between the card and list views and the preview pane, in pixels.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/evolution/addressbook/display/show_preview</key> + <applyto>/apps/evolution/addressbook/display/show_preview</applyto> + <owner>evolution-addressbook</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Show preview pane</short> + <long>Whether to show the preview pane.</long> + </locale> + </schema> + + </schemalist> +</gconfschemafile> diff --git a/modules/addressbook/autocompletion-config.c b/modules/addressbook/autocompletion-config.c new file mode 100644 index 0000000000..370c1a1e7e --- /dev/null +++ b/modules/addressbook/autocompletion-config.c @@ -0,0 +1,137 @@ +/* + * 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 <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libedataserver/e-source-list.h> +#include <libedataserverui/e-source-selector.h> +#include <libedataserverui/e-name-selector-entry.h> + +#include "widgets/misc/e-preferences-window.h" + +static void +source_selection_changed_cb (ESourceSelector *source_selector) +{ + ESourceList *source_list; + GSList *selection; + GSList *l; + GSList *groups; + + source_list = e_source_selector_get_source_list (source_selector); + + /* first we clear all the completion flags from all sources */ + for (groups = e_source_list_peek_groups (source_list); groups; groups = groups->next) { + ESourceGroup *group = E_SOURCE_GROUP (groups->data); + GSList *sources; + + for (sources = e_source_group_peek_sources (group); sources; sources = sources->next) { + ESource *source = E_SOURCE (sources->data); + + e_source_set_property (source, "completion", NULL); + } + } + + /* then we loop over the selector's selection, setting the + property on those sources */ + selection = e_source_selector_get_selection (source_selector); + for (l = selection; l; l = l->next) { + ESource *source = E_SOURCE (l->data); + + e_source_set_property (source, "completion", "true"); + } + e_source_selector_free_selection (selection); + + /* XXX we should pop up a dialog if this fails */ + e_source_list_sync (source_list, NULL); +} + +static void +initialize_selection (ESourceSelector *source_selector) +{ + ESourceList *source_list; + GSList *groups; + + source_list = e_source_selector_get_source_list (source_selector); + + for (groups = e_source_list_peek_groups (source_list); groups; groups = groups->next) { + ESourceGroup *group = E_SOURCE_GROUP (groups->data); + GSList *sources; + + for (sources = e_source_group_peek_sources (group); sources; sources = sources->next) { + ESource *source = E_SOURCE (sources->data); + const gchar *completion; + + completion = e_source_get_property (source, "completion"); + if (completion && !g_ascii_strcasecmp (completion, "true")) + e_source_selector_select_source (source_selector, source); + } + } +} + +void +autocompletion_config_init (EShell *shell) +{ + ESourceList *source_list; + GtkWidget *scrolled_window; + GtkWidget *source_selector; + GtkWidget *preferences_window; + + g_return_if_fail (E_IS_SHELL (shell)); + + source_list = e_source_list_new_for_gconf_default ( + "/apps/evolution/addressbook/sources"); + + /* XXX should we watch for the source list to change and + update it in the control? what about our local changes? */ + /* g_signal_connect (ac->source_list, "changed", G_CALLBACK (source_list_changed), ac); */ + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN); + gtk_widget_show (scrolled_window); + + source_selector = e_source_selector_new (source_list); + g_signal_connect ( + source_selector, "selection_changed", + G_CALLBACK (source_selection_changed_cb), NULL); + gtk_container_add (GTK_CONTAINER (scrolled_window), source_selector); + gtk_widget_show (source_selector); + + initialize_selection (E_SOURCE_SELECTOR (source_selector)); + + preferences_window = e_shell_get_preferences_window (shell); + + e_preferences_window_add_page ( + E_PREFERENCES_WINDOW (preferences_window), + "autocompletion", + "preferences-autocompletion", + _("Autocompletion"), + scrolled_window, + 200); +} diff --git a/modules/addressbook/autocompletion-config.h b/modules/addressbook/autocompletion-config.h new file mode 100644 index 0000000000..5769bdce9d --- /dev/null +++ b/modules/addressbook/autocompletion-config.h @@ -0,0 +1,37 @@ +/* + * 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 <glib.h> +#include <shell/e-shell.h> + +G_BEGIN_DECLS + +void autocompletion_config_init (EShell *shell); + +G_END_DECLS + +#endif /* _AUTOCOMPLETION_CONFIG_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..68af7ed03b --- /dev/null +++ b/modules/addressbook/e-book-shell-backend.c @@ -0,0 +1,576 @@ +/* + * 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) + * + */ + +#include "e-book-shell-backend.h" + +#include <config.h> + +#include <string.h> +#include <glib/gi18n.h> +#include <libebook/e-book.h> +#include <libedataserver/e-url.h> +#include <libedataserver/e-source.h> +#include <libedataserver/e-source-group.h> + +#include "shell/e-shell.h" +#include "shell/e-shell-window.h" + +#include "e-util/e-import.h" +#include "addressbook/gui/widgets/eab-gui-util.h" +#include "addressbook/gui/contact-editor/e-contact-editor.h" +#include "addressbook/gui/contact-list-editor/e-contact-list-editor.h" +#include "addressbook/importers/evolution-addressbook-importers.h" + +#include "eab-config.h" +#include "addressbook-config.h" +#include "autocompletion-config.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)) + +#define LDAP_BASE_URI "ldap://" +#define PERSONAL_RELATIVE_URI "system" + +struct _EBookShellBackendPrivate { + ESourceList *source_list; +}; + +enum { + PROP_0, + PROP_SOURCE_LIST +}; + +static gpointer parent_class; +static GType book_shell_backend_type; + +static void +book_shell_backend_ensure_sources (EShellBackend *shell_backend) +{ + /* XXX This is basically the same algorithm across all backends. + * Maybe we could somehow integrate this into EShellBackend? */ + + EBookShellBackendPrivate *priv; + ESourceGroup *on_this_computer; + ESourceGroup *on_ldap_servers; + ESource *personal; + GSList *groups, *iter; + const gchar *data_dir; + const gchar *name; + gchar *base_uri; + gchar *filename; + + on_this_computer = NULL; + on_ldap_servers = NULL; + personal = NULL; + + priv = E_BOOK_SHELL_BACKEND_GET_PRIVATE (shell_backend); + + if (!e_book_get_addressbooks (&priv->source_list, NULL)) { + g_warning ("Could not get addressbook sources from GConf!"); + return; + } + + data_dir = e_shell_backend_get_data_dir (shell_backend); + filename = g_build_filename (data_dir, "local", NULL); + base_uri = g_filename_to_uri (filename, NULL, NULL); + g_free (filename); + + groups = e_source_list_peek_groups (priv->source_list); + for (iter = groups; iter != NULL; iter = iter->next) { + ESourceGroup *source_group = iter->data; + const gchar *group_base_uri; + + group_base_uri = e_source_group_peek_base_uri (source_group); + + /* Compare only "file://" part. If the user's home + * changes, we do not want to create another group. */ + if (on_this_computer == NULL && + strncmp (base_uri, group_base_uri, 7) == 0) + on_this_computer = source_group; + + else if (on_ldap_servers == NULL && + strcmp (LDAP_BASE_URI, group_base_uri) == 0) + on_ldap_servers = source_group; + } + + name = _("On This Computer"); + + if (on_this_computer != NULL) { + GSList *sources; + const gchar *group_base_uri; + + /* Force the group name to the current locale. */ + e_source_group_set_name (on_this_computer, name); + + sources = e_source_group_peek_sources (on_this_computer); + group_base_uri = e_source_group_peek_base_uri (on_this_computer); + + /* Make sure this group includes a "Personal" source. */ + for (iter = sources; iter != NULL; iter = iter->next) { + ESource *source = iter->data; + const gchar *relative_uri; + + relative_uri = e_source_peek_relative_uri (source); + if (relative_uri == NULL) + continue; + + if (strcmp (PERSONAL_RELATIVE_URI, relative_uri) != 0) + continue; + + personal = source; + break; + } + + /* Make sure we have the correct base URI. This can + * change when the user's home directory changes. */ + if (strcmp (base_uri, group_base_uri) != 0) { + e_source_group_set_base_uri ( + on_this_computer, base_uri); + + /* XXX We shouldn't need this sync call here as + * set_base_uri() results in synching to GConf, + * but that happens in an idle loop and too late + * to prevent the user from seeing a "Cannot + * Open ... because of invalid URI" error. */ + e_source_list_sync (priv->source_list, NULL); + } + + } else { + ESourceGroup *source_group; + + source_group = e_source_group_new (name, base_uri); + e_source_list_add_group (priv->source_list, source_group, -1); + g_object_unref (source_group); + } + + name = _("Personal"); + + if (personal == NULL) { + ESource *source; + + /* Create the default Personal address book. */ + source = e_source_new (name, PERSONAL_RELATIVE_URI); + e_source_group_add_source (on_this_computer, source, -1); + e_source_set_property (source, "completion", "true"); + g_object_unref (source); + } else { + /* Force the source name to the current locale. */ + e_source_set_name (personal, name); + } + + name = _("On LDAP Servers"); + + if (on_ldap_servers == NULL) { + ESourceGroup *source_group; + + source_group = e_source_group_new (name, LDAP_BASE_URI); + e_source_list_add_group (priv->source_list, source_group, -1); + g_object_unref (source_group); + } else { + /* Force the group name to the current locale. */ + e_source_group_set_name (on_ldap_servers, name); + } + + g_free (base_uri); +} + +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_book_loaded_cb (EBook *book, + EBookStatus status, + gpointer user_data) +{ + EContact *contact; + GtkAction *action; + GtkWidget *editor; + const gchar *action_name; + + /* XXX Handle errors better. */ + if (status != E_BOOK_ERROR_OK) + return; + + contact = e_contact_new (); + action = GTK_ACTION (user_data); + action_name = gtk_action_get_name (action); + + if (strcmp (action_name, "contact-new") == 0) + editor = e_contact_editor_new (book, contact, TRUE, TRUE); + + if (strcmp (action_name, "contact-new-list") == 0) + editor = e_contact_list_editor_new (book, contact, TRUE, TRUE); + + eab_editor_show (EAB_EDITOR (editor)); + + g_object_unref (contact); + g_object_unref (book); +} + +static void +action_contact_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + EBook *book = NULL; + GConfClient *client; + ESourceList *source_list; + const gchar *key; + gchar *uid; + + /* This callback is used for both contacts and contact lists. */ + + if (!e_book_get_addressbooks (&source_list, NULL)) { + g_warning ("Could not get addressbook sources from GConf!"); + return; + } + + shell = e_shell_window_get_shell (shell_window); + client = e_shell_get_gconf_client (shell); + + key = "/apps/evolution/addressbook/display/primary_addressbook"; + uid = gconf_client_get_string (client, key, NULL); + + if (uid != NULL) { + ESource *source; + + source = e_source_list_peek_source_by_uid (source_list, uid); + if (source != NULL) + book = e_book_new (source, NULL); + g_free (uid); + } + + if (book == NULL) + book = e_book_new_default_addressbook (NULL); + + e_book_async_open ( + book, FALSE, book_shell_backend_book_loaded_cb, action); +} + +static void +action_address_book_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + addressbook_config_create_new_source (NULL); +} + +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", + N_("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_handle_uri_cb (EShellBackend *shell_backend, + const gchar *uri) +{ + EUri *euri; + const gchar *cp; + gchar *source_uid = NULL; + gchar *contact_uid = NULL; + + if (!g_str_has_prefix (uri, "contacts:")) + return FALSE; + + euri = e_uri_new (uri); + cp = euri->query; + + if (cp == NULL) { + e_uri_free (euri); + 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); + + e_uri_free (euri); + + return TRUE; +} + +static void +book_shell_backend_window_created_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_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_SOURCE_LIST: + g_value_set_object ( + value, + e_book_shell_backend_get_source_list ( + E_BOOK_SHELL_BACKEND (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +book_shell_backend_dispose (GObject *object) +{ + EBookShellBackendPrivate *priv; + + priv = E_BOOK_SHELL_BACKEND_GET_PRIVATE (object); + + if (priv->source_list != NULL) { + g_object_unref (priv->source_list); + priv->source_list = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +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 (); + certificate_manager_config_init (shell); +#endif + + book_shell_backend_init_importers (); + book_shell_backend_ensure_sources (shell_backend); + + e_plugin_hook_register_type (eab_config_get_type ()); + + g_signal_connect_swapped ( + shell, "handle-uri", + G_CALLBACK (book_shell_backend_handle_uri_cb), + shell_backend); + + g_signal_connect_swapped ( + shell, "window-created", + G_CALLBACK (book_shell_backend_window_created_cb), + shell_backend); + + autocompletion_config_init (shell); +} + +static gboolean +book_shell_backend_is_busy (EShellBackend *shell_backend) +{ + return !eab_editor_request_close_all (); +} + +static gboolean +book_shell_backend_shutdown (EShellBackend *shell_backend) +{ + /* FIXME */ + return TRUE; +} + +static void +book_shell_backend_class_init (EBookShellBackendClass *class) +{ + GObjectClass *object_class; + EShellBackendClass *shell_backend_class; + + parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (EBookShellBackendPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = book_shell_backend_get_property; + object_class->dispose = book_shell_backend_dispose; + 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->start = NULL; + shell_backend_class->is_busy = book_shell_backend_is_busy; + shell_backend_class->shutdown = book_shell_backend_shutdown; + shell_backend_class->migrate = e_book_shell_backend_migrate; + + g_object_class_install_property ( + object_class, + PROP_SOURCE_LIST, + g_param_spec_object ( + "source-list", + _("Source List"), + _("The registry of address books"), + E_TYPE_SOURCE_LIST, + G_PARAM_READABLE)); +} + +static void +book_shell_backend_init (EBookShellBackend *book_shell_backend) +{ + book_shell_backend->priv = + E_BOOK_SHELL_BACKEND_GET_PRIVATE (book_shell_backend); +} + +GType +e_book_shell_backend_get_type (void) +{ + return book_shell_backend_type; +} + +void +e_book_shell_backend_register_type (GTypeModule *type_module) +{ + const GTypeInfo type_info = { + sizeof (EBookShellBackendClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) book_shell_backend_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EBookShellBackend), + 0, /* n_preallocs */ + (GInstanceInitFunc) book_shell_backend_init, + NULL /* value_table */ + }; + + book_shell_backend_type = g_type_module_register_type ( + type_module, E_TYPE_SHELL_BACKEND, + "EBookShellBackend", &type_info, 0); +} + +ESourceList * +e_book_shell_backend_get_source_list (EBookShellBackend *book_shell_backend) +{ + g_return_val_if_fail ( + E_IS_BOOK_SHELL_BACKEND (book_shell_backend), NULL); + + return book_shell_backend->priv->source_list; +} diff --git a/modules/addressbook/e-book-shell-backend.h b/modules/addressbook/e-book-shell-backend.h new file mode 100644 index 0000000000..c61e43b814 --- /dev/null +++ b/modules/addressbook/e-book-shell-backend.h @@ -0,0 +1,70 @@ +/* + * 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> +#include <libedataserver/e-source-list.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_register_type + (GTypeModule *type_module); +ESourceList * e_book_shell_backend_get_source_list + (EBookShellBackend *book_shell_backend); + +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..cce03b1575 --- /dev/null +++ b/modules/addressbook/e-book-shell-content.c @@ -0,0 +1,492 @@ +/* + * 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) + * + */ + +#include "e-book-shell-content.h" + +#include <glib/gi18n.h> + +#include "e-util/gconf-bridge.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; +}; + +enum { + PROP_0, + PROP_CURRENT_VIEW, + PROP_PREVIEW_CONTACT, + PROP_PREVIEW_VISIBLE +}; + +static gpointer parent_class; +static GType book_shell_view_type; + +static void +book_shell_content_send_message_cb (EBookShellContent *book_shell_content, + EDestination *destination, + EABContactDisplay *display) +{ + GList node = { destination, NULL, NULL }; + + eab_send_as_to (&node); +} + +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_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; + } + + 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_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; + } + + 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 != NULL) { + g_object_unref (priv->preview); + priv->preview = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +book_shell_content_constructed (GObject *object) +{ + EBookShellContentPrivate *priv; + GConfBridge *bridge; + GtkWidget *container; + GtkWidget *widget; + const gchar *key; + + priv = E_BOOK_SHELL_CONTENT_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (parent_class)->constructed (object); + + container = GTK_WIDGET (object); + + widget = gtk_vpaned_new (); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->paned = g_object_ref (widget); + gtk_widget_show (widget); + + 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_add1 (GTK_PANED (container), widget); + priv->notebook = g_object_ref (widget); + 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_paned_add2 (GTK_PANED (container), widget); + gtk_widget_show (widget); + + container = widget; + + widget = eab_contact_display_new (); + eab_contact_display_set_mode ( + EAB_CONTACT_DISPLAY (widget), + EAB_CONTACT_DISPLAY_RENDER_NORMAL); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->preview = g_object_ref (widget); + gtk_widget_show (widget); + + g_signal_connect_swapped ( + priv->preview, "send-message", + G_CALLBACK (book_shell_content_send_message_cb), object); + + /* Bind GObject properties to GConf keys. */ + + bridge = gconf_bridge_get (); + + object = G_OBJECT (priv->paned); + key = "/apps/evolution/addressbook/display/vpane_position"; + gconf_bridge_bind_property_delayed (bridge, key, object, "position"); +} + +static guint32 +book_shell_content_check_state (EShellContent *shell_content) +{ + EBookShellContent *book_shell_content; + ESelectionModel *selection_model; + EAddressbookModel *model; + EAddressbookView *view; + guint32 state = 0; + gint n_contacts; + gint n_selected; + + book_shell_content = E_BOOK_SHELL_CONTENT (shell_content); + 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_contacts = (selection_model != NULL) ? + e_selection_model_row_count (selection_model) : 0; + n_selected = (selection_model != NULL) ? + e_selection_model_selected_count (selection_model) : 0; + + /* FIXME Finish the rest of the flags. */ + if (n_selected == 1) + state |= E_BOOK_SHELL_CONTENT_SELECTION_SINGLE; + if (n_selected > 1) + state |= E_BOOK_SHELL_CONTENT_SELECTION_MULTIPLE; + 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; + if (n_contacts == 0) + state |= E_BOOK_SHELL_CONTENT_SOURCE_IS_EMPTY; + + return state; +} + +static void +book_shell_content_class_init (EBookShellContentClass *class) +{ + GObjectClass *object_class; + EShellContentClass *shell_content_class; + + parent_class = g_type_class_peek_parent (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; + + 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)); +} + +static void +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. */ +} + +GType +e_book_shell_content_get_type (void) +{ + return book_shell_view_type; +} + +void +e_book_shell_content_register_type (GTypeModule *type_module) +{ + static const GTypeInfo type_info = { + sizeof (EBookShellContentClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) book_shell_content_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EBookShellContent), + 0, /* n_preallocs */ + (GInstanceInitFunc) book_shell_content_init, + NULL /* value_table */ + }; + + book_shell_view_type = g_type_module_register_type ( + type_module, E_TYPE_SHELL_CONTENT, + "EBookShellContent", &type_info, 0); +} + +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) +{ + 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_set_current_page (notebook, page_num); + g_object_notify (G_OBJECT (book_shell_content), "current-view"); +} + +EContact * +e_book_shell_content_get_preview_contact (EBookShellContent *book_shell_content) +{ + EABContactDisplay *display; + + g_return_val_if_fail ( + E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL); + + display = EAB_CONTACT_DISPLAY (book_shell_content->priv->preview); + + return eab_contact_display_get_contact (display); +} + +void +e_book_shell_content_set_preview_contact (EBookShellContent *book_shell_content, + EContact *preview_contact) +{ + EABContactDisplay *display; + + g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content)); + + display = EAB_CONTACT_DISPLAY (book_shell_content->priv->preview); + + eab_contact_display_set_contact (display, preview_contact); + g_object_notify (G_OBJECT (book_shell_content), "preview-contact"); +} + +gboolean +e_book_shell_content_get_preview_visible (EBookShellContent *book_shell_content) +{ + GtkPaned *paned; + GtkWidget *child; + + g_return_val_if_fail ( + E_IS_BOOK_SHELL_CONTENT (book_shell_content), FALSE); + + paned = GTK_PANED (book_shell_content->priv->paned); + child = gtk_paned_get_child2 (paned); + + return GTK_WIDGET_VISIBLE (child); +} + +void +e_book_shell_content_set_preview_visible (EBookShellContent *book_shell_content, + gboolean preview_visible) +{ + GtkPaned *paned; + GtkWidget *child; + + g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content)); + + paned = GTK_PANED (book_shell_content->priv->paned); + child = gtk_paned_get_child2 (paned); + + if (preview_visible) + gtk_widget_show (child); + else + gtk_widget_hide (child); + + g_object_notify (G_OBJECT (book_shell_content), "preview-visible"); +} + +void +e_book_shell_content_clipboard_copy (EBookShellContent *book_shell_content) +{ + EAddressbookView *addressbook_view; + GtkHTML *html; + gchar *selection; + + g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content)); + + html = GTK_HTML (book_shell_content->priv->preview); + addressbook_view = + e_book_shell_content_get_current_view (book_shell_content); + g_return_if_fail (addressbook_view != NULL); + + if (!GTK_WIDGET_HAS_FOCUS (html)) { + e_addressbook_view_copy (addressbook_view); + return; + } + + selection = gtk_html_get_selection_html (html, NULL); + if (selection != NULL) + gtk_html_copy (html); + g_free (selection); +} diff --git a/modules/addressbook/e-book-shell-content.h b/modules/addressbook/e-book-shell-content.h new file mode 100644 index 0000000000..da78c01274 --- /dev/null +++ b/modules/addressbook/e-book-shell-content.h @@ -0,0 +1,110 @@ +/* + * 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/e-contact.h> + +#include "shell/e-shell-content.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_SELECTION_HAS_HTTP_URI = 1 << 4, + E_BOOK_SHELL_CONTENT_SELECTION_HAS_MAILTO_URI = 1 << 5, + E_BOOK_SHELL_CONTENT_SOURCE_IS_BUSY = 1 << 6, + E_BOOK_SHELL_CONTENT_SOURCE_IS_EDITABLE = 1 << 7, + E_BOOK_SHELL_CONTENT_SOURCE_IS_EMPTY = 1 << 8 +}; + +struct _EBookShellContent { + EShellContent parent; + EBookShellContentPrivate *priv; +}; + +struct _EBookShellContentClass { + EShellContentClass parent_class; +}; + +GType e_book_shell_content_get_type (void); +void e_book_shell_content_register_type + (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); +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); +void e_book_shell_content_clipboard_copy + (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..fce6e0a634 --- /dev/null +++ b/modules/addressbook/e-book-shell-migrate.c @@ -0,0 +1,1230 @@ +/* + * 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) + * + */ + +#include <config.h> + +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include <gtk/gtk.h> + +#include <libebook/e-destination.h> +#include <libebook/e-book.h> +#include <glib/gi18n.h> + +#include <libedataserver/e-xml-utils.h> + +#include "e-util/e-util.h" +#include "e-util/e-util-private.h" +#include "e-util/e-xml-utils.h" +#include "e-util/e-folder-map.h" + +#include "e-book-shell-migrate.h" + +/*#define SLOW_MIGRATION*/ + +typedef struct { + /* this hash table maps old folder uris to new uids. It's + build in migrate_contact_folder and it's used in + migrate_completion_folders. */ + GHashTable *folder_uid_map; + + ESourceList *source_list; + + const gchar *data_dir; + + GtkWidget *window; + GtkWidget *label; + GtkWidget *folder_label; + GtkWidget *progress; +} MigrationContext; + +static void +setup_progress_dialog (MigrationContext *context) +{ + GtkWidget *vbox, *hbox; + + context->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (context->window), _("Migrating...")); + gtk_window_set_modal (GTK_WINDOW (context->window), TRUE); + gtk_container_set_border_width (GTK_CONTAINER (context->window), 6); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (context->window), vbox); + + context->label = gtk_label_new (""); + gtk_label_set_line_wrap (GTK_LABEL (context->label), TRUE); + gtk_widget_show (context->label); + gtk_box_pack_start (GTK_BOX (vbox), context->label, TRUE, TRUE, 0); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + + context->folder_label = gtk_label_new (""); + gtk_widget_show (context->folder_label); + gtk_box_pack_start (GTK_BOX (hbox), context->folder_label, TRUE, TRUE, 0); + + context->progress = gtk_progress_bar_new (); + gtk_widget_show (context->progress); + gtk_box_pack_start (GTK_BOX (hbox), context->progress, TRUE, TRUE, 0); + + gtk_widget_show (context->window); +} + +static void +dialog_close (MigrationContext *context) +{ + gtk_widget_destroy (context->window); +} + +static void +dialog_set_label (MigrationContext *context, const gchar *str) +{ + gtk_label_set_text (GTK_LABEL (context->label), str); + + while (gtk_events_pending ()) + gtk_main_iteration (); + +#ifdef SLOW_MIGRATION + sleep (1); +#endif +} + +static void +dialog_set_folder_name (MigrationContext *context, const gchar *folder_name) +{ + gchar *text; + + text = g_strdup_printf (_("Migrating '%s':"), folder_name); + gtk_label_set_text (GTK_LABEL (context->folder_label), text); + g_free (text); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (context->progress), 0.0); + + while (gtk_events_pending ()) + gtk_main_iteration (); + +#ifdef SLOW_MIGRATION + sleep (1); +#endif +} + +static void +dialog_set_progress (MigrationContext *context, double percent) +{ + gchar text[5]; + + snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f)); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (context->progress), percent); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (context->progress), text); + + while (gtk_events_pending ()) + gtk_main_iteration (); + +#ifdef SLOW_MIGRATION + sleep (1); +#endif +} + +static gboolean +check_for_conflict (ESourceGroup *group, gchar *name) +{ + GSList *sources; + GSList *s; + + sources = e_source_group_peek_sources (group); + + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + + if (!strcmp (e_source_peek_name (source), name)) + return TRUE; + } + + return FALSE; +} + +static gchar * +get_source_name (ESourceGroup *group, const gchar *path) +{ +#ifndef G_OS_WIN32 + gchar **p = g_strsplit (path, "/", 0); +#else + gchar **p = g_strsplit_set (path, "\\/", 0); +#endif + gint i, j, starting_index; + gint num_elements; + gboolean conflict; + GString *s = g_string_new (""); + + for (i = 0; p[i]; i ++); + + num_elements = i; + i--; + + /* p[i] is now the last path element */ + + /* check if it conflicts */ + starting_index = i; + do { + g_string_assign (s, ""); + for (j = starting_index; j < num_elements; j += 2) { + if (j != starting_index) + g_string_append_c (s, '_'); + g_string_append (s, p[j]); + } + + conflict = check_for_conflict (group, s->str); + + + /* if there was a conflict back up 2 levels (skipping the /subfolder/ element) */ + if (conflict) + starting_index -= 2; + + /* we always break out if we can't go any further, + regardless of whether or not we conflict. */ + if (starting_index < 0) + break; + + } while (conflict); + + g_strfreev (p); + + return g_string_free (s, FALSE); +} + +static void +migrate_contacts (MigrationContext *context, EBook *old_book, EBook *new_book) +{ + EBookQuery *query = e_book_query_any_field_contains (""); + GList *l, *contacts; + gint num_added = 0; + gint num_contacts; + + /* both books are loaded, start the actual migration */ + e_book_get_contacts (old_book, query, &contacts, NULL); + e_book_query_unref (query); + + num_contacts = g_list_length (contacts); + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + GError *e = NULL; + GList *attrs, *attr; + + /* do some last minute massaging of the contact's attributes */ + + attrs = e_vcard_get_attributes (E_VCARD (contact)); + for (attr = attrs; attr;) { + EVCardAttribute *a = attr->data; + + /* evo 1.4 used the non-standard X-EVOLUTION-OFFICE attribute, + evo 1.5 uses the third element in the ORG list attribute. */ + if (!strcmp ("X-EVOLUTION-OFFICE", e_vcard_attribute_get_name (a))) { + GList *v = e_vcard_attribute_get_values (a); + GList *next_attr; + + if (v && v->data) + e_contact_set (contact, E_CONTACT_OFFICE, v->data); + + next_attr = attr->next; + e_vcard_remove_attribute (E_VCARD (contact), a); + attr = next_attr; + } + /* evo 1.4 didn't put TYPE=VOICE in for phone numbers. + evo 1.5 does. + + so we search through the attribute params for + either TYPE=VOICE or TYPE=FAX. If we find + either we do nothing. If we find neither, we + add TYPE=VOICE. + */ + else if (!strcmp ("TEL", e_vcard_attribute_get_name (a))) { + GList *params, *param; + gboolean found = FALSE; + + params = e_vcard_attribute_get_params (a); + for (param = params; param; param = param->next) { + EVCardAttributeParam *p = param->data; + if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { + GList *v = e_vcard_attribute_param_get_values (p); + while (v && v->data) { + if (!strcmp ("VOICE", v->data) + || !strcmp ("FAX", v->data)) { + found = TRUE; + break; + } + v = v->next; + } + } + } + + if (!found) + e_vcard_attribute_add_param_with_value (a, + e_vcard_attribute_param_new (EVC_TYPE), + "VOICE"); + attr = attr->next; + } + /* Replace "POSTAL" (1.4) addresses with "OTHER" (1.5) */ + else if (!strcmp ("ADR", e_vcard_attribute_get_name (a))) { + GList *params, *param; + gboolean found = FALSE; + EVCardAttributeParam *p; + + params = e_vcard_attribute_get_params (a); + for (param = params; param; param = param->next) { + p = param->data; + if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { + GList *v = e_vcard_attribute_param_get_values (p); + while (v && v->data ) { + if (!strcmp ("POSTAL", v->data)) { + found = TRUE; + break; + } + v = v->next; + } + if (found) + break; + } + } + + if (found) { + e_vcard_attribute_param_remove_values (p); + e_vcard_attribute_param_add_value (p, "OTHER"); + } + + attr = attr->next; + } + /* this is kinda gross. The new vcard parser + needs ';'s to be escaped by \'s. but the + 1.4 vcard generator would put unescaped xml + (including entities like >) in the value + of attributes, so we need to go through and + escape those ';'s. */ + else if (!strcmp ("EMAIL", e_vcard_attribute_get_name (a))) { + GList *params; + GList *v = e_vcard_attribute_get_values (a); + + /* Add TYPE=OTHER if there is no type set */ + params = e_vcard_attribute_get_params (a); + if (!params) + e_vcard_attribute_add_param_with_value (a, + e_vcard_attribute_param_new (EVC_TYPE), + "OTHER"); + + if (v && v->data) { + if (!strncmp ((gchar *)v->data, "<?xml", 5)) { + /* k, this is the nasty part. we glomb all the + value strings back together again (if there is + more than one), then work our magic */ + GString *str = g_string_new (""); + while (v) { + g_string_append (str, v->data); + if (v->next) + g_string_append_c (str, ';'); + v = v->next; + } + + e_vcard_attribute_remove_values (a); + e_vcard_attribute_add_value (a, str->str); + g_string_free (str, TRUE); + } + } + + attr = attr->next; + } + else { + attr = attr->next; + } + } + + if (!e_book_add_contact (new_book, + contact, + &e)) + g_warning ("contact add failed: `%s'", e->message); + + num_added ++; + + dialog_set_progress (context, (double)num_added / num_contacts); + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); +} + +static void +migrate_contact_folder_to_source (MigrationContext *context, gchar *old_path, ESource *new_source) +{ + gchar *old_uri = g_filename_to_uri (old_path, NULL, NULL); + GError *e = NULL; + + EBook *old_book = NULL, *new_book = NULL; + ESource *old_source; + ESourceGroup *group; + + group = e_source_group_new ("", old_uri); + old_source = e_source_new ("", ""); + e_source_group_add_source (group, old_source, -1); + + dialog_set_folder_name (context, e_source_peek_name (new_source)); + + old_book = e_book_new (old_source, &e); + if (!old_book + || !e_book_open (old_book, TRUE, &e)) { + g_warning ("failed to load source book for migration: `%s'", e->message); + goto finish; + } + + new_book = e_book_new (new_source, &e); + if (!new_book + || !e_book_open (new_book, FALSE, &e)) { + g_warning ("failed to load destination book for migration: `%s'", e->message); + goto finish; + } + + migrate_contacts (context, old_book, new_book); + + finish: + g_object_unref (old_source); + g_object_unref (group); + if (old_book) + g_object_unref (old_book); + if (new_book) + g_object_unref (new_book); + g_free (old_uri); +} + +static void +migrate_contact_folder (MigrationContext *context, gchar *old_path, ESourceGroup *dest_group, gchar *source_name) +{ + ESource *new_source; + + new_source = e_source_new (source_name, source_name); + e_source_set_relative_uri (new_source, e_source_peek_uid (new_source)); + e_source_group_add_source (dest_group, new_source, -1); + + g_hash_table_insert (context->folder_uid_map, g_strdup (old_path), g_strdup (e_source_peek_uid (new_source))); + + migrate_contact_folder_to_source (context, old_path, new_source); + + g_object_unref (new_source); +} + +#define LDAP_BASE_URI "ldap://" +#define PERSONAL_RELATIVE_URI "system" + +static void +create_groups (MigrationContext *context, + ESourceGroup **on_this_computer, + ESourceGroup **on_ldap_servers, + ESource **personal_source) +{ + GSList *groups; + ESourceGroup *group; + gchar *base_uri, *base_uri_proto; + + *on_this_computer = NULL; + *on_ldap_servers = NULL; + *personal_source = NULL; + + base_uri = g_build_filename (context->data_dir, "local", NULL); + + base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); + + groups = e_source_list_peek_groups (context->source_list); + if (groups) { + /* groups are already there, we need to search for things... */ + GSList *g; + + for (g = groups; g; g = g->next) { + + group = E_SOURCE_GROUP (g->data); + + if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) + *on_this_computer = g_object_ref (group); + else if (!*on_ldap_servers && !strcmp (LDAP_BASE_URI, e_source_group_peek_base_uri (group))) + *on_ldap_servers = g_object_ref (group); + } + } + + if (*on_this_computer) { + /* make sure "Personal" shows up as a source under + this group */ + GSList *sources = e_source_group_peek_sources (*on_this_computer); + GSList *s; + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const gchar *relative_uri; + + relative_uri = e_source_peek_relative_uri (source); + if (relative_uri == NULL) + continue; + if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { + *personal_source = g_object_ref (source); + break; + } + } + } + else { + /* create the local source group */ + group = e_source_group_new (_("On This Computer"), base_uri_proto); + e_source_list_add_group (context->source_list, group, -1); + + *on_this_computer = group; + } + + if (!*personal_source) { + /* Create the default Person addressbook */ + ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); + e_source_group_add_source (*on_this_computer, source, -1); + + e_source_set_property (source, "completion", "true"); + + *personal_source = source; + } + + if (!*on_ldap_servers) { + /* Create the LDAP source group */ + group = e_source_group_new (_("On LDAP Servers"), LDAP_BASE_URI); + e_source_list_add_group (context->source_list, group, -1); + + *on_ldap_servers = group; + } + + g_free (base_uri_proto); + g_free (base_uri); +} + +static gboolean +migrate_local_folders (MigrationContext *context, ESourceGroup *on_this_computer, ESource *personal_source) +{ + gchar *old_path = NULL; + GSList *dirs, *l; + gchar *local_contact_folder = NULL; + + old_path = g_strdup_printf ("%s/evolution/local", g_get_home_dir ()); + + dirs = e_folder_map_local_folders (old_path, "contacts"); + + /* migrate the local addressbook first, to local/system */ + local_contact_folder = g_build_filename (g_get_home_dir (), + "evolution", "local", "Contacts", + NULL); + + for (l = dirs; l; l = l->next) { + gchar *source_name; + /* we handle the system folder differently */ + if (personal_source && !strcmp ((gchar *)l->data, local_contact_folder)) { + g_hash_table_insert (context->folder_uid_map, g_strdup (l->data), g_strdup (e_source_peek_uid (personal_source))); + migrate_contact_folder_to_source (context, local_contact_folder, personal_source); + continue; + } + + source_name = get_source_name (on_this_computer, (gchar *)l->data); + migrate_contact_folder (context, l->data, on_this_computer, source_name); + g_free (source_name); + } + + g_slist_foreach (dirs, (GFunc)g_free, NULL); + g_slist_free (dirs); + g_free (local_contact_folder); + g_free (old_path); + + return TRUE; +} + +static gchar * +get_string_child (xmlNode *node, + const gchar *name) +{ + xmlNode *p; + xmlChar *xml_string; + gchar *retval; + + p = e_xml_get_child_by_name (node, (xmlChar *) name); + if (p == NULL) + return NULL; + + p = e_xml_get_child_by_name (p, (xmlChar *) "text"); + if (p == NULL) /* there's no text between the tags, return the empty string */ + return g_strdup(""); + + xml_string = xmlNodeListGetString (node->doc, p, 1); + retval = g_strdup ((gchar *) xml_string); + xmlFree (xml_string); + + return retval; +} + +static gint +get_integer_child (xmlNode *node, + const gchar *name, + gint defval) +{ + xmlNode *p; + xmlChar *xml_string; + gint retval; + + p = e_xml_get_child_by_name (node, (xmlChar *) name); + if (p == NULL) + return defval; + + p = e_xml_get_child_by_name (p, (xmlChar *) "text"); + if (p == NULL) /* there's no text between the tags, return the default */ + return defval; + + xml_string = xmlNodeListGetString (node->doc, p, 1); + retval = atoi ((gchar *)xml_string); + xmlFree (xml_string); + + return retval; +} + +static gboolean +migrate_ldap_servers (MigrationContext *context, ESourceGroup *on_ldap_servers) +{ + gchar *sources_xml = g_strdup_printf ("%s/evolution/addressbook-sources.xml", + g_get_home_dir ()); + + printf ("trying to migrate from %s\n", sources_xml); + + if (g_file_test (sources_xml, G_FILE_TEST_EXISTS)) { + xmlDoc *doc = xmlParseFile (sources_xml); + xmlNode *root; + xmlNode *child; + gint num_contactservers; + gint servernum; + + if (!doc) + return FALSE; + + root = xmlDocGetRootElement (doc); + if (root == NULL || strcmp ((const gchar *)root->name, "addressbooks") != 0) { + xmlFreeDoc (doc); + return FALSE; + } + + /* count the number of servers, so we can give progress */ + num_contactservers = 0; + for (child = root->children; child; child = child->next) { + if (!strcmp ((const gchar *)child->name, "contactserver")) { + num_contactservers++; + } + } + printf ("found %d contact servers to migrate\n", num_contactservers); + + dialog_set_folder_name (context, _("LDAP Servers")); + + servernum = 0; + for (child = root->children; child; child = child->next) { + if (!strcmp ((const gchar *)child->name, "contactserver")) { + gchar *port, *host, *rootdn, *scope, *authmethod, *ssl; + gchar *emailaddr, *binddn, *limitstr; + gint limit; + gchar *name, *description; + GString *uri = g_string_new (""); + ESource *source; + + name = get_string_child (child, "name"); + description = get_string_child (child, "description"); + port = get_string_child (child, "port"); + host = get_string_child (child, "host"); + rootdn = get_string_child (child, "rootdn"); + scope = get_string_child (child, "scope"); + authmethod = get_string_child (child, "authmethod"); + ssl = get_string_child (child, "ssl"); + emailaddr = get_string_child (child, "emailaddr"); + binddn = get_string_child (child, "binddn"); + limit = get_integer_child (child, "limit", 100); + limitstr = g_strdup_printf ("%d", limit); + + g_string_append_printf (uri, + "%s:%s/%s?"/*trigraph prevention*/"?%s", + host, port, rootdn, scope); + + source = e_source_new (name, uri->str); + e_source_set_property (source, "description", description); + e_source_set_property (source, "limit", limitstr); + e_source_set_property (source, "ssl", ssl); + e_source_set_property (source, "auth", authmethod); + if (emailaddr) + e_source_set_property (source, "email_addr", emailaddr); + if (binddn) + e_source_set_property (source, "binddn", binddn); + + e_source_group_add_source (on_ldap_servers, source, -1); + + g_string_free (uri, TRUE); + g_free (port); + g_free (host); + g_free (rootdn); + g_free (scope); + g_free (authmethod); + g_free (ssl); + g_free (emailaddr); + g_free (binddn); + g_free (limitstr); + g_free (name); + g_free (description); + + servernum++; + dialog_set_progress (context, (double)servernum/num_contactservers); + } + } + + xmlFreeDoc (doc); + } + + g_free (sources_xml); + + return TRUE; +} + +static ESource* +get_source_by_name (ESourceList *source_list, const gchar *name) +{ + GSList *groups; + GSList *g; + + groups = e_source_list_peek_groups (source_list); + if (!groups) + return NULL; + + for (g = groups; g; g = g->next) { + GSList *sources; + GSList *s; + ESourceGroup *group = E_SOURCE_GROUP (g->data); + + sources = e_source_group_peek_sources (group); + if (!sources) + continue; + + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const gchar *source_name = e_source_peek_name (source); + + if (!strcmp (name, source_name)) + return source; + } + } + + return NULL; +} + +static gboolean +migrate_completion_folders (MigrationContext *context) +{ + GConfClient *client; + const gchar *key; + gchar *uris_xml; + + printf ("trying to migrate completion folders\n"); + + client = gconf_client_get_default (); + key = "/apps/evolution/addressbook/completion/uris"; + uris_xml = gconf_client_get_string (client, key, NULL); + g_object_unref (client); + + if (uris_xml) { + xmlDoc *doc = xmlParseMemory (uris_xml, strlen (uris_xml)); + xmlNode *root; + xmlNode *child; + + if (!doc) + return FALSE; + + dialog_set_folder_name (context, _("Autocompletion Settings")); + + root = xmlDocGetRootElement (doc); + if (root == NULL || strcmp ((const gchar *)root->name, "EvolutionFolderList") != 0) { + xmlFreeDoc (doc); + return FALSE; + } + + for (child = root->children; child; child = child->next) { + if (!strcmp ((const gchar *)child->name, "folder")) { + gchar *physical_uri = e_xml_get_string_prop_by_name (child, (const guchar *)"physical-uri"); + ESource *source = NULL; + + /* if the physical uri is file://... + we look it up in our folder_uid_map + hashtable. If it's a folder we + converted over, we should get back + a uid we can search for. + + if the physical_uri is anything + else, we strip off the args + (anything after;) before searching + for the uri. */ + + if (!strncmp (physical_uri, "file://", 7)) { + gchar *filename = g_filename_from_uri (physical_uri, NULL, NULL); + gchar *uid = NULL; + + if (filename) + uid = g_hash_table_lookup (context->folder_uid_map, + filename); + g_free (filename); + if (uid) + source = e_source_list_peek_source_by_uid (context->source_list, uid); + } + else { + gchar *name = e_xml_get_string_prop_by_name (child, (const guchar *)"display-name"); + + source = get_source_by_name (context->source_list, name); + + g_free (name); + } + + if (source) { + e_source_set_property (source, "completion", "true"); + } + else { + g_warning ("found completion folder with uri `%s' that " + "doesn't correspond to anything we migrated.", physical_uri); + } + + g_free (physical_uri); + } + } + + g_free (uris_xml); + } + else { + g_message ("no completion folder settings to migrate"); + } + + return TRUE; +} + +static void +migrate_contact_lists_for_local_folders (MigrationContext *context, ESourceGroup *on_this_computer) +{ + GSList *sources, *s; + + sources = e_source_group_peek_sources (on_this_computer); + for (s = sources; s; s = s->next) { + ESource *source = s->data; + EBook *book; + EBookQuery *query; + GList *l, *contacts; + gint num_contacts, num_converted; + + dialog_set_folder_name (context, e_source_peek_name (source)); + + book = e_book_new (source, NULL); + if (!book + || !e_book_open (book, TRUE, NULL)) { + gchar *uri = e_source_get_uri (source); + g_warning ("failed to migrate contact lists for source %s", uri); + g_free (uri); + continue; + } + + query = e_book_query_any_field_contains (""); + e_book_get_contacts (book, query, &contacts, NULL); + e_book_query_unref (query); + + num_converted = 0; + num_contacts = g_list_length (contacts); + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + GError *e = NULL; + GList *attrs, *attr; + gboolean converted = FALSE; + + attrs = e_contact_get_attributes (contact, E_CONTACT_EMAIL); + for (attr = attrs; attr; attr = attr->next) { + EVCardAttribute *a = attr->data; + GList *v = e_vcard_attribute_get_values (a); + + if (v && v->data) { + if (!strncmp ((gchar *)v->data, "<?xml", 5)) { + EDestination *dest = e_destination_import ((gchar *)v->data); + + e_destination_export_to_vcard_attribute (dest, a); + + g_object_unref (dest); + + converted = TRUE; + } + } + } + + if (converted) { + e_contact_set_attributes (contact, E_CONTACT_EMAIL, attrs); + + if (!e_book_commit_contact (book, + contact, + &e)) + g_warning ("contact commit failed: `%s'", e->message); + } + + num_converted ++; + + dialog_set_progress (context, (double)num_converted / num_contacts); + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); + + g_object_unref (book); + } +} + +static void +migrate_company_phone_for_local_folders (MigrationContext *context, ESourceGroup *on_this_computer) +{ + GSList *sources, *s; + + sources = e_source_group_peek_sources (on_this_computer); + for (s = sources; s; s = s->next) { + ESource *source = s->data; + EBook *book; + EBookQuery *query; + GList *l, *contacts; + gint num_contacts, num_converted; + + dialog_set_folder_name (context, e_source_peek_name (source)); + + book = e_book_new (source, NULL); + if (!book + || !e_book_open (book, TRUE, NULL)) { + gchar *uri = e_source_get_uri (source); + g_warning ("failed to migrate company phone numbers for source %s", uri); + g_free (uri); + continue; + } + + query = e_book_query_any_field_contains (""); + e_book_get_contacts (book, query, &contacts, NULL); + e_book_query_unref (query); + + num_converted = 0; + num_contacts = g_list_length (contacts); + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + GError *e = NULL; + GList *attrs, *attr; + gboolean converted = FALSE; + gint num_work_voice = 0; + + attrs = e_vcard_get_attributes (E_VCARD (contact)); + for (attr = attrs; attr;) { + EVCardAttribute *a = attr->data; + GList *next_attr = attr->next; + + if (!strcmp ("TEL", e_vcard_attribute_get_name (a))) { + GList *params, *param; + gboolean found_voice = FALSE; + gboolean found_work = FALSE; + + params = e_vcard_attribute_get_params (a); + for (param = params; param; param = param->next) { + EVCardAttributeParam *p = param->data; + if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { + GList *v = e_vcard_attribute_param_get_values (p); + while (v && v->data) { + if (!strcmp ("VOICE", v->data)) + found_voice = TRUE; + else if (!strcmp ("WORK", v->data)) + found_work = TRUE; + v = v->next; + } + } + + if (found_work && found_voice) + num_work_voice++; + + if (num_work_voice == 3) { + GList *v = e_vcard_attribute_get_values (a); + + if (v && v->data) + e_contact_set (contact, E_CONTACT_PHONE_COMPANY, v->data); + + e_vcard_remove_attribute (E_VCARD (contact), a); + + converted = TRUE; + break; + } + } + } + + attr = next_attr; + + if (converted) + break; + } + + if (converted) { + if (!e_book_commit_contact (book, + contact, + &e)) + g_warning ("contact commit failed: `%s'", e->message); + } + + num_converted ++; + + dialog_set_progress (context, (double)num_converted / num_contacts); + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); + + g_object_unref (book); + } +} + +static void +migrate_pilot_data (const gchar *old_path, const gchar *new_path) +{ + const gchar *dent; + const gchar *ext; + gchar *filename; + GDir *dir; + + if (!(dir = g_dir_open (old_path, 0, NULL))) + return; + + while ((dent = g_dir_read_name (dir))) { + if ((!strncmp (dent, "pilot-map-", 10) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) || + (!strncmp (dent, "pilot-sync-evolution-addressbook-", 33) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db")))) { + /* src and dest file formats are identical for both map and changelog files */ + guchar inbuf[4096]; + gsize nread, nwritten; + gint fd0, fd1; + gssize n; + + filename = g_build_filename (old_path, dent, NULL); + if ((fd0 = g_open (filename, O_RDONLY | O_BINARY, 0)) == -1) { + g_free (filename); + continue; + } + + g_free (filename); + filename = g_build_filename (new_path, dent, NULL); + if ((fd1 = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) { + g_free (filename); + close (fd0); + continue; + } + + do { + do { + n = read (fd0, inbuf, sizeof (inbuf)); + } while (n == -1 && errno == EINTR); + + if (n < 1) + break; + + nread = n; + nwritten = 0; + do { + do { + n = write (fd1, inbuf + nwritten, nread - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (nwritten < nread && n != -1); + + if (n == -1) + break; + } while (1); + + if (n != -1) + n = fsync (fd1); + + if (n == -1) { + g_warning ("Failed to migrate %s: %s", dent, g_strerror (errno)); + g_unlink (filename); + } + + close (fd0); + close (fd1); + g_free (filename); + } + } + + g_dir_close (dir); +} + +static MigrationContext * +migration_context_new (const gchar *data_dir) +{ + MigrationContext *context = g_new (MigrationContext, 1); + + /* set up the mapping from old uris to new uids */ + context->folder_uid_map = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + e_book_get_addressbooks (&context->source_list, NULL); + + context->data_dir = data_dir; + + return context; +} + +static void +migration_context_free (MigrationContext *context) +{ + e_source_list_sync (context->source_list, NULL); + + g_hash_table_destroy (context->folder_uid_map); + + g_object_unref (context->source_list); + + g_free (context); +} + +gboolean +e_book_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint micro, + GError **error) +{ + ESourceGroup *on_this_computer; + ESourceGroup *on_ldap_servers; + ESource *personal_source; + MigrationContext *context; + gboolean need_dialog = FALSE; + const gchar *data_dir; + + g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE); + + data_dir = e_shell_backend_get_data_dir (shell_backend); + context = migration_context_new (data_dir); + + /* we call this unconditionally now - create_groups either + creates the groups/sources or it finds the necessary + groups/sources. */ + create_groups (context, &on_this_computer, &on_ldap_servers, &personal_source); + + /* figure out if we need the dialog displayed */ + if (major == 1 + /* we only need the most recent upgrade point here. + further decomposition will happen below. */ + && (minor < 5 || (minor == 5 && micro <= 10))) + need_dialog = TRUE; + + if (need_dialog) + setup_progress_dialog (context); + + if (major == 1) { + + if (minor < 5 || (minor == 5 && micro <= 2)) { + /* initialize our dialog */ + dialog_set_label (context, + _("The location and hierarchy of the Evolution contact " + "folders has changed since Evolution 1.x.\n\nPlease be " + "patient while Evolution migrates your folders...")); + + if (on_this_computer) + migrate_local_folders (context, on_this_computer, personal_source); + if (on_ldap_servers) + migrate_ldap_servers (context, on_ldap_servers); + + migrate_completion_folders (context); + } + + if (minor < 5 || (minor == 5 && micro <= 7)) { + dialog_set_label (context, + _("The format of mailing list contacts has changed.\n\n" + "Please be patient while Evolution migrates your " + "folders...")); + + migrate_contact_lists_for_local_folders (context, on_this_computer); + } + + if (minor < 5 || (minor == 5 && micro <= 8)) { + dialog_set_label (context, + _("The way Evolution stores some phone numbers has changed.\n\n" + "Please be patient while Evolution migrates your " + "folders...")); + + migrate_company_phone_for_local_folders (context, on_this_computer); + } + + if (minor < 5 || (minor == 5 && micro <= 10)) { + gchar *old_path, *new_path; + + dialog_set_label (context, _("Evolution's Palm Sync changelog and map files have changed.\n\n" + "Please be patient while Evolution migrates your Pilot Sync data...")); + + old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Contacts", NULL); + new_path = g_build_filename (data_dir, "local", "system", NULL); + migrate_pilot_data (old_path, new_path); + g_free (new_path); + g_free (old_path); + } + + /* we only need to do this next step if people ran + older versions of 1.5. We need to clear out the + absolute URI's that were assigned to ESources + during one phase of development, as they take + precedent over relative uris (but aren't updated + when editing an ESource). */ + if (minor == 5 && micro <= 11) { + GSList *g; + for (g = e_source_list_peek_groups (context->source_list); g; g = g->next) { + ESourceGroup *group = g->data; + GSList *s; + + for (s = e_source_group_peek_sources (group); s; s = s->next) { + ESource *source = s->data; + e_source_set_absolute_uri (source, NULL); + } + } + } + } + + if (need_dialog) + dialog_close (context); + + if (on_this_computer) + g_object_unref (on_this_computer); + if (on_ldap_servers) + g_object_unref (on_ldap_servers); + if (personal_source) + g_object_unref (personal_source); + + + migration_context_free (context); + + 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..cb6128910a --- /dev/null +++ b/modules/addressbook/e-book-shell-migrate.h @@ -0,0 +1,41 @@ +/* + * 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 <glib.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..fc283e28d7 --- /dev/null +++ b/modules/addressbook/e-book-shell-sidebar.c @@ -0,0 +1,232 @@ +/* + * 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) + * + */ + +#include "e-book-shell-sidebar.h" + +#include <string.h> +#include <glib/gi18n.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 +}; + +static gpointer parent_class; +static GType book_shell_sidebar_type; + +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 (parent_class)->dispose (object); +} + +static void +book_shell_sidebar_constructed (GObject *object) +{ + EBookShellSidebarPrivate *priv; + EShellView *shell_view; + EShellBackend *shell_backend; + EShellSidebar *shell_sidebar; + ESourceList *source_list; + GtkContainer *container; + GtkWidget *widget; + + priv = E_BOOK_SHELL_SIDEBAR_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (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); + + source_list = e_book_shell_backend_get_source_list ( + E_BOOK_SHELL_BACKEND (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); + + widget = e_addressbook_selector_new (source_list); + e_source_selector_show_selection (E_SOURCE_SELECTOR (widget), FALSE); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->selector = g_object_ref (widget); + gtk_widget_show (widget); +} + +static guint32 +book_shell_sidebar_check_state (EShellSidebar *shell_sidebar) +{ + EBookShellSidebar *book_shell_sidebar; + ESourceSelector *selector; + ESource *source; + gboolean is_system = 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_peek_primary_selection (selector); + + if (source != NULL) { + const gchar *uri; + + uri = e_source_peek_relative_uri (source); + is_system = (uri == NULL || strcmp (uri, "system") == 0); + } + + if (source != NULL) + state |= E_BOOK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE; + if (is_system) + state |= E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM; + + return state; +} + +static void +book_shell_sidebar_class_init (EBookShellSidebarClass *class) +{ + GObjectClass *object_class; + EShellSidebarClass *shell_sidebar_class; + + parent_class = g_type_class_peek_parent (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 +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. */ +} + +GType +e_book_shell_sidebar_get_type (void) +{ + return book_shell_sidebar_type; +} + +void +e_book_shell_sidebar_register_type (GTypeModule *type_module) +{ + static const GTypeInfo type_info = { + sizeof (EBookShellSidebarClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) book_shell_sidebar_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EBookShellSidebar), + 0, /* n_preallocs */ + (GInstanceInitFunc) book_shell_sidebar_init, + NULL /* value_table */ + }; + + book_shell_sidebar_type = g_type_module_register_type ( + type_module, E_TYPE_SHELL_SIDEBAR, + "EBookShellSidebar", &type_info, 0); +} + +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..716523f971 --- /dev/null +++ b/modules/addressbook/e-book-shell-sidebar.h @@ -0,0 +1,79 @@ +/* + * 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 <libedataserverui/e-source-selector.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_SYSTEM = 1 << 1 +}; + +struct _EBookShellSidebar { + EShellSidebar parent; + EBookShellSidebarPrivate *priv; +}; + +struct _EBookShellSidebarClass { + EShellSidebarClass parent_class; +}; + +GType e_book_shell_sidebar_get_type (void); +void e_book_shell_sidebar_register_type + (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..773eb3fa7a --- /dev/null +++ b/modules/addressbook/e-book-shell-view-actions.c @@ -0,0 +1,1011 @@ +/* + * 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) + * + */ + +#include "e-book-shell-view-private.h" + +#include <e-util/e-error.h> +#include <e-util/e-util.h> +#include <filter/filter-rule.h> + +#include <addressbook-config.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; + EBookShellBackend *book_shell_backend; + EBookShellSidebar *book_shell_sidebar; + ESource *source; + ESourceSelector *selector; + ESourceGroup *source_group; + ESourceList *source_list; + EBook *book; + gint response; + GError *error = NULL; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + book_shell_backend = book_shell_view->priv->book_shell_backend; + source_list = e_book_shell_backend_get_source_list (book_shell_backend); + + book_shell_sidebar = book_shell_view->priv->book_shell_sidebar; + selector = e_book_shell_sidebar_get_selector (book_shell_sidebar); + source = e_source_selector_peek_primary_selection (selector); + g_return_if_fail (source != NULL); + + response = e_error_run ( + GTK_WINDOW (shell_window), + "addressbook:ask-delete-addressbook", + e_source_peek_name (source)); + + if (response != GTK_RESPONSE_YES) + return; + + book = e_book_new (source, &error); + if (error != NULL) { + g_warning ("Error removing addressbook: %s", error->message); + g_error_free (error); + return; + } + + if (!e_book_remove (book, NULL)) { + e_error_run ( + GTK_WINDOW (shell_window), + "addressbook:remove-addressbook", NULL); + g_object_unref (book); + return; + } + + if (e_source_selector_source_is_selected (selector, source)) + e_source_selector_unselect_source (selector, source); + + source_group = e_source_peek_group (source); + e_source_group_remove_source (source_group, source); + + e_source_list_sync (source_list, NULL); + + g_object_unref (book); +} + +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; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + addressbook_config_create_new_source (GTK_WIDGET (shell_window)); +} + +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; + EditorUidClosure *closure; + GHashTable *uid_to_editor; + const gchar *uid; + + 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_peek_primary_selection (selector); + g_return_if_fail (source != NULL); + + uid = e_source_peek_uid (source); + uid_to_editor = book_shell_view->priv->uid_to_editor; + + closure = g_hash_table_lookup (uid_to_editor, uid); + if (closure == NULL) { + GtkWidget *editor; + + editor = addressbook_config_edit_source ( + GTK_WIDGET (shell_window), source); + + closure = g_new (EditorUidClosure, 1); + closure->editor = editor; + closure->uid = g_strdup (uid); + closure->view = book_shell_view; + + g_hash_table_insert (uid_to_editor, closure->uid, closure); + + g_object_weak_ref ( + G_OBJECT (closure->editor), (GWeakNotify) + e_book_shell_view_editor_weak_notify, closure); + } + + gtk_window_present (GTK_WINDOW (closure->editor)); +} + +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) +{ + 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_save_as (view, TRUE); +} + +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_clipboard_copy_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + + book_shell_content = book_shell_view->priv->book_shell_content; + e_book_shell_content_clipboard_copy (book_shell_content); +} + +static void +action_contact_clipboard_cut_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_cut (view); +} + +static void +action_contact_clipboard_paste_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_paste (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_addressbook_view_delete_selection (view, TRUE); +} + +static void +action_contact_forward_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + EAddressbookView *view; + GList *list, *iter; + + 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 (list); + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); +} + +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) +{ + EBookShellContent *book_shell_content; + EAddressbookView *view; + EAddressbookModel *model; + EContact *contact; + GtkWidget *editor; + EBook *book; + + 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_book (model); + g_return_if_fail (book != NULL); + + contact = e_contact_new (); + editor = e_contact_editor_new (book, contact, TRUE, TRUE); + eab_editor_show (EAB_EDITOR (editor)); + g_object_unref (contact); +} + +static void +action_contact_new_list_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + EAddressbookView *view; + EAddressbookModel *model; + EContact *contact; + GtkWidget *editor; + EBook *book; + + 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_book (model); + g_return_if_fail (book != NULL); + + contact = e_contact_new (); + editor = e_contact_list_editor_new (book, contact, TRUE, TRUE); + eab_editor_show (EAB_EDITOR (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_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, print_action); +} + +static void +action_contact_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, print_action); +} + +static void +action_contact_save_as_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_save_as (view, FALSE); +} + +static void +action_contact_search_cb (GtkRadioAction *action, + GtkRadioAction *current, + EBookShellView *book_shell_view) +{ + EShellView *shell_view; + EShellContent *shell_content; + const gchar *search_hint; + + /* XXX Figure out a way to handle this in EShellContent + * instead of every shell view having to handle it. + * The problem is EShellContent does not know what + * the search option actions are for this view. It + * would have to dig up the popup menu and retrieve + * the action for each menu item. Seems messy. */ + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + + search_hint = gtk_action_get_label (GTK_ACTION (current)); + e_shell_content_set_search_hint (shell_content, search_hint); +} + +static void +action_contact_select_all_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_select_all (view); +} + +static void +action_contact_send_message_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + EAddressbookView *view; + GList *list, *iter; + + 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 (list); + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); +} + +static void +action_gal_save_custom_view_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + EShellView *shell_view; + EAddressbookView *address_view; + GalViewInstance *view_instance; + + /* All shell views respond to the activation of this action, + * which is defined by EShellWindow. But only the currently + * active shell view proceeds with saving the custom view. */ + shell_view = E_SHELL_VIEW (book_shell_view); + if (!e_shell_view_is_active (shell_view)) + return; + + 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); + gal_view_instance_save_as (view_instance); +} + +static void +action_search_execute_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EShellView *shell_view; + + /* All shell views respond to the activation of this action, + * which is defined by EShellWindow. But only the currently + * active shell view proceeds with executing the search. */ + shell_view = E_SHELL_VIEW (book_shell_view); + if (!e_shell_view_is_active (shell_view)) + return; + + e_book_shell_view_execute_search (book_shell_view); +} + +static void +action_search_filter_cb (GtkRadioAction *action, + GtkRadioAction *current, + EBookShellView *book_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + gtk_action_activate (ACTION (SEARCH_EXECUTE)); +} + +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_("Del_ete 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-rename", + NULL, + N_("_Rename..."), + "F2", + N_("Rename the selected address book"), + G_CALLBACK (action_address_book_rename_cb) }, + + { "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) }, + + { "address-book-stop", + GTK_STOCK_STOP, + NULL, + NULL, + N_("Stop loading"), + G_CALLBACK (action_address_book_stop_cb) }, + + { "contact-clipboard-copy", + GTK_STOCK_COPY, + NULL, + NULL, + N_("Copy the selection"), + G_CALLBACK (action_contact_clipboard_copy_cb) }, + + { "contact-clipboard-cut", + GTK_STOCK_CUT, + NULL, + NULL, + N_("Cut the selection"), + G_CALLBACK (action_contact_clipboard_cut_cb) }, + + { "contact-clipboard-paste", + GTK_STOCK_PASTE, + NULL, + NULL, + N_("Paste the clipboard"), + G_CALLBACK (action_contact_clipboard_paste_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-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"), + "<Control>o", + N_("View the current contact"), + G_CALLBACK (action_contact_open_cb) }, + + { "contact-save-as", + GTK_STOCK_SAVE_AS, + N_("Save as vCard..."), + NULL, + N_("Save selected contacts as a vCard"), + G_CALLBACK (action_contact_save_as_cb) }, + + { "contact-select-all", + GTK_STOCK_SELECT_ALL, + NULL, + NULL, + N_("Select all contacts"), + G_CALLBACK (action_contact_select_all_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 ***/ + + { "actions-menu", + NULL, + N_("_Actions"), + 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-rename", + NULL, + "address-book-rename" }, + + { "address-book-popup-save-as", + N_("_Save as vCard..."), + "address-book-save-as" }, + + { "contact-popup-clipboard-copy", + NULL, + "contact-clipboard-copy" }, + + { "contact-popup-clipboard-cut", + NULL, + "contact-clipboard-cut" }, + + { "contact-popup-clipboard-paste", + NULL, + "contact-clipboard-paste" }, + + { "contact-popup-copy", + NULL, + "contact-copy" }, + + { "contact-popup-delete", + NULL, + "contact-delete" }, + + { "contact-popup-forward", + NULL, + "contact-forward" }, + + { "contact-popup-move", + NULL, + "contact-move" }, + + { "contact-popup-open", + NULL, + "contact-open" }, + + { "contact-popup-save-as", + NULL, + "contact-save-as" }, + + { "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 } +}; + +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-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[] = { + + { "contact-print", + GTK_STOCK_PRINT, + NULL, + "<Control>p", + N_("Print selected contacts"), + G_CALLBACK (action_contact_print_cb) }, + + { "contact-print-preview", + GTK_STOCK_PRINT_PREVIEW, + NULL, + NULL, + N_("Preview the contacts to be printed"), + G_CALLBACK (action_contact_print_preview_cb) } +}; + +static EPopupActionEntry lockdown_printing_popup_entries[] = { + + { "contact-popup-print", + NULL, + "contact-print" } +}; + +void +e_book_shell_view_actions_init (EBookShellView *book_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + GtkActionGroup *action_group; + GConfBridge *bridge; + GtkAction *action; + GObject *object; + const gchar *key; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + /* 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_search_entries, + G_N_ELEMENTS (contact_search_entries), + CONTACT_SEARCH_NAME_CONTAINS, + G_CALLBACK (action_contact_search_cb), book_shell_view); + + /* 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)); + + /* Bind GObject properties to GConf keys. */ + + bridge = gconf_bridge_get (); + + object = G_OBJECT (ACTION (CONTACT_PREVIEW)); + key = "/apps/evolution/addressbook/display/show_preview"; + gconf_bridge_bind_property (bridge, key, object, "active"); + + /* Fine tuning. */ + + action = ACTION (CONTACT_DELETE); + g_object_set (action, "short-label", _("Delete"), NULL); + + g_signal_connect ( + ACTION (GAL_SAVE_CUSTOM_VIEW), "activate", + G_CALLBACK (action_gal_save_custom_view_cb), book_shell_view); + + g_signal_connect ( + ACTION (SEARCH_EXECUTE), "activate", + G_CALLBACK (action_search_execute_cb), book_shell_view); +} + +void +e_book_shell_view_update_search_filter (EBookShellView *book_shell_view) +{ + EShellView *shell_view; + EShellContent *shell_content; + EShellWindow *shell_window; + GtkActionGroup *action_group; + GtkRadioAction *radio_action; + GList *list, *iter; + GSList *group; + gint ii; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_content = e_shell_view_get_shell_content (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. */ + gtk_action_group_add_radio_actions ( + action_group, contact_filter_entries, + G_N_ELEMENTS (contact_filter_entries), + CONTACT_FILTER_ANY_CATEGORY, + G_CALLBACK (action_search_filter_cb), + book_shell_view); + + /* 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_categories_get_list (); + 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); + + /* Use any action in the group; doesn't matter which. */ + e_shell_content_set_filter_action (shell_content, radio_action); + + ii = CONTACT_FILTER_UNMATCHED; + e_shell_content_add_filter_separator_after (shell_content, ii); +} 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..8e3d31f7bf --- /dev/null +++ b/modules/addressbook/e-book-shell-view-actions.h @@ -0,0 +1,91 @@ +/* + * 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_PROPERTIES(window) \ + E_SHELL_WINDOW_ACTION ((window), "address-book-properties") +#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") + +/* Contact Actions */ +#define E_SHELL_WINDOW_ACTION_CONTACT_CLIPBOARD_COPY(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-clipboard-copy") +#define E_SHELL_WINDOW_ACTION_CONTACT_CLIPBOARD_CUT(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-clipboard-cut") +#define E_SHELL_WINDOW_ACTION_CONTACT_CLIPBOARD_PASTE(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-clipboard-paste") +#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_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_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-print") +#define E_SHELL_WINDOW_ACTION_CONTACT_PRINT_PREVIEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-print-preview") +#define E_SHELL_WINDOW_ACTION_CONTACT_SAVE_AS(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-save-as") +#define E_SHELL_WINDOW_ACTION_CONTACT_SELECT_ALL(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-select-all") +#define E_SHELL_WINDOW_ACTION_CONTACT_SEND_MESSAGE(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-send-message") + +/* Search Actions */ +#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..ec3562e87a --- /dev/null +++ b/modules/addressbook/e-book-shell-view-private.c @@ -0,0 +1,621 @@ +/* + * 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) + * + */ + +#include "e-book-shell-view-private.h" + +#include "widgets/menus/gal-view-factory-etable.h" +#include "addressbook/gui/widgets/gal-view-factory-minicard.h" + +#include "addressbook.h" + +static void +open_contact (EBookShellView *book_shell_view, + EContact *contact, + gboolean is_new_contact, + EAddressbookView *view) +{ + EAddressbookModel *model; + GtkWidget *editor; + EBook *book; + gboolean editable; + + model = e_addressbook_view_get_model (view); + book = e_addressbook_model_get_book (model); + editable = e_addressbook_model_get_editable (model); + + if (e_contact_get (contact, E_CONTACT_IS_LIST)) + editor = e_contact_list_editor_new ( + book, contact, is_new_contact, editable); + else + editor = e_contact_editor_new ( + book, contact, is_new_contact, editable); + + eab_editor_show (EAB_EDITOR (editor)); +} + +static void +popup_event (EBookShellView *book_shell_view, + GdkEventButton *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, 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); +} + +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); +} + +static void +contact_changed (EBookShellView *book_shell_view, + EContact *contact) +{ + EBookShellContent *book_shell_content; + EContact *preview_contact; + + book_shell_content = book_shell_view->priv->book_shell_content; + + preview_contact = + e_book_shell_content_get_preview_contact (book_shell_content); + + if (contact != preview_contact) + 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; + + 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); +} + +static void +book_open_cb (EBook *book, + EBookStatus status, + gpointer user_data) +{ + EAddressbookView *view = user_data; + EAddressbookModel *model; + ESource *source; + + source = e_book_get_source (book); + model = e_addressbook_view_get_model (view); + + if (status == E_BOOK_ERROR_OK) { + e_addressbook_model_set_book (model, book); + e_addressbook_model_force_folder_bar_message (model); + } else if (status != E_BOOK_ERROR_CANCELLED) + eab_load_error_dialog (NULL /* XXX */, source, status); +} + +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_peek_primary_selection (selector); + + if (source == NULL) + return; + + uid = e_source_peek_uid (source); + hash_table = book_shell_view->priv->uid_to_view; + widget = g_hash_table_lookup (hash_table, uid); + + if (widget != NULL) { + EBook *book; + + /* There is a view for this UID. Make sure the view + * actually contains an EBook. The absence of an EBook + * suggests a previous load failed, so try again. */ + view = E_ADDRESSBOOK_VIEW (widget); + model = e_addressbook_view_get_model (view); + source = e_addressbook_view_get_source (view); + + if (e_addressbook_model_get_book (model) == NULL) { + book = e_book_new (source, NULL); + + if (book != NULL) + addressbook_load (book, book_open_cb, view); + } + + } else { + EBook *book; + + /* Create a view for this UID. */ + widget = e_addressbook_view_new (shell_view, source); + gtk_widget_show (widget); + + 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_swapped ( + widget, "open-contact", + G_CALLBACK (open_contact), book_shell_view); + + g_signal_connect_swapped ( + widget, "popup-event", + G_CALLBACK (popup_event), book_shell_view); + + g_signal_connect_swapped ( + widget, "command-state-change", + G_CALLBACK (e_shell_view_update_actions), + book_shell_view); + + g_signal_connect_swapped ( + widget, "selection-change", + G_CALLBACK (selection_change), book_shell_view); + + book = e_book_new (source, NULL); + view = E_ADDRESSBOOK_VIEW (widget); + + if (book != NULL) + addressbook_load (book, book_open_cb, view); + + model = e_addressbook_view_get_model (view); + + g_signal_connect_swapped ( + model, "contact-changed", + G_CALLBACK (contact_changed), book_shell_view); + + g_signal_connect_swapped ( + model, "contacts-removed", + G_CALLBACK (contacts_removed), book_shell_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); + 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); +} + +static gboolean +book_shell_view_show_popup_menu (GdkEventButton *event, + EShellView *shell_view) +{ + const gchar *widget_path; + + widget_path = "/address-book-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, event); + + return TRUE; +} + +static gboolean +book_shell_view_selector_button_press_event_cb (EShellView *shell_view, + GdkEventButton *event) +{ + /* XXX Use ESourceSelector's "popup-event" signal instead. */ + + if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + return book_shell_view_show_popup_menu (event, shell_view); + + return FALSE; +} + +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 gboolean +book_shell_view_selector_key_press_event_cb (EShellView *shell_view, + GdkEventKey *event) +{ + EShellWindow *shell_window; + + /* Needed for the ACTION() macro. */ + shell_window = e_shell_view_get_shell_window (shell_view); + + if (event->keyval == GDK_Delete) { + gtk_action_activate (ACTION (ADDRESS_BOOK_DELETE)); + return TRUE; + } + + return FALSE; +} + +static void +book_shell_view_load_view_collection (EShellViewClass *shell_view_class) +{ + GalViewCollection *collection; + GalViewFactory *factory; + ETableSpecification *spec; + const gchar *base_dir; + gchar *filename; + + collection = shell_view_class->view_collection; + + base_dir = EVOLUTION_ETSPECDIR; + spec = e_table_specification_new (); + filename = g_build_filename (base_dir, ETSPEC_FILENAME, NULL); + if (!e_table_specification_load_from_file (spec, filename)) + g_critical ("Unable to load ETable specification file " + "for address book"); + g_free (filename); + + factory = gal_view_factory_etable_new (spec); + gal_view_collection_add_factory (collection, factory); + g_object_unref (factory); + g_object_unref (spec); + + factory = gal_view_factory_minicard_new (); + gal_view_collection_add_factory (collection, factory); + g_object_unref (factory); + + gal_view_collection_load (collection); +} + +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, + EShellViewClass *shell_view_class) +{ + EBookShellViewPrivate *priv = book_shell_view->priv; + GHashTable *uid_to_view; + GHashTable *uid_to_editor; + + uid_to_view = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + uid_to_editor = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + priv->uid_to_view = uid_to_view; + priv->uid_to_editor = uid_to_editor; + + if (!gal_view_collection_loaded (shell_view_class->view_collection)) + book_shell_view_load_view_collection (shell_view_class); + + 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; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellBackend *shell_backend; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceSelector *selector; + + 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); + + 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); + + selector = e_book_shell_sidebar_get_selector ( + E_BOOK_SHELL_SIDEBAR (shell_sidebar)); + + g_signal_connect_swapped ( + selector, "button-press-event", + G_CALLBACK (book_shell_view_selector_button_press_event_cb), + book_shell_view); + + g_signal_connect_swapped ( + selector, "key-press-event", + G_CALLBACK (book_shell_view_selector_key_press_event_cb), + book_shell_view); + + g_signal_connect_swapped ( + selector, "popup-menu", + G_CALLBACK (book_shell_view_selector_popup_menu_cb), + book_shell_view); + + g_signal_connect_swapped ( + selector, "primary-selection-changed", + G_CALLBACK (book_shell_view_activate_selected_source), + book_shell_view); + + e_categories_register_change_listener ( + G_CALLBACK (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; + + DISPOSE (priv->book_shell_backend); + DISPOSE (priv->book_shell_content); + DISPOSE (priv->book_shell_sidebar); + + g_hash_table_remove_all (priv->uid_to_view); + g_hash_table_remove_all (priv->uid_to_editor); +} + +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); + g_hash_table_destroy (priv->uid_to_editor); +} + +void +e_book_shell_view_execute_search (EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellContent *shell_content; + GtkAction *action; + GString *string; + EAddressbookView *view; + EAddressbookModel *model; + FilterRule *rule; + const gchar *format; + const gchar *text; + gchar *query; + gchar *temp; + gint value; + + shell_view = E_SHELL_VIEW (book_shell_view); + if (!e_shell_view_is_active (shell_view)) + return; + + shell_content = e_shell_view_get_shell_content (shell_view); + text = e_shell_content_get_search_text (shell_content); + + shell_window = e_shell_view_get_shell_window (shell_view); + action = ACTION (CONTACT_SEARCH_ANY_FIELD_CONTAINS); + value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (action)); + + if (text == NULL || *text == '\0') { + text = ""; + value = CONTACT_SEARCH_ANY_FIELD_CONTAINS; + } + + switch (value) { + 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. */ + value = e_shell_content_get_filter_value (shell_content); + switch (value) { + 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_categories_get_list (); + category_name = g_list_nth_data (categories, value); + g_list_free (categories); + + temp = g_strdup_printf ( + "(and (is \"category_list\" \"%s\") %s)", + category_name, query); + g_free (query); + query = temp; + } + } + + /* XXX This is wrong. We need to programmatically construct a + * FilterRule, tell it to build code, and pass the resulting + * expression string to EAddressbookModel. */ + rule = filter_rule_new (); + e_shell_content_set_search_rule (shell_content, rule); + g_object_unref (rule); + + /* Submit the query. */ + 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); + e_addressbook_model_set_query (model, query); + g_free (query); + + e_book_shell_content_set_preview_contact (book_shell_content, NULL); +} + +void +e_book_shell_view_editor_weak_notify (EditorUidClosure *closure, + GObject *where_the_object_was) +{ + GHashTable *hash_table; + + hash_table = closure->view->priv->uid_to_editor; + g_hash_table_remove (hash_table, closure->uid); +} 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..b4701aea81 --- /dev/null +++ b/modules/addressbook/e-book-shell-view-private.h @@ -0,0 +1,129 @@ +/* + * 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/e-book.h> +#include <libedataserver/e-categories.h> +#include <libedataserver/e-sexp.h> +#include <libedataserverui/e-source-selector.h> + +#include "e-util/gconf-bridge.h" +#include "shell/e-shell-content.h" +#include "shell/e-shell-sidebar.h" +#include "misc/e-popup-action.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 "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)) + +/* For use in dispose() methods. */ +#define DISPOSE(obj) \ + G_STMT_START { \ + if ((obj) != NULL) { g_object_unref (obj); (obj) = NULL; } \ + } G_STMT_END + +/* ETable Specifications */ +#define ETSPEC_FILENAME "e-addressbook-view.etspec" + +G_BEGIN_DECLS + +typedef struct _EditorUidClosure EditorUidClosure; + +struct _EditorUidClosure { + GtkWidget *editor; + gchar *uid; + EBookShellView *view; +}; + +/* 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_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; + + GHashTable *uid_to_view; + GHashTable *uid_to_editor; +}; + +void e_book_shell_view_private_init + (EBookShellView *book_shell_view, + EShellViewClass *shell_view_class); +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_execute_search + (EBookShellView *book_shell_view); +void e_book_shell_view_editor_weak_notify + (EditorUidClosure *closure, + GObject *where_the_object_was); +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..ea48bb534c --- /dev/null +++ b/modules/addressbook/e-book-shell-view.c @@ -0,0 +1,318 @@ +/* + * 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) + * + */ + +#include "e-book-shell-view-private.h" + +static gpointer parent_class; +static GType book_shell_view_type; + +static void +book_shell_view_source_list_changed_cb (EBookShellView *book_shell_view, + ESourceList *source_list) +{ + EBookShellViewPrivate *priv = book_shell_view->priv; + EBookShellContent *book_shell_content; + EShellView *shell_view; + GList *keys, *iter; + + shell_view = E_SHELL_VIEW (book_shell_view); + book_shell_content = book_shell_view->priv->book_shell_content; + + keys = g_hash_table_get_keys (priv->uid_to_view); + for (iter = keys; iter != NULL; iter = iter->next) { + gchar *uid = iter->data; + EAddressbookView *view; + + /* If the source still exists, move on. */ + if (e_source_list_peek_source_by_uid (source_list, uid)) + continue; + + /* Remove the view for the deleted source. */ + view = g_hash_table_lookup (priv->uid_to_view, uid); + e_book_shell_content_remove_view (book_shell_content, view); + g_hash_table_remove (priv->uid_to_view, uid); + } + g_list_free (keys); + + keys = g_hash_table_get_keys (priv->uid_to_editor); + for (iter = keys; iter != NULL; iter = iter->next) { + gchar *uid = iter->data; + EditorUidClosure *closure; + + /* If the source still exists, move on. */ + if (e_source_list_peek_source_by_uid (source_list, uid)) + continue; + + /* Remove the editor for the deleted source. */ + closure = g_hash_table_lookup (priv->uid_to_editor, uid); + g_object_weak_unref ( + G_OBJECT (closure->editor), (GWeakNotify) + e_book_shell_view_editor_weak_notify, closure); + gtk_widget_destroy (closure->editor); + g_hash_table_remove (priv->uid_to_editor, uid); + } + g_list_free (keys); + + e_shell_view_update_actions (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 (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 (parent_class)->finalize (object); +} + +static void +book_shell_view_constructed (GObject *object) +{ + EBookShellView *book_shell_view; + EBookShellBackend *book_shell_backend; + ESourceList *source_list; + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (parent_class)->constructed (object); + + book_shell_view = E_BOOK_SHELL_VIEW (object); + e_book_shell_view_private_constructed (book_shell_view); + + book_shell_backend = book_shell_view->priv->book_shell_backend; + source_list = e_book_shell_backend_get_source_list (book_shell_backend); + + g_signal_connect_swapped ( + source_list, "changed", + G_CALLBACK (book_shell_view_source_list_changed_cb), + book_shell_view); +} + +static void +book_shell_view_update_actions (EShellView *shell_view) +{ + EBookShellViewPrivate *priv; + 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_system; + gboolean single_contact_selected; + gboolean selection_is_contact_list; + gboolean selection_has_email; + gboolean source_is_busy; + gboolean source_is_editable; + gboolean source_is_empty; + + priv = E_BOOK_SHELL_VIEW_GET_PRIVATE (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); + source_is_empty = + (state & E_BOOK_SHELL_CONTENT_SOURCE_IS_EMPTY); + + 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_system = + (state & E_BOOK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM); + + any_contacts_selected = + (single_contact_selected || multiple_contacts_selected); + + action = ACTION (ADDRESS_BOOK_DELETE); + sensitive = has_primary_source && !primary_source_is_system; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (ADDRESS_BOOK_RENAME); + sensitive = has_primary_source; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (ADDRESS_BOOK_STOP); + sensitive = source_is_busy; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CONTACT_CLIPBOARD_COPY); + sensitive = any_contacts_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CONTACT_CLIPBOARD_CUT); + sensitive = source_is_editable && any_contacts_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CONTACT_CLIPBOARD_PASTE); + sensitive = source_is_editable; + 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_FORWARD); + sensitive = any_contacts_selected; + gtk_action_set_sensitive (action, sensitive); + if (multiple_contacts_selected) + label = _("_Forward Contacts"); + else + label = _("_Forward Contact"); + g_object_set (action, "label", label, NULL); + + action = ACTION (CONTACT_MOVE); + sensitive = source_is_editable && any_contacts_selected; + 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_PRINT_PREVIEW); + 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_SELECT_ALL); + sensitive = !(source_is_empty); + 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"); + g_object_set (action, "label", label, NULL); +} + +static void +book_shell_view_class_init (EBookShellViewClass *class) +{ + GObjectClass *object_class; + EShellViewClass *shell_view_class; + + parent_class = g_type_class_peek_parent (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->update_actions = book_shell_view_update_actions; +} + +static void +book_shell_view_init (EBookShellView *book_shell_view, + EShellViewClass *shell_view_class) +{ + book_shell_view->priv = + E_BOOK_SHELL_VIEW_GET_PRIVATE (book_shell_view); + + e_book_shell_view_private_init (book_shell_view, shell_view_class); +} + +GType +e_book_shell_view_get_type (void) +{ + return book_shell_view_type; +} + +void +e_book_shell_view_register_type (GTypeModule *type_module) +{ + const GTypeInfo type_info = { + sizeof (EBookShellViewClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) book_shell_view_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EBookShellView), + 0, /* n_preallocs */ + (GInstanceInitFunc) book_shell_view_init, + NULL /* value_table */ + }; + + book_shell_view_type = g_type_module_register_type ( + type_module, E_TYPE_SHELL_VIEW, + "EBookShellView", &type_info, 0); +} diff --git a/modules/addressbook/e-book-shell-view.h b/modules/addressbook/e-book-shell-view.h new file mode 100644 index 0000000000..33a0c8a75d --- /dev/null +++ b/modules/addressbook/e-book-shell-view.h @@ -0,0 +1,66 @@ +/* + * 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_register_type (GTypeModule *type_module); + +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..6d45f13a94 --- /dev/null +++ b/modules/addressbook/eab-composer-util.c @@ -0,0 +1,197 @@ +/* + * 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) + * + */ + +#include "eab-composer-util.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libebook/e-contact.h> +#include <libebook/e-destination.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 (GList *destinations) +{ + EMsgComposer *composer; + EComposerHeaderTable *table; + GPtrArray *to_array; + GPtrArray *bcc_array; + + union { + gpointer *pdata; + EDestination **destinations; + } convert; + + if (destinations == NULL) + return; + + composer = e_msg_composer_new (); + 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_list_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. */ + + /* Add "To" destinations. */ + convert.pdata = to_array->pdata; + e_composer_header_table_set_destinations_to ( + table, convert.destinations); + g_ptr_array_free (to_array, FALSE); + e_destination_freev (convert.destinations); + + /* Add "Bcc" destinations. */ + convert.pdata = bcc_array->pdata; + e_composer_header_table_set_destinations_bcc ( + table, convert.destinations); + g_ptr_array_free (bcc_array, FALSE); + e_destination_freev (convert.destinations); + + 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 (GList *destinations) +{ + EMsgComposer *composer; + EComposerHeaderTable *table; + CamelMimePart *attachment; + GList *contacts, *iter; + gchar *data; + + if (destinations == NULL) + return; + + composer = e_msg_composer_new (); + table = e_msg_composer_get_header_table (composer); + + attachment = camel_mime_part_new (); + + contacts = g_list_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_list_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); + camel_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..4aec23074d --- /dev/null +++ b/modules/addressbook/eab-composer-util.h @@ -0,0 +1,31 @@ +/* + * 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 <gtk/gtk.h> + +G_BEGIN_DECLS + +void eab_send_as_to (GList *destinations); +void eab_send_as_attachment (GList *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..3089133e43 --- /dev/null +++ b/modules/addressbook/evolution-module-addressbook.c @@ -0,0 +1,45 @@ +/* + * 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) + * + */ + +#include "e-book-shell-backend.h" +#include "e-book-shell-content.h" +#include "e-book-shell-sidebar.h" +#include "e-book-shell-view.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_shell_backend_register_type (type_module); + e_book_shell_content_register_type (type_module); + e_book_shell_sidebar_register_type (type_module); + e_book_shell_view_register_type (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ +} diff --git a/modules/addressbook/ldap-config.glade b/modules/addressbook/ldap-config.glade new file mode 100644 index 0000000000..f87cf84b14 --- /dev/null +++ b/modules/addressbook/ldap-config.glade @@ -0,0 +1,1454 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkDialog" id="account-editor-window"> + <property name="visible">True</property> + <property name="title" translatable="yes">Address Book Properties</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="focus_on_map">True</property> + <property name="urgency_hint">False</property> + <property name="has_separator">False</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox7"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area7"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="account-editor-cancel-button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-6</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="account-editor-ok-button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-5</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkNotebook" id="account-editor-notebook"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">True</property> + <property name="show_border">True</property> + <property name="tab_pos">GTK_POS_TOP</property> + <property name="scrollable">False</property> + <property name="enable_popup">False</property> + + <child> + <widget class="GtkVBox" id="account-editor-general-vbox"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label564"> + <property name="visible">True</property> + <property name="label">Display</property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox122"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkLabel" id="label563"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label431"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Name:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">account-editor-display-name-entry</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="account-editor-display-name-entry"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">This is the name for this server that will appear in your Evolution folder list. It is for display purposes only. </property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="account-editor-connecting-vbox"> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label557"> + <property name="visible">True</property> + <property name="label">Server Information</property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox123"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label558"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="table31"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkLabel" id="label554"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Port:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label556"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Server:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">server-name-entry</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox123"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkLabel" id="label555"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Use secure connection:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">ssl-combobox</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkComboBox" id="ssl-combobox"> + <property name="visible">True</property> + <property name="items" translatable="yes">SSL encryption +TLS encryption +No encryption</property> + <property name="add_tearoffs">False</property> + <property name="focus_on_click">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="server-name-entry"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">This is the full name of your LDAP server. For example, "ldap.mycompany.com".</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkComboBoxEntry" id="port-comboentry"> + <property name="visible">True</property> + <property name="items" translatable="yes">389 +636 +3268</property> + <property name="add_tearoffs">False</property> + <property name="has_frame">True</property> + <property name="focus_on_click">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options">fill</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label559"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Authentication</b></property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox124"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label562"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="table32"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkLabel" id="label560"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Login method:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label561"> + <property name="visible">True</property> + <property name="label" translatable="yes">Lo_gin:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">auth-entry</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="auth-entry"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">Evolution will use this email address to authenticate you with the server.</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkComboBox" id="auth-combobox"> + <property name="visible">True</property> + <property name="items" translatable="yes">Anonymously +Using email address +Using distinguished name (DN)</property> + <property name="add_tearoffs">False</property> + <property name="focus_on_click">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="tab_expand">False</property> + <property name="tab_fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label334"> + <property name="visible">True</property> + <property name="label" translatable="yes">General</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="type">tab</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="account-editor-searching-vbox"> + <property name="border_width">12</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label565"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Searching</b></property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox125"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label571"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="table33"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">3</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkLabel" id="label569"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label566"> + <property name="visible">True</property> + <property name="label" translatable="yes">Search _base:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">rootdn-entry</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label567"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Search scope:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="rootdn-entry"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">The search base is the distinguished name (DN) of the entry where your searches will begin. If you leave this blank, the search will begin at the root of the directory tree.</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkButton" id="rootdn-button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + + <child> + <widget class="GtkAlignment" id="alignment61"> + <property name="visible">True</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xscale">0</property> + <property name="yscale">0</property> + <property name="top_padding">0</property> + <property name="bottom_padding">0</property> + <property name="left_padding">0</property> + <property name="right_padding">0</property> + + <child> + <widget class="GtkHBox" id="hbox126"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">2</property> + + <child> + <widget class="GtkImage" id="image10"> + <property name="visible">True</property> + <property name="stock">gtk-find</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label568"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Find Possible Search Bases</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label581"> + <property name="visible">True</property> + <property name="label" translatable="yes">Search _filter:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">search-filter-entry</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="search-filter-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + <accessibility> + <atkproperty name="AtkObject::accessible_name" translatable="yes">Search Filter</atkproperty> + <atkproperty name="AtkObject::accessible_description" translatable="yes">Search filter is the type of object to be searched for. If this is not modified, the default search will be performed on the type "person".</atkproperty> + </accessibility> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkComboBox" id="scope-combobox"> + <property name="visible">True</property> + <property name="items" translatable="yes">One +Sub</property> + <property name="add_tearoffs">False</property> + <property name="focus_on_click">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label572"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Downloading</b></property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox127"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label579"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="table34"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">3</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkHBox" id="hbox128"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkLabel" id="label575"> + <property name="visible">True</property> + <property name="label" translatable="yes">1</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHScale" id="timeout-scale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="draw_value">False</property> + <property name="value_pos">GTK_POS_TOP</property> + <property name="digits">1</property> + <property name="update_policy">GTK_UPDATE_CONTINUOUS</property> + <property name="inverted">False</property> + <property name="adjustment">3 1 5 0.5 1 0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label576"> + <property name="visible">True</property> + <property name="label" translatable="yes">5</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label574"> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label578"> + <property name="visible">True</property> + <property name="label" translatable="yes">cards</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label573"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Timeout:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">timeout-scale</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label577"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Download limit:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">download-limit-spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkSpinButton" id="download-limit-spinbutton"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">This is the maximum number of entries to download. Setting this number to be too large will slow down your address book.</property> + <property name="can_focus">True</property> + <property name="climb_rate">1</property> + <property name="digits">0</property> + <property name="numeric">False</property> + <property name="update_policy">GTK_UPDATE_ALWAYS</property> + <property name="snap_to_ticks">False</property> + <property name="wrap">False</property> + <property name="adjustment">0 0 1000 1 10 0</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">expand|shrink|fill</property> + <property name="y_options">shrink|fill</property> + </packing> + </child> + + <child> + <widget class="GtkCheckButton" id="canbrowsecheck"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes" comments="To translators: If enabled, addressbook will only fetch contacts from the server until either set time limit or amount of contacts limit reached">B_rowse this book until limit reached</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="tab_expand">False</property> + <property name="tab_fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label344"> + <property name="visible">True</property> + <property name="label" translatable="yes">Details</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="type">tab</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +<widget class="GtkDialog" id="supported-bases-dialog"> + <property name="title" translatable="yes">Supported Search Bases</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_CENTER</property> + <property name="modal">False</property> + <property name="default_width">320</property> + <property name="default_height">200</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="focus_on_map">True</property> + <property name="urgency_hint">False</property> + <property name="has_separator">False</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox8"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area8"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="cancelbutton1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-6</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="okbutton1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-5</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="vbox40"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="Custom" id="supported-bases-table"> + <property name="visible">True</property> + <property name="creation_function">supported_bases_create_table</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Fri, 12 Apr 2002 20:06:45 GMT</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> diff --git a/modules/addressbook/openldap-extract.h b/modules/addressbook/openldap-extract.h new file mode 100644 index 0000000000..996bf370a8 --- /dev/null +++ b/modules/addressbook/openldap-extract.h @@ -0,0 +1,1427 @@ +/* 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 + +#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; +} + |