aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2009-08-30 13:37:36 +0800
committerMatthew Barnes <mbarnes@redhat.com>2009-08-30 13:40:49 +0800
commitcfb9c32b6657165e4d5e11aa7b47804f679a61f8 (patch)
tree1f9c8954df7a357b5dc20a13ac82bf31c1112083 /modules
parentfefeb30f58447f2fa7bcbee16dbe68a9333ce89d (diff)
parent0f7f4cfe38b3c4cd83efbe9922ae15c5aee00317 (diff)
downloadgsoc2013-evolution-cfb9c32b6657165e4d5e11aa7b47804f679a61f8.tar.gz
gsoc2013-evolution-cfb9c32b6657165e4d5e11aa7b47804f679a61f8.tar.zst
gsoc2013-evolution-cfb9c32b6657165e4d5e11aa7b47804f679a61f8.zip
Merge commit 'origin/kill-bonobo'
Diffstat (limited to 'modules')
-rw-r--r--modules/Makefile.am11
-rw-r--r--modules/addressbook/Makefile.am133
-rw-r--r--modules/addressbook/addressbook-config.c1247
-rw-r--r--modules/addressbook/addressbook-config.h52
-rw-r--r--modules/addressbook/apps_evolution_addressbook.schemas.in116
-rw-r--r--modules/addressbook/autocompletion-config.c202
-rw-r--r--modules/addressbook/autocompletion-config.h37
-rw-r--r--modules/addressbook/e-book-config-hook.c67
-rw-r--r--modules/addressbook/e-book-config-hook.h33
-rw-r--r--modules/addressbook/e-book-shell-backend.c591
-rw-r--r--modules/addressbook/e-book-shell-backend.h70
-rw-r--r--modules/addressbook/e-book-shell-content.c541
-rw-r--r--modules/addressbook/e-book-shell-content.h110
-rw-r--r--modules/addressbook/e-book-shell-migrate.c1228
-rw-r--r--modules/addressbook/e-book-shell-migrate.h41
-rw-r--r--modules/addressbook/e-book-shell-settings.c34
-rw-r--r--modules/addressbook/e-book-shell-settings.h33
-rw-r--r--modules/addressbook/e-book-shell-sidebar.c232
-rw-r--r--modules/addressbook/e-book-shell-sidebar.h79
-rw-r--r--modules/addressbook/e-book-shell-view-actions.c1093
-rw-r--r--modules/addressbook/e-book-shell-view-actions.h95
-rw-r--r--modules/addressbook/e-book-shell-view-private.c634
-rw-r--r--modules/addressbook/e-book-shell-view-private.h131
-rw-r--r--modules/addressbook/e-book-shell-view.c318
-rw-r--r--modules/addressbook/e-book-shell-view.h66
-rw-r--r--modules/addressbook/eab-composer-util.c197
-rw-r--r--modules/addressbook/eab-composer-util.h31
-rw-r--r--modules/addressbook/evolution-module-addressbook.c49
-rw-r--r--modules/addressbook/ldap-config.glade1454
-rw-r--r--modules/addressbook/openldap-extract.h1426
-rw-r--r--modules/calendar/Makefile.am84
-rw-r--r--modules/calendar/e-cal-attachment-handler.c512
-rw-r--r--modules/calendar/e-cal-attachment-handler.h67
-rw-r--r--modules/calendar/e-cal-config-hook.c68
-rw-r--r--modules/calendar/e-cal-config-hook.h33
-rw-r--r--modules/calendar/e-cal-event-hook.c72
-rw-r--r--modules/calendar/e-cal-event-hook.h33
-rw-r--r--modules/calendar/e-cal-shell-backend.c854
-rw-r--r--modules/calendar/e-cal-shell-backend.h70
-rw-r--r--modules/calendar/e-cal-shell-content.c768
-rw-r--r--modules/calendar/e-cal-shell-content.h108
-rw-r--r--modules/calendar/e-cal-shell-migrate.c794
-rw-r--r--modules/calendar/e-cal-shell-migrate.h38
-rw-r--r--modules/calendar/e-cal-shell-settings.c742
-rw-r--r--modules/calendar/e-cal-shell-settings.h33
-rw-r--r--modules/calendar/e-cal-shell-sidebar.c757
-rw-r--r--modules/calendar/e-cal-shell-sidebar.h101
-rw-r--r--modules/calendar/e-cal-shell-view-actions.c1792
-rw-r--r--modules/calendar/e-cal-shell-view-actions.h153
-rw-r--r--modules/calendar/e-cal-shell-view-memopad.c526
-rw-r--r--modules/calendar/e-cal-shell-view-private.c1098
-rw-r--r--modules/calendar/e-cal-shell-view-private.h180
-rw-r--r--modules/calendar/e-cal-shell-view-taskpad.c654
-rw-r--r--modules/calendar/e-cal-shell-view.c219
-rw-r--r--modules/calendar/e-cal-shell-view.h66
-rw-r--r--modules/calendar/e-memo-shell-backend.c653
-rw-r--r--modules/calendar/e-memo-shell-backend.h70
-rw-r--r--modules/calendar/e-memo-shell-content.c721
-rw-r--r--modules/calendar/e-memo-shell-content.h96
-rw-r--r--modules/calendar/e-memo-shell-migrate.c268
-rw-r--r--modules/calendar/e-memo-shell-migrate.h38
-rw-r--r--modules/calendar/e-memo-shell-sidebar.c716
-rw-r--r--modules/calendar/e-memo-shell-sidebar.h97
-rw-r--r--modules/calendar/e-memo-shell-view-actions.c1019
-rw-r--r--modules/calendar/e-memo-shell-view-actions.h91
-rw-r--r--modules/calendar/e-memo-shell-view-private.c561
-rw-r--r--modules/calendar/e-memo-shell-view-private.h128
-rw-r--r--modules/calendar/e-memo-shell-view.c222
-rw-r--r--modules/calendar/e-memo-shell-view.h67
-rw-r--r--modules/calendar/e-task-shell-backend.c661
-rw-r--r--modules/calendar/e-task-shell-backend.h70
-rw-r--r--modules/calendar/e-task-shell-content.c744
-rw-r--r--modules/calendar/e-task-shell-content.h100
-rw-r--r--modules/calendar/e-task-shell-migrate.c675
-rw-r--r--modules/calendar/e-task-shell-migrate.h38
-rw-r--r--modules/calendar/e-task-shell-sidebar.c714
-rw-r--r--modules/calendar/e-task-shell-sidebar.h97
-rw-r--r--modules/calendar/e-task-shell-view-actions.c1222
-rw-r--r--modules/calendar/e-task-shell-view-actions.h109
-rw-r--r--modules/calendar/e-task-shell-view-private.c726
-rw-r--r--modules/calendar/e-task-shell-view-private.h141
-rw-r--r--modules/calendar/e-task-shell-view.c325
-rw-r--r--modules/calendar/e-task-shell-view.h72
-rw-r--r--modules/calendar/evolution-module-calendar.c75
-rw-r--r--modules/mail/Makefile.am60
-rw-r--r--modules/mail/e-mail-attachment-handler.c525
-rw-r--r--modules/mail/e-mail-attachment-handler.h67
-rw-r--r--modules/mail/e-mail-config-hook.c69
-rw-r--r--modules/mail/e-mail-config-hook.h33
-rw-r--r--modules/mail/e-mail-event-hook.c93
-rw-r--r--modules/mail/e-mail-event-hook.h33
-rw-r--r--modules/mail/e-mail-junk-hook.c323
-rw-r--r--modules/mail/e-mail-junk-hook.h66
-rw-r--r--modules/mail/e-mail-shell-backend.c987
-rw-r--r--modules/mail/e-mail-shell-backend.h75
-rw-r--r--modules/mail/e-mail-shell-content.c1029
-rw-r--r--modules/mail/e-mail-shell-content.h89
-rw-r--r--modules/mail/e-mail-shell-migrate.c3100
-rw-r--r--modules/mail/e-mail-shell-migrate.h38
-rw-r--r--modules/mail/e-mail-shell-settings.c229
-rw-r--r--modules/mail/e-mail-shell-settings.h33
-rw-r--r--modules/mail/e-mail-shell-sidebar.c657
-rw-r--r--modules/mail/e-mail-shell-sidebar.h81
-rw-r--r--modules/mail/e-mail-shell-view-actions.c1844
-rw-r--r--modules/mail/e-mail-shell-view-actions.h259
-rw-r--r--modules/mail/e-mail-shell-view-private.c1009
-rw-r--r--modules/mail/e-mail-shell-view-private.h173
-rw-r--r--modules/mail/e-mail-shell-view.c262
-rw-r--r--modules/mail/e-mail-shell-view.h72
-rw-r--r--modules/mail/em-account-prefs.c323
-rw-r--r--modules/mail/em-account-prefs.h69
-rw-r--r--modules/mail/em-composer-prefs.c567
-rw-r--r--modules/mail/em-composer-prefs.h86
-rw-r--r--modules/mail/em-mailer-prefs.c1320
-rw-r--r--modules/mail/em-mailer-prefs.h131
-rw-r--r--modules/mail/em-network-prefs.c497
-rw-r--r--modules/mail/em-network-prefs.h102
-rw-r--r--modules/mail/evolution-module-mail.c71
-rw-r--r--modules/plugin-lib/Makefile.am20
-rw-r--r--modules/plugin-lib/e-plugin-lib.c249
-rw-r--r--modules/plugin-lib/e-plugin-lib.h92
-rw-r--r--modules/plugin-lib/evolution-module-plugin-lib.c41
-rw-r--r--modules/plugin-mono/Camel.cs1278
-rw-r--r--modules/plugin-mono/Evolution.cs158
-rw-r--r--modules/plugin-mono/Makefile.am22
-rw-r--r--modules/plugin-mono/e-plugin-mono.c261
-rw-r--r--modules/plugin-mono/e-plugin-mono.h69
-rw-r--r--modules/plugin-mono/evolution-module-plugin-mono.c41
-rw-r--r--modules/plugin-python/Makefile.am31
-rw-r--r--modules/plugin-python/e-plugin-python.c230
-rw-r--r--modules/plugin-python/e-plugin-python.h70
-rw-r--r--modules/plugin-python/evolution-module-plugin-python.c41
-rw-r--r--modules/plugin-python/example/Makefile.am29
-rw-r--r--modules/plugin-python/example/hello_python.py5
-rw-r--r--modules/plugin-python/example/org-gnome-hello-python-ui.xml16
-rw-r--r--modules/plugin-python/example/org-gnome-hello-python.eplug.xml20
136 files changed, 47734 insertions, 0 deletions
diff --git a/modules/Makefile.am b/modules/Makefile.am
new file mode 100644
index 0000000000..f792a4d9ee
--- /dev/null
+++ b/modules/Makefile.am
@@ -0,0 +1,11 @@
+if ENABLE_MONO
+MONO_DIR = plugin-mono
+endif
+
+if ENABLE_PYTHON
+PYTHON_DIR = plugin-python
+endif
+
+SUBDIRS = addressbook calendar mail plugin-lib $(MONO_DIR) $(PYTHON_DIR)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/addressbook/Makefile.am b/modules/addressbook/Makefile.am
new file mode 100644
index 0000000000..05b169841e
--- /dev/null
+++ b/modules/addressbook/Makefile.am
@@ -0,0 +1,133 @@
+if OS_WIN32
+WIN32_BOOTSTRAP_LIBS = $(top_builddir)/win32/libevolution-mail.la
+endif
+
+AM_CPPFLAGS = \
+ -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-config-hook.c \
+ e-book-config-hook.h \
+ e-book-shell-backend.c \
+ e-book-shell-backend.h \
+ e-book-shell-content.c \
+ e-book-shell-content.h \
+ e-book-shell-migrate.c \
+ e-book-shell-migrate.h \
+ e-book-shell-settings.c \
+ e-book-shell-settings.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..901ee4353b
--- /dev/null
+++ b/modules/addressbook/addressbook-config.c
@@ -0,0 +1,1247 @@
+/*
+ * 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 (assistant 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..7ce3902c82
--- /dev/null
+++ b/modules/addressbook/apps_evolution_addressbook.schemas.in
@@ -0,0 +1,116 @@
+<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/layout</key>
+ <applyto>/apps/evolution/addressbook/display/layout</applyto>
+ <owner>evolution-addressbook</owner>
+ <type>int</type>
+ <default>0</default>
+ <locale name="C">
+ <short>Contact layout style</short>
+ <long>
+ The layout style determines where to place the preview pane
+ in relation to the contact list. "0" (Classic View) places
+ the preview pane below the contact list. "1" (Vertical View)
+ places the preview pane next to the contact list.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/evolution/addressbook/display/hpane_position</key>
+ <applyto>/apps/evolution/addressbook/display/hpane_position</applyto>
+ <owner>evolution-addressbook</owner>
+ <type>int</type>
+ <default>200</default>
+ <locale name="C">
+ <short>Contact preview pane position (horizontal)</short>
+ <long>
+ Position of the contact preview pane when oriented horizontally.
+ </long>
+ </locale>
+ </schema>
+
+ <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>
+ <default>400</default>
+ <locale name="C">
+ <short>Contact preview pane position (vertical)</short>
+ <long>
+ Position of the contact preview pane when oriented vertically.
+ </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..ac6a7d1860
--- /dev/null
+++ b/modules/addressbook/autocompletion-config.c
@@ -0,0 +1,202 @@
+/*
+ * 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 "e-util/e-binding.h"
+#include "e-util/e-datetime-format.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);
+ }
+ }
+}
+
+static GtkWidget *
+add_section (GtkWidget *container,
+ const gchar *caption,
+ gboolean expand)
+{
+ GtkWidget *widget;
+ gchar *markup;
+
+ widget = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, expand, expand, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ markup = g_markup_printf_escaped ("<b>%s</b>", caption);
+ widget = gtk_label_new (markup);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 12, 0);
+ gtk_box_pack_start (GTK_BOX (container), widget, expand, expand, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_vbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ return widget;
+}
+
+void
+autocompletion_config_init (EShell *shell)
+{
+ EShellSettings *shell_settings;
+ ESourceList *source_list;
+ GtkWidget *scrolled_window;
+ GtkWidget *source_selector;
+ GtkWidget *preferences_window;
+ GtkWidget *itembox;
+ GtkWidget *widget;
+ GtkWidget *vbox;
+
+ g_return_if_fail (E_IS_SHELL (shell));
+
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ source_list = e_source_list_new_for_gconf_default (
+ "/apps/evolution/addressbook/sources");
+
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_widget_show (vbox);
+
+ itembox = add_section (vbox, _("Date/Time Format"), FALSE);
+
+ widget = gtk_table_new (1, 3, FALSE);
+ gtk_box_pack_start (GTK_BOX (itembox), widget, TRUE, TRUE, 0);
+ e_datetime_format_add_setup_widget (
+ widget, 0, "addressbook", "table",
+ DTFormatKindDateTime, _("Table column:"));
+ gtk_widget_show (widget);
+
+ itembox = add_section (vbox, _("Autocompletion"), TRUE);
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("Always _show address of the autocompleted contact"));
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "book-completion-show-address",
+ G_OBJECT (widget), "active");
+ gtk_box_pack_start (GTK_BOX (itembox), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ 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);
+
+ gtk_box_pack_start (GTK_BOX (itembox), scrolled_window, TRUE, TRUE, 0);
+
+ 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),
+ "contacts",
+ "preferences-autocompletion",
+ _("Contacts"),
+ vbox,
+ 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-config-hook.c b/modules/addressbook/e-book-config-hook.c
new file mode 100644
index 0000000000..d8c03a5329
--- /dev/null
+++ b/modules/addressbook/e-book-config-hook.c
@@ -0,0 +1,67 @@
+/*
+ * e-book-config-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-book-config-hook.h"
+
+#include "e-util/e-config.h"
+#include "addressbook/gui/widgets/eab-config.h"
+
+static const EConfigHookTargetMask no_masks[] = {
+ { NULL }
+};
+
+static const EConfigHookTargetMap targets[] = {
+ { "source", EAB_CONFIG_TARGET_SOURCE, no_masks },
+ { NULL }
+};
+
+static void
+book_config_hook_class_init (EPluginHookClass *class)
+{
+ gint ii;
+
+ class->id = "org.gnome.evolution.addressbook.config:1.0";
+
+ for (ii = 0; targets[ii].type != NULL; ii++)
+ e_config_hook_class_add_target_map (
+ (EConfigHookClass *) class, &targets[ii]);
+}
+
+void
+e_book_config_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EConfigHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) book_config_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EConfigHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ g_type_module_register_type (
+ type_module, e_config_hook_get_type (),
+ "EBookConfigHook", &type_info, 0);
+}
diff --git a/modules/addressbook/e-book-config-hook.h b/modules/addressbook/e-book-config-hook.h
new file mode 100644
index 0000000000..a5d9e06457
--- /dev/null
+++ b/modules/addressbook/e-book-config-hook.h
@@ -0,0 +1,33 @@
+/*
+ * e-book-config-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_BOOK_CONFIG_HOOK_H
+#define E_BOOK_CONFIG_HOOK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_book_config_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_BOOK_CONFIG_HOOK_H */
diff --git a/modules/addressbook/e-book-shell-backend.c b/modules/addressbook/e-book-shell-backend.c
new file mode 100644
index 0000000000..f96cb0dd22
--- /dev/null
+++ b/modules/addressbook/e-book-shell-backend.c
@@ -0,0 +1,591 @@
+/*
+ * 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 "addressbook-config.h"
+#include "autocompletion-config.h"
+
+#include "e-book-shell-migrate.h"
+#include "e-book-shell-settings.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_new_contact_cb (EBook *book,
+ EBookStatus status,
+ gpointer user_data)
+{
+ EShell *shell;
+ EContact *contact;
+ EABEditor *editor;
+
+ /* XXX Handle errors better. */
+ if (status != E_BOOK_ERROR_OK)
+ return;
+
+ contact = e_contact_new ();
+ shell = E_SHELL (user_data);
+
+ editor = e_contact_editor_new (
+ shell, book, contact, TRUE, TRUE);
+
+ eab_editor_show (editor);
+
+ g_object_unref (contact);
+ g_object_unref (book);
+}
+
+static void
+book_shell_backend_new_contact_list_cb (EBook *book,
+ EBookStatus status,
+ gpointer user_data)
+{
+ EShell *shell;
+ EContact *contact;
+ EABEditor *editor;
+
+ /* XXX Handle errors better. */
+ if (status != E_BOOK_ERROR_OK)
+ return;
+
+ contact = e_contact_new ();
+ shell = E_SHELL (user_data);
+
+ editor = e_contact_list_editor_new (
+ shell, book, contact, TRUE, TRUE);
+
+ eab_editor_show (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 *action_name;
+ 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);
+ action_name = gtk_action_get_name (action);
+
+ 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);
+
+ if (strcmp (action_name, "contact-new") == 0)
+ e_book_async_open (
+ book, FALSE,
+ book_shell_backend_new_contact_cb, shell);
+
+ if (strcmp (action_name, "contact-list-new") == 0)
+ e_book_async_open (
+ book, FALSE,
+ book_shell_backend_new_contact_list_cb, shell);
+}
+
+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);
+
+ 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);
+
+ /* Initialize settings before initializing preferences,
+ * since the preferences bind to the shell settings. */
+ e_book_shell_backend_init_settings (shell);
+ autocompletion_config_init (shell);
+}
+
+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->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..f138da3d00
--- /dev/null
+++ b/modules/addressbook/e-book-shell-content.c
@@ -0,0 +1,541 @@
+/*
+ * 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/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/misc/e-paned.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;
+
+ GtkOrientation orientation;
+
+ guint preview_visible : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_CURRENT_VIEW,
+ PROP_ORIENTATION,
+ PROP_PREVIEW_CONTACT,
+ PROP_PREVIEW_VISIBLE
+};
+
+static gpointer parent_class;
+static GType book_shell_content_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 GtkOrientation
+book_shell_content_get_orientation (EBookShellContent *book_shell_content)
+{
+ return book_shell_content->priv->orientation;
+}
+
+static void
+book_shell_content_set_orientation (EBookShellContent *book_shell_content,
+ GtkOrientation orientation)
+{
+ book_shell_content->priv->orientation = orientation;
+
+ g_object_notify (G_OBJECT (book_shell_content), "orientation");
+}
+
+static void
+book_shell_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_VIEW:
+ e_book_shell_content_set_current_view (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_ORIENTATION:
+ book_shell_content_set_orientation (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_PREVIEW_CONTACT:
+ e_book_shell_content_set_preview_contact (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ e_book_shell_content_set_preview_visible (
+ E_BOOK_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+book_shell_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_VIEW:
+ g_value_set_object (
+ value,
+ e_book_shell_content_get_current_view (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_ORIENTATION:
+ g_value_set_enum (
+ value,
+ book_shell_content_get_orientation (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_CONTACT:
+ g_value_set_object (
+ value,
+ e_book_shell_content_get_preview_contact (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ g_value_set_boolean (
+ value,
+ e_book_shell_content_get_preview_visible (
+ E_BOOK_SHELL_CONTENT (object)));
+ return;
+ }
+
+ 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 = e_paned_new (GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->paned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "orientation",
+ G_OBJECT (widget), "orientation");
+
+ container = widget;
+
+ widget = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ priv->notebook = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = 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_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "preview-visible",
+ G_OBJECT (widget), "visible");
+
+ 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/hpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "hposition");
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/addressbook/display/vpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "vposition");
+}
+
+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 |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_override_property (
+ object_class, PROP_ORIENTATION, "orientation");
+}
+
+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_content_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 */
+ };
+
+ static const GInterfaceInfo orientable_info = {
+ (GInterfaceInitFunc) NULL,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ book_shell_content_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_CONTENT,
+ "EBookShellContent", &type_info, 0);
+
+ g_type_module_add_interface (
+ type_module, book_shell_content_type,
+ GTK_TYPE_ORIENTABLE, &orientable_info);
+}
+
+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)
+{
+ g_return_val_if_fail (
+ E_IS_BOOK_SHELL_CONTENT (book_shell_content), FALSE);
+
+ return book_shell_content->priv->preview_visible;
+}
+
+void
+e_book_shell_content_set_preview_visible (EBookShellContent *book_shell_content,
+ gboolean preview_visible)
+{
+ g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+
+ book_shell_content->priv->preview_visible = preview_visible;
+
+ 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..c237f40d94
--- /dev/null
+++ b/modules/addressbook/e-book-shell-migrate.c
@@ -0,0 +1,1228 @@
+/*
+ * 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 &gt;) 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-settings.c b/modules/addressbook/e-book-shell-settings.c
new file mode 100644
index 0000000000..7a9544d721
--- /dev/null
+++ b/modules/addressbook/e-book-shell-settings.c
@@ -0,0 +1,34 @@
+/*
+ * e-book-shell-settings.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-settings.h"
+
+void
+e_book_shell_backend_init_settings (EShell *shell)
+{
+ EShellSettings *shell_settings;
+
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ e_shell_settings_install_property_for_key (
+ "book-completion-show-address",
+ "/apps/evolution/addressbook/completion/show_address");
+}
diff --git a/modules/addressbook/e-book-shell-settings.h b/modules/addressbook/e-book-shell-settings.h
new file mode 100644
index 0000000000..9e05de41df
--- /dev/null
+++ b/modules/addressbook/e-book-shell-settings.h
@@ -0,0 +1,33 @@
+/*
+ * e-book-shell-settings.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_SETTINGS_H
+#define E_BOOK_SHELL_SETTINGS_H
+
+#include <shell/e-shell.h>
+
+G_BEGIN_DECLS
+
+void e_book_shell_backend_init_settings (EShell *shell);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_SETTINGS_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..c2195e52de
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-actions.c
@@ -0,0 +1,1093 @@
+/*
+ * 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)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ EContact *contact;
+ EABEditor *editor;
+ EBook *book;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_book (model);
+ g_return_if_fail (book != NULL);
+
+ contact = e_contact_new ();
+ editor = e_contact_editor_new (shell, book, contact, TRUE, TRUE);
+ eab_editor_show (editor);
+ g_object_unref (contact);
+}
+
+static void
+action_contact_new_list_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+ EAddressbookModel *model;
+ EContact *contact;
+ EABEditor *editor;
+ EBook *book;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_book (model);
+ g_return_if_fail (book != NULL);
+
+ contact = e_contact_new ();
+ editor = e_contact_list_editor_new (shell, book, contact, TRUE, TRUE);
+ eab_editor_show (editor);
+ g_object_unref (contact);
+}
+
+static void
+action_contact_open_cb (GtkAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *view;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ view = e_book_shell_content_get_current_view (book_shell_content);
+ g_return_if_fail (view != NULL);
+
+ e_addressbook_view_view (view);
+}
+
+static void
+action_contact_preview_cb (GtkToggleAction *action,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ gboolean visible;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ visible = gtk_toggle_action_get_active (action);
+ e_book_shell_content_set_preview_visible (book_shell_content, visible);
+}
+
+static void
+action_contact_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_contact_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EBookShellView *book_shell_view)
+{
+ EBookShellContent *book_shell_content;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ orientable = GTK_ORIENTABLE (book_shell_content);
+
+ switch (gtk_radio_action_get_current_value (action)) {
+ case 0:
+ orientation = GTK_ORIENTATION_VERTICAL;
+ break;
+ case 1:
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ gtk_orientable_set_orientation (orientable, orientation);
+}
+
+static 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 ***/
+
+ { "contact-actions-menu",
+ NULL,
+ N_("_Actions"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "contact-preview-menu",
+ NULL,
+ N_("_Preview"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static EPopupActionEntry contact_popup_entries[] = {
+
+ { "address-book-popup-delete",
+ N_("_Delete"),
+ "address-book-delete" },
+
+ { "address-book-popup-properties",
+ N_("_Properties"),
+ "address-book-properties" },
+
+ { "address-book-popup-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_view_entries[] = {
+
+ /* This action represents the initial active contact view.
+ * It should not be visible in the UI, nor should it be
+ * possible to switch to it from another shell view. */
+ { "contact-view-initial",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ -1 },
+
+ { "contact-view-classic",
+ NULL,
+ N_("_Classic View"),
+ NULL,
+ N_("Show contact preview below the contact list"),
+ 0 },
+
+ { "contact-view-vertical",
+ NULL,
+ N_("_Vertical View"),
+ NULL,
+ N_("Show contact preview alongside the contact list"),
+ 1 }
+};
+
+static GtkRadioActionEntry contact_filter_entries[] = {
+
+ { "contact-filter-any-category",
+ NULL,
+ N_("Any Category"),
+ NULL,
+ NULL,
+ CONTACT_FILTER_ANY_CATEGORY },
+
+ { "contact-filter-unmatched",
+ NULL,
+ N_("Unmatched"),
+ NULL,
+ NULL,
+ CONTACT_FILTER_UNMATCHED }
+};
+
+static GtkRadioActionEntry contact_search_entries[] = {
+
+ { "contact-search-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_view_entries,
+ G_N_ELEMENTS (contact_view_entries), -1,
+ G_CALLBACK (action_contact_view_cb), book_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, contact_search_entries,
+ G_N_ELEMENTS (contact_search_entries),
+ 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");
+
+ object = G_OBJECT (ACTION (CONTACT_VIEW_VERTICAL));
+ key = "/apps/evolution/addressbook/display/layout";
+ gconf_bridge_bind_property (bridge, key, object, "current-value");
+
+ /* 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..4c3a18bddf
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-actions.h
@@ -0,0 +1,95 @@
+/*
+ * 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")
+#define E_SHELL_WINDOW_ACTION_CONTACT_VIEW_CLASSIC(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-view-classic")
+#define E_SHELL_WINDOW_ACTION_CONTACT_VIEW_VERTICAL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "contact-view-vertical")
+
+/* Search Actions */
+#define E_SHELL_WINDOW_ACTION_CONTACT_SEARCH_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..6770800dbd
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-private.c
@@ -0,0 +1,634 @@
+/*
+ * 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)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EAddressbookModel *model;
+ EABEditor *editor;
+ EBook *book;
+ gboolean editable;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ model = e_addressbook_view_get_model (view);
+ book = e_addressbook_model_get_book (model);
+ editable = e_addressbook_model_get_editable (model);
+
+ if (e_contact_get (contact, E_CONTACT_IS_LIST))
+ editor = e_contact_list_editor_new (
+ shell, book, contact, is_new_contact, editable);
+ else
+ editor = e_contact_editor_new (
+ shell, book, contact, is_new_contact, editable);
+
+ eab_editor_show (editor);
+}
+
+static void
+popup_event (EBookShellView *book_shell_view,
+ 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);
+ book_shell_view->priv->preview_index = row;
+}
+
+static void
+selection_change (EBookShellView *book_shell_view,
+ EAddressbookView *view)
+{
+ EBookShellContent *book_shell_content;
+ EAddressbookView *current_view;
+ ESelectionModel *selection_model;
+ EShellView *shell_view;
+ gint n_selected;
+
+ shell_view = E_SHELL_VIEW (book_shell_view);
+ book_shell_content = book_shell_view->priv->book_shell_content;
+ current_view = e_book_shell_content_get_current_view (book_shell_content);
+
+ if (view != current_view)
+ return;
+
+ e_shell_view_update_actions (shell_view);
+
+ selection_model = e_addressbook_view_get_selection_model (view);
+
+ n_selected = (selection_model != NULL) ?
+ e_selection_model_selected_count (selection_model) : 0;
+
+ if (n_selected == 1)
+ e_selection_model_foreach (
+ selection_model, (EForeachFunc)
+ book_shell_view_selection_change_foreach,
+ book_shell_view);
+ else {
+ e_book_shell_content_set_preview_contact (
+ book_shell_content, NULL);
+ book_shell_view->priv->preview_index = -1;
+ }
+}
+
+static void
+contact_changed (EBookShellView *book_shell_view,
+ gint index,
+ EAddressbookModel *model)
+{
+ EBookShellContent *book_shell_content;
+ EContact *contact;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+
+ contact = e_addressbook_model_contact_at (model, index);
+
+ if (book_shell_view->priv->preview_index != index)
+ return;
+
+ /* Re-render the same contact. */
+ e_book_shell_content_set_preview_contact (book_shell_content, contact);
+}
+
+static void
+contacts_removed (EBookShellView *book_shell_view,
+ GArray *removed_indices,
+ EAddressbookModel *model)
+{
+ EBookShellContent *book_shell_content;
+ EContact *preview_contact;
+
+ book_shell_content = book_shell_view->priv->book_shell_content;
+
+ preview_contact =
+ e_book_shell_content_get_preview_contact (book_shell_content);
+
+ if (preview_contact == NULL)
+ return;
+
+ /* Is the displayed contact still in the model? */
+ if (e_addressbook_model_find (model, preview_contact) < 0)
+ return;
+
+ /* If not, clear the contact display. */
+ e_book_shell_content_set_preview_contact (book_shell_content, NULL);
+ book_shell_view->priv->preview_index = -1;
+}
+
+static void
+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;
+ priv->preview_index = -1;
+
+ 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;
+ GtkRadioAction *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 = GTK_RADIO_ACTION (ACTION (CONTACT_SEARCH_ANY_FIELD_CONTAINS));
+ value = gtk_radio_action_get_current_value (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);
+ book_shell_view->priv->preview_index = -1;
+}
+
+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..b49677680f
--- /dev/null
+++ b/modules/addressbook/e-book-shell-view-private.h
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+ gint preview_index;
+};
+
+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..73b3634b91
--- /dev/null
+++ b/modules/addressbook/evolution-module-addressbook.c
@@ -0,0 +1,49 @@
+/*
+ * 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-config-hook.h"
+
+#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_config_hook_register_type (type_module);
+
+ 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, &quot;ldap.mycompany.com&quot;.</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">&lt;b&gt;Authentication&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <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">&lt;b&gt;Searching&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <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 &quot;person&quot;.</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">&lt;b&gt;Downloading&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <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..29175fb494
--- /dev/null
+++ b/modules/addressbook/openldap-extract.h
@@ -0,0 +1,1426 @@
+/* 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;
+}
+
diff --git a/modules/calendar/Makefile.am b/modules/calendar/Makefile.am
new file mode 100644
index 0000000000..490d42a05e
--- /dev/null
+++ b/modules/calendar/Makefile.am
@@ -0,0 +1,84 @@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"calendar-modules\" \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/widgets \
+ -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \
+ $(EVOLUTION_CALENDAR_CFLAGS)
+
+module_LTLIBRARIES = \
+ libevolution-module-calendar.la
+
+libevolution_module_calendar_la_SOURCES = \
+ evolution-module-calendar.c \
+ e-cal-attachment-handler.c \
+ e-cal-attachment-handler.h \
+ e-cal-config-hook.c \
+ e-cal-config-hook.h \
+ e-cal-event-hook.c \
+ e-cal-event-hook.h \
+ e-cal-shell-backend.c \
+ e-cal-shell-backend.h \
+ e-cal-shell-content.c \
+ e-cal-shell-content.h \
+ e-cal-shell-migrate.c \
+ e-cal-shell-migrate.h \
+ e-cal-shell-settings.c \
+ e-cal-shell-settings.h \
+ e-cal-shell-sidebar.c \
+ e-cal-shell-sidebar.h \
+ e-cal-shell-view.c \
+ e-cal-shell-view.h \
+ e-cal-shell-view-actions.c \
+ e-cal-shell-view-actions.h \
+ e-cal-shell-view-memopad.c \
+ e-cal-shell-view-private.c \
+ e-cal-shell-view-private.h \
+ e-cal-shell-view-taskpad.c \
+ e-memo-shell-backend.c \
+ e-memo-shell-backend.h \
+ e-memo-shell-content.c \
+ e-memo-shell-content.h \
+ e-memo-shell-migrate.c \
+ e-memo-shell-migrate.h \
+ e-memo-shell-sidebar.c \
+ e-memo-shell-sidebar.h \
+ e-memo-shell-view.c \
+ e-memo-shell-view.h \
+ e-memo-shell-view-actions.c \
+ e-memo-shell-view-actions.h \
+ e-memo-shell-view-private.c \
+ e-memo-shell-view-private.h \
+ e-task-shell-backend.c \
+ e-task-shell-backend.h \
+ e-task-shell-content.c \
+ e-task-shell-content.h \
+ e-task-shell-migrate.c \
+ e-task-shell-migrate.h \
+ e-task-shell-sidebar.c \
+ e-task-shell-sidebar.h \
+ e-task-shell-view.c \
+ e-task-shell-view.h \
+ e-task-shell-view-actions.c \
+ e-task-shell-view-actions.h \
+ e-task-shell-view-private.c \
+ e-task-shell-view-private.h
+
+libevolution_module_calendar_la_LIBADD = \
+ $(top_builddir)/shell/libeshell.la \
+ $(top_builddir)/calendar/gui/libevolution-calendar.la \
+ $(top_builddir)/calendar/importers/libevolution-calendar-importers.la \
+ $(top_builddir)/mail/libevolution-mail.la \
+ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
+ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/filter/libfilter.la \
+ $(top_builddir)/widgets/menus/libmenus.la \
+ $(top_builddir)/widgets/misc/libemiscwidgets.la \
+ $(top_builddir)/widgets/table/libetable.la \
+ $(CAMEL_LIBS) \
+ $(EVOLUTION_CALENDAR_LIBS)
+
+libevolution_module_calendar_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/calendar/e-cal-attachment-handler.c b/modules/calendar/e-cal-attachment-handler.c
new file mode 100644
index 0000000000..dd95cc5d08
--- /dev/null
+++ b/modules/calendar/e-cal-attachment-handler.c
@@ -0,0 +1,512 @@
+/*
+ * e-cal-attachment-handler.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-cal-attachment-handler.h"
+
+#include <glib/gi18n.h>
+#include <libical/ical.h>
+#include <libecal/e-cal.h>
+#include <camel/camel-stream-mem.h>
+#include <libedataserverui/e-source-selector.h>
+
+#include "calendar/common/authentication.h"
+
+#define E_CAL_ATTACHMENT_HANDLER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandlerPrivate))
+
+typedef struct _ImportContext ImportContext;
+
+struct _ECalAttachmentHandlerPrivate {
+ gint placeholder;
+};
+
+struct _ImportContext {
+ ECal *client;
+ icalcomponent *component;
+ ECalSourceType source_type;
+};
+
+static gpointer parent_class;
+static GType cal_attachment_handler_type;
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <placeholder name='custom-actions'>"
+" <menuitem action='import-to-calendar'/>"
+" <menuitem action='import-to-tasks'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+static icalcomponent *
+attachment_handler_get_component (EAttachment *attachment)
+{
+ CamelDataWrapper *wrapper;
+ CamelMimePart *mime_part;
+ CamelStream *stream;
+ GByteArray *buffer;
+ icalcomponent *component;
+ const gchar *key = "__icalcomponent__";
+
+ component = g_object_get_data (G_OBJECT (attachment), key);
+ if (component != NULL)
+ return component;
+
+ mime_part = e_attachment_get_mime_part (attachment);
+ if (!CAMEL_IS_MIME_PART (mime_part))
+ return NULL;
+
+ buffer = g_byte_array_new ();
+ stream = camel_stream_mem_new ();
+ camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer);
+ wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+ camel_data_wrapper_decode_to_stream (wrapper, stream);
+ camel_object_unref (stream);
+
+ component = e_cal_util_parse_ics_string ((gchar *) buffer->data);
+
+ g_byte_array_free (buffer, TRUE);
+
+ if (component == NULL)
+ return NULL;
+
+ g_object_set_data_full (
+ G_OBJECT (attachment), key, component,
+ (GDestroyNotify) icalcomponent_free);
+
+ return component;
+}
+
+static gboolean
+attachment_handler_update_objects (ECal *client,
+ icalcomponent *component)
+{
+ icalcomponent_kind kind;
+ icalcomponent *vcalendar;
+ gboolean success;
+
+ kind = icalcomponent_isa (component);
+
+ switch (kind) {
+ case ICAL_VTODO_COMPONENT:
+ case ICAL_VEVENT_COMPONENT:
+ vcalendar = e_cal_util_new_top_level ();
+ if (icalcomponent_get_method (component) == ICAL_METHOD_CANCEL)
+ icalcomponent_set_method (vcalendar, ICAL_METHOD_CANCEL);
+ else
+ icalcomponent_set_method (vcalendar, ICAL_METHOD_PUBLISH);
+ icalcomponent_add_component (
+ vcalendar, icalcomponent_new_clone (component));
+ break;
+
+ case ICAL_VCALENDAR_COMPONENT:
+ vcalendar = icalcomponent_new_clone (component);
+ if (!icalcomponent_get_first_property (vcalendar, ICAL_METHOD_PROPERTY))
+ icalcomponent_set_method (vcalendar, ICAL_METHOD_PUBLISH);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ success = e_cal_receive_objects (client, vcalendar, NULL);
+
+ icalcomponent_free (vcalendar);
+
+ return success;
+}
+
+static void
+attachment_handler_import_event (ECal *client,
+ ECalendarStatus status,
+ EAttachment *attachment)
+{
+ icalcomponent *component;
+ icalcomponent *subcomponent;
+ icalcompiter iter;
+
+ /* FIXME Notify the user somehow. */
+ g_return_if_fail (status == E_CALENDAR_STATUS_OK);
+
+ component = attachment_handler_get_component (attachment);
+ g_return_if_fail (component != NULL);
+
+ iter = icalcomponent_begin_component (component, ICAL_ANY_COMPONENT);
+
+ while ((subcomponent = icalcompiter_deref (&iter)) != NULL) {
+ icalcomponent_kind kind;
+
+ kind = icalcomponent_isa (subcomponent);
+ icalcompiter_next (&iter);
+
+ if (kind == ICAL_VEVENT_COMPONENT)
+ continue;
+
+ if (kind == ICAL_VTIMEZONE_COMPONENT)
+ continue;
+
+ icalcomponent_remove_component (component, subcomponent);
+ icalcomponent_free (subcomponent);
+ }
+
+ /* XXX Do something with the return value. */
+ attachment_handler_update_objects (client, component);
+
+ g_object_unref (attachment);
+ g_object_unref (client);
+}
+
+static void
+attachment_handler_import_todo (ECal *client,
+ ECalendarStatus status,
+ EAttachment *attachment)
+{
+ icalcomponent *component;
+ icalcomponent *subcomponent;
+ icalcompiter iter;
+
+ /* FIXME Notify the user somehow. */
+ g_return_if_fail (status == E_CALENDAR_STATUS_OK);
+
+ component = attachment_handler_get_component (attachment);
+ g_return_if_fail (component != NULL);
+
+ iter = icalcomponent_begin_component (component, ICAL_ANY_COMPONENT);
+
+ while ((subcomponent = icalcompiter_deref (&iter)) != NULL) {
+ icalcomponent_kind kind;
+
+ kind = icalcomponent_isa (subcomponent);
+ icalcompiter_next (&iter);
+
+ if (kind == ICAL_VTODO_COMPONENT)
+ continue;
+
+ if (kind == ICAL_VTIMEZONE_COMPONENT)
+ continue;
+
+ icalcomponent_remove_component (component, subcomponent);
+ icalcomponent_free (subcomponent);
+ }
+
+ /* XXX Do something with the return value. */
+ attachment_handler_update_objects (client, component);
+
+ g_object_unref (attachment);
+ g_object_unref (client);
+}
+
+static void
+attachment_handler_row_activated_cb (GtkDialog *dialog)
+{
+ gtk_dialog_response (dialog, GTK_RESPONSE_OK);
+}
+
+static void
+attachment_handler_run_dialog (GtkWindow *parent,
+ EAttachment *attachment,
+ ECalSourceType source_type,
+ const gchar *title)
+{
+ GtkWidget *dialog;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GCallback callback;
+ ESourceSelector *selector;
+ ESourceList *source_list;
+ ESource *source;
+ ECal *client;
+ icalcomponent *component;
+ GError *error = NULL;
+
+ component = attachment_handler_get_component (attachment);
+ g_return_if_fail (component != NULL);
+
+ e_cal_get_sources (&source_list, source_type, &error);
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ source = e_source_list_peek_source_any (source_list);
+ g_return_if_fail (source != NULL);
+
+ dialog = gtk_dialog_new_with_buttons (
+ title, parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+
+ widget = gtk_button_new_with_mnemonic (_("I_mport"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_icon_name (
+ "stock_mail-import", GTK_ICON_SIZE_MENU));
+ gtk_dialog_add_action_widget (
+ GTK_DIALOG (dialog), widget, GTK_RESPONSE_OK);
+ gtk_widget_show (widget);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 300, 400);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ widget = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (widget),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (
+ GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_source_selector_new (source_list);
+ selector = E_SOURCE_SELECTOR (widget);
+ e_source_selector_set_primary_selection (selector, source);
+ e_source_selector_show_selection (selector, FALSE);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "row-activated",
+ G_CALLBACK (attachment_handler_row_activated_cb), dialog);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
+ goto exit;
+
+ source = e_source_selector_peek_primary_selection (selector);
+ if (source == NULL)
+ goto exit;
+
+ client = auth_new_cal_from_source (source, source_type);
+ if (client == NULL)
+ goto exit;
+
+ if (source_type == E_CAL_SOURCE_TYPE_EVENT)
+ callback = G_CALLBACK (attachment_handler_import_event);
+ else if (source_type == E_CAL_SOURCE_TYPE_TODO)
+ callback = G_CALLBACK (attachment_handler_import_todo);
+ else
+ goto exit;
+
+ g_object_ref (attachment);
+ g_signal_connect (client, "cal-opened", callback, attachment);
+ e_cal_open_async (client, FALSE);
+
+exit:
+ gtk_widget_destroy (dialog);
+}
+
+static void
+attachment_handler_import_to_calendar (GtkAction *action,
+ EAttachmentHandler *handler)
+{
+ EAttachment *attachment;
+ EAttachmentView *view;
+ GList *selected;
+ gpointer parent;
+
+ view = e_attachment_handler_get_view (handler);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = E_ATTACHMENT (selected->data);
+
+ attachment_handler_run_dialog (
+ parent, attachment,
+ E_CAL_SOURCE_TYPE_EVENT,
+ _("Select a Calendar"));
+
+ g_object_unref (attachment);
+ g_list_free (selected);
+}
+
+static void
+attachment_handler_import_to_tasks (GtkAction *action,
+ EAttachmentHandler *handler)
+{
+ EAttachment *attachment;
+ EAttachmentView *view;
+ GList *selected;
+ gpointer parent;
+
+ view = e_attachment_handler_get_view (handler);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = E_ATTACHMENT (selected->data);
+
+ attachment_handler_run_dialog (
+ parent, attachment,
+ E_CAL_SOURCE_TYPE_TODO,
+ _("Select a Task List"));
+
+ g_object_unref (attachment);
+ g_list_free (selected);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+ { "import-to-calendar",
+ "stock_mail-import",
+ N_("I_mport to Calendar"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (attachment_handler_import_to_calendar) },
+
+ { "import-to-tasks",
+ "stock_mail-import",
+ N_("I_mport to Tasks"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (attachment_handler_import_to_tasks) }
+};
+
+static void
+cal_attachment_handler_update_actions (EAttachmentView *view)
+{
+ EAttachment *attachment;
+ GtkAction *action;
+ GList *selected;
+ icalcomponent *component;
+ icalcomponent *subcomponent;
+ icalcomponent_kind kind;
+ gboolean is_vevent = FALSE;
+ gboolean is_vtodo = FALSE;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+
+ if (g_list_length (selected) != 1)
+ goto exit;
+
+ attachment = E_ATTACHMENT (selected->data);
+ component = attachment_handler_get_component (attachment);
+
+ if (component == NULL)
+ goto exit;
+
+ subcomponent = icalcomponent_get_inner (component);
+
+ if (subcomponent == NULL)
+ goto exit;
+
+ kind = icalcomponent_isa (subcomponent);
+ is_vevent = (kind == ICAL_VEVENT_COMPONENT);
+ is_vtodo = (kind == ICAL_VTODO_COMPONENT);
+
+exit:
+ action = e_attachment_view_get_action (view, "import-to-calendar");
+ gtk_action_set_visible (action, is_vevent);
+
+ action = e_attachment_view_get_action (view, "import-to-tasks");
+ gtk_action_set_visible (action, is_vtodo);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+cal_attachment_handler_constructed (GObject *object)
+{
+ EAttachmentHandler *handler;
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GError *error = NULL;
+
+ handler = E_ATTACHMENT_HANDLER (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ view = e_attachment_handler_get_view (handler);
+
+ action_group = e_attachment_view_add_action_group (view, "calendar");
+ gtk_action_group_add_actions (
+ action_group, standard_entries,
+ G_N_ELEMENTS (standard_entries), handler);
+
+ ui_manager = e_attachment_view_get_ui_manager (view);
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_signal_connect (
+ view, "update_actions",
+ G_CALLBACK (cal_attachment_handler_update_actions),
+ NULL);
+}
+
+static void
+cal_attachment_handler_class_init (ECalAttachmentHandlerClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ECalAttachmentHandlerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = cal_attachment_handler_constructed;
+}
+
+static void
+cal_attachment_handler_init (ECalAttachmentHandler *handler)
+{
+ handler->priv = E_CAL_ATTACHMENT_HANDLER_GET_PRIVATE (handler);
+}
+
+GType
+e_cal_attachment_handler_get_type (void)
+{
+ return cal_attachment_handler_type;
+}
+
+void
+e_cal_attachment_handler_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (ECalAttachmentHandlerClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_attachment_handler_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ECalAttachmentHandler),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) cal_attachment_handler_init,
+ NULL /* value_table */
+ };
+
+ cal_attachment_handler_type = g_type_module_register_type (
+ type_module, E_TYPE_ATTACHMENT_HANDLER,
+ "ECalAttachmentHandler", &type_info, 0);
+}
diff --git a/modules/calendar/e-cal-attachment-handler.h b/modules/calendar/e-cal-attachment-handler.h
new file mode 100644
index 0000000000..b792fbf765
--- /dev/null
+++ b/modules/calendar/e-cal-attachment-handler.h
@@ -0,0 +1,67 @@
+/*
+ * e-cal-attachment-handler.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_CAL_ATTACHMENT_HANDLER_H
+#define E_CAL_ATTACHMENT_HANDLER_H
+
+#include <misc/e-attachment-handler.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_ATTACHMENT_HANDLER \
+ (e_cal_attachment_handler_get_type ())
+#define E_CAL_ATTACHMENT_HANDLER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandler))
+#define E_CAL_ATTACHMENT_HANDLER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandlerClass))
+#define E_IS_CAL_ATTACHMENT_HANDLER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER))
+#define E_IS_CAL_ATTACHMENT_HANDLER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_ATTACHMENT_HANDLER))
+#define E_CAL_ATTACHMENT_HANDLER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandlerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalAttachmentHandler ECalAttachmentHandler;
+typedef struct _ECalAttachmentHandlerClass ECalAttachmentHandlerClass;
+typedef struct _ECalAttachmentHandlerPrivate ECalAttachmentHandlerPrivate;
+
+struct _ECalAttachmentHandler {
+ EAttachmentHandler parent;
+ ECalAttachmentHandlerPrivate *priv;
+};
+
+struct _ECalAttachmentHandlerClass {
+ EAttachmentHandlerClass parent_class;
+};
+
+GType e_cal_attachment_handler_get_type (void);
+void e_cal_attachment_handler_register_type
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_CAL_ATTACHMENT_HANDLER_H */
diff --git a/modules/calendar/e-cal-config-hook.c b/modules/calendar/e-cal-config-hook.c
new file mode 100644
index 0000000000..4a0522460c
--- /dev/null
+++ b/modules/calendar/e-cal-config-hook.c
@@ -0,0 +1,68 @@
+/*
+ * e-cal-config-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-cal-config-hook.h"
+
+#include "e-util/e-config.h"
+#include "calendar/gui/e-cal-config.h"
+
+static const EConfigHookTargetMask no_masks[] = {
+ { NULL }
+};
+
+static const EConfigHookTargetMap targets[] = {
+ { "source", EC_CONFIG_TARGET_SOURCE, no_masks },
+ { "prefs", EC_CONFIG_TARGET_PREFS, no_masks },
+ { NULL }
+};
+
+static void
+cal_config_hook_class_init (EPluginHookClass *class)
+{
+ gint ii;
+
+ class->id = "org.gnome.evolution.calendar.config:1.0";
+
+ for (ii = 0; targets[ii].type != NULL; ii++)
+ e_config_hook_class_add_target_map (
+ (EConfigHookClass *) class, &targets[ii]);
+}
+
+void
+e_cal_config_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EConfigHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_config_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EConfigHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ g_type_module_register_type (
+ type_module, e_config_hook_get_type (),
+ "ECalConfigHook", &type_info, 0);
+}
diff --git a/modules/calendar/e-cal-config-hook.h b/modules/calendar/e-cal-config-hook.h
new file mode 100644
index 0000000000..a22ec56bbc
--- /dev/null
+++ b/modules/calendar/e-cal-config-hook.h
@@ -0,0 +1,33 @@
+/*
+ * e-cal-config-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_CAL_CONFIG_HOOK_H
+#define E_CAL_CONFIG_HOOK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_cal_config_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_CAL_CONFIG_HOOK_H */
diff --git a/modules/calendar/e-cal-event-hook.c b/modules/calendar/e-cal-event-hook.c
new file mode 100644
index 0000000000..b263727107
--- /dev/null
+++ b/modules/calendar/e-cal-event-hook.c
@@ -0,0 +1,72 @@
+/*
+ * e-cal-event-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-cal-event-hook.h"
+
+#include "e-util/e-event.h"
+#include "calendar/gui/e-cal-event.h"
+
+static const EEventHookTargetMask masks[] = {
+ { "migration", E_CAL_EVENT_MODULE_MIGRATION },
+ { NULL }
+};
+
+static const EEventHookTargetMap targets[] = {
+ { "module", E_CAL_EVENT_TARGET_BACKEND, masks },
+ { NULL }
+};
+
+static void
+cal_event_hook_class_init (EPluginHookClass *class)
+{
+ EEventHookClass *event_hook_class;
+ gint ii;
+
+ event_hook_class = (EEventHookClass *) class;
+ event_hook_class->event = (EEvent *) e_cal_event_peek ();
+
+ class->id = "org.gnome.evolution.calendar.events:1.0";
+
+ for (ii = 0; targets[ii].type != NULL; ii++)
+ e_event_hook_class_add_target_map (
+ (EEventHookClass *) class, &targets[ii]);
+}
+
+void
+e_cal_event_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EEventHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_event_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EEventHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ g_type_module_register_type (
+ type_module, e_event_hook_get_type (),
+ "ECalEventHook", &type_info, 0);
+}
diff --git a/modules/calendar/e-cal-event-hook.h b/modules/calendar/e-cal-event-hook.h
new file mode 100644
index 0000000000..9dde31f900
--- /dev/null
+++ b/modules/calendar/e-cal-event-hook.h
@@ -0,0 +1,33 @@
+/*
+ * e-cal-event-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_CAL_EVENT_HOOK_H
+#define E_CAL_EVENT_HOOK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_cal_event_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_CAL_EVENT_HOOK_H */
diff --git a/modules/calendar/e-cal-shell-backend.c b/modules/calendar/e-cal-shell-backend.c
new file mode 100644
index 0000000000..5149574323
--- /dev/null
+++ b/modules/calendar/e-cal-shell-backend.c
@@ -0,0 +1,854 @@
+/*
+ * e-cal-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-cal-shell-backend.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal.h>
+#include <libecal/e-cal-time-util.h>
+#include <libedataserver/e-url.h>
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-group.h>
+
+#include "e-util/e-import.h"
+#include "shell/e-shell.h"
+#include "shell/e-shell-backend.h"
+#include "shell/e-shell-window.h"
+#include "widgets/misc/e-preferences-window.h"
+
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/dialogs/cal-prefs-dialog.h"
+#include "calendar/gui/dialogs/calendar-setup.h"
+#include "calendar/gui/dialogs/event-editor.h"
+#include "calendar/importers/evolution-calendar-importer.h"
+
+#include "e-cal-shell-migrate.h"
+#include "e-cal-shell-settings.h"
+#include "e-cal-shell-view.h"
+
+#define E_CAL_SHELL_BACKEND_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackendPrivate))
+
+#define CONTACTS_BASE_URI "contacts://"
+#define WEATHER_BASE_URI "weather://"
+#define WEB_BASE_URI "webcal://"
+#define PERSONAL_RELATIVE_URI "system"
+
+struct _ECalShellBackendPrivate {
+ ESourceList *source_list;
+};
+
+enum {
+ PROP_0,
+ PROP_SOURCE_LIST
+};
+
+static gpointer parent_class;
+static GType cal_shell_backend_type;
+
+static void
+cal_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? */
+
+ ECalShellBackendPrivate *priv;
+ ESourceGroup *on_this_computer;
+ ESourceGroup *on_the_web;
+ ESourceGroup *contacts;
+ ESourceGroup *weather;
+ ESource *birthdays;
+ ESource *personal;
+ EShell *shell;
+ EShellSettings *shell_settings;
+ GSList *groups, *iter;
+ const gchar *data_dir;
+ const gchar *name;
+ gchar *base_uri;
+ gchar *filename;
+ gchar *property;
+
+ on_this_computer = NULL;
+ on_the_web = NULL;
+ contacts = NULL;
+ weather = NULL;
+ birthdays = NULL;
+ personal = NULL;
+
+ priv = E_CAL_SHELL_BACKEND_GET_PRIVATE (shell_backend);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ if (!e_cal_get_sources (&priv->source_list, E_CAL_SOURCE_TYPE_EVENT, NULL)) {
+ g_warning ("Could not get calendar 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_the_web == NULL &&
+ strcmp (WEB_BASE_URI, group_base_uri) == 0)
+ on_the_web = source_group;
+
+ else if (contacts == NULL &&
+ strcmp (CONTACTS_BASE_URI, group_base_uri) == 0)
+ contacts = source_group;
+
+ else if (weather == NULL &&
+ strcmp (WEATHER_BASE_URI, group_base_uri) == 0)
+ weather = 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;
+ GSList *selected;
+ gchar *primary;
+
+ source = e_source_new (name, PERSONAL_RELATIVE_URI);
+ e_source_group_add_source (on_this_computer, source, -1);
+ g_object_unref (source);
+
+ primary = e_shell_settings_get_string (
+ shell_settings, "cal-primary-calendar");
+
+ selected = calendar_config_get_calendars_selected ();
+
+ if (primary == NULL && selected == NULL) {
+ const gchar *uid;
+
+ uid = e_source_peek_uid (source);
+ selected = g_slist_prepend (NULL, g_strdup (uid));
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-calendar", uid);
+ calendar_config_set_calendars_selected (selected);
+ }
+
+ g_slist_foreach (selected, (GFunc) g_free, NULL);
+ g_slist_free (selected);
+ g_free (primary);
+ } else {
+ /* Force the source name to the current locale. */
+ e_source_set_name (personal, name);
+ }
+
+ name = _("On The Web");
+
+ if (on_the_web == NULL) {
+ ESourceGroup *source_group;
+
+ source_group = e_source_group_new (name, WEB_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_the_web, name);
+ }
+
+ name = _("Contacts");
+
+ if (contacts != NULL) {
+ GSList *sources;
+
+ /* Force the group name to the current locale. */
+ e_source_group_set_name (contacts, name);
+
+ sources = e_source_group_peek_sources (contacts);
+
+ if (sources != NULL) {
+ GSList *trash;
+
+ /* There is only one source under Contacts. */
+ birthdays = E_SOURCE (sources->data);
+ sources = g_slist_next (sources);
+
+ /* Delete any other sources in this group.
+ * Earlier versions allowed you to create
+ * additional sources under Contacts. */
+ trash = g_slist_copy (sources);
+ while (trash != NULL) {
+ ESource *source = trash->data;
+ e_source_group_remove_source (contacts, source);
+ trash = g_slist_delete_link (trash, trash);
+ }
+
+ }
+ } else {
+ ESourceGroup *source_group;
+
+ source_group = e_source_group_new (name, CONTACTS_BASE_URI);
+ e_source_list_add_group (priv->source_list, source_group, -1);
+ g_object_unref (source_group);
+
+ /* This is now a borrowed reference. */
+ contacts = source_group;
+ }
+
+ /* XXX e_source_group_get_property() returns a newly-allocated
+ * string when it could just as easily return a const string.
+ * Unfortunately, fixing that would break the API. */
+ property = e_source_group_get_property (contacts, "create_source");
+ if (property == NULL)
+ e_source_group_set_property (contacts, "create_source", "no");
+ g_free (property);
+
+ name = _("Birthdays & Anniversaries");
+
+ if (birthdays == NULL) {
+ ESource *source;
+ const gchar *name;
+
+ name = _("Birthdays & Anniversaries");
+ source = e_source_new (name, "/");
+ e_source_group_add_source (contacts, source, -1);
+ g_object_unref (source);
+
+ /* This is now a borrowed reference. */
+ birthdays = source;
+ } else {
+ /* Force the source name to the current locale. */
+ e_source_set_name (birthdays, name);
+ }
+
+ if (e_source_get_property (birthdays, "delete") == NULL)
+ e_source_set_property (birthdays, "delete", "no");
+
+ if (e_source_peek_color_spec (birthdays) == NULL)
+ e_source_set_color_spec (birthdays, "#DDBECE");
+
+ name = _("Weather");
+
+ if (weather == NULL) {
+ ESourceGroup *source_group;
+
+ source_group = e_source_group_new (name, WEATHER_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 (weather, name);
+ }
+
+ g_free (base_uri);
+}
+
+static void
+cal_shell_backend_event_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = event_editor_new (cal, shell, flags);
+ comp = cal_comp_event_new_with_current_time (cal, FALSE);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+cal_shell_backend_event_all_day_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = event_editor_new (cal, shell, flags);
+ comp = cal_comp_event_new_with_current_time (cal, TRUE);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+cal_shell_backend_event_meeting_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+ flags |= COMP_EDITOR_USER_ORG;
+ flags |= COMP_EDITOR_MEETING;
+
+ editor = event_editor_new (cal, shell, flags);
+ comp = cal_comp_event_new_with_current_time (cal, FALSE);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+action_event_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ ECal *cal = NULL;
+ ECalSourceType source_type;
+ ESourceList *source_list;
+ EShellSettings *shell_settings;
+ EShell *shell;
+ const gchar *action_name;
+ gchar *uid;
+
+ /* This callback is used for both appointments and meetings. */
+
+ source_type = E_CAL_SOURCE_TYPE_EVENT;
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ if (!e_cal_get_sources (&source_list, source_type, NULL)) {
+ g_warning ("Could not get calendar sources from GConf!");
+ return;
+ }
+
+ uid = e_shell_settings_get_string (
+ shell_settings, "cal-primary-calendar");
+
+ if (uid != NULL) {
+ ESource *source;
+
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ if (source != NULL)
+ cal = auth_new_cal_from_source (source, source_type);
+ g_free (uid);
+ }
+
+ if (cal == NULL)
+ cal = auth_new_cal_from_default (source_type);
+
+ g_return_if_fail (cal != NULL);
+
+ /* Connect the appropriate signal handler. */
+ action_name = gtk_action_get_name (action);
+ if (strcmp (action_name, "event-all-day-new") == 0)
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (cal_shell_backend_event_all_day_new_cb),
+ shell);
+ else if (strcmp (action_name, "event-meeting-new") == 0)
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (cal_shell_backend_event_meeting_new_cb),
+ shell);
+ else
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (cal_shell_backend_event_new_cb),
+ shell);
+
+ e_cal_open_async (cal, FALSE);
+}
+
+static void
+action_calendar_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ calendar_setup_new_calendar (GTK_WINDOW (shell_window));
+}
+
+static GtkActionEntry item_entries[] = {
+
+ { "event-new",
+ "appointment-new",
+ NC_("New", "_Appointment"),
+ "<Shift><Control>a",
+ N_("Create a new appointment"),
+ G_CALLBACK (action_event_new_cb) },
+
+ { "event-all-day-new",
+ "stock_new-24h-appointment",
+ NC_("New", "All Day A_ppointment"),
+ NULL,
+ N_("Create a new all-day appointment"),
+ G_CALLBACK (action_event_new_cb) },
+
+ { "event-meeting-new",
+ "stock_new-meeting",
+ NC_("New", "M_eeting"),
+ "<Shift><Control>e",
+ N_("Create a new meeting request"),
+ G_CALLBACK (action_event_new_cb) }
+};
+
+static GtkActionEntry source_entries[] = {
+
+ { "calendar-new",
+ "x-office-calendar",
+ NC_("New", "Cale_ndar"),
+ NULL,
+ N_("Create a new calendar"),
+ G_CALLBACK (action_calendar_new_cb) }
+};
+
+static void
+cal_shell_backend_init_importers (void)
+{
+ EImportClass *import_class;
+ EImportImporter *importer;
+
+ import_class = g_type_class_ref (e_import_get_type ());
+
+ importer = gnome_calendar_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = ical_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = vcal_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+}
+
+static void
+cal_shell_backend_init_preferences (EShell *shell)
+{
+ GtkWidget *preferences_window;
+
+ preferences_window = e_shell_get_preferences_window (shell);
+
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "calendar-and-tasks",
+ "preferences-calendar-and-tasks",
+ _("Calendar and Tasks"),
+ calendar_prefs_dialog_new (shell),
+ 600);
+}
+
+static gboolean
+cal_shell_backend_handle_uri_cb (EShellBackend *shell_backend,
+ const gchar *uri)
+{
+ EShell *shell;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECal *client;
+ ECalComponent *comp;
+ ESource *source;
+ ESourceList *source_list;
+ ECalSourceType source_type;
+ EUri *euri;
+ icalcomponent *icalcomp;
+ icalproperty *icalprop;
+ const gchar *cp;
+ gchar *source_uid = NULL;
+ gchar *comp_uid = NULL;
+ gchar *comp_rid = NULL;
+ time_t startdate = -1;
+ time_t enddate = -1;
+ gboolean handled = FALSE;
+ GError *error = NULL;
+
+ source_type = E_CAL_SOURCE_TYPE_EVENT;
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ if (strncmp (uri, "calendar:", 9) != 0)
+ return FALSE;
+
+ euri = e_uri_new (uri);
+ cp = euri->query;
+ if (cp == NULL)
+ goto exit;
+
+ while (*cp != '\0') {
+ gchar *header;
+ gchar *content;
+ gsize header_len;
+ gsize content_len;
+
+ header_len = strcspn (cp, "=&");
+
+ /* It it's malformed, give up. */
+ if (cp[header_len] != '=')
+ break;
+
+ header = (gchar *) cp;
+ header[header_len] = '\0';
+ cp += header_len + 1;
+
+ content_len = strcspn (cp, "&");
+
+ content = g_strndup (cp, content_len);
+ if (g_ascii_strcasecmp (header, "startdate") == 0)
+ startdate = time_from_isodate (content);
+ else if (g_ascii_strcasecmp (header, "enddate") == 0)
+ enddate = time_from_isodate (content);
+ else if (g_ascii_strcasecmp (header, "source-uid") == 0)
+ source_uid = g_strdup (content);
+ else if (g_ascii_strcasecmp (header, "comp-uid") == 0)
+ comp_uid = g_strdup (content);
+ else if (g_ascii_strcasecmp (header, "comp-rid") == 0)
+ comp_rid = g_strdup (content);
+ g_free (content);
+
+ cp += content_len;
+ if (*cp == '&') {
+ cp++;
+ if (strcmp (cp, "amp;") == 0)
+ cp += 4;
+ }
+ }
+
+ if (source_uid == NULL || comp_uid == NULL)
+ goto exit;
+
+ /* URI is valid, so consider it handled. Whether
+ * we successfully open it is another matter... */
+ handled = TRUE;
+
+ if (!e_cal_get_sources (&source_list, source_type, NULL)) {
+ g_printerr ("Could not get calendar sources from GConf!\n");
+ goto exit;
+ }
+
+ source = e_source_list_peek_source_by_uid (source_list, source_uid);
+ if (source == NULL) {
+ g_printerr ("No source for UID `%s'\n", source_uid);
+ g_object_unref (source_list);
+ goto exit;
+ }
+
+ client = auth_new_cal_from_source (source, source_type);
+ if (client == NULL || !e_cal_open (client, TRUE, &error)) {
+ g_printerr ("%s\n", error->message);
+ g_object_unref (source_list);
+ g_error_free (error);
+ goto exit;
+ }
+
+ /* XXX Copied from e_cal_shell_view_open_event().
+ * Clearly a new utility function is needed. */
+
+ editor = comp_editor_find_instance (comp_uid);
+
+ if (editor != NULL)
+ goto present;
+
+ if (!e_cal_get_object (client, comp_uid, comp_rid, &icalcomp, &error)) {
+ g_printerr ("%s\n", error->message);
+ g_object_unref (source_list);
+ g_error_free (error);
+ goto exit;
+ }
+
+ comp = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (comp, icalcomp);
+
+ icalprop = icalcomponent_get_first_property (
+ icalcomp, ICAL_ATTENDEE_PROPERTY);
+ if (icalprop != NULL)
+ flags |= COMP_EDITOR_MEETING;
+
+ if (itip_organizer_is_user (comp, client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (itip_sentby_is_user (comp, client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (!e_cal_component_has_attendees (comp))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = event_editor_new (client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_unref (comp);
+
+present:
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (source_list);
+ g_object_unref (client);
+
+exit:
+ g_free (source_uid);
+ g_free (comp_uid);
+ g_free (comp_rid);
+
+ e_uri_free (euri);
+
+ return handled;
+}
+
+static void
+cal_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
+cal_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_cal_shell_backend_get_source_list (
+ E_CAL_SHELL_BACKEND (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_shell_backend_dispose (GObject *object)
+{
+ ECalShellBackendPrivate *priv;
+
+ priv = E_CAL_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
+cal_shell_backend_constructed (GObject *object)
+{
+ EShell *shell;
+ EShellBackend *shell_backend;
+
+ shell_backend = E_SHELL_BACKEND (object);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ cal_shell_backend_ensure_sources (shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "handle-uri",
+ G_CALLBACK (cal_shell_backend_handle_uri_cb),
+ shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "window-created",
+ G_CALLBACK (cal_shell_backend_window_created_cb),
+ shell_backend);
+
+ cal_shell_backend_init_importers ();
+
+ /* Initialize settings before initializing preferences,
+ * since the preferences bind to the shell settings. */
+ e_cal_shell_backend_init_settings (shell);
+ cal_shell_backend_init_preferences (shell);
+}
+
+static void
+cal_shell_backend_class_init (ECalShellBackendClass *class)
+{
+ GObjectClass *object_class;
+ EShellBackendClass *shell_backend_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ECalShellBackendPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = cal_shell_backend_get_property;
+ object_class->dispose = cal_shell_backend_dispose;
+ object_class->constructed = cal_shell_backend_constructed;
+
+ shell_backend_class = E_SHELL_BACKEND_CLASS (class);
+ shell_backend_class->shell_view_type = E_TYPE_CAL_SHELL_VIEW;
+ shell_backend_class->name = "calendar";
+ shell_backend_class->aliases = "";
+ shell_backend_class->schemes = "calendar";
+ shell_backend_class->sort_order = 400;
+ shell_backend_class->start = NULL;
+ shell_backend_class->migrate = e_cal_shell_backend_migrate;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE_LIST,
+ g_param_spec_object (
+ "source-list",
+ _("Source List"),
+ _("The registry of calendars"),
+ E_TYPE_SOURCE_LIST,
+ G_PARAM_READABLE));
+}
+
+static void
+cal_shell_backend_init (ECalShellBackend *cal_shell_backend)
+{
+ cal_shell_backend->priv =
+ E_CAL_SHELL_BACKEND_GET_PRIVATE (cal_shell_backend);
+}
+
+GType
+e_cal_shell_backend_get_type (void)
+{
+ return cal_shell_backend_type;
+}
+
+void
+e_cal_shell_backend_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (ECalShellBackendClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_shell_backend_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ECalShellBackend),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) cal_shell_backend_init,
+ NULL /* value_table */
+ };
+
+ cal_shell_backend_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_BACKEND,
+ "ECalShellBackend", &type_info, 0);
+}
+
+ESourceList *
+e_cal_shell_backend_get_source_list (ECalShellBackend *cal_shell_backend)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_BACKEND (cal_shell_backend), NULL);
+
+ return cal_shell_backend->priv->source_list;
+}
diff --git a/modules/calendar/e-cal-shell-backend.h b/modules/calendar/e-cal-shell-backend.h
new file mode 100644
index 0000000000..497e200490
--- /dev/null
+++ b/modules/calendar/e-cal-shell-backend.h
@@ -0,0 +1,70 @@
+/*
+ * e-cal-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_CAL_SHELL_BACKEND_H
+#define E_CAL_SHELL_BACKEND_H
+
+#include <shell/e-shell-backend.h>
+#include <libedataserver/e-source-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_SHELL_BACKEND \
+ (e_cal_shell_backend_get_type ())
+#define E_CAL_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackend))
+#define E_CAL_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackendClass))
+#define E_IS_CAL_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_SHELL_BACKEND))
+#define E_IS_CAL_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_SHELL_BACKEND))
+#define E_CAL_SHELL_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalShellBackend ECalShellBackend;
+typedef struct _ECalShellBackendClass ECalShellBackendClass;
+typedef struct _ECalShellBackendPrivate ECalShellBackendPrivate;
+
+struct _ECalShellBackend {
+ EShellBackend parent;
+ ECalShellBackendPrivate *priv;
+};
+
+struct _ECalShellBackendClass {
+ EShellBackendClass parent_class;
+};
+
+GType e_cal_shell_backend_get_type (void);
+void e_cal_shell_backend_register_type
+ (GTypeModule *type_module);
+ESourceList * e_cal_shell_backend_get_source_list
+ (ECalShellBackend *cal_shell_backend);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_BACKEND_H */
diff --git a/modules/calendar/e-cal-shell-content.c b/modules/calendar/e-cal-shell-content.c
new file mode 100644
index 0000000000..e199f0524d
--- /dev/null
+++ b/modules/calendar/e-cal-shell-content.c
@@ -0,0 +1,768 @@
+/*
+ * e-cal-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-cal-shell-content.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/menus/gal-view-etable.h"
+#include "widgets/misc/e-paned.h"
+
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/calendar-view.h"
+#include "calendar/gui/e-cal-list-view.h"
+#include "calendar/gui/e-cal-model-calendar.h"
+#include "calendar/gui/e-calendar-table.h"
+#include "calendar/gui/e-calendar-view.h"
+#include "calendar/gui/e-day-view.h"
+#include "calendar/gui/e-week-view.h"
+
+#define E_CAL_SHELL_CONTENT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentPrivate))
+
+struct _ECalShellContentPrivate {
+ GtkWidget *hpaned;
+ GtkWidget *notebook;
+ GtkWidget *vpaned;
+
+ GtkWidget *calendar;
+ GtkWidget *task_table;
+ GtkWidget *memo_table;
+
+ GalViewInstance *view_instance;
+
+ guint paned_binding_id;
+};
+
+enum {
+ PROP_0
+};
+
+/* Used to indicate who has the focus within the calendar view. */
+typedef enum {
+ FOCUS_CALENDAR,
+ FOCUS_MEMO_TABLE,
+ FOCUS_TASK_TABLE,
+ FOCUS_OTHER
+} FocusLocation;
+
+static gpointer parent_class;
+static GType cal_shell_content_type;
+
+static void
+cal_shell_content_display_view_cb (ECalShellContent *cal_shell_content,
+ GalView *gal_view)
+{
+ GnomeCalendar *calendar;
+ GnomeCalendarViewType view_type;
+
+ /* XXX This is confusing: we have CalendarView and ECalendarView.
+ * ECalendarView is an abstract base class for calendar view
+ * widgets (day view, week view, etc). CalendarView is a
+ * simple GalView subclass that represents a calendar view. */
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ if (GAL_IS_VIEW_ETABLE (gal_view)) {
+ ECalendarView *calendar_view;
+ ETable *table;
+
+ view_type = GNOME_CAL_LIST_VIEW;
+ calendar_view = gnome_calendar_get_calendar_view (
+ calendar, view_type);
+ table = e_table_scrolled_get_table (
+ E_CAL_LIST_VIEW (calendar_view)->table_scrolled);
+ gal_view_etable_attach_table (
+ GAL_VIEW_ETABLE (gal_view), table);
+ } else {
+ view_type = calendar_view_get_view_type (
+ CALENDAR_VIEW (gal_view));
+ }
+
+ gnome_calendar_display_view (calendar, view_type);
+}
+
+static void
+cal_shell_content_notify_view_id_cb (ECalShellContent *cal_shell_content)
+{
+ EShellContent *shell_content;
+ EShellView *shell_view;
+ GConfBridge *bridge;
+ GtkWidget *paned;
+ guint binding_id;
+ const gchar *key;
+ const gchar *view_id;
+
+ bridge = gconf_bridge_get ();
+ paned = cal_shell_content->priv->hpaned;
+ binding_id = cal_shell_content->priv->paned_binding_id;
+
+ shell_content = E_SHELL_CONTENT (cal_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ view_id = e_shell_view_get_view_id (shell_view);
+
+ if (binding_id > 0)
+ gconf_bridge_unbind (bridge, binding_id);
+
+ if (view_id != NULL && strcmp (view_id, "Month_View") == 0)
+ key = "/apps/evolution/calendar/display/month_hpane_position";
+ else
+ key = "/apps/evolution/calendar/display/hpane_position";
+
+ binding_id = gconf_bridge_bind_property_delayed (
+ bridge, key, G_OBJECT (paned), "hposition");
+
+ cal_shell_content->priv->paned_binding_id = binding_id;
+}
+
+static FocusLocation
+cal_shell_content_get_focus_location (ECalShellContent *cal_shell_content)
+{
+ GtkWidget *widget;
+ GnomeCalendar *calendar;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ ECalendarTable *task_table;
+ EMemoTable *memo_table;
+ ETable *table;
+
+ calendar = GNOME_CALENDAR (cal_shell_content->priv->calendar);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ memo_table = E_MEMO_TABLE (cal_shell_content->priv->memo_table);
+ task_table = E_CALENDAR_TABLE (cal_shell_content->priv->task_table);
+
+ table = e_memo_table_get_table (memo_table);
+ if (GTK_WIDGET_HAS_FOCUS (table->table_canvas))
+ return FOCUS_MEMO_TABLE;
+
+ table = e_calendar_table_get_table (task_table);
+ if (GTK_WIDGET_HAS_FOCUS (table->table_canvas))
+ return FOCUS_TASK_TABLE;
+
+ if (E_IS_DAY_VIEW (calendar_view)) {
+ EDayView *day_view = E_DAY_VIEW (calendar_view);
+
+ if (GTK_WIDGET_HAS_FOCUS (day_view->top_canvas))
+ return FOCUS_CALENDAR;
+
+ if (GNOME_CANVAS (day_view->top_canvas)->focused_item != NULL)
+ return FOCUS_CALENDAR;
+
+ if (GTK_WIDGET_HAS_FOCUS (day_view->main_canvas))
+ return FOCUS_CALENDAR;
+
+ if (GNOME_CANVAS (day_view->main_canvas)->focused_item != NULL)
+ return FOCUS_CALENDAR;
+
+ if (GTK_WIDGET_HAS_FOCUS (day_view))
+ return FOCUS_CALENDAR;
+
+ } else if (E_IS_WEEK_VIEW (calendar_view)) {
+ EWeekView *week_view = E_WEEK_VIEW (calendar_view);
+
+ if (GTK_WIDGET_HAS_FOCUS (week_view->main_canvas))
+ return FOCUS_CALENDAR;
+
+ if (GNOME_CANVAS (week_view->main_canvas)->focused_item != NULL)
+ return FOCUS_CALENDAR;
+
+ if (GTK_WIDGET_HAS_FOCUS (week_view))
+ return FOCUS_CALENDAR;
+
+ } else if (E_IS_CAL_LIST_VIEW (calendar_view)) {
+ ECalListView *list_view = E_CAL_LIST_VIEW (widget);
+
+ table = e_table_scrolled_get_table (list_view->table_scrolled);
+ if (GTK_WIDGET_HAS_FOCUS (table))
+ return FOCUS_CALENDAR;
+
+ if (GTK_WIDGET_HAS_FOCUS (list_view))
+ return FOCUS_CALENDAR;
+ }
+
+ return FOCUS_OTHER;
+}
+
+static void
+cal_shell_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_shell_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_shell_content_dispose (GObject *object)
+{
+ ECalShellContentPrivate *priv;
+
+ priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (object);
+
+ if (priv->hpaned != NULL) {
+ g_object_unref (priv->hpaned);
+ priv->hpaned = NULL;
+ }
+
+ if (priv->notebook != NULL) {
+ g_object_unref (priv->notebook);
+ priv->notebook = NULL;
+ }
+
+ if (priv->vpaned != NULL) {
+ g_object_unref (priv->vpaned);
+ priv->vpaned = NULL;
+ }
+
+ if (priv->calendar != NULL) {
+ g_object_unref (priv->calendar);
+ priv->calendar = NULL;
+ }
+
+ if (priv->task_table != NULL) {
+ g_object_unref (priv->task_table);
+ priv->task_table = NULL;
+ }
+
+ if (priv->memo_table != NULL) {
+ g_object_unref (priv->memo_table);
+ priv->memo_table = NULL;
+ }
+
+ if (priv->view_instance != NULL) {
+ g_object_unref (priv->view_instance);
+ priv->view_instance = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+cal_shell_content_finalize (GObject *object)
+{
+ ECalShellContentPrivate *priv;
+
+ priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (object);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+cal_shell_content_constructed (GObject *object)
+{
+ ECalShellContentPrivate *priv;
+ ECalendarView *calendar_view;
+ ECalModel *memo_model;
+ ECalModel *task_model;
+ EShell *shell;
+ EShellContent *shell_content;
+ EShellBackend *shell_backend;
+ EShellSettings *shell_settings;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *foreign_content;
+ EShellView *foreign_view;
+ GnomeCalendar *calendar;
+ GalViewInstance *view_instance;
+ GConfBridge *bridge;
+ GtkWidget *container;
+ GtkWidget *widget;
+ const gchar *config_dir;
+ const gchar *key;
+ gchar *filename;
+ gchar *markup;
+ gint ii;
+
+ priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ shell_content = E_SHELL_CONTENT (object);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ config_dir = e_shell_backend_get_config_dir (shell_backend);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ /* We borrow the memopad and taskpad models from the memo
+ * and task views, loading the views if necessary. */
+
+ foreign_view = e_shell_window_get_shell_view (shell_window, "memos");
+ foreign_content = e_shell_view_get_shell_content (foreign_view);
+ g_object_get (foreign_content, "model", &memo_model, NULL);
+
+ foreign_view = e_shell_window_get_shell_view (shell_window, "tasks");
+ foreign_content = e_shell_view_get_shell_content (foreign_view);
+ g_object_get (foreign_content, "model", &task_model, NULL);
+
+ /* Build content widgets. */
+
+ container = GTK_WIDGET (object);
+
+ widget = e_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->hpaned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = priv->hpaned;
+
+ widget = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ priv->notebook = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* FIXME Need to deal with saving and restoring the position.
+ * Month view has its own position. */
+ widget = gtk_vpaned_new ();
+ gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, TRUE);
+ priv->vpaned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = priv->notebook;
+
+ /* Add views in the order defined by GnomeCalendarViewType, such
+ * that the notebook page number corresponds to the view type. */
+
+ /* XXX GnomeCalendar is a widget, but we don't pack it.
+ * Maybe it should just be a GObject instead? */
+ priv->calendar = gnome_calendar_new (shell_settings);
+ g_object_ref_sink (priv->calendar);
+ calendar = GNOME_CALENDAR (priv->calendar);
+
+ for (ii = 0; ii < GNOME_CAL_LAST_VIEW; ii++) {
+ calendar_view = gnome_calendar_get_calendar_view (calendar, ii);
+
+ gtk_notebook_append_page (
+ GTK_NOTEBOOK (container),
+ GTK_WIDGET (calendar_view), NULL);
+ gtk_widget_show (GTK_WIDGET (calendar_view));
+ }
+
+ e_binding_new (
+ G_OBJECT (priv->calendar), "view",
+ G_OBJECT (priv->notebook), "page");
+
+ container = priv->vpaned;
+
+ widget = gtk_vbox_new (FALSE, 0);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, TRUE);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_hseparator_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (NULL);
+ markup = g_strdup_printf ("<b>%s</b>", _("Tasks"));
+ gtk_label_set_markup (GTK_LABEL (widget), markup);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = e_calendar_table_new (shell_view, task_model);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ priv->task_table = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ filename = g_build_filename (config_dir, "TaskPad", NULL);
+ e_calendar_table_load_state (E_CALENDAR_TABLE (widget), filename);
+ g_free (filename);
+
+ container = priv->vpaned;
+
+ widget = gtk_vbox_new (FALSE, 0);
+ gtk_paned_pack2 (GTK_PANED (container), widget, TRUE, TRUE);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_label_new (NULL);
+ markup = g_strdup_printf ("<b>%s</b>", _("Memos"));
+ gtk_label_set_markup (GTK_LABEL (widget), markup);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = e_memo_table_new (shell_view, memo_model);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ priv->memo_table = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ filename = g_build_filename (config_dir, "MemoPad", NULL);
+ e_memo_table_load_state (E_MEMO_TABLE (widget), filename);
+ g_free (filename);
+
+ /* Load the view instance. */
+
+ view_instance = e_shell_view_new_view_instance (shell_view, NULL);
+ g_signal_connect_swapped (
+ view_instance, "display-view",
+ G_CALLBACK (cal_shell_content_display_view_cb),
+ object);
+ /* XXX Actually, don't load the view instance just yet.
+ * The GtkWidget::map() callback below explains why. */
+ priv->view_instance = view_instance;
+
+ g_signal_connect_swapped (
+ shell_view, "notify::view-id",
+ G_CALLBACK (cal_shell_content_notify_view_id_cb),
+ object);
+
+ /* Bind GObject properties to GConf keys. */
+
+ bridge = gconf_bridge_get ();
+
+ object = G_OBJECT (priv->vpaned);
+ key = "/apps/evolution/calendar/display/vpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "position");
+
+ g_object_unref (memo_model);
+ g_object_unref (task_model);
+}
+
+static void
+cal_shell_content_map (GtkWidget *widget)
+{
+ ECalShellContentPrivate *priv;
+
+ /* XXX Delay loading the GalViewInstance until after ECalShellView
+ * has a chance to install the sidebar's date navigator into
+ * GnomeCalendar, since loading the GalViewInstance triggers a
+ * callback in GnomeCalendar that requires the date navigator.
+ * Ordinarily we would do this at the end of constructed(), but
+ * that's too soon in this case. (This feels kind of kludgy.) */
+ priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (widget);
+ gal_view_instance_load (priv->view_instance);
+
+ /* Chain up to parent's map() method. */
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+}
+
+static void
+cal_shell_content_class_init (ECalShellContentClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ECalShellContentPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = cal_shell_content_set_property;
+ object_class->get_property = cal_shell_content_get_property;
+ object_class->dispose = cal_shell_content_dispose;
+ object_class->finalize = cal_shell_content_finalize;
+ object_class->constructed = cal_shell_content_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->map = cal_shell_content_map;
+}
+
+static void
+cal_shell_content_init (ECalShellContent *cal_shell_content)
+{
+ cal_shell_content->priv =
+ E_CAL_SHELL_CONTENT_GET_PRIVATE (cal_shell_content);
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_cal_shell_content_get_type (void)
+{
+ return cal_shell_content_type;
+}
+
+void
+e_cal_shell_content_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (ECalShellContentClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_shell_content_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ECalShellContent),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) cal_shell_content_init,
+ NULL /* value_table */
+ };
+
+ cal_shell_content_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_CONTENT,
+ "ECalShellContent", &type_info, 0);
+}
+
+GtkWidget *
+e_cal_shell_content_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_CAL_SHELL_CONTENT,
+ "shell-view", shell_view, NULL);
+}
+
+ECalModel *
+e_cal_shell_content_get_model (ECalShellContent *cal_shell_content)
+{
+ GnomeCalendar *calendar;
+
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL);
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ return gnome_calendar_get_model (calendar);
+}
+
+GnomeCalendar *
+e_cal_shell_content_get_calendar (ECalShellContent *cal_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL);
+
+ return GNOME_CALENDAR (cal_shell_content->priv->calendar);
+}
+
+EMemoTable *
+e_cal_shell_content_get_memo_table (ECalShellContent *cal_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL);
+
+ return E_MEMO_TABLE (cal_shell_content->priv->memo_table);
+}
+
+ECalendarTable *
+e_cal_shell_content_get_task_table (ECalShellContent *cal_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL);
+
+ return E_CALENDAR_TABLE (cal_shell_content->priv->task_table);
+}
+
+GalViewInstance *
+e_cal_shell_content_get_view_instance (ECalShellContent *cal_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL);
+
+ return cal_shell_content->priv->view_instance;
+}
+
+void
+e_cal_shell_content_copy_clipboard (ECalShellContent *cal_shell_content)
+{
+ GnomeCalendar *calendar;
+ EMemoTable *memo_table;
+ ECalendarTable *task_table;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+
+ g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content));
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ switch (cal_shell_content_get_focus_location (cal_shell_content)) {
+ case FOCUS_CALENDAR:
+ e_calendar_view_copy_clipboard (calendar_view);
+ break;
+
+ case FOCUS_MEMO_TABLE:
+ e_memo_table_copy_clipboard (memo_table);
+ break;
+
+ case FOCUS_TASK_TABLE:
+ e_calendar_table_copy_clipboard (task_table);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+void
+e_cal_shell_content_cut_clipboard (ECalShellContent *cal_shell_content)
+{
+ GnomeCalendar *calendar;
+ EMemoTable *memo_table;
+ ECalendarTable *task_table;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+
+ g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content));
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ switch (cal_shell_content_get_focus_location (cal_shell_content)) {
+ case FOCUS_CALENDAR:
+ e_calendar_view_cut_clipboard (calendar_view);
+ break;
+
+ case FOCUS_MEMO_TABLE:
+ e_memo_table_copy_clipboard (memo_table);
+ break;
+
+ case FOCUS_TASK_TABLE:
+ e_calendar_table_copy_clipboard (task_table);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+void
+e_cal_shell_content_paste_clipboard (ECalShellContent *cal_shell_content)
+{
+ GnomeCalendar *calendar;
+ EMemoTable *memo_table;
+ ECalendarTable *task_table;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+
+ g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content));
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ switch (cal_shell_content_get_focus_location (cal_shell_content)) {
+ case FOCUS_CALENDAR:
+ e_calendar_view_paste_clipboard (calendar_view);
+ break;
+
+ case FOCUS_MEMO_TABLE:
+ e_memo_table_copy_clipboard (memo_table);
+ break;
+
+ case FOCUS_TASK_TABLE:
+ e_calendar_table_copy_clipboard (task_table);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+void
+e_cal_shell_content_delete_selection (ECalShellContent *cal_shell_content)
+{
+ GnomeCalendar *calendar;
+ EMemoTable *memo_table;
+ ECalendarTable *task_table;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+
+ g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content));
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ switch (cal_shell_content_get_focus_location (cal_shell_content)) {
+ case FOCUS_CALENDAR:
+ e_calendar_view_delete_selected_events (calendar_view);
+ break;
+
+ case FOCUS_MEMO_TABLE:
+ e_memo_table_delete_selected (memo_table);
+ break;
+
+ case FOCUS_TASK_TABLE:
+ e_calendar_table_delete_selected (task_table);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+void
+e_cal_shell_content_delete_selected_occurrence (ECalShellContent *cal_shell_content)
+{
+ GnomeCalendar *calendar;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ FocusLocation focus;
+
+ g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content));
+
+ focus = cal_shell_content_get_focus_location (cal_shell_content);
+ if (focus != FOCUS_CALENDAR)
+ return;
+
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ e_calendar_view_delete_selected_occurrence (calendar_view);
+}
diff --git a/modules/calendar/e-cal-shell-content.h b/modules/calendar/e-cal-shell-content.h
new file mode 100644
index 0000000000..3db7d33260
--- /dev/null
+++ b/modules/calendar/e-cal-shell-content.h
@@ -0,0 +1,108 @@
+/*
+ * e-cal-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_CAL_SHELL_CONTENT_H
+#define E_CAL_SHELL_CONTENT_H
+
+#include <shell/e-shell-content.h>
+#include <shell/e-shell-view.h>
+
+#include <calendar/gui/e-memo-table.h>
+#include <calendar/gui/gnome-cal.h>
+#include <menus/gal-view-instance.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_SHELL_CONTENT \
+ (e_cal_shell_content_get_type ())
+#define E_CAL_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContent))
+#define E_CAL_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentClass))
+#define E_IS_CAL_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_SHELL_CONTENT))
+#define E_IS_CAL_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_SHELL_CONTENT))
+#define E_CAL_SHELL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalShellContent ECalShellContent;
+typedef struct _ECalShellContentClass ECalShellContentClass;
+typedef struct _ECalShellContentPrivate ECalShellContentPrivate;
+
+enum {
+ E_CAL_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0,
+ E_CAL_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1,
+ E_CAL_SHELL_CONTENT_SELECTION_IS_ASSIGNABLE = 1 << 2,
+ E_CAL_SHELL_CONTENT_SELECTION_IS_COMPLETE = 1 << 3,
+ E_CAL_SHELL_CONTENT_SELECTION_IS_EDITABLE = 1 << 4,
+ E_CAL_SHELL_CONTENT_SELECTION_IS_MEETING = 1 << 5,
+ E_CAL_SHELL_CONTENT_SELECTION_IS_ORGANIZER = 1 << 6,
+ E_CAL_SHELL_CONTENT_SELECTION_IS_RECURRING = 1 << 7,
+ E_CAL_SHELL_CONTENT_SELECTION_CAN_ACCEPT = 1 << 8,
+ E_CAL_SHELL_CONTENT_SELECTION_CAN_DELEGATE = 1 << 9,
+ E_CAL_SHELL_CONTENT_SELECTION_CAN_SAVE = 1 << 10
+};
+
+struct _ECalShellContent {
+ EShellContent parent;
+ ECalShellContentPrivate *priv;
+};
+
+struct _ECalShellContentClass {
+ EShellContentClass parent_class;
+};
+
+GType e_cal_shell_content_get_type (void);
+void e_cal_shell_content_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_cal_shell_content_new (EShellView *shell_view);
+ECalModel * e_cal_shell_content_get_model
+ (ECalShellContent *cal_shell_content);
+GnomeCalendar * e_cal_shell_content_get_calendar
+ (ECalShellContent *cal_shell_content);
+EMemoTable * e_cal_shell_content_get_memo_table
+ (ECalShellContent *cal_shell_content);
+ECalendarTable *e_cal_shell_content_get_task_table
+ (ECalShellContent *cal_shell_content);
+GalViewInstance *
+ e_cal_shell_content_get_view_instance
+ (ECalShellContent *cal_shell_content);
+void e_cal_shell_content_copy_clipboard
+ (ECalShellContent *cal_shell_content);
+void e_cal_shell_content_cut_clipboard
+ (ECalShellContent *cal_shell_content);
+void e_cal_shell_content_paste_clipboard
+ (ECalShellContent *cal_shell_content);
+void e_cal_shell_content_delete_selection
+ (ECalShellContent *cal_shell_content);
+void e_cal_shell_content_delete_selected_occurrence
+ (ECalShellContent *cal_shell_content);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_CONTENT_H */
diff --git a/modules/calendar/e-cal-shell-migrate.c b/modules/calendar/e-cal-shell-migrate.c
new file mode 100644
index 0000000000..5ec9c99bf3
--- /dev/null
+++ b/modules/calendar/e-cal-shell-migrate.c
@@ -0,0 +1,794 @@
+/*
+ * e-cal-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-cal-shell-migrate.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <libebackend/e-dbhash.h>
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-group.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserver/e-xml-hash-utils.h>
+
+#include "e-util/e-bconf-map.h"
+#include "e-util/e-folder-map.h"
+#include "e-util/e-util-private.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/calendar-config-keys.h"
+#include "calendar/gui/e-cal-event.h"
+#include "shell/e-shell.h"
+
+#define WEBCAL_BASE_URI "webcal://"
+#define CONTACTS_BASE_URI "contacts://"
+#define BAD_CONTACTS_BASE_URI "contact://"
+#define PERSONAL_RELATIVE_URI "system"
+
+static e_gconf_map_t calendar_display_map[] = {
+ /* /Calendar/Display */
+ { "Timezone", "calendar/display/timezone", E_GCONF_MAP_STRING },
+ { "Use24HourFormat", "calendar/display/use_24hour_format", E_GCONF_MAP_BOOL },
+ { "WeekStartDay", "calendar/display/week_start_day", E_GCONF_MAP_INT },
+ { "DayStartHour", "calendar/display/day_start_hour", E_GCONF_MAP_INT },
+ { "DayStartMinute", "calendar/display/day_start_minute", E_GCONF_MAP_INT },
+ { "DayEndHour", "calendar/display/day_end_hour", E_GCONF_MAP_INT },
+ { "DayEndMinute", "calendar/display/day_end_minute", E_GCONF_MAP_INT },
+ { "TimeDivisions", "calendar/display/time_divisions", E_GCONF_MAP_INT },
+ { "View", "calendar/display/default_view", E_GCONF_MAP_INT },
+ { "HPanePosition", "calendar/display/hpane_position", E_GCONF_MAP_FLOAT },
+ { "VPanePosition", "calendar/display/vpane_position", E_GCONF_MAP_FLOAT },
+ { "MonthHPanePosition", "calendar/display/month_hpane_position", E_GCONF_MAP_FLOAT },
+ { "MonthVPanePosition", "calendar/display/month_vpane_position", E_GCONF_MAP_FLOAT },
+ { "CompressWeekend", "calendar/display/compress_weekend", E_GCONF_MAP_BOOL },
+ { "ShowEventEndTime", "calendar/display/show_event_end", E_GCONF_MAP_BOOL },
+ { "WorkingDays", "calendar/display/working_days", E_GCONF_MAP_INT },
+ { NULL },
+};
+
+static e_gconf_map_t calendar_other_map[] = {
+ /* /Calendar/Other */
+ { "ConfirmDelete", "calendar/prompts/confirm_delete", E_GCONF_MAP_BOOL },
+ { "ConfirmExpunge", "calendar/prompts/confirm_purge", E_GCONF_MAP_BOOL },
+ { "UseDefaultReminder", "calendar/other/use_default_reminder", E_GCONF_MAP_BOOL },
+ { "DefaultReminderInterval", "calendar/other/default_reminder_interval", E_GCONF_MAP_INT },
+ { "DefaultReminderUnits", "calendar/other/default_reminder_units", E_GCONF_MAP_STRING },
+ { NULL },
+};
+
+static e_gconf_map_t calendar_datenavigator_map[] = {
+ /* /Calendar/DateNavigator */
+ { "ShowWeekNumbers", "calendar/date_navigator/show_week_numbers", E_GCONF_MAP_BOOL },
+ { NULL },
+};
+
+static e_gconf_map_t calendar_alarmnotify_map[] = {
+ /* /Calendar/AlarmNotify */
+ { "LastNotificationTime", "calendar/notify/last_notification_time", E_GCONF_MAP_INT },
+ { "CalendarToLoad%i", "calendar/notify/calendars", E_GCONF_MAP_STRING|E_GCONF_MAP_LIST },
+ { "BlessedProgram%i", "calendar/notify/programs", E_GCONF_MAP_STRING|E_GCONF_MAP_LIST },
+ { NULL },
+};
+
+static e_gconf_map_list_t calendar_remap_list[] = {
+
+ { "/Calendar/Display", calendar_display_map },
+ { "/Calendar/Other/Map", calendar_other_map },
+ { "/Calendar/DateNavigator", calendar_datenavigator_map },
+ { "/Calendar/AlarmNotify", calendar_alarmnotify_map },
+
+ { NULL },
+};
+
+static GtkWidget *window;
+static GtkLabel *label;
+static GtkProgressBar *progress;
+
+#ifndef G_OS_WIN32
+
+/* No previous versions have been available on Win32, so don't
+ * bother with upgrade support from 1.x on Win32.
+ */
+
+static void
+setup_progress_dialog (void)
+{
+ GtkWidget *vbox, *hbox, *w;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title ((GtkWindow *) window, _("Migrating..."));
+ gtk_window_set_modal ((GtkWindow *) window, TRUE);
+ gtk_container_set_border_width ((GtkContainer *) window, 6);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_container_add ((GtkContainer *) window, vbox);
+
+ w = gtk_label_new (_("The location and hierarchy of the Evolution calendar "
+ "folders has changed since Evolution 1.x.\n\nPlease be "
+ "patient while Evolution migrates your folders..."));
+
+ gtk_label_set_line_wrap ((GtkLabel *) w, TRUE);
+ gtk_widget_show (w);
+ gtk_box_pack_start ((GtkBox *) vbox, w, TRUE, TRUE, 0);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start ((GtkBox *) vbox, hbox, TRUE, TRUE, 0);
+
+ label = (GtkLabel *) gtk_label_new ("");
+ gtk_widget_show ((GtkWidget *) label);
+ gtk_box_pack_start ((GtkBox *) hbox, (GtkWidget *) label, TRUE, TRUE, 0);
+
+ progress = (GtkProgressBar *) gtk_progress_bar_new ();
+ gtk_widget_show ((GtkWidget *) progress);
+ gtk_box_pack_start ((GtkBox *) hbox, (GtkWidget *) progress, TRUE, TRUE, 0);
+
+ gtk_widget_show (window);
+}
+
+static void
+dialog_close (void)
+{
+ gtk_widget_destroy ((GtkWidget *) window);
+}
+
+static void
+dialog_set_folder_name (const gchar *folder_name)
+{
+ gchar *text;
+
+ text = g_strdup_printf (_("Migrating '%s':"), folder_name);
+ gtk_label_set_text (label, text);
+ g_free (text);
+
+ gtk_progress_bar_set_fraction (progress, 0.0);
+
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+}
+
+static void
+dialog_set_progress (double percent)
+{
+ gchar text[5];
+
+ snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f));
+
+ gtk_progress_bar_set_fraction (progress, percent);
+ gtk_progress_bar_set_text (progress, text);
+
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+}
+
+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)
+{
+ gchar **p = g_strsplit (path, "/", 0);
+ gint i, j, starting_index;
+ gint num_elements;
+ gboolean conflict;
+ GString *s = g_string_new (NULL);
+
+ 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 {
+ 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 gboolean
+migrate_ical (ECal *old_ecal, ECal *new_ecal)
+{
+ GList *l, *objects;
+ gint num_added = 0;
+ gint num_objects;
+ gboolean retval = TRUE;
+
+ /* both ecals are loaded, start the actual migration */
+ if (!e_cal_get_object_list (old_ecal, "#t", &objects, NULL))
+ return FALSE;
+
+ num_objects = g_list_length (objects);
+ for (l = objects; l; l = l->next) {
+ icalcomponent *ical_comp = l->data;
+ GError *error = NULL;
+
+ if (!e_cal_create_object (new_ecal, ical_comp, NULL, &error)) {
+ g_warning ("Migration of object failed: %s", error->message);
+ retval = FALSE;
+ }
+
+ g_clear_error (&error);
+
+ num_added ++;
+ dialog_set_progress ((double)num_added / num_objects);
+ }
+
+ g_list_foreach (objects, (GFunc) icalcomponent_free, NULL);
+ g_list_free (objects);
+
+ return retval;
+}
+
+static gboolean
+migrate_ical_folder_to_source (gchar *old_path, ESource *new_source, ECalSourceType type)
+{
+ ECal *old_ecal = NULL, *new_ecal = NULL;
+ ESource *old_source;
+ ESourceGroup *group;
+ gchar *old_uri = g_strdup_printf ("file://%s", old_path);
+ GError *error = NULL;
+ gboolean retval = FALSE;
+
+ 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 (e_source_peek_name (new_source));
+
+ if (!(old_ecal = e_cal_new (old_source, type))) {
+ g_warning ("could not find a backend for '%s'", e_source_get_uri (old_source));
+ goto finish;
+ }
+ if (!e_cal_open (old_ecal, FALSE, &error)) {
+ g_warning ("failed to load source ecal for migration: '%s' (%s)", error->message,
+ e_source_get_uri (old_source));
+ goto finish;
+ }
+
+ if (!(new_ecal = e_cal_new (new_source, type))) {
+ g_warning ("could not find a backend for '%s'", e_source_get_uri (new_source));
+ goto finish;
+ }
+ if (!e_cal_open (new_ecal, FALSE, &error)) {
+ g_warning ("failed to load destination ecal for migration: '%s' (%s)", error->message,
+ e_source_get_uri (new_source));
+ goto finish;
+ }
+
+ retval = migrate_ical (old_ecal, new_ecal);
+
+finish:
+ g_clear_error (&error);
+ if (old_ecal)
+ g_object_unref (old_ecal);
+ g_object_unref (group);
+ if (new_ecal)
+ g_object_unref (new_ecal);
+ g_free (old_uri);
+
+ return retval;
+}
+
+static gboolean
+migrate_ical_folder (gchar *old_path, ESourceGroup *dest_group, gchar *source_name, ECalSourceType type)
+{
+ ESource *new_source;
+ gboolean retval;
+
+ 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);
+
+ retval = migrate_ical_folder_to_source (old_path, new_source, type);
+
+ g_object_unref (new_source);
+
+ return retval;
+}
+
+#endif /* !G_OS_WIN32 */
+
+#ifndef G_OS_WIN32
+
+static void
+migrate_pilot_db_key (const gchar *key, gpointer user_data)
+{
+ EXmlHash *xmlhash = user_data;
+
+ e_xmlhash_add (xmlhash, key, "");
+}
+
+static void
+migrate_pilot_data (const gchar *component, const gchar *conduit, const gchar *old_path, const gchar *new_path)
+{
+ gchar *changelog, *map;
+ const gchar *dent;
+ const gchar *ext;
+ gchar *filename;
+ GDir *dir;
+
+ if (!(dir = g_dir_open (old_path, 0, NULL)))
+ return;
+
+ map = g_alloca (12 + strlen (conduit));
+ sprintf (map, "pilot-map-%s-", conduit);
+
+ changelog = g_alloca (24 + strlen (conduit));
+ sprintf (changelog, "pilot-sync-evolution-%s-", conduit);
+
+ while ((dent = g_dir_read_name (dir))) {
+ if (!strncmp (dent, map, strlen (map)) &&
+ ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) {
+ /* pilot map file - src and dest file formats are identical */
+ 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, strerror (errno));
+ g_unlink (filename);
+ }
+
+ close (fd0);
+ close (fd1);
+ g_free (filename);
+ } else if (!strncmp (dent, changelog, strlen (changelog)) &&
+ ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db"))) {
+ /* src and dest formats differ, src format is db3 while dest format is xml */
+ EXmlHash *xmlhash;
+ EDbHash *dbhash;
+ struct stat st;
+
+ filename = g_build_filename (old_path, dent, NULL);
+ if (g_stat (filename, &st) == -1) {
+ g_free (filename);
+ continue;
+ }
+
+ dbhash = e_dbhash_new (filename);
+ g_free (filename);
+
+ filename = g_strdup_printf ("%s/%s.ics-%s", new_path, component, dent);
+ if (g_stat (filename, &st) != -1)
+ g_unlink (filename);
+ xmlhash = e_xmlhash_new (filename);
+ g_free (filename);
+
+ e_dbhash_foreach_key (dbhash, migrate_pilot_db_key, xmlhash);
+
+ e_dbhash_destroy (dbhash);
+
+ e_xmlhash_write (xmlhash);
+ e_xmlhash_destroy (xmlhash);
+ }
+ }
+
+ g_dir_close (dir);
+}
+
+#endif
+
+static ESourceGroup *
+create_calendar_contact_source (ESourceList *source_list)
+{
+ ESourceGroup *group;
+ ESource *source;
+
+ /* Create the contacts group */
+ group = e_source_group_new (_("Contacts"), CONTACTS_BASE_URI);
+ e_source_list_add_group (source_list, group, -1);
+
+ source = e_source_new (_("Birthdays & Anniversaries"), "/");
+ e_source_group_add_source (group, source, -1);
+ g_object_unref (source);
+
+ e_source_set_color_spec (source, "#FED4D3");
+ e_source_group_set_readonly (group, TRUE);
+
+ return group;
+}
+
+static void
+create_calendar_sources (EShellBackend *shell_backend,
+ ESourceList *source_list,
+ ESourceGroup **on_this_computer,
+ ESource **personal_source,
+ ESourceGroup **on_the_web,
+ ESourceGroup **contacts)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+ GSList *groups;
+ ESourceGroup *group;
+ gchar *base_uri, *base_uri_proto;
+ const gchar *base_dir;
+
+ *on_this_computer = NULL;
+ *on_the_web = NULL;
+ *contacts = NULL;
+ *personal_source = NULL;
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ base_dir = e_shell_backend_get_config_dir (shell_backend);
+ base_uri = g_build_filename (base_dir, "local", NULL);
+
+ base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL);
+
+ groups = e_source_list_peek_groups (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 (!strcmp (BAD_CONTACTS_BASE_URI, e_source_group_peek_base_uri (group)))
+ e_source_group_set_base_uri (group, CONTACTS_BASE_URI);
+
+ if (!strcmp (base_uri, e_source_group_peek_base_uri (group)))
+ e_source_group_set_base_uri (group, base_uri_proto);
+
+ 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_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group)))
+ *on_the_web = g_object_ref (group);
+ else if (!*contacts && !strcmp (CONTACTS_BASE_URI, e_source_group_peek_base_uri (group)))
+ *contacts = 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 (source_list, group, -1);
+
+ *on_this_computer = group;
+ }
+
+ if (!*personal_source) {
+ gchar *primary_calendar;
+
+ /* Create the default Person calendar */
+ ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI);
+ e_source_group_add_source (*on_this_computer, source, -1);
+
+ primary_calendar = e_shell_settings_get_string (
+ shell_settings, "cal-primary-calendar");
+
+ if (!primary_calendar && !calendar_config_get_calendars_selected ()) {
+ GSList selected;
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-calendar",
+ e_source_peek_uid (source));
+
+ selected.data = (gpointer)e_source_peek_uid (source);
+ selected.next = NULL;
+ calendar_config_set_calendars_selected (&selected);
+ }
+
+ g_free (primary_calendar);
+ e_source_set_color_spec (source, "#BECEDD");
+ *personal_source = source;
+ }
+
+ if (!*on_the_web) {
+ /* Create the Webcal source group */
+ group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI);
+ e_source_list_add_group (source_list, group, -1);
+
+ *on_the_web = group;
+ }
+
+ if (!*contacts) {
+ group = create_calendar_contact_source (source_list);
+
+ *contacts = group;
+ }
+
+ g_free (base_uri_proto);
+ g_free (base_uri);
+}
+
+gboolean
+e_cal_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error)
+{
+ ESourceGroup *on_this_computer = NULL, *on_the_web = NULL, *contacts = NULL;
+ ESource *personal_source = NULL;
+ ESourceList *source_list;
+ ECalEvent *ece;
+ ECalEventTargetBackend *target;
+ gboolean retval = FALSE;
+
+ g_object_get (shell_backend, "source-list", &source_list, NULL);
+
+ /* we call this unconditionally now - create_groups either
+ creates the groups/sources or it finds the necessary
+ groups/sources. */
+ create_calendar_sources (
+ shell_backend, source_list, &on_this_computer,
+ &personal_source, &on_the_web, &contacts);
+
+#ifndef G_OS_WIN32
+ if (major == 1) {
+ xmlDocPtr config_doc = NULL;
+ gchar *conf_file;
+ struct stat st;
+
+ conf_file = g_build_filename (g_get_home_dir (), "evolution", "config.xmldb", NULL);
+ if (lstat (conf_file, &st) == 0 && S_ISREG (st.st_mode))
+ config_doc = xmlParseFile (conf_file);
+ g_free (conf_file);
+
+ if (config_doc && minor <= 2) {
+ GConfClient *gconf;
+ gint res = 0;
+
+ /* move bonobo config to gconf */
+ gconf = gconf_client_get_default ();
+
+ res = e_bconf_import (gconf, config_doc, calendar_remap_list);
+
+ g_object_unref (gconf);
+
+ xmlFreeDoc(config_doc);
+
+ if (res != 0) {
+ /* FIXME: set proper domain/code */
+ g_set_error(error, 0, 0, _("Unable to migrate old settings from evolution/config.xmldb"));
+ goto fail;
+ }
+ }
+
+ if (minor <= 4) {
+ GSList *migration_dirs, *l;
+ gchar *path, *local_cal_folder;
+
+ setup_progress_dialog ();
+
+ path = g_build_filename (g_get_home_dir (), "evolution", "local", NULL);
+ migration_dirs = e_folder_map_local_folders (path, "calendar");
+ local_cal_folder = g_build_filename (path, "Calendar", NULL);
+ g_free (path);
+
+ if (personal_source)
+ migrate_ical_folder_to_source (local_cal_folder, personal_source, E_CAL_SOURCE_TYPE_EVENT);
+
+ for (l = migration_dirs; l; l = l->next) {
+ gchar *source_name;
+
+ if (personal_source && !strcmp ((gchar *)l->data, local_cal_folder))
+ continue;
+
+ source_name = get_source_name (on_this_computer, (gchar *)l->data);
+
+ if (!migrate_ical_folder (l->data, on_this_computer, source_name, E_CAL_SOURCE_TYPE_EVENT)) {
+ /* FIXME: domain/code */
+ g_set_error(error, 0, 0, _("Unable to migrate calendar `%s'"), source_name);
+ g_free(source_name);
+ goto fail;
+ }
+
+ g_free (source_name);
+ }
+
+ g_free (local_cal_folder);
+
+ dialog_close ();
+ }
+
+ if (minor <= 4 || (minor == 5 && micro < 5)) {
+ GConfClient *gconf;
+ GConfValue *gconf_val;
+ gint i;
+ const gchar *keys[] = {
+ CALENDAR_CONFIG_HPANE_POS,
+ CALENDAR_CONFIG_VPANE_POS,
+ CALENDAR_CONFIG_MONTH_HPANE_POS,
+ CALENDAR_CONFIG_MONTH_VPANE_POS,
+ NULL
+ };
+
+ gconf = gconf_client_get_default ();
+
+ for (i = 0; keys[i]; i++) {
+ gconf_val = gconf_client_get (gconf, keys[i], NULL);
+ if (gconf_val) {
+ if (gconf_val->type != GCONF_VALUE_INT)
+ gconf_client_unset (gconf, keys[i], NULL);
+ gconf_value_free (gconf_val);
+ }
+ }
+
+ g_object_unref (gconf);
+ }
+
+ if (minor < 5 || (minor == 5 && micro <= 10)) {
+ gchar *old_path, *new_path;
+
+ old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Calendar", NULL);
+ new_path = g_build_filename (e_shell_backend_get_config_dir (shell_backend),
+ "local", "system", NULL);
+ migrate_pilot_data ("calendar", "calendar", 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 (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);
+ }
+ }
+ }
+
+ }
+#endif /* !G_OS_WIN32 */
+
+ e_source_list_sync (source_list, NULL);
+
+ /** @Event: component.migration
+ * @Title: Migration step in component initialization
+ * @Target: ECalEventTargetComponent
+ *
+ * component.migration is emitted during the calendar component
+ * initialization process. This allows new calendar backend types
+ * to be distributed as an e-d-s backend and a plugin without
+ * reaching their grubby little fingers into migration.c
+ */
+ /* Fire off migration event */
+ ece = e_cal_event_peek ();
+ target = e_cal_event_target_new_module (ece, shell_backend, source_list, 0);
+ e_event_emit ((EEvent *) ece, "module.migration", (EEventTarget *) target);
+
+ retval = TRUE;
+fail:
+ if (on_this_computer)
+ g_object_unref (on_this_computer);
+ if (on_the_web)
+ g_object_unref (on_the_web);
+ if (contacts)
+ g_object_unref (contacts);
+ if (personal_source)
+ g_object_unref (personal_source);
+
+ return retval;
+}
+
diff --git a/modules/calendar/e-cal-shell-migrate.h b/modules/calendar/e-cal-shell-migrate.h
new file mode 100644
index 0000000000..359ca5ea22
--- /dev/null
+++ b/modules/calendar/e-cal-shell-migrate.h
@@ -0,0 +1,38 @@
+/*
+ * e-cal-shell-backend-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_CAL_SHELL_BACKEND_MIGRATE_H
+#define E_CAL_SHELL_BACKEND_MIGRATE_H
+
+#include <glib.h>
+#include <shell/e-shell-backend.h>
+
+G_BEGIN_DECLS
+
+gboolean e_cal_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_BACKEND_MIGRATE_H */
diff --git a/modules/calendar/e-cal-shell-settings.c b/modules/calendar/e-cal-shell-settings.c
new file mode 100644
index 0000000000..4e6939c892
--- /dev/null
+++ b/modules/calendar/e-cal-shell-settings.c
@@ -0,0 +1,742 @@
+/*
+ * e-cal-shell-settings.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-cal-shell-settings.h"
+
+#include <gconf/gconf-client.h>
+#include <libecal/e-cal-util.h>
+
+#include "e-util/e-binding.h"
+
+static gboolean
+transform_string_to_icaltimezone (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gboolean use_system_timezone;
+ const gchar *location = NULL;
+ icaltimezone *timezone = NULL;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ use_system_timezone = e_shell_settings_get_boolean (
+ shell_settings, "cal-use-system-timezone");
+
+ if (use_system_timezone)
+ timezone = e_cal_util_get_system_timezone ();
+ else
+ location = g_value_get_string (src_value);
+
+ if (location != NULL && *location != '\0')
+ timezone = icaltimezone_get_builtin_timezone (location);
+
+ if (timezone == NULL)
+ timezone = icaltimezone_get_utc_timezone ();
+
+ g_value_set_pointer (dst_value, timezone);
+
+ return TRUE;
+}
+
+static gboolean
+transform_icaltimezone_to_string (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ const gchar *location = NULL;
+ icaltimezone *timezone;
+
+ timezone = g_value_get_pointer (src_value);
+
+ if (timezone != NULL)
+ location = icaltimezone_get_location (timezone);
+
+ if (location == NULL)
+ location = "UTC";
+
+ g_value_set_string (dst_value, location);
+
+ return TRUE;
+}
+
+static gboolean
+transform_weekdays_gconf_to_evolution (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ GDateWeekday weekday;
+
+ /* XXX At some point, Evolution changed its weekday numbering
+ * from 0 = Sunday to 0 = Monday, but did not migrate the
+ * "week_start_day" key. Both enumerations are of course
+ * different from GDateWeekday. We should have saved the
+ * weekday as a string instead. */
+
+ /* This is purposefully verbose for better readability. */
+
+ /* GConf numbering */
+ switch (g_value_get_int (src_value)) {
+ case 0:
+ weekday = G_DATE_SUNDAY;
+ break;
+ case 1:
+ weekday = G_DATE_MONDAY;
+ break;
+ case 2:
+ weekday = G_DATE_TUESDAY;
+ break;
+ case 3:
+ weekday = G_DATE_WEDNESDAY;
+ break;
+ case 4:
+ weekday = G_DATE_THURSDAY;
+ break;
+ case 5:
+ weekday = G_DATE_FRIDAY;
+ break;
+ case 6:
+ weekday = G_DATE_SATURDAY;
+ break;
+ default:
+ return FALSE;
+ }
+
+ /* Evolution numbering */
+ switch (weekday) {
+ case G_DATE_MONDAY:
+ g_value_set_int (dst_value, 0);
+ break;
+ case G_DATE_TUESDAY:
+ g_value_set_int (dst_value, 1);
+ break;
+ case G_DATE_WEDNESDAY:
+ g_value_set_int (dst_value, 2);
+ break;
+ case G_DATE_THURSDAY:
+ g_value_set_int (dst_value, 3);
+ break;
+ case G_DATE_FRIDAY:
+ g_value_set_int (dst_value, 4);
+ break;
+ case G_DATE_SATURDAY:
+ g_value_set_int (dst_value, 5);
+ break;
+ case G_DATE_SUNDAY:
+ g_value_set_int (dst_value, 6);
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+transform_weekdays_evolution_to_gconf (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ GDateWeekday weekday;
+
+ /* XXX At some point, Evolution changed its weekday numbering
+ * from 0 = Sunday to 0 = Monday, but did not migrate the
+ * "week_start_day" key. Both enumerations are of course
+ * different from GDateWeekday. We should have saved the
+ * weekday as a string instead. */
+
+ /* This is purposefully verbose for better readability. */
+
+ /* GConf numbering */
+ switch (g_value_get_int (src_value)) {
+ case 0:
+ weekday = G_DATE_MONDAY;
+ break;
+ case 1:
+ weekday = G_DATE_TUESDAY;
+ break;
+ case 2:
+ weekday = G_DATE_WEDNESDAY;
+ break;
+ case 3:
+ weekday = G_DATE_THURSDAY;
+ break;
+ case 4:
+ weekday = G_DATE_FRIDAY;
+ break;
+ case 5:
+ weekday = G_DATE_SATURDAY;
+ break;
+ case 6:
+ weekday = G_DATE_SUNDAY;
+ break;
+ default:
+ return FALSE;
+ }
+
+ /* Evolution numbering */
+ switch (weekday) {
+ case G_DATE_MONDAY:
+ g_value_set_int (dst_value, 1);
+ break;
+ case G_DATE_TUESDAY:
+ g_value_set_int (dst_value, 2);
+ break;
+ case G_DATE_WEDNESDAY:
+ g_value_set_int (dst_value, 3);
+ break;
+ case G_DATE_THURSDAY:
+ g_value_set_int (dst_value, 4);
+ break;
+ case G_DATE_FRIDAY:
+ g_value_set_int (dst_value, 5);
+ break;
+ case G_DATE_SATURDAY:
+ g_value_set_int (dst_value, 6);
+ break;
+ case G_DATE_SUNDAY:
+ g_value_set_int (dst_value, 0);
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Working day flags */
+enum {
+ WORKING_DAY_SUNDAY = 1 << 0,
+ WORKING_DAY_MONDAY = 1 << 1,
+ WORKING_DAY_TUESDAY = 1 << 2,
+ WORKING_DAY_WEDNESDAY = 1 << 3,
+ WORKING_DAY_THURSDAY = 1 << 4,
+ WORKING_DAY_FRIDAY = 1 << 5,
+ WORKING_DAY_SATURDAY = 1 << 6
+};
+
+static gboolean
+transform_working_days_bitset_to_sunday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_SUNDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_sunday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_SUNDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_SUNDAY) | bit);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_bitset_to_monday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_MONDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_monday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_MONDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_MONDAY) | bit);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_bitset_to_tuesday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_TUESDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_tuesday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_TUESDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_TUESDAY) | bit);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_bitset_to_wednesday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_WEDNESDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_wednesday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_WEDNESDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_WEDNESDAY) | bit);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_bitset_to_thursday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_THURSDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_thursday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_THURSDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_THURSDAY) | bit);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_bitset_to_friday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_FRIDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_friday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_FRIDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_FRIDAY) | bit);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_bitset_to_saturday (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gint bitset;
+ gboolean working_day;
+
+ bitset = g_value_get_int (src_value);
+ working_day = ((bitset & WORKING_DAY_SATURDAY) != 0);
+ g_value_set_boolean (dst_value, working_day);
+
+ return TRUE;
+}
+
+static gboolean
+transform_working_days_saturday_to_bitset (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ EShellSettings *shell_settings;
+ gint bitset, bit;
+
+ shell_settings = E_SHELL_SETTINGS (user_data);
+
+ bitset = e_shell_settings_get_int (
+ shell_settings, "cal-working-days-bitset");
+
+ bit = g_value_get_boolean (src_value) ? WORKING_DAY_SATURDAY : 0;
+ g_value_set_int (dst_value, (bitset & ~WORKING_DAY_SATURDAY) | bit);
+
+ return TRUE;
+}
+
+void
+e_cal_shell_backend_init_settings (EShell *shell)
+{
+ EShellSettings *shell_settings;
+
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ e_shell_settings_install_property_for_key (
+ "cal-compress-weekend",
+ "/apps/evolution/calendar/display/compress_weekend");
+
+ e_shell_settings_install_property_for_key (
+ "cal-confirm-delete",
+ "/apps/evolution/calendar/prompts/confirm_delete");
+
+ e_shell_settings_install_property_for_key (
+ "cal-confirm-purge",
+ "/apps/evolution/calendar/prompts/confirm_purge");
+
+ e_shell_settings_install_property_for_key (
+ "cal-day-view-show-week-numbers",
+ "/apps/evolution/calendar/display/day_view_show_week_number");
+
+ e_shell_settings_install_property_for_key (
+ "cal-free-busy-template",
+ "/apps/evolution/calendar/publish/template");
+
+ e_shell_settings_install_property_for_key (
+ "cal-hide-completed-tasks",
+ "/apps/evolution/calendar/tasks/hide_completed");
+
+ e_shell_settings_install_property_for_key (
+ "cal-hide-completed-tasks-units",
+ "/apps/evolution/calendar/tasks/hide_completed_units");
+
+ e_shell_settings_install_property_for_key (
+ "cal-hide-completed-tasks-value",
+ "/apps/evolution/calendar/tasks/hide_completed_value");
+
+ e_shell_settings_install_property_for_key (
+ "cal-marcus-bains-day-view-color",
+ "/apps/evolution/calendar/display/marcus_bains_color_dayview");
+
+ e_shell_settings_install_property_for_key (
+ "cal-marcus-bains-time-bar-color",
+ "/apps/evolution/calendar/display/marcus_bains_color_timebar");
+
+ e_shell_settings_install_property_for_key (
+ "cal-marcus-bains-show-line",
+ "/apps/evolution/calendar/display/marcus_bains_line");
+
+ e_shell_settings_install_property_for_key (
+ "cal-primary-calendar",
+ "/apps/evolution/calendar/display/primary_calendar");
+
+ e_shell_settings_install_property_for_key (
+ "cal-primary-memo-list",
+ "/apps/evolution/calendar/memos/primary_memos");
+
+ e_shell_settings_install_property_for_key (
+ "cal-primary-task-list",
+ "/apps/evolution/calendar/tasks/primary_tasks");
+
+ e_shell_settings_install_property_for_key (
+ "cal-show-event-end-times",
+ "/apps/evolution/calendar/display/show_event_end");
+
+ e_shell_settings_install_property_for_key (
+ "cal-show-week-numbers",
+ "/apps/evolution/calendar/date_navigator/show_week_numbers");
+
+ e_shell_settings_install_property_for_key (
+ "cal-tasks-color-due-today",
+ "/apps/evolution/calendar/tasks/colors/due_today");
+
+ e_shell_settings_install_property_for_key (
+ "cal-tasks-color-overdue",
+ "/apps/evolution/calendar/tasks/colors/overdue");
+
+ e_shell_settings_install_property_for_key (
+ "cal-time-divisions",
+ "/apps/evolution/calendar/display/time_divisions");
+
+ /* Do not bind to this. Use "cal-timezone" instead. */
+ e_shell_settings_install_property_for_key (
+ "cal-timezone-string",
+ "/apps/evolution/calendar/display/timezone");
+
+ e_shell_settings_install_property_for_key (
+ "cal-use-24-hour-format",
+ "/apps/evolution/calendar/display/use_24hour_format");
+
+ e_shell_settings_install_property_for_key (
+ "cal-use-system-timezone",
+ "/apps/evolution/calendar/display/use_system_timezone");
+
+ /* Do not bind to this. Use "cal-week-start-day" instead. */
+ e_shell_settings_install_property_for_key (
+ "cal-week-start-day-gconf",
+ "/apps/evolution/calendar/display/week_start_day");
+
+ e_shell_settings_install_property_for_key (
+ "cal-work-day-end-hour",
+ "/apps/evolution/calendar/display/day_end_hour");
+
+ e_shell_settings_install_property_for_key (
+ "cal-work-day-end-minute",
+ "/apps/evolution/calendar/display/day_end_minute");
+
+ e_shell_settings_install_property_for_key (
+ "cal-work-day-start-hour",
+ "/apps/evolution/calendar/display/day_start_hour");
+
+ e_shell_settings_install_property_for_key (
+ "cal-work-day-start-minute",
+ "/apps/evolution/calendar/display/day_start_minute");
+
+ e_shell_settings_install_property_for_key (
+ "cal-working-days-bitset",
+ "/apps/evolution/calendar/display/working_days");
+
+ /* These properties use transform functions to convert
+ * GConf values to forms more useful to Evolution. We
+ * have to use separate properties because GConfBridge
+ * does not support transform functions. Much of this
+ * is backward-compatibility cruft for poorly designed
+ * GConf schemas. */
+
+ e_shell_settings_install_property (
+ g_param_spec_pointer (
+ "cal-timezone",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-timezone-string",
+ G_OBJECT (shell_settings), "cal-timezone",
+ transform_string_to_icaltimezone,
+ transform_icaltimezone_to_string,
+ (GDestroyNotify) g_object_unref,
+ g_object_ref (shell_settings));
+
+ e_shell_settings_install_property (
+ g_param_spec_int (
+ "cal-week-start-day",
+ NULL,
+ NULL,
+ 0, /* Monday */
+ 6, /* Sunday */
+ 0,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-week-start-day-gconf",
+ G_OBJECT (shell_settings), "cal-week-start-day",
+ transform_weekdays_gconf_to_evolution,
+ transform_weekdays_evolution_to_gconf,
+ (GDestroyNotify) NULL, NULL);
+
+ /* XXX These are my favorite. Storing a bit array in GConf
+ * instead of separate boolean keys. Brilliant move. */
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-sunday",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-sunday",
+ transform_working_days_bitset_to_sunday,
+ transform_working_days_sunday_to_bitset,
+ (GDestroyNotify) NULL, shell_settings);
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-monday",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-monday",
+ transform_working_days_bitset_to_monday,
+ transform_working_days_monday_to_bitset,
+ (GDestroyNotify) NULL, shell_settings);
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-tuesday",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-tuesday",
+ transform_working_days_bitset_to_tuesday,
+ transform_working_days_tuesday_to_bitset,
+ (GDestroyNotify) NULL, shell_settings);
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-wednesday",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-wednesday",
+ transform_working_days_bitset_to_wednesday,
+ transform_working_days_wednesday_to_bitset,
+ (GDestroyNotify) NULL, shell_settings);
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-thursday",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-thursday",
+ transform_working_days_bitset_to_thursday,
+ transform_working_days_thursday_to_bitset,
+ (GDestroyNotify) NULL, shell_settings);
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-friday",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-friday",
+ transform_working_days_bitset_to_friday,
+ transform_working_days_friday_to_bitset,
+ (GDestroyNotify) NULL, shell_settings);
+
+ e_shell_settings_install_property (
+ g_param_spec_boolean (
+ "cal-working-days-saturday",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "cal-working-days-bitset",
+ G_OBJECT (shell_settings), "cal-working-days-saturday",
+ transform_working_days_bitset_to_saturday,
+ transform_working_days_saturday_to_bitset,
+ (GDestroyNotify) g_object_unref,
+ g_object_ref (shell_settings));
+}
diff --git a/modules/calendar/e-cal-shell-settings.h b/modules/calendar/e-cal-shell-settings.h
new file mode 100644
index 0000000000..5f7293bbed
--- /dev/null
+++ b/modules/calendar/e-cal-shell-settings.h
@@ -0,0 +1,33 @@
+/*
+ * e-cal-shell-settings.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_CAL_SHELL_SETTINGS_H
+#define E_CAL_SHELL_SETTINGS_H
+
+#include <shell/e-shell.h>
+
+G_BEGIN_DECLS
+
+void e_cal_shell_backend_init_settings (EShell *shell);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_SETTINGS_H */
diff --git a/modules/calendar/e-cal-shell-sidebar.c b/modules/calendar/e-cal-shell-sidebar.c
new file mode 100644
index 0000000000..05b3a47d72
--- /dev/null
+++ b/modules/calendar/e-cal-shell-sidebar.c
@@ -0,0 +1,757 @@
+/*
+ * e-cal-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-cal-shell-sidebar.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "e-util/e-error.h"
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/misc/e-paned.h"
+
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/e-calendar-selector.h"
+#include "calendar/gui/misc.h"
+
+#include "e-cal-shell-backend.h"
+#include "e-cal-shell-view.h"
+
+#define E_CAL_SHELL_SIDEBAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarPrivate))
+
+struct _ECalShellSidebarPrivate {
+ GtkWidget *paned;
+ GtkWidget *selector;
+ GtkWidget *date_navigator;
+
+ /* UID -> Client */
+ GHashTable *client_table;
+};
+
+enum {
+ PROP_0,
+ PROP_DATE_NAVIGATOR,
+ PROP_SELECTOR
+};
+
+enum {
+ CLIENT_ADDED,
+ CLIENT_REMOVED,
+ STATUS_MESSAGE,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+static GType cal_shell_sidebar_type;
+
+static void
+cal_shell_sidebar_emit_client_added (ECalShellSidebar *cal_shell_sidebar,
+ ECal *client)
+{
+ guint signal_id = signals[CLIENT_ADDED];
+
+ g_signal_emit (cal_shell_sidebar, signal_id, 0, client);
+}
+
+static void
+cal_shell_sidebar_emit_client_removed (ECalShellSidebar *cal_shell_sidebar,
+ ECal *client)
+{
+ guint signal_id = signals[CLIENT_REMOVED];
+
+ g_signal_emit (cal_shell_sidebar, signal_id, 0, client);
+}
+
+static void
+cal_shell_sidebar_emit_status_message (ECalShellSidebar *cal_shell_sidebar,
+ const gchar *status_message)
+{
+ guint signal_id = signals[STATUS_MESSAGE];
+
+ g_signal_emit (cal_shell_sidebar, signal_id, 0, status_message);
+}
+
+static void
+cal_shell_sidebar_backend_died_cb (ECalShellSidebar *cal_shell_sidebar,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ GHashTable *client_table;
+ ESource *source;
+ const gchar *uid;
+
+ client_table = cal_shell_sidebar->priv->client_table;
+
+ shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ source = e_cal_get_source (client);
+ uid = e_source_peek_uid (source);
+
+ g_object_ref (source);
+
+ g_hash_table_remove (client_table, uid);
+ cal_shell_sidebar_emit_status_message (cal_shell_sidebar, NULL);
+
+ e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:calendar-crashed", NULL);
+
+ g_object_unref (source);
+}
+
+static void
+cal_shell_sidebar_backend_error_cb (ECalShellSidebar *cal_shell_sidebar,
+ const gchar *message,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ GtkWidget *dialog;
+ const gchar *uri;
+ gchar *uri_no_passwd;
+
+ shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ uri = e_cal_get_uri (client);
+ uri_no_passwd = get_uri_without_password (uri);
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (shell_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Error on %s\n%s"),
+ uri_no_passwd, message);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_free (uri_no_passwd);
+}
+
+static void
+cal_shell_sidebar_client_opened_cb (ECalShellSidebar *cal_shell_sidebar,
+ ECalendarStatus status,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ ESource *source;
+
+ source = e_cal_get_source (client);
+
+ shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ if (status == E_CALENDAR_STATUS_AUTHENTICATION_FAILED ||
+ status == E_CALENDAR_STATUS_AUTHENTICATION_REQUIRED)
+ auth_cal_forget_password (client);
+
+ switch (status) {
+ case E_CALENDAR_STATUS_OK:
+ g_signal_handlers_disconnect_matched (
+ client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+ cal_shell_sidebar_client_opened_cb, NULL);
+
+ cal_shell_sidebar_emit_status_message (
+ cal_shell_sidebar, _("Loading calendars"));
+ cal_shell_sidebar_emit_client_added (
+ cal_shell_sidebar, client);
+ cal_shell_sidebar_emit_status_message (
+ cal_shell_sidebar, NULL);
+ break;
+
+ case E_CALENDAR_STATUS_AUTHENTICATION_FAILED:
+ e_cal_open_async (client, FALSE);
+ break;
+
+ case E_CALENDAR_STATUS_BUSY:
+ break;
+
+ case E_CALENDAR_STATUS_REPOSITORY_OFFLINE:
+ e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:prompt-no-contents-offline-calendar",
+ NULL);
+ break;
+
+ default:
+ cal_shell_sidebar_emit_client_removed (
+ cal_shell_sidebar, client);
+ break;
+ }
+}
+
+static void
+cal_shell_sidebar_row_changed_cb (ECalShellSidebar *cal_shell_sidebar,
+ GtkTreePath *tree_path,
+ GtkTreeIter *tree_iter,
+ GtkTreeModel *tree_model)
+{
+ ESourceSelector *selector;
+ ESource *source;
+
+ /* XXX ESourceSelector's underlying tree store has only one
+ * column: ESource objects. While we're not supposed to
+ * know this, listening for "row-changed" signals from
+ * the model is easier to deal with than the selector's
+ * "selection-changed" signal, which doesn't tell you
+ * _which_ row changed. */
+
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+ gtk_tree_model_get (tree_model, tree_iter, 0, &source, -1);
+
+ /* XXX This signal gets emitted a lot while the model is being
+ * rebuilt, during which time we won't get a valid ESource.
+ * ESourceSelector should probably block this signal while
+ * rebuilding the model, but we'll be forgiving and not
+ * emit a warning. */
+ if (!E_IS_SOURCE (source))
+ return;
+
+ if (e_source_selector_source_is_selected (selector, source))
+ e_cal_shell_sidebar_add_source (cal_shell_sidebar, source);
+ else
+ e_cal_shell_sidebar_remove_source (cal_shell_sidebar, source);
+}
+
+static void
+cal_shell_sidebar_selection_changed_cb (ECalShellSidebar *cal_shell_sidebar,
+ ESourceSelector *selector)
+{
+ GSList *list, *iter;
+
+ /* This signal is emitted less frequently than "row-changed",
+ * especially when the model is being rebuilt. So we'll take
+ * it easy on poor GConf. */
+
+ list = e_source_selector_get_selection (selector);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ESource *source = iter->data;
+
+ iter->data = (gpointer) e_source_peek_uid (source);
+ g_object_unref (source);
+ }
+
+ calendar_config_set_calendars_selected (list);
+
+ g_slist_free (list);
+}
+
+static void
+cal_shell_sidebar_primary_selection_changed_cb (ECalShellSidebar *cal_shell_sidebar,
+ ESourceSelector *selector)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ EShellSettings *shell_settings;
+ ESource *source;
+
+ /* XXX ESourceSelector needs a "primary-selection-uid" property
+ * so we can just bind the property with GConfBridge. */
+
+ source = e_source_selector_peek_primary_selection (selector);
+ if (source == NULL)
+ return;
+
+ shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-calendar",
+ e_source_peek_uid (source));
+}
+
+static void
+cal_shell_sidebar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DATE_NAVIGATOR:
+ g_value_set_object (
+ value, e_cal_shell_sidebar_get_date_navigator (
+ E_CAL_SHELL_SIDEBAR (object)));
+ return;
+
+ case PROP_SELECTOR:
+ g_value_set_object (
+ value, e_cal_shell_sidebar_get_selector (
+ E_CAL_SHELL_SIDEBAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_shell_sidebar_dispose (GObject *object)
+{
+ ECalShellSidebarPrivate *priv;
+
+ priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ if (priv->paned != NULL) {
+ g_object_unref (priv->paned);
+ priv->paned = NULL;
+ }
+
+ if (priv->selector != NULL) {
+ g_object_unref (priv->selector);
+ priv->selector = NULL;
+ }
+
+ if (priv->date_navigator != NULL) {
+ g_object_unref (priv->date_navigator);
+ priv->date_navigator = NULL;
+ }
+
+ g_hash_table_remove_all (priv->client_table);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+cal_shell_sidebar_finalize (GObject *object)
+{
+ ECalShellSidebarPrivate *priv;
+
+ priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->client_table);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+cal_shell_sidebar_constructed (GObject *object)
+{
+ ECalShellSidebarPrivate *priv;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellSidebar *shell_sidebar;
+ EShellSettings *shell_settings;
+ ESourceSelector *selector;
+ ESourceList *source_list;
+ ESource *source;
+ ECalendarItem *calitem;
+ GConfBridge *bridge;
+ GtkTreeModel *model;
+ GtkWidget *container;
+ GtkWidget *widget;
+ AtkObject *a11y;
+ GSList *list, *iter;
+ const gchar *key;
+ gchar *uid;
+
+ priv = E_CAL_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);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ source_list = e_cal_shell_backend_get_source_list (
+ E_CAL_SHELL_BACKEND (shell_backend));
+
+ container = GTK_WIDGET (shell_sidebar);
+
+ widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->paned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = 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_pack1 (GTK_PANED (container), widget, TRUE, TRUE);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_calendar_selector_new (source_list);
+ e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ a11y = gtk_widget_get_accessible (widget);
+ atk_object_set_name (a11y, _("Calendar Selector"));
+ priv->selector = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = priv->paned;
+
+ widget = e_calendar_new ();
+ calitem = E_CALENDAR (widget)->calitem;
+ e_calendar_item_set_days_start_week_sel (calitem, 9);
+ e_calendar_item_set_max_days_sel (calitem, 42);
+ gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, TRUE);
+ priv->date_navigator = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "cal-show-week-numbers",
+ G_OBJECT (calitem), "show-week-numbers");
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "cal-week-start-day",
+ G_OBJECT (calitem), "week-start-day");
+
+ /* Restore the selector state from the last session. */
+
+ selector = E_SOURCE_SELECTOR (priv->selector);
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+
+ g_signal_connect_swapped (
+ model, "row-changed",
+ G_CALLBACK (cal_shell_sidebar_row_changed_cb),
+ object);
+
+ source = NULL;
+ uid = e_shell_settings_get_string (
+ shell_settings, "cal-primary-calendar");
+ if (uid != NULL)
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ if (source == NULL)
+ source = e_source_list_peek_source_any (source_list);
+ if (source != NULL)
+ e_source_selector_set_primary_selection (selector, source);
+ g_free (uid);
+
+ list = calendar_config_get_calendars_selected ();
+ for (iter = list; iter != NULL; iter = iter->next) {
+ uid = iter->data;
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ g_free (uid);
+
+ if (source == NULL)
+ continue;
+
+ e_source_selector_select_source (selector, source);
+ }
+ g_slist_free (list);
+
+ /* Listen for subsequent changes to the selector. */
+
+ g_signal_connect_swapped (
+ selector, "selection-changed",
+ G_CALLBACK (cal_shell_sidebar_selection_changed_cb),
+ object);
+
+ g_signal_connect_swapped (
+ selector, "primary-selection-changed",
+ G_CALLBACK (cal_shell_sidebar_primary_selection_changed_cb),
+ object);
+
+ /* Bind GObject properties to GConf keys. */
+
+ bridge = gconf_bridge_get ();
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/calendar/display/date_navigator_vpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "vposition");
+}
+
+static void
+cal_shell_sidebar_client_removed (ECalShellSidebar *cal_shell_sidebar,
+ ECal *client)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ESource *source;
+ const gchar *uid;
+
+ client_table = cal_shell_sidebar->priv->client_table;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+
+ g_signal_handlers_disconnect_matched (
+ client, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, cal_shell_sidebar);
+
+ source = e_cal_get_source (client);
+ e_source_selector_unselect_source (selector, source);
+
+ uid = e_source_peek_uid (source);
+ g_hash_table_remove (client_table, uid);
+
+ cal_shell_sidebar_emit_status_message (cal_shell_sidebar, NULL);
+}
+
+static void
+cal_shell_sidebar_class_init (ECalShellSidebarClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ECalShellSidebarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = cal_shell_sidebar_get_property;
+ object_class->dispose = cal_shell_sidebar_dispose;
+ object_class->finalize = cal_shell_sidebar_finalize;
+ object_class->constructed = cal_shell_sidebar_constructed;
+
+ class->client_removed = cal_shell_sidebar_client_removed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DATE_NAVIGATOR,
+ g_param_spec_object (
+ "date-navigator",
+ _("Date Navigator Widget"),
+ _("This widget displays a miniature calendar"),
+ E_TYPE_CALENDAR,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SELECTOR,
+ g_param_spec_object (
+ "selector",
+ _("Source Selector Widget"),
+ _("This widget displays groups of calendars"),
+ E_TYPE_SOURCE_SELECTOR,
+ G_PARAM_READABLE));
+
+ signals[CLIENT_ADDED] = g_signal_new (
+ "client-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ECalShellSidebarClass, client_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_CAL);
+
+ signals[CLIENT_REMOVED] = g_signal_new (
+ "client-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ECalShellSidebarClass, client_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_CAL);
+
+ signals[STATUS_MESSAGE] = g_signal_new (
+ "status-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ECalShellSidebarClass, status_message),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+}
+
+static void
+cal_shell_sidebar_init (ECalShellSidebar *cal_shell_sidebar)
+{
+ GHashTable *client_table;
+
+ client_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ cal_shell_sidebar->priv =
+ E_CAL_SHELL_SIDEBAR_GET_PRIVATE (cal_shell_sidebar);
+
+ cal_shell_sidebar->priv->client_table = client_table;
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_cal_shell_sidebar_get_type (void)
+{
+ return cal_shell_sidebar_type;
+}
+
+void
+e_cal_shell_sidebar_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (ECalShellSidebarClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_shell_sidebar_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ECalShellSidebar),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) cal_shell_sidebar_init,
+ NULL /* value_table */
+ };
+
+ cal_shell_sidebar_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_SIDEBAR,
+ "ECalShellSidebar", &type_info, 0);
+}
+
+GtkWidget *
+e_cal_shell_sidebar_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_CAL_SHELL_SIDEBAR,
+ "shell-view", shell_view, NULL);
+}
+
+GList *
+e_cal_shell_sidebar_get_clients (ECalShellSidebar *cal_shell_sidebar)
+{
+ GHashTable *client_table;
+
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
+
+ client_table = cal_shell_sidebar->priv->client_table;
+
+ return g_hash_table_get_values (client_table);
+}
+
+ECalendar *
+e_cal_shell_sidebar_get_date_navigator (ECalShellSidebar *cal_shell_sidebar)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
+
+ return E_CALENDAR (cal_shell_sidebar->priv->date_navigator);
+}
+
+ESourceSelector *
+e_cal_shell_sidebar_get_selector (ECalShellSidebar *cal_shell_sidebar)
+{
+ g_return_val_if_fail (
+ E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
+
+ return E_SOURCE_SELECTOR (cal_shell_sidebar->priv->selector);
+}
+
+void
+e_cal_shell_sidebar_add_source (ECalShellSidebar *cal_shell_sidebar,
+ ESource *source)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ECal *client;
+ const gchar *uid;
+ const gchar *uri;
+ gchar *message;
+
+ g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ client_table = cal_shell_sidebar->priv->client_table;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+
+ uid = e_source_peek_uid (source);
+ client = g_hash_table_lookup (client_table, uid);
+
+ if (client != NULL)
+ return;
+
+ client = auth_new_cal_from_source (source, E_CAL_SOURCE_TYPE_EVENT);
+ g_return_if_fail (client != NULL);
+
+ g_signal_connect_swapped (
+ client, "backend-died",
+ G_CALLBACK (cal_shell_sidebar_backend_died_cb),
+ cal_shell_sidebar);
+
+ g_signal_connect_swapped (
+ client, "backend-error",
+ G_CALLBACK (cal_shell_sidebar_backend_error_cb),
+ cal_shell_sidebar);
+
+ g_hash_table_insert (client_table, g_strdup (uid), client);
+ e_source_selector_select_source (selector, source);
+
+ uri = e_cal_get_uri (client);
+ message = g_strdup_printf (_("Opening calendar at %s"), uri);
+ cal_shell_sidebar_emit_status_message (cal_shell_sidebar, message);
+ g_free (message);
+
+ g_signal_connect_swapped (
+ client, "cal-opened",
+ G_CALLBACK (cal_shell_sidebar_client_opened_cb),
+ cal_shell_sidebar);
+
+ e_cal_open_async (client, FALSE);
+}
+
+void
+e_cal_shell_sidebar_remove_source (ECalShellSidebar *cal_shell_sidebar,
+ ESource *source)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ECal *client;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ client_table = cal_shell_sidebar->priv->client_table;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+
+ uid = e_source_peek_uid (source);
+ client = g_hash_table_lookup (client_table, uid);
+
+ if (client == NULL)
+ return;
+
+ cal_shell_sidebar_emit_client_removed (cal_shell_sidebar, client);
+}
diff --git a/modules/calendar/e-cal-shell-sidebar.h b/modules/calendar/e-cal-shell-sidebar.h
new file mode 100644
index 0000000000..3b7c0fd3b3
--- /dev/null
+++ b/modules/calendar/e-cal-shell-sidebar.h
@@ -0,0 +1,101 @@
+/*
+ * e-cal-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_CAL_SHELL_SIDEBAR_H
+#define E_CAL_SHELL_SIDEBAR_H
+
+#include <libecal/e-cal.h>
+#include <libedataserverui/e-source-selector.h>
+
+#include <shell/e-shell-sidebar.h>
+#include <shell/e-shell-view.h>
+#include <misc/e-calendar.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_SHELL_SIDEBAR \
+ (e_cal_shell_sidebar_get_type ())
+#define E_CAL_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebar))
+#define E_CAL_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarClass))
+#define E_IS_CAL_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_SHELL_SIDEBAR))
+#define E_IS_CAL_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_SHELL_SIDEBAR))
+#define E_CAL_SHELL_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalShellSidebar ECalShellSidebar;
+typedef struct _ECalShellSidebarClass ECalShellSidebarClass;
+typedef struct _ECalShellSidebarPrivate ECalShellSidebarPrivate;
+
+enum {
+ E_CAL_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0,
+ E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_EMPTY = 1 << 1,
+ E_CAL_SHELL_SIDEBAR_SOURCE_CAN_GO_OFFLINE = 1 << 2,
+ E_CAL_SHELL_SIDEBAR_SOURCE_CAN_DELETE = 1 << 3
+};
+
+struct _ECalShellSidebar {
+ EShellSidebar parent;
+ ECalShellSidebarPrivate *priv;
+};
+
+struct _ECalShellSidebarClass {
+ EShellSidebarClass parent_class;
+
+ /* Signals */
+ void (*client_added) (ECalShellSidebar *cal_shell_sidebar,
+ ECal *client);
+ void (*client_removed) (ECalShellSidebar *cal_shell_sidebar,
+ ECal *client);
+ void (*status_message) (ECalShellSidebar *cal_shell_sidebar,
+ const gchar *status_message);
+};
+
+GType e_cal_shell_sidebar_get_type (void);
+void e_cal_shell_sidebar_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_cal_shell_sidebar_new (EShellView *shell_view);
+GList * e_cal_shell_sidebar_get_clients
+ (ECalShellSidebar *cal_shell_sidebar);
+ECalendar * e_cal_shell_sidebar_get_date_navigator
+ (ECalShellSidebar *cal_shell_sidebar);
+ESourceSelector *
+ e_cal_shell_sidebar_get_selector
+ (ECalShellSidebar *cal_shell_sidebar);
+void e_cal_shell_sidebar_add_source
+ (ECalShellSidebar *cal_shell_sidebar,
+ ESource *source);
+void e_cal_shell_sidebar_remove_source
+ (ECalShellSidebar *cal_shell_sidebar,
+ ESource *source);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_SIDEBAR_H */
diff --git a/modules/calendar/e-cal-shell-view-actions.c b/modules/calendar/e-cal-shell-view-actions.c
new file mode 100644
index 0000000000..b36c94b8d1
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view-actions.c
@@ -0,0 +1,1792 @@
+/*
+ * e-cal-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-cal-shell-view-private.h"
+
+static void
+action_calendar_copy_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellSidebar *cal_shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ESourceSelector *selector;
+ ESource *source;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ copy_source_dialog (
+ GTK_WINDOW (shell_window),
+ source, E_CAL_SOURCE_TYPE_EVENT);
+}
+
+static void
+action_calendar_delete_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalShellSidebar *cal_shell_sidebar;
+ EShellBackend *shell_backend;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ECalendarView *calendar_view;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalModel *model;
+ ECal *client;
+ ESourceSelector *selector;
+ ESourceGroup *source_group;
+ ESourceList *source_list;
+ ESource *source;
+ gint response;
+ gchar *uri;
+ GError *error = NULL;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+ model = e_calendar_view_get_model (calendar_view);
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ /* Ask for confirmation. */
+ response = e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:prompt-delete-calendar",
+ e_source_peek_name (source));
+ if (response != GTK_RESPONSE_YES)
+ return;
+
+ uri = e_source_get_uri (source);
+ client = e_cal_model_get_client_for_uri (model, uri);
+ if (client == NULL)
+ client = e_cal_new_from_uri (uri, E_CAL_SOURCE_TYPE_EVENT);
+ g_free (uri);
+
+ g_return_if_fail (client != NULL);
+
+ if (!e_cal_remove (client, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (e_source_selector_source_is_selected (selector, source)) {
+ e_cal_shell_sidebar_remove_source (
+ cal_shell_sidebar, source);
+ e_source_selector_unselect_source (selector, source);
+ }
+
+ source_group = e_source_peek_group (source);
+ e_source_group_remove_source (source_group, source);
+
+ source_list = e_cal_shell_backend_get_source_list (
+ E_CAL_SHELL_BACKEND (shell_backend));
+ if (!e_source_list_sync (source_list, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+action_calendar_go_back_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ gnome_calendar_previous (calendar);
+}
+
+static void
+action_calendar_go_forward_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ gnome_calendar_next (calendar);
+}
+
+static void
+action_calendar_go_today_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ gnome_calendar_goto_today (calendar);
+}
+
+static void
+action_calendar_jump_to_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ goto_dialog (calendar);
+}
+
+static void
+action_calendar_new_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ calendar_setup_new_calendar (GTK_WINDOW (shell_window));
+}
+
+static void
+action_calendar_print_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *view;
+ GtkPrintOperationAction print_action;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ view = gnome_calendar_get_calendar_view (calendar, view_type);
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+
+ if (E_IS_CAL_LIST_VIEW (view)) {
+ ECalListView *list_view;
+ ETable *table;
+
+ list_view = E_CAL_LIST_VIEW (view);
+ table = e_table_scrolled_get_table (list_view->table_scrolled);
+ print_table (table, _("Print"), _("Calendar"), print_action);
+ } else {
+ time_t start;
+
+ gnome_calendar_get_current_time_range (calendar, &start, NULL);
+ print_calendar (calendar, print_action, start);
+ }
+}
+
+static void
+action_calendar_print_preview_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *view;
+ GtkPrintOperationAction print_action;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ view = gnome_calendar_get_calendar_view (calendar, view_type);
+ print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW;
+
+ if (E_IS_CAL_LIST_VIEW (view)) {
+ ECalListView *list_view;
+ ETable *table;
+
+ list_view = E_CAL_LIST_VIEW (view);
+ table = e_table_scrolled_get_table (list_view->table_scrolled);
+ print_table (table, _("Print"), _("Calendar"), print_action);
+ } else {
+ time_t start;
+
+ gnome_calendar_get_current_time_range (calendar, &start, NULL);
+ print_calendar (calendar, print_action, start);
+ }
+}
+
+static void
+action_calendar_properties_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellSidebar *cal_shell_sidebar;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ESource *source;
+ ESourceSelector *selector;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ /* XXX Does this -really- need a source group parameter? */
+ calendar_setup_edit_calendar (
+ GTK_WINDOW (shell_window), source,
+ e_source_peek_group (source));
+}
+
+static void
+action_calendar_purge_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+ GtkSpinButton *spin_button;
+ GtkWidget *container;
+ GtkWidget *dialog;
+ GtkWidget *widget;
+ gint days;
+ time_t tt;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (shell_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_OK_CANCEL,
+ _("This operation will permanently erase all events older "
+ "than the selected amount of time. If you continue, you "
+ "will not be able to recover these events."));
+
+ gtk_dialog_set_default_response (
+ GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 6);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ /* Translators: This is the first part of the sentence:
+ * "Purge events older than <<spin-button>> days" */
+ widget = gtk_label_new (_("Purge events older than"));
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 6);
+ gtk_widget_show (widget);
+
+ widget = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 60.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 6);
+ gtk_widget_show (widget);
+
+ spin_button = GTK_SPIN_BUTTON (widget);
+
+ /* Translators: This is the last part of the sentence:
+ * "Purge events older than <<spin-button>> days" */
+ widget = gtk_label_new (_("days"));
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 6);
+ gtk_widget_show (widget);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
+ goto exit;
+
+ days = gtk_spin_button_get_value_as_int (spin_button);
+
+ tt = time (NULL);
+ tt -= (days * (24 * 3600));
+
+ gnome_calendar_purge (calendar, tt);
+
+exit:
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_calendar_rename_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellSidebar *cal_shell_sidebar;
+ ESourceSelector *selector;
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+
+ e_source_selector_edit_primary_selection (selector);
+}
+
+static void
+action_calendar_search_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ECalShellView *cal_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 (cal_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_calendar_select_one_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellSidebar *cal_shell_sidebar;
+ ESourceSelector *selector;
+ ESource *primary;
+ GSList *list, *iter;
+
+ /* XXX ESourceSelector should provide a function for this. */
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+ primary = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (primary != NULL);
+
+ list = e_source_selector_get_selection (selector);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ESource *source = iter->data;
+
+ if (source == primary)
+ continue;
+
+ e_source_selector_unselect_source (selector, source);
+ }
+ e_source_selector_free_selection (list);
+
+ e_source_selector_select_source (selector, primary);
+}
+
+static void
+action_calendar_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ GnomeCalendarViewType view_type;
+ const gchar *view_id;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ view_type = gtk_radio_action_get_current_value (action);
+
+ switch (view_type) {
+ case GNOME_CAL_DAY_VIEW:
+ view_id = "Day_View";
+ break;
+
+ case GNOME_CAL_WORK_WEEK_VIEW:
+ view_id = "Work_Week_View";
+ break;
+
+ case GNOME_CAL_WEEK_VIEW:
+ view_id = "Week_View";
+ break;
+
+ case GNOME_CAL_MONTH_VIEW:
+ view_id = "Month_View";
+ break;
+
+ case GNOME_CAL_LIST_VIEW:
+ view_id = "List_View";
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ e_shell_view_set_view_id (shell_view, view_id);
+}
+
+static void
+action_event_all_day_new_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ GnomeCalendar *calendar;
+
+ /* These are just for readability. */
+ gboolean all_day = TRUE;
+ gboolean meeting = FALSE;
+ gboolean no_past_date = FALSE;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ e_calendar_view_new_appointment_full (
+ calendar_view, all_day, meeting, no_past_date);
+}
+
+static void
+action_event_clipboard_copy_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ e_cal_shell_content_copy_clipboard (cal_shell_content);
+}
+
+static void
+action_event_clipboard_cut_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ e_cal_shell_content_cut_clipboard (cal_shell_content);
+}
+
+static void
+action_event_clipboard_paste_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ e_cal_shell_content_paste_clipboard (cal_shell_content);
+}
+
+static void
+action_event_copy_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *calendar_view;
+ ESource *destination_source = NULL;
+ ECal *destination_client = NULL;
+ GList *selected, *iter;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (selected != NULL);
+
+ /* Get a destination source from the user. */
+ destination_source = select_source_dialog (
+ GTK_WINDOW (shell_window), E_CAL_SOURCE_TYPE_EVENT);
+ if (destination_source == NULL)
+ return;
+
+ /* Open the destination calendar. */
+ destination_client = auth_new_cal_from_source (
+ destination_source, E_CAL_SOURCE_TYPE_EVENT);
+ if (destination_client == NULL)
+ goto exit;
+ if (!e_cal_open (destination_client, FALSE, NULL))
+ goto exit;
+
+ e_cal_shell_view_set_status_message (
+ cal_shell_view, _("Copying Items"), -1.0);
+
+ for (iter = selected; iter != NULL; iter = iter->next) {
+ ECalendarViewEvent *event = iter->data;
+ gboolean remove = FALSE;
+
+ e_cal_shell_view_transfer_item_to (
+ cal_shell_view, event, destination_client, remove);
+ }
+
+ e_cal_shell_view_set_status_message (cal_shell_view, NULL, -1.0);
+
+exit:
+ if (destination_client != NULL)
+ g_object_unref (destination_client);
+ if (destination_source != NULL)
+ g_object_unref (destination_source);
+ g_list_free (selected);
+}
+
+static void
+action_event_delegate_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *calendar_view;
+ ECalendarViewEvent *event;
+ ECalComponent *component;
+ ECal *client;
+ GList *selected;
+ icalcomponent *clone;
+ icalproperty *property;
+ gboolean found = FALSE;
+ gchar *attendee;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ clone = icalcomponent_new_clone (event->comp_data->icalcomp);
+
+ /* Set the attendee status for the delegate. */
+
+ component = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (
+ component, icalcomponent_new_clone (clone));
+
+ attendee = itip_get_comp_attendee (component, client);
+ property = icalcomponent_get_first_property (
+ clone, ICAL_ATTENDEE_PROPERTY);
+
+ while (property != NULL) {
+ const gchar *candidate;
+
+ candidate = icalproperty_get_attendee (property);
+ candidate = itip_strip_mailto (candidate);
+
+ if (g_ascii_strcasecmp (candidate, attendee) == 0) {
+ icalparameter *parameter;
+
+ parameter = icalparameter_new_role (
+ ICAL_ROLE_NONPARTICIPANT);
+ icalproperty_set_parameter (property, parameter);
+
+ parameter = icalparameter_new_partstat (
+ ICAL_PARTSTAT_DELEGATED);
+ icalproperty_set_parameter (property, parameter);
+
+ found = TRUE;
+ break;
+ }
+
+ property = icalcomponent_get_next_property (
+ clone, ICAL_ATTENDEE_PROPERTY);
+ }
+
+ /* If the attendee is not already in the component, add it. */
+ if (!found) {
+ icalparameter *parameter;
+ gchar *address;
+
+ address = g_strdup_printf ("MAILTO:%s", attendee);
+
+ property = icalproperty_new_attendee (address);
+ icalcomponent_add_property (clone, property);
+
+ parameter = icalparameter_new_role (ICAL_ROLE_NONPARTICIPANT);
+ icalproperty_add_parameter (property, parameter);
+
+ parameter = icalparameter_new_cutype (ICAL_CUTYPE_INDIVIDUAL);
+ icalproperty_add_parameter (property, parameter);
+
+ parameter = icalparameter_new_rsvp (ICAL_RSVP_TRUE);
+ icalproperty_add_parameter (property, parameter);
+
+ g_free (address);
+ }
+
+ g_free (attendee);
+ g_object_unref (component);
+
+ e_calendar_view_open_event_with_flags (
+ calendar_view, event->comp_data->client, clone,
+ COMP_EDITOR_MEETING | COMP_EDITOR_DELEGATE);
+
+ icalcomponent_free (clone);
+ g_list_free (selected);
+}
+
+static void
+action_event_delete_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ e_cal_shell_content_delete_selection (cal_shell_content);
+}
+
+static void
+action_event_delete_occurrence_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ e_cal_shell_content_delete_selected_occurrence (cal_shell_content);
+}
+
+static void
+action_event_delete_occurrence_all_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+
+ /* XXX Same as "event-delete". */
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ e_cal_shell_content_delete_selection (cal_shell_content);
+}
+
+static void
+action_event_forward_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ GnomeCalendar *calendar;
+ ECalendarViewEvent *event;
+ ECalComponent *component;
+ ECal *client;
+ icalcomponent *icalcomp;
+ GList *selected;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ component = e_cal_component_new ();
+
+ e_cal_component_set_icalcomponent (
+ component, icalcomponent_new_clone (icalcomp));
+ itip_send_comp (
+ E_CAL_COMPONENT_METHOD_PUBLISH,
+ component, client, NULL, NULL, NULL, TRUE, FALSE);
+
+ g_object_unref (component);
+
+ g_list_free (selected);
+}
+
+static void
+action_event_meeting_new_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ GnomeCalendar *calendar;
+
+ /* These are just for readability. */
+ gboolean all_day = FALSE;
+ gboolean meeting = TRUE;
+ gboolean no_past_date = FALSE;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ e_calendar_view_new_appointment_full (
+ calendar_view, all_day, meeting, no_past_date);
+}
+
+static void
+action_event_move_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *calendar_view;
+ ESource *destination_source = NULL;
+ ECal *destination_client = NULL;
+ GList *selected, *iter;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (selected != NULL);
+
+ /* Get a destination source from the user. */
+ destination_source = select_source_dialog (
+ GTK_WINDOW (shell_window), E_CAL_SOURCE_TYPE_EVENT);
+ if (destination_source == NULL)
+ return;
+
+ /* Open the destination calendar. */
+ destination_client = auth_new_cal_from_source (
+ destination_source, E_CAL_SOURCE_TYPE_EVENT);
+ if (destination_client == NULL)
+ goto exit;
+ if (!e_cal_open (destination_client, FALSE, NULL))
+ goto exit;
+
+ e_cal_shell_view_set_status_message (
+ cal_shell_view, _("Moving Items"), -1.0);
+
+ for (iter = selected; iter != NULL; iter = iter->next) {
+ ECalendarViewEvent *event = iter->data;
+ gboolean remove = TRUE;
+
+ e_cal_shell_view_transfer_item_to (
+ cal_shell_view, event, destination_client, remove);
+ }
+
+ e_cal_shell_view_set_status_message (cal_shell_view, NULL, -1.0);
+
+exit:
+ if (destination_client != NULL)
+ g_object_unref (destination_client);
+ if (destination_source != NULL)
+ g_object_unref (destination_source);
+ g_list_free (selected);
+}
+
+static void
+action_event_new_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ GnomeCalendar *calendar;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ e_calendar_view_new_appointment (calendar_view);
+}
+
+static void
+action_event_occurrence_movable_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalModel *model;
+ ECalendarView *calendar_view;
+ ECalendarViewEvent *event;
+ ECalComponent *exception_component;
+ ECalComponent *recurring_component;
+ ECalComponentDateTime date;
+ ECalComponentId *id;
+ ECal *client;
+ icalcomponent *icalcomp;
+ icaltimetype itt;
+ icaltimezone *timezone;
+ GList *selected;
+ gchar *uid;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ model = e_calendar_view_get_model (calendar_view);
+ timezone = e_cal_model_get_timezone (model);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ /* For the recurring object, we add an exception
+ * to get rid of the instance. */
+
+ recurring_component = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (
+ recurring_component, icalcomponent_new_clone (icalcomp));
+ id = e_cal_component_get_id (recurring_component);
+
+ /* For the unrecurred instance, we duplicate the original object,
+ * create a new UID for it, get rid of the recurrence rules, and
+ * set the start and end times to the instance times. */
+
+ exception_component = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (
+ exception_component, icalcomponent_new_clone (icalcomp));
+
+ uid = e_cal_component_gen_uid ();
+ e_cal_component_set_uid (exception_component, uid);
+ g_free (uid);
+
+ e_cal_component_set_recurid (exception_component, NULL);
+ e_cal_component_set_rdate_list (exception_component, NULL);
+ e_cal_component_set_rrule_list (exception_component, NULL);
+ e_cal_component_set_exdate_list (exception_component, NULL);
+ e_cal_component_set_exrule_list (exception_component, NULL);
+
+ date.value = &itt;
+ date.tzid = icaltimezone_get_tzid (timezone);
+ *date.value = icaltime_from_timet_with_zone (
+ event->comp_data->instance_start, FALSE, timezone);
+ cal_comp_set_dtstart_with_oldzone (client, exception_component, &date);
+ *date.value = icaltime_from_timet_with_zone (
+ event->comp_data->instance_end, FALSE, timezone);
+ cal_comp_set_dtstart_with_oldzone (client, exception_component, &date);
+ e_cal_component_commit_sequence (exception_component);
+
+ /* Now update both ECalComponents. Note that we do this last
+ * since at present the updates happend synchronously so our
+ * event may disappear. */
+
+ e_cal_remove_object_with_mod (
+ client, id->uid, id->rid, CALOBJ_MOD_THIS, NULL);
+
+ e_cal_component_free_id (id);
+ g_object_unref (recurring_component);
+
+ icalcomp = e_cal_component_get_icalcomponent (exception_component);
+ if (e_cal_create_object (client, icalcomp, &uid, NULL))
+ g_free (uid);
+
+ g_object_unref (exception_component);
+
+ g_list_free (selected);
+}
+
+static void
+action_event_open_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *view;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ e_calendar_view_open_event (view);
+}
+
+static void
+action_event_print_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *calendar_view;
+ ECalendarViewEvent *event;
+ ECalComponent *component;
+ ECal *client;
+ icalcomponent *icalcomp;
+ GList *selected;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ component = e_cal_component_new ();
+
+ e_cal_component_set_icalcomponent (
+ component, icalcomponent_new_clone (icalcomp));
+ print_comp (
+ component, client, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
+
+ g_object_unref (component);
+
+ g_list_free (selected);
+}
+
+static void
+action_event_reply_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ GnomeCalendar *calendar;
+ ECalendarViewEvent *event;
+ ECalComponent *component;
+ ECal *client;
+ icalcomponent *icalcomp;
+ GList *selected;
+ gboolean reply_all = FALSE;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ component = e_cal_component_new ();
+
+ e_cal_component_set_icalcomponent (
+ component, icalcomponent_new_clone (icalcomp));
+ reply_to_calendar_comp (
+ E_CAL_COMPONENT_METHOD_REPLY,
+ component, client, reply_all, NULL, NULL);
+
+ g_object_unref (component);
+
+ g_list_free (selected);
+}
+
+static void
+action_event_reply_all_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ GnomeCalendar *calendar;
+ ECalendarViewEvent *event;
+ ECalComponent *component;
+ ECal *client;
+ icalcomponent *icalcomp;
+ GList *selected;
+ gboolean reply_all = TRUE;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ component = e_cal_component_new ();
+
+ e_cal_component_set_icalcomponent (
+ component, icalcomponent_new_clone (icalcomp));
+ reply_to_calendar_comp (
+ E_CAL_COMPONENT_METHOD_REPLY,
+ component, client, reply_all, NULL, NULL);
+
+ g_object_unref (component);
+
+ g_list_free (selected);
+}
+
+static void
+action_event_save_as_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *calendar_view;
+ ECalendarViewEvent *event;
+ ECal *client;
+ icalcomponent *icalcomp;
+ GList *selected;
+ gchar *filename = NULL;
+ gchar *string = NULL;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ filename = e_file_dialog_save (_("Save As..."), NULL);
+ if (filename == NULL)
+ goto exit;
+
+ string = e_cal_get_component_as_string (client, icalcomp);
+ if (string == NULL) {
+ g_warning ("Could not convert item to a string");
+ goto exit;
+ }
+
+ e_write_file_uri (filename, string);
+
+exit:
+ g_free (filename);
+ g_free (string);
+
+ g_list_free (selected);
+}
+
+static void
+action_event_schedule_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *calendar_view;
+ ECalendarViewEvent *event;
+ ECal *client;
+ icalcomponent *icalcomp;
+ GList *selected;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ selected = e_calendar_view_get_selected_events (calendar_view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ event = selected->data;
+ client = event->comp_data->client;
+ icalcomp = event->comp_data->icalcomp;
+
+ e_calendar_view_edit_appointment (
+ calendar_view, client, icalcomp, TRUE);
+
+ g_list_free (selected);
+}
+
+static void
+action_gal_save_custom_view_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EShellView *shell_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 (cal_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ view_instance = e_cal_shell_content_get_view_instance (cal_shell_content);
+ gal_view_instance_save_as (view_instance);
+}
+
+static void
+action_search_execute_cb (GtkAction *action,
+ ECalShellView *cal_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 (cal_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ e_cal_shell_view_execute_search (cal_shell_view);
+}
+
+static void
+action_search_filter_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ gtk_action_activate (ACTION (SEARCH_EXECUTE));
+}
+
+static GtkActionEntry calendar_entries[] = {
+
+ { "calendar-copy",
+ GTK_STOCK_COPY,
+ N_("_Copy..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_copy_cb) },
+
+ { "calendar-delete",
+ GTK_STOCK_DELETE,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_delete_cb) },
+
+ { "calendar-go-back",
+ GTK_STOCK_GO_BACK,
+ N_("Previous"),
+ NULL,
+ N_("Go Back"),
+ G_CALLBACK (action_calendar_go_back_cb) },
+
+ { "calendar-go-forward",
+ GTK_STOCK_GO_FORWARD,
+ N_("Next"),
+ NULL,
+ N_("Go Forward"),
+ G_CALLBACK (action_calendar_go_forward_cb) },
+
+ { "calendar-go-today",
+ "go-today",
+ N_("Select _Today"),
+ "<Control>t",
+ N_("Select today"),
+ G_CALLBACK (action_calendar_go_today_cb) },
+
+ { "calendar-jump-to",
+ GTK_STOCK_JUMP_TO,
+ N_("Select _Date"),
+ "<Control>g",
+ N_("Select a specific date"),
+ G_CALLBACK (action_calendar_jump_to_cb) },
+
+ { "calendar-new",
+ "x-office-calendar",
+ N_("_New Calendar"),
+ NULL,
+ N_("Create a new calendar"),
+ G_CALLBACK (action_calendar_new_cb) },
+
+ { "calendar-properties",
+ GTK_STOCK_PROPERTIES,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_properties_cb) },
+
+ { "calendar-purge",
+ NULL,
+ N_("Purg_e"),
+ "<Control>e",
+ N_("Purge old appointments and meetings"),
+ G_CALLBACK (action_calendar_purge_cb) },
+
+ { "calendar-rename",
+ NULL,
+ N_("_Rename..."),
+ "F2",
+ N_("Rename the selected calendar"),
+ G_CALLBACK (action_calendar_rename_cb) },
+
+ { "calendar-select-one",
+ "stock_check-filled",
+ N_("Show _Only This Calendar"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_select_one_cb) },
+
+ { "event-clipboard-copy",
+ GTK_STOCK_COPY,
+ NULL,
+ NULL,
+ N_("Copy the selection"),
+ G_CALLBACK (action_event_clipboard_copy_cb) },
+
+ { "event-clipboard-cut",
+ GTK_STOCK_CUT,
+ NULL,
+ NULL,
+ N_("Cut the selection"),
+ G_CALLBACK (action_event_clipboard_cut_cb) },
+
+ { "event-clipboard-paste",
+ GTK_STOCK_PASTE,
+ NULL,
+ NULL,
+ N_("Paste the clipboard"),
+ G_CALLBACK (action_event_clipboard_paste_cb) },
+
+ { "event-copy",
+ NULL,
+ N_("Cop_y to Calendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_copy_cb) },
+
+ { "event-delegate",
+ NULL,
+ N_("_Delegate Meeting..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_delegate_cb) },
+
+ { "event-delete",
+ GTK_STOCK_DELETE,
+ NULL,
+ NULL,
+ N_("Delete the appointment"),
+ G_CALLBACK (action_event_delete_cb) },
+
+ { "event-delete-occurrence",
+ GTK_STOCK_DELETE,
+ N_("Delete This _Occurrence"),
+ NULL,
+ N_("Delete this occurrence"),
+ G_CALLBACK (action_event_delete_occurrence_cb) },
+
+ { "event-delete-occurrence-all",
+ GTK_STOCK_DELETE,
+ N_("Delete _All Occurrences"),
+ NULL,
+ N_("Delete all occurrences"),
+ G_CALLBACK (action_event_delete_occurrence_all_cb) },
+
+ { "event-all-day-new",
+ NULL,
+ N_("New All Day _Event..."),
+ NULL,
+ N_("Create a new all day event"),
+ G_CALLBACK (action_event_all_day_new_cb) },
+
+ { "event-forward",
+ "mail-forward",
+ N_("_Forward as iCalendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_forward_cb) },
+
+ { "event-meeting-new",
+ NULL,
+ N_("New _Meeting..."),
+ NULL,
+ N_("Create a new meeting"),
+ G_CALLBACK (action_event_meeting_new_cb) },
+
+ { "event-move",
+ NULL,
+ N_("Mo_ve to Calendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_move_cb) },
+
+ { "event-new",
+ NULL,
+ N_("New _Appointment..."),
+ NULL,
+ N_("Create a new appointment"),
+ G_CALLBACK (action_event_new_cb) },
+
+ { "event-occurrence-movable",
+ NULL,
+ N_("Make this Occurrence _Movable"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_occurrence_movable_cb) },
+
+ { "event-open",
+ NULL,
+ N_("_Open Appointment"),
+ "<Control>o",
+ N_("View the current appointment"),
+ G_CALLBACK (action_event_open_cb) },
+
+ { "event-reply",
+ "mail-reply-sender",
+ N_("_Reply"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_reply_cb) },
+
+ { "event-reply-all",
+ "mail-reply-all",
+ N_("Reply to _All"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_reply_all_cb) },
+
+ { "event-save-as",
+ GTK_STOCK_SAVE_AS,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_save_as_cb) },
+
+ { "event-schedule",
+ NULL,
+ N_("_Schedule Meeting..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_schedule_cb) },
+
+ /*** Menus ***/
+
+ { "calendar-actions-menu",
+ NULL,
+ N_("_Actions"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static EPopupActionEntry calendar_popup_entries[] = {
+
+ /* FIXME No equivalent main menu items for the any of the calendar
+ * popup menu items and for many of the event popup menu items.
+ * This is an accessibility issue. */
+
+ { "calendar-popup-copy",
+ NULL,
+ "calendar-copy" },
+
+ { "calendar-popup-delete",
+ NULL,
+ "calendar-delete" },
+
+ { "calendar-popup-go-today",
+ NULL,
+ "calendar-go-today" },
+
+ { "calendar-popup-jump-to",
+ NULL,
+ "calendar-jump-to" },
+
+ { "calendar-popup-properties",
+ NULL,
+ "calendar-properties" },
+
+ { "calendar-popup-rename",
+ NULL,
+ "calendar-rename" },
+
+ { "calendar-popup-select-one",
+ NULL,
+ "calendar-select-one" },
+
+ { "event-popup-clipboard-copy",
+ NULL,
+ "event-clipboard-copy" },
+
+ { "event-popup-clipboard-cut",
+ NULL,
+ "event-clipboard-cut" },
+
+ { "event-popup-clipboard-paste",
+ NULL,
+ "event-clipboard-paste" },
+
+ { "event-popup-copy",
+ NULL,
+ "event-copy" },
+
+ { "event-popup-delegate",
+ NULL,
+ "event-delegate" },
+
+ { "event-popup-delete",
+ NULL,
+ "event-delete" },
+
+ { "event-popup-delete-occurrence",
+ NULL,
+ "event-delete-occurrence" },
+
+ { "event-popup-delete-occurrence-all",
+ NULL,
+ "event-delete-occurrence-all" },
+
+ { "event-popup-forward",
+ NULL,
+ "event-forward" },
+
+ { "event-popup-move",
+ NULL,
+ "event-move" },
+
+ { "event-popup-occurrence-movable",
+ NULL,
+ "event-occurrence-movable" },
+
+ { "event-popup-open",
+ NULL,
+ "event-open" },
+
+ { "event-popup-reply",
+ NULL,
+ "event-reply" },
+
+ { "event-popup-reply-all",
+ NULL,
+ "event-reply-all" },
+
+ { "event-popup-save-as",
+ NULL,
+ "event-save-as" },
+
+ { "event-popup-schedule",
+ NULL,
+ "event-schedule" }
+};
+
+static GtkRadioActionEntry calendar_view_entries[] = {
+
+ { "calendar-view-day",
+ "view-calendar-day",
+ N_("Day"),
+ NULL,
+ N_("Show one day"),
+ GNOME_CAL_DAY_VIEW },
+
+ { "calendar-view-list",
+ "view-calendar-list",
+ N_("List"),
+ NULL,
+ N_("Show as list"),
+ GNOME_CAL_LIST_VIEW },
+
+ { "calendar-view-month",
+ "view-calendar-month",
+ N_("Month"),
+ NULL,
+ N_("Show one month"),
+ GNOME_CAL_MONTH_VIEW },
+
+ { "calendar-view-week",
+ "view-calendar-week",
+ N_("Week"),
+ NULL,
+ N_("Show one week"),
+ GNOME_CAL_WEEK_VIEW },
+
+ { "calendar-view-workweek",
+ "view-calendar-workweek",
+ N_("Work Week"),
+ NULL,
+ N_("Show one work week"),
+ GNOME_CAL_WORK_WEEK_VIEW }
+};
+
+static GtkRadioActionEntry calendar_filter_entries[] = {
+
+ { "calendar-filter-active-appointments",
+ NULL,
+ N_("Active Appointements"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_FILTER_ACTIVE_APPOINTMENTS },
+
+ { "calendar-filter-any-category",
+ NULL,
+ N_("Any Category"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_FILTER_ANY_CATEGORY },
+
+ { "calendar-filter-next-7-days-appointments",
+ NULL,
+ N_("Next 7 Days' Appointments"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS },
+
+ { "calendar-filter-unmatched",
+ NULL,
+ N_("Unmatched"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_FILTER_UNMATCHED }
+};
+
+static GtkRadioActionEntry calendar_search_entries[] = {
+
+ { "calendar-search-any-field-contains",
+ NULL,
+ N_("Any field contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_SEARCH_ANY_FIELD_CONTAINS },
+
+ { "calendar-search-description-contains",
+ NULL,
+ N_("Description contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_SEARCH_DESCRIPTION_CONTAINS },
+
+ { "calendar-search-summary-contains",
+ NULL,
+ N_("Summary contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ CALENDAR_SEARCH_SUMMARY_CONTAINS }
+};
+
+static GtkActionEntry lockdown_printing_entries[] = {
+
+ { "calendar-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ "<Control>p",
+ N_("Print this calendar"),
+ G_CALLBACK (action_calendar_print_cb) },
+
+ { "calendar-print-preview",
+ GTK_STOCK_PRINT_PREVIEW,
+ NULL,
+ NULL,
+ N_("Preview the calendar to be printed"),
+ G_CALLBACK (action_calendar_print_preview_cb) },
+
+ { "event-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_event_print_cb) }
+};
+
+static EPopupActionEntry lockdown_printing_popup_entries[] = {
+
+ { "event-popup-print",
+ NULL,
+ "event-print" }
+};
+
+void
+e_cal_shell_view_actions_init (ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ GtkActionGroup *action_group;
+ GtkAction *action;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ /* Calendar Actions */
+ action_group = ACTION_GROUP (CALENDAR);
+ gtk_action_group_add_actions (
+ action_group, calendar_entries,
+ G_N_ELEMENTS (calendar_entries), cal_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, calendar_popup_entries,
+ G_N_ELEMENTS (calendar_popup_entries));
+ gtk_action_group_add_radio_actions (
+ action_group, calendar_view_entries,
+ G_N_ELEMENTS (calendar_view_entries), GNOME_CAL_DAY_VIEW,
+ G_CALLBACK (action_calendar_view_cb), cal_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, calendar_search_entries,
+ G_N_ELEMENTS (calendar_search_entries),
+ CALENDAR_SEARCH_SUMMARY_CONTAINS,
+ G_CALLBACK (action_calendar_search_cb), cal_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), cal_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, lockdown_printing_popup_entries,
+ G_N_ELEMENTS (lockdown_printing_popup_entries));
+
+ /* Fine tuning. */
+
+ action = ACTION (CALENDAR_GO_TODAY);
+ g_object_set (action, "short-label", _("Today"), NULL);
+
+ action = ACTION (CALENDAR_JUMP_TO);
+ g_object_set (action, "short-label", _("Go To"), NULL);
+
+ action = ACTION (CALENDAR_VIEW_DAY);
+ g_object_set (action, "is-important", TRUE, NULL);
+
+ action = ACTION (CALENDAR_VIEW_LIST);
+ g_object_set (action, "is-important", TRUE, NULL);
+
+ action = ACTION (CALENDAR_VIEW_MONTH);
+ g_object_set (action, "is-important", TRUE, NULL);
+
+ action = ACTION (CALENDAR_VIEW_WEEK);
+ g_object_set (action, "is-important", TRUE, NULL);
+
+ action = ACTION (CALENDAR_VIEW_WORKWEEK);
+ g_object_set (action, "is-important", TRUE, NULL);
+
+ action = ACTION (EVENT_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), cal_shell_view);
+
+ g_signal_connect (
+ ACTION (SEARCH_EXECUTE), "activate",
+ G_CALLBACK (action_search_execute_cb), cal_shell_view);
+
+ /* Initialize the memo and task pad actions. */
+ e_cal_shell_view_memopad_actions_init (cal_shell_view);
+ e_cal_shell_view_taskpad_actions_init (cal_shell_view);
+}
+
+void
+e_cal_shell_view_update_search_filter (ECalShellView *cal_shell_view)
+{
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GList *list, *iter;
+ GSList *group;
+ gint ii;
+
+ shell_view = E_SHELL_VIEW (cal_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 (CALENDAR_FILTER);
+ e_action_group_remove_all_actions (action_group);
+
+ /* Add the standard filter actions. */
+ gtk_action_group_add_radio_actions (
+ action_group, calendar_filter_entries,
+ G_N_ELEMENTS (calendar_filter_entries),
+ CALENDAR_FILTER_ANY_CATEGORY,
+ G_CALLBACK (action_search_filter_cb),
+ cal_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 (
+ "calendar-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 = CALENDAR_FILTER_UNMATCHED;
+ e_shell_content_add_filter_separator_after (shell_content, ii);
+
+ ii = CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS;
+ e_shell_content_add_filter_separator_after (shell_content, ii);
+}
diff --git a/modules/calendar/e-cal-shell-view-actions.h b/modules/calendar/e-cal-shell-view-actions.h
new file mode 100644
index 0000000000..b02906f179
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view-actions.h
@@ -0,0 +1,153 @@
+/*
+ * e-cal-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_CAL_SHELL_VIEW_ACTIONS_H
+#define E_CAL_SHELL_VIEW_ACTIONS_H
+
+#include <shell/e-shell-window-actions.h>
+
+/* Calendar Actions */
+#define E_SHELL_WINDOW_ACTION_CALENDAR_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-copy")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-delete")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_GO_BACK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-go-back")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_GO_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-go-forward")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_GO_TODAY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-go-today")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_JUMP_TO(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-jump-to")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-new")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-print")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PRINT_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-print-preview")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PROPERTIES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-properties")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PURGE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-purge")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_RENAME(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-rename")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_SELECT_ONE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-select-one")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_DAY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-view-day")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_LIST(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-view-list")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_MONTH(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-view-month")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_WEEK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-view-week")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_WORKWEEK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-view-workweek")
+
+/* Event Actions */
+#define E_SHELL_WINDOW_ACTION_EVENT_CLIPBOARD_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-clipboard-copy")
+#define E_SHELL_WINDOW_ACTION_EVENT_CLIPBOARD_CUT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-clipboard-cut")
+#define E_SHELL_WINDOW_ACTION_EVENT_CLIPBOARD_PASTE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-clipboard-paste")
+#define E_SHELL_WINDOW_ACTION_EVENT_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-delete")
+#define E_SHELL_WINDOW_ACTION_EVENT_DELETE_OCCURRENCE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-delete-occurrence")
+#define E_SHELL_WINDOW_ACTION_EVENT_DELETE_OCCURRENCE_ALL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-delete-occurrence-all")
+#define E_SHELL_WINDOW_ACTION_EVENT_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "event-open")
+
+/* Memo Pad Actions */
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_CLIPBOARD_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-clipboard-copy")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_CLIPBOARD_CUT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-clipboard-cut")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_CLIPBOARD_PASTE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-clipboard-paste")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-delete")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-forward")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-new")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-open")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_OPEN_URL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-open-url")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-print")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_SAVE_AS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-save-as")
+
+/* Task Pad Actions */
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_ASSIGN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-assign")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_CLIPBOARD_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-clipboard-copy")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_CLIPBOARD_CUT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-clipboard-cut")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_CLIPBOARD_PASTE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-clipboard-paste")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-delete")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-forward")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_MARK_COMPLETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-mark-complete")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_MARK_INCOMPLETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-mark-incomplete")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-new")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-open")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_OPEN_URL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-open-url")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-print")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_SAVE_AS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-save-as")
+
+/* Calendar Query Actions */
+#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_ACTIVE_APPOINTMENTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-filter-active-appointments")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_ANY_CATEGORY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-filter-any-category")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-filter-next-7-days-appointments")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_UNMATCHED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-filter-unmatched")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_ANY_FIELD_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-search-any-field-contains")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_DESCRIPTION_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-search-description-contains")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_SUMMARY_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "calendar-search-summary-contains")
+
+/* Action Groups */
+#define E_SHELL_WINDOW_ACTION_GROUP_CALENDAR(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "calendar")
+#define E_SHELL_WINDOW_ACTION_GROUP_CALENDAR_FILTER(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "calendar-filter")
+
+#endif /* E_CAL_SHELL_VIEW_ACTIONS_H */
diff --git a/modules/calendar/e-cal-shell-view-memopad.c b/modules/calendar/e-cal-shell-view-memopad.c
new file mode 100644
index 0000000000..1c4e863345
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view-memopad.c
@@ -0,0 +1,526 @@
+/*
+ * e-cal-shell-view-memopad.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-cal-shell-view-private.h"
+
+/* Much of this file is based on e-memo-shell-view-actions.c. */
+
+static void
+action_calendar_memopad_clipboard_copy_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ e_memo_table_copy_clipboard (memo_table);
+}
+
+static void
+action_calendar_memopad_clipboard_cut_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ e_memo_table_cut_clipboard (memo_table);
+}
+
+static void
+action_calendar_memopad_clipboard_paste_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ e_memo_table_paste_clipboard (memo_table);
+}
+
+static void
+action_calendar_memopad_delete_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ e_cal_shell_view_memopad_set_status_message (
+ cal_shell_view, _("Deleting selected memos..."), -1.0);
+ e_memo_table_delete_selected (memo_table);
+ e_cal_shell_view_memopad_set_status_message (
+ cal_shell_view, NULL, -1.0);
+}
+
+static void
+action_calendar_memopad_forward_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only forward the first selected memo. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+ itip_send_comp (
+ E_CAL_COMPONENT_METHOD_PUBLISH, comp,
+ comp_data->client, NULL, NULL, NULL, TRUE, FALSE);
+ g_object_unref (comp);
+}
+
+static void
+action_calendar_memopad_new_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ ECal *client;
+ ECalComponent *comp;
+ CompEditor *editor;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ client = comp_data->client;
+ editor = memo_editor_new (client, shell, COMP_EDITOR_NEW_ITEM);
+ comp = cal_comp_memo_new_with_defaults (client);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (client);
+}
+
+static void
+action_calendar_memopad_open_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the first selected memo. */
+ e_cal_shell_view_memopad_open_memo (cal_shell_view, comp_data);
+}
+
+static void
+action_calendar_memopad_open_url_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ icalproperty *prop;
+ const gchar *uri;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the URI of the first selected memo. */
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ g_return_if_fail (prop == NULL);
+
+ uri = icalproperty_get_url (prop);
+ e_show_uri (GTK_WINDOW (shell_window), uri);
+}
+
+static void
+action_calendar_memopad_print_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GtkPrintOperationAction print_action;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only print the first selected memo. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_cal_component_set_icalcomponent (comp, clone);
+ print_comp (comp, comp_data->client, print_action);
+ g_object_unref (comp);
+}
+
+static void
+action_calendar_memopad_save_as_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+ gchar *filename;
+ gchar *string;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ filename = e_file_dialog_save (_("Save as..."), NULL);
+ if (filename == NULL)
+ return;
+
+ /* XXX We only save the first selected memo. */
+ string = e_cal_get_component_as_string (
+ comp_data->client, comp_data->icalcomp);
+ if (string == NULL) {
+ g_warning ("Could not convert memo to a string.");
+ return;
+ }
+
+ e_write_file_uri (filename, string);
+
+ g_free (filename);
+ g_free (string);
+}
+
+static GtkActionEntry calendar_memopad_entries[] = {
+
+ { "calendar-memopad-clipboard-copy",
+ GTK_STOCK_COPY,
+ NULL,
+ NULL,
+ N_("Copy selected memo"),
+ G_CALLBACK (action_calendar_memopad_clipboard_copy_cb) },
+
+ { "calendar-memopad-clipboard-cut",
+ GTK_STOCK_CUT,
+ NULL,
+ NULL,
+ N_("Cut selected memo"),
+ G_CALLBACK (action_calendar_memopad_clipboard_cut_cb) },
+
+ { "calendar-memopad-clipboard-paste",
+ GTK_STOCK_PASTE,
+ NULL,
+ NULL,
+ N_("Paste memo from the clipboard"),
+ G_CALLBACK (action_calendar_memopad_clipboard_paste_cb) },
+
+ { "calendar-memopad-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete Memo"),
+ NULL,
+ N_("Delete selected memos"),
+ G_CALLBACK (action_calendar_memopad_delete_cb) },
+
+ { "calendar-memopad-forward",
+ "mail-forward",
+ N_("_Forward as iCalendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_memopad_forward_cb) },
+
+ { "calendar-memopad-new",
+ "stock_insert-note",
+ N_("New _Memo"),
+ NULL,
+ N_("Create a new memo"),
+ G_CALLBACK (action_calendar_memopad_new_cb) },
+
+ { "calendar-memopad-open",
+ GTK_STOCK_OPEN,
+ N_("_Open Memo"),
+ NULL,
+ N_("View the selected memo"),
+ G_CALLBACK (action_calendar_memopad_open_cb) },
+
+ { "calendar-memopad-open-url",
+ "applications-internet",
+ N_("Open _Web Page"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_memopad_open_url_cb) },
+
+ { "calendar-memopad-save-as",
+ GTK_STOCK_SAVE_AS,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_memopad_save_as_cb) }
+};
+
+static GtkActionEntry lockdown_printing_entries[] = {
+
+ { "calendar-memopad-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ NULL,
+ N_("Print the selected memo"),
+ G_CALLBACK (action_calendar_memopad_print_cb) }
+};
+
+void
+e_cal_shell_view_memopad_actions_init (ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ GtkActionGroup *action_group;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ /* Calendar Actions */
+ action_group = ACTION_GROUP (CALENDAR);
+ gtk_action_group_add_actions (
+ action_group, calendar_memopad_entries,
+ G_N_ELEMENTS (calendar_memopad_entries), cal_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), cal_shell_view);
+}
+
+void
+e_cal_shell_view_memopad_actions_update (ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMemoTable *memo_table;
+ ETable *table;
+ GtkAction *action;
+ GSList *list, *iter;
+ const gchar *label;
+ gboolean editable = TRUE;
+ gboolean has_url = FALSE;
+ gboolean sensitive;
+ gint n_selected;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+
+ table = e_memo_table_get_table (memo_table);
+ n_selected = e_table_selected_count (table);
+
+ list = e_memo_table_get_selected (memo_table);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ icalproperty *prop;
+ gboolean read_only;
+
+ e_cal_is_read_only (comp_data->client, &read_only, NULL);
+ editable &= !read_only;
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ has_url |= (prop != NULL);
+ }
+ g_slist_free (list);
+
+ action = ACTION (CALENDAR_MEMOPAD_CLIPBOARD_COPY);
+ sensitive = (n_selected > 0);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_CLIPBOARD_CUT);
+ sensitive = (n_selected > 0) && editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_CLIPBOARD_PASTE);
+ sensitive = editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_DELETE);
+ sensitive = (n_selected > 0) && editable;
+ gtk_action_set_sensitive (action, sensitive);
+ label = ngettext ("Delete Memo", "Delete Memos", n_selected);
+ g_object_set (action, "label", label, NULL);
+
+ action = ACTION (CALENDAR_MEMOPAD_FORWARD);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_OPEN);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_OPEN_URL);
+ sensitive = (n_selected == 1) && has_url;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_PRINT);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_MEMOPAD_SAVE_AS);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+void
+e_cal_shell_view_memopad_open_memo (ECalShellView *cal_shell_view,
+ ECalModelComponent *comp_data)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+ g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ editor = comp_editor_find_instance (uid);
+
+ if (editor != NULL)
+ goto exit;
+
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+
+ if (e_cal_component_has_organizer (comp))
+ flags |= COMP_EDITOR_IS_SHARED;
+
+ if (itip_organizer_is_user (comp, comp_data->client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = memo_editor_new (comp_data->client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_unref (comp);
+
+exit:
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+void
+e_cal_shell_view_memopad_set_status_message (ECalShellView *cal_shell_view,
+ const gchar *status_message,
+ gdouble percent)
+{
+ EActivity *activity;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ activity = cal_shell_view->priv->memopad_activity;
+
+ if (status_message == NULL || *status_message == '\0') {
+ if (activity != NULL) {
+ e_activity_complete (activity);
+ g_object_unref (activity);
+ activity = NULL;
+ }
+
+ } else if (activity == NULL) {
+ activity = e_activity_new (status_message);
+ e_activity_set_percent (activity, percent);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ } else {
+ e_activity_set_percent (activity, percent);
+ e_activity_set_primary_text (activity, status_message);
+ }
+
+ cal_shell_view->priv->memopad_activity = activity;
+}
diff --git a/modules/calendar/e-cal-shell-view-private.c b/modules/calendar/e-cal-shell-view-private.c
new file mode 100644
index 0000000000..1b9c8d367a
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view-private.c
@@ -0,0 +1,1098 @@
+/*
+ * e-cal-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-cal-shell-view-private.h"
+
+#include "calendar/gui/calendar-view-factory.h"
+#include "widgets/menus/gal-view-factory-etable.h"
+
+static void
+cal_shell_view_process_completed_tasks (ECalShellView *cal_shell_view,
+ gboolean config_changed)
+{
+#if 0
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ e_calendar_table_process_completed_tasks (
+ task_table, clients, config_changed);
+#endif
+}
+
+static struct tm
+cal_shell_view_get_current_time (ECalendarItem *calitem,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ struct icaltimetype tt;
+ icaltimezone *timezone;
+ ECalModel *model;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ model = e_cal_shell_content_get_model (cal_shell_content);
+ timezone = e_cal_model_get_timezone (model);
+
+ tt = icaltime_from_timet_with_zone (time (NULL), FALSE, timezone);
+
+ return icaltimetype_to_tm (&tt);
+}
+
+static void
+cal_shell_view_date_navigator_date_range_changed_cb (ECalShellView *cal_shell_view,
+ ECalendarItem *calitem)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ gnome_calendar_update_query (calendar);
+}
+
+static void
+cal_shell_view_date_navigator_selection_changed_cb (ECalShellView *cal_shell_view,
+ ECalendarItem *calitem)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendarViewType switch_to;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalModel *model;
+ GDate start_date, end_date;
+ GDate new_start_date, new_end_date;
+ icaltimetype tt;
+ icaltimezone *timezone;
+ time_t start, end, new_time;
+ gboolean starts_on_week_start_day;
+ gint new_days_shown;
+ gint week_start_day;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ model = gnome_calendar_get_model (calendar);
+ view_type = gnome_calendar_get_view (calendar);
+ switch_to = view_type;
+
+ timezone = e_cal_model_get_timezone (model);
+ week_start_day = e_cal_model_get_week_start_day (model);
+ e_cal_model_get_time_range (model, &start, &end);
+
+ time_to_gdate_with_zone (&start_date, start, timezone);
+ time_to_gdate_with_zone (&end_date, end, timezone);
+
+ if (view_type == GNOME_CAL_MONTH_VIEW) {
+ EWeekView *week_view;
+ ECalendarView *calendar_view;
+ gboolean multi_week_view;
+ gboolean compress_weekend;
+
+ calendar_view = gnome_calendar_get_calendar_view (
+ calendar, GNOME_CAL_MONTH_VIEW);
+
+ week_view = E_WEEK_VIEW (calendar_view);
+ multi_week_view = e_week_view_get_multi_week_view (week_view);
+ compress_weekend = e_week_view_get_compress_weekend (week_view);
+
+ if (week_start_day == 0 && (!multi_week_view || compress_weekend))
+ g_date_add_days (&start_date, 1);
+ }
+
+ g_date_subtract_days (&end_date, 1);
+
+ e_calendar_item_get_selection (
+ calitem, &new_start_date, &new_end_date);
+
+ /* If the selection hasn't changed, just return. */
+ if (g_date_compare (&start_date, &new_start_date) == 0 &&
+ g_date_compare (&end_date, &new_end_date) == 0)
+ return;
+
+ new_days_shown =
+ g_date_get_julian (&new_end_date) -
+ g_date_get_julian (&new_start_date) + 1;
+
+ /* If a complete week is selected we show the week view.
+ * Note that if weekends are compressed and the week start
+ * day is set to Sunday, we don't actually show complete
+ * weeks in the week view, so this may need tweaking. */
+ starts_on_week_start_day =
+ (g_date_get_weekday (&new_start_date) % 7 == week_start_day);
+
+ /* Update selection to be in the new time range. */
+ tt = icaltime_null_time ();
+ tt.year = g_date_get_year (&new_start_date);
+ tt.month = g_date_get_month (&new_start_date);
+ tt.day = g_date_get_day (&new_start_date);
+ new_time = icaltime_as_timet_with_zone (tt, timezone);
+
+ /* Switch views as appropriate, and change the number of
+ * days or weeks shown. */
+ if (new_days_shown > 9) {
+ if (view_type != GNOME_CAL_LIST_VIEW) {
+ ECalendarView *calendar_view;
+
+ calendar_view = gnome_calendar_get_calendar_view (
+ calendar, GNOME_CAL_MONTH_VIEW);
+ e_week_view_set_weeks_shown (
+ E_WEEK_VIEW (calendar_view),
+ (new_days_shown + 6) / 7);
+ switch_to = GNOME_CAL_MONTH_VIEW;
+ }
+ } else if (new_days_shown == 7 && starts_on_week_start_day)
+ switch_to = GNOME_CAL_WEEK_VIEW;
+ else {
+ ECalendarView *calendar_view;
+
+ calendar_view = gnome_calendar_get_calendar_view (
+ calendar, GNOME_CAL_DAY_VIEW);
+ e_day_view_set_days_shown (
+ E_DAY_VIEW (calendar_view), new_days_shown);
+
+ if (new_days_shown != 5 || !starts_on_week_start_day)
+ switch_to = GNOME_CAL_DAY_VIEW;
+
+ else if (view_type != GNOME_CAL_WORK_WEEK_VIEW)
+ switch_to = GNOME_CAL_DAY_VIEW;
+ }
+
+ /* Make the views display things properly. */
+ gnome_calendar_update_view_times (calendar, new_time);
+ gnome_calendar_set_view (calendar, switch_to);
+ gnome_calendar_set_range_selected (calendar, TRUE);
+
+ gnome_calendar_notify_dates_shown_changed (calendar);
+}
+
+static void
+cal_shell_view_date_navigator_scroll_event_cb (ECalShellView *cal_shell_view,
+ GdkEventScroll *event,
+ ECalendar *date_navigator)
+{
+ ECalendarItem *calitem;
+ GDate start_date, end_date;
+
+ calitem = date_navigator->calitem;
+ if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
+ return;
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ g_date_subtract_months (&start_date, 1);
+ g_date_subtract_months (&end_date, 1);
+ break;
+
+ case GDK_SCROLL_DOWN:
+ g_date_add_months (&start_date, 1);
+ g_date_add_months (&end_date, 1);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ /* XXX Does ECalendarItem emit a signal for this? If so, maybe
+ * we could move this handler into ECalShellSidebar. */
+ e_calendar_item_set_selection (calitem, &start_date, &end_date);
+
+ cal_shell_view_date_navigator_date_range_changed_cb (
+ cal_shell_view, calitem);
+}
+
+static void
+cal_shell_view_popup_event_cb (EShellView *shell_view,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/calendar-event-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+}
+
+static gboolean
+cal_shell_view_selector_popup_event_cb (EShellView *shell_view,
+ ESource *primary_source,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/calendar-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+
+ return TRUE;
+}
+
+static void
+cal_shell_view_selector_client_added_cb (ECalShellView *cal_shell_view,
+ ECal *client)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+ ECalModel *model;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ model = gnome_calendar_get_model (calendar);
+
+ e_cal_model_add_client (model, client);
+}
+
+static void
+cal_shell_view_selector_client_removed_cb (ECalShellView *cal_shell_view,
+ ECal *client)
+{
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+ ECalModel *model;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ model = gnome_calendar_get_model (calendar);
+
+ e_cal_model_remove_client (model, client);
+}
+
+static void
+cal_shell_view_memopad_popup_event_cb (EShellView *shell_view,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/calendar-memopad-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+}
+
+static void
+cal_shell_view_taskpad_popup_event_cb (EShellView *shell_view,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/calendar-taskpad-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+}
+
+static void
+cal_shell_view_user_created_cb (ECalShellView *cal_shell_view,
+ ECalendarView *calendar_view)
+{
+ ECalShellSidebar *cal_shell_sidebar;
+ ECalModel *model;
+ ECal *client;
+ ESource *source;
+
+ model = e_calendar_view_get_model (calendar_view);
+ client = e_cal_model_get_default_client (model);
+ source = e_cal_get_source (client);
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ e_cal_shell_sidebar_add_source (cal_shell_sidebar, source);
+
+ e_cal_model_add_client (model, client);
+}
+
+static void
+cal_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 calendars");
+ g_free (filename);
+
+ factory = calendar_view_factory_new (GNOME_CAL_DAY_VIEW);
+ gal_view_collection_add_factory (collection, factory);
+ g_object_unref (factory);
+
+ factory = calendar_view_factory_new (GNOME_CAL_WORK_WEEK_VIEW);
+ gal_view_collection_add_factory (collection, factory);
+ g_object_unref (factory);
+
+ factory = calendar_view_factory_new (GNOME_CAL_WEEK_VIEW);
+ gal_view_collection_add_factory (collection, factory);
+ g_object_unref (factory);
+
+ factory = calendar_view_factory_new (GNOME_CAL_MONTH_VIEW);
+ gal_view_collection_add_factory (collection, factory);
+ g_object_unref (factory);
+
+ factory = gal_view_factory_etable_new (spec);
+ gal_view_collection_add_factory (collection, factory);
+ g_object_unref (factory);
+ g_object_unref (spec);
+
+ gal_view_collection_load (collection);
+}
+
+static void
+cal_shell_view_notify_view_id_cb (ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ GalViewInstance *view_instance;
+ const gchar *view_id;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ view_instance =
+ e_cal_shell_content_get_view_instance (cal_shell_content);
+ view_id = e_shell_view_get_view_id (E_SHELL_VIEW (cal_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_cal_shell_view_private_init (ECalShellView *cal_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ if (!gal_view_collection_loaded (shell_view_class->view_collection))
+ cal_shell_view_load_view_collection (shell_view_class);
+
+ g_signal_connect (
+ cal_shell_view, "notify::view-id",
+ G_CALLBACK (cal_shell_view_notify_view_id_cb), NULL);
+}
+
+void
+e_cal_shell_view_private_constructed (ECalShellView *cal_shell_view)
+{
+ ECalShellViewPrivate *priv = cal_shell_view->priv;
+ ECalShellContent *cal_shell_content;
+ ECalShellSidebar *cal_shell_sidebar;
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GnomeCalendar *calendar;
+ ECalendar *date_navigator;
+ EMemoTable *memo_table;
+ ECalendarTable *task_table;
+ ESourceSelector *selector;
+ ECalModel *model;
+ gint ii;
+
+ shell_view = E_SHELL_VIEW (cal_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, "calendar");
+ e_shell_window_add_action_group (shell_window, "calendar-filter");
+
+ /* Cache these to avoid lots of awkward casting. */
+ priv->cal_shell_backend = g_object_ref (shell_backend);
+ priv->cal_shell_content = g_object_ref (shell_content);
+ priv->cal_shell_sidebar = g_object_ref (shell_sidebar);
+
+ cal_shell_content = E_CAL_SHELL_CONTENT (shell_content);
+ model = e_cal_shell_content_get_model (cal_shell_content);
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ memo_table = e_cal_shell_content_get_memo_table (cal_shell_content);
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ cal_shell_sidebar = E_CAL_SHELL_SIDEBAR (shell_sidebar);
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+ date_navigator = e_cal_shell_sidebar_get_date_navigator (cal_shell_sidebar);
+
+ /* Give GnomeCalendar a handle to the date navigator. */
+ gnome_calendar_set_date_navigator (calendar, date_navigator);
+
+ e_calendar_item_set_get_time_callback (
+ date_navigator->calitem, (ECalendarItemGetTimeCallback)
+ cal_shell_view_get_current_time, cal_shell_view, NULL);
+
+ for (ii = 0; ii < GNOME_CAL_LAST_VIEW; ii++) {
+ ECalendarView *calendar_view;
+
+ calendar_view =
+ gnome_calendar_get_calendar_view (calendar, ii);
+
+ g_signal_connect_swapped (
+ calendar_view, "popup-event",
+ G_CALLBACK (cal_shell_view_popup_event_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ calendar_view, "user-created",
+ G_CALLBACK (cal_shell_view_user_created_cb),
+ cal_shell_view);
+ }
+
+ g_signal_connect_swapped (
+ calendar, "dates-shown-changed",
+ G_CALLBACK (e_cal_shell_view_update_sidebar),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ model, "notify::timezone",
+ G_CALLBACK (e_cal_shell_view_update_timezone),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ date_navigator, "scroll-event",
+ G_CALLBACK (cal_shell_view_date_navigator_scroll_event_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ date_navigator->calitem, "date-range-changed",
+ G_CALLBACK (cal_shell_view_date_navigator_date_range_changed_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ date_navigator->calitem, "selection-changed",
+ G_CALLBACK (cal_shell_view_date_navigator_selection_changed_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ selector, "popup-event",
+ G_CALLBACK (cal_shell_view_selector_popup_event_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ cal_shell_sidebar, "client-added",
+ G_CALLBACK (cal_shell_view_selector_client_added_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ cal_shell_sidebar, "client-removed",
+ G_CALLBACK (cal_shell_view_selector_client_removed_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ memo_table, "popup-event",
+ G_CALLBACK (cal_shell_view_memopad_popup_event_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ memo_table, "status-message",
+ G_CALLBACK (e_cal_shell_view_memopad_set_status_message),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ task_table, "popup-event",
+ G_CALLBACK (cal_shell_view_taskpad_popup_event_cb),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ task_table, "status-message",
+ G_CALLBACK (e_cal_shell_view_taskpad_set_status_message),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ e_memo_table_get_table (memo_table), "selection-change",
+ G_CALLBACK (e_cal_shell_view_memopad_actions_update),
+ cal_shell_view);
+
+ g_signal_connect_swapped (
+ e_calendar_table_get_table (task_table), "selection-change",
+ G_CALLBACK (e_cal_shell_view_taskpad_actions_update),
+ cal_shell_view);
+
+ e_categories_register_change_listener (
+ G_CALLBACK (e_cal_shell_view_update_search_filter),
+ cal_shell_view);
+
+ e_cal_shell_view_actions_init (cal_shell_view);
+ e_cal_shell_view_update_sidebar (cal_shell_view);
+ e_cal_shell_view_update_search_filter (cal_shell_view);
+ e_cal_shell_view_update_timezone (cal_shell_view);
+
+ /* Keep the toolbar view buttons in sync with the calendar. */
+ e_mutual_binding_new (
+ G_OBJECT (calendar), "view",
+ G_OBJECT (ACTION (CALENDAR_VIEW_DAY)), "current-value");
+}
+
+void
+e_cal_shell_view_private_dispose (ECalShellView *cal_shell_view)
+{
+ ECalShellViewPrivate *priv = cal_shell_view->priv;
+
+ DISPOSE (priv->cal_shell_backend);
+ DISPOSE (priv->cal_shell_content);
+ DISPOSE (priv->cal_shell_sidebar);
+
+ if (priv->calendar_activity != NULL) {
+ /* XXX Activity is not cancellable. */
+ e_activity_complete (priv->calendar_activity);
+ g_object_unref (priv->calendar_activity);
+ priv->calendar_activity = NULL;
+ }
+
+ if (priv->memopad_activity != NULL) {
+ /* XXX Activity is not cancellable. */
+ e_activity_complete (priv->memopad_activity);
+ g_object_unref (priv->memopad_activity);
+ priv->memopad_activity = NULL;
+ }
+
+ if (priv->taskpad_activity != NULL) {
+ /* XXX Activity is not cancellable. */
+ e_activity_complete (priv->taskpad_activity);
+ g_object_unref (priv->taskpad_activity);
+ priv->taskpad_activity = NULL;
+ }
+}
+
+void
+e_cal_shell_view_private_finalize (ECalShellView *cal_shell_view)
+{
+ /* XXX Nothing to do? */
+}
+
+void
+e_cal_shell_view_execute_search (ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalShellSidebar *cal_shell_sidebar;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ GnomeCalendar *calendar;
+ ECalendar *date_navigator;
+ GtkRadioAction *action;
+ GString *string;
+ FilterRule *rule;
+ const gchar *format;
+ const gchar *text;
+ time_t start_range;
+ time_t end_range;
+ gboolean range_search;
+ gchar *start, *end;
+ gchar *query;
+ gchar *temp;
+ gint value;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ 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 = GTK_RADIO_ACTION (ACTION (CALENDAR_SEARCH_ANY_FIELD_CONTAINS));
+ value = gtk_radio_action_get_current_value (action);
+
+ if (text == NULL || *text == '\0') {
+ text = "";
+ value = CALENDAR_SEARCH_SUMMARY_CONTAINS;
+ }
+
+ switch (value) {
+ default:
+ text = "";
+ /* fall through */
+
+ case CALENDAR_SEARCH_SUMMARY_CONTAINS:
+ format = "(contains? \"summary\" %s)";
+ break;
+
+ case CALENDAR_SEARCH_DESCRIPTION_CONTAINS:
+ format = "(contains? \"description\" %s)";
+ break;
+
+ case CALENDAR_SEARCH_ANY_FIELD_CONTAINS:
+ format = "(contains? \"any\" %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);
+
+ range_search = FALSE;
+ start_range = end_range = 0;
+
+ /* Apply selected filter. */
+ value = e_shell_content_get_filter_value (shell_content);
+ switch (value) {
+ case CALENDAR_FILTER_ANY_CATEGORY:
+ break;
+
+ case CALENDAR_FILTER_UNMATCHED:
+ temp = g_strdup_printf (
+ "(and (has-categories? #f) %s)", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case CALENDAR_FILTER_ACTIVE_APPOINTMENTS:
+ /* Show a year's worth of appointments. */
+ start_range = time (NULL);
+ end_range = time_add_day (start_range, 365);
+ start = isodate_from_time_t (start_range);
+ end = isodate_from_time_t (end_range);
+
+ temp = g_strdup_printf (
+ "(and %s (occur-in-time-range? "
+ "(make-time \"%s\") (make-time \"%s\")))",
+ query, start, end);
+ g_free (query);
+ query = temp;
+
+ range_search = TRUE;
+ break;
+
+ case CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS:
+ start_range = time (NULL);
+ end_range = time_add_day (start_range, 7);
+ start = isodate_from_time_t (start_range);
+ end = isodate_from_time_t (end_range);
+
+ temp = g_strdup_printf (
+ "(and %s (occur-in-time-range? "
+ "(make-time \"%s\") (make-time \"%s\")))",
+ query, start, end);
+ g_free (query);
+ query = temp;
+
+ range_search = TRUE;
+ 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 (has-categories? \"%s\") %s)",
+ category_name, query);
+ g_free (query);
+ query = temp;
+ break;
+ }
+ }
+
+ /* XXX This is wrong. We need to programmatically construct a
+ * FilterRule, tell it to build code, and pass the resulting
+ * expressing string to ECalModel. */
+ rule = filter_rule_new ();
+ e_shell_content_set_search_rule (shell_content, rule);
+ g_object_unref (rule);
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ date_navigator = e_cal_shell_sidebar_get_date_navigator (cal_shell_sidebar);
+
+ if (range_search) {
+ /* Switch to list view and hide the date navigator. */
+ action = GTK_RADIO_ACTION (ACTION (CALENDAR_VIEW_LIST));
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+ gtk_widget_hide (GTK_WIDGET (date_navigator));
+ } else {
+ /* Ensure the date navigator is visible. */
+ gtk_widget_show (GTK_WIDGET (date_navigator));
+ }
+
+ /* Submit the query. */
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ gnome_calendar_set_search_query (
+ calendar, query, range_search, start_range, end_range);
+ g_free (query);
+}
+
+void
+e_cal_shell_view_open_event (ECalShellView *cal_shell_view,
+ ECalModelComponent *comp_data)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ icalproperty *prop;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+ g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ editor = comp_editor_find_instance (uid);
+
+ if (editor != NULL)
+ goto exit;
+
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_ATTENDEE_PROPERTY);
+ if (prop != NULL)
+ flags |= COMP_EDITOR_MEETING;
+
+ if (itip_organizer_is_user (comp, comp_data->client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (itip_sentby_is_user (comp, comp_data->client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (!e_cal_component_has_attendees (comp))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = event_editor_new (comp_data->client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_unref (comp);
+
+exit:
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+void
+e_cal_shell_view_set_status_message (ECalShellView *cal_shell_view,
+ const gchar *status_message,
+ gdouble percent)
+{
+ EActivity *activity;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ activity = cal_shell_view->priv->calendar_activity;
+
+ if (status_message == NULL || *status_message == '\0') {
+ if (activity != NULL) {
+ e_activity_complete (activity);
+ g_object_unref (activity);
+ activity = NULL;
+ }
+
+ } else if (activity == NULL) {
+ activity = e_activity_new (status_message);
+ e_activity_set_percent (activity, percent);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ } else {
+ e_activity_set_percent (activity, percent);
+ e_activity_set_primary_text (activity, status_message);
+ }
+
+ cal_shell_view->priv->calendar_activity = activity;
+}
+
+void
+e_cal_shell_view_transfer_item_to (ECalShellView *cal_shell_view,
+ ECalendarViewEvent *event,
+ ECal *destination_client,
+ gboolean remove)
+{
+ icalcomponent *icalcomp;
+ icalcomponent *icalcomp_clone;
+ icalcomponent *icalcomp_event;
+ gboolean success;
+ const gchar *uid;
+
+ /* XXX This function should be split up into
+ * smaller, more understandable pieces. */
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+ g_return_if_fail (event != NULL);
+ g_return_if_fail (E_IS_CAL (destination_client));
+
+ icalcomp_event = event->comp_data->icalcomp;
+ uid = icalcomponent_get_uid (icalcomp_event);
+
+ /* Put the new object into the destination calendar. */
+
+ success = e_cal_get_object (
+ destination_client, uid, NULL, &icalcomp, NULL);
+
+ if (success) {
+ icalcomponent_free (icalcomp);
+ success = e_cal_modify_object (
+ destination_client, icalcomp_event,
+ CALOBJ_MOD_ALL, NULL);
+ if (!success)
+ return;
+ } else {
+ icalproperty *icalprop;
+ gchar *new_uid;
+
+ if (e_cal_util_component_is_instance (icalcomp_event)) {
+ success = e_cal_get_object (
+ event->comp_data->client,
+ uid, NULL, &icalcomp, NULL);
+ if (success) {
+ /* Use master object when working
+ * with a recurring event ... */
+ icalcomp_clone =
+ icalcomponent_new_clone (icalcomp);
+ icalcomponent_free (icalcomp);
+ } else {
+ /* ... or remove the recurrence ID ... */
+ icalcomp_clone =
+ icalcomponent_new_clone (icalcomp_event);
+ if (e_cal_util_component_has_recurrences (icalcomp_clone)) {
+ /* ... for non-detached instances,
+ * to make it a master object. */
+ icalprop = icalcomponent_get_first_property (
+ icalcomp_clone, ICAL_RECURRENCEID_PROPERTY);
+ if (icalprop != NULL)
+ icalcomponent_remove_property (
+ icalcomp_clone, icalprop);
+ }
+ }
+ } else
+ icalcomp_clone =
+ icalcomponent_new_clone (icalcomp_event);
+
+ icalprop = icalproperty_new_x ("1");
+ icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR");
+ icalcomponent_add_property (icalcomp_clone, icalprop);
+
+ if (!remove) {
+ /* Change the UID to avoid problems with
+ * duplicated UIDs. */
+ new_uid = e_cal_component_gen_uid ();
+ icalcomponent_set_uid (icalcomp_clone, new_uid);
+ g_free (new_uid);
+ }
+
+ new_uid = NULL;
+ success = e_cal_create_object (
+ destination_client, icalcomp_clone, &new_uid, NULL);
+ if (!success) {
+ icalcomponent_free (icalcomp_clone);
+ return;
+ }
+
+ icalcomponent_free (icalcomp_clone);
+ g_free (new_uid);
+ }
+
+ if (remove) {
+ ECal *source_client = event->comp_data->client;
+
+ /* Remove the item from the source calendar. */
+ if (e_cal_util_component_is_instance (icalcomp_event) ||
+ e_cal_util_component_has_recurrences (icalcomp_event)) {
+ icaltimetype icaltime;
+ gchar *rid;
+
+ icaltime =
+ icalcomponent_get_recurrenceid (icalcomp_event);
+ if (!icaltime_is_null_time (icaltime))
+ rid = icaltime_as_ical_string_r (icaltime);
+ else
+ rid = NULL;
+ e_cal_remove_object_with_mod (
+ source_client, uid, rid, CALOBJ_MOD_ALL, NULL);
+ g_free (rid);
+ } else
+ e_cal_remove_object (source_client, uid, NULL);
+ }
+}
+
+void
+e_cal_shell_view_update_sidebar (ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellSidebar *shell_sidebar;
+ ECalShellContent *cal_shell_content;
+ GnomeCalendar *calendar;
+ GnomeCalendarViewType view_type;
+ ECalendarView *calendar_view;
+ ECalModel *model;
+ time_t start_time, end_time;
+ struct tm start_tm, end_tm;
+ struct icaltimetype start_tt, end_tt;
+ icaltimezone *timezone;
+ gchar buffer[512];
+ gchar end_buffer[512];
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+
+ model = gnome_calendar_get_model (calendar);
+ timezone = e_cal_model_get_timezone (model);
+
+ view_type = gnome_calendar_get_view (calendar);
+ calendar_view = gnome_calendar_get_calendar_view (calendar, view_type);
+
+ e_calendar_view_get_visible_time_range (
+ calendar_view, &start_time, &end_time);
+
+ start_tt = icaltime_from_timet_with_zone (start_time, FALSE, timezone);
+ start_tm.tm_year = start_tt.year - 1900;
+ start_tm.tm_mon = start_tt.month - 1;
+ start_tm.tm_mday = start_tt.day;
+ start_tm.tm_hour = start_tt.hour;
+ start_tm.tm_min = start_tt.minute;
+ start_tm.tm_sec = start_tt.second;
+ start_tm.tm_isdst = -1;
+ start_tm.tm_wday = time_day_of_week (
+ start_tt.day, start_tt.month - 1, start_tt.year);
+
+ /* Subtract one from end_time so we don't get an extra day. */
+ end_tt = icaltime_from_timet_with_zone (end_time - 1, FALSE, timezone);
+ end_tm.tm_year = end_tt.year - 1900;
+ end_tm.tm_mon = end_tt.month - 1;
+ end_tm.tm_mday = end_tt.day;
+ end_tm.tm_hour = end_tt.hour;
+ end_tm.tm_min = end_tt.minute;
+ end_tm.tm_sec = end_tt.second;
+ end_tm.tm_isdst = -1;
+ end_tm.tm_wday = time_day_of_week (
+ end_tt.day, end_tt.month - 1, end_tt.year);
+
+ switch (view_type) {
+ case GNOME_CAL_DAY_VIEW:
+ case GNOME_CAL_WORK_WEEK_VIEW:
+ case GNOME_CAL_WEEK_VIEW:
+ if (start_tm.tm_year == end_tm.tm_year &&
+ start_tm.tm_mon == end_tm.tm_mon &&
+ start_tm.tm_mday == end_tm.tm_mday) {
+ e_utf8_strftime (
+ buffer, sizeof (buffer),
+ _("%A %d %b %Y"), &start_tm);
+ } else if (start_tm.tm_year == end_tm.tm_year) {
+ e_utf8_strftime (
+ buffer, sizeof (buffer),
+ _("%a %d %b"), &start_tm);
+ e_utf8_strftime (
+ end_buffer, sizeof (end_buffer),
+ _("%a %d %b %Y"), &end_tm);
+ strcat (buffer, " - ");
+ strcat (buffer, end_buffer);
+ } else {
+ e_utf8_strftime (
+ buffer, sizeof (buffer),
+ _("%a %d %b %Y"), &start_tm);
+ e_utf8_strftime (
+ end_buffer, sizeof (end_buffer),
+ _("%a %d %b %Y"), &end_tm);
+ strcat (buffer, " - ");
+ strcat (buffer, end_buffer);
+ }
+ break;
+
+ case GNOME_CAL_MONTH_VIEW:
+ case GNOME_CAL_LIST_VIEW:
+ if (start_tm.tm_year == end_tm.tm_year) {
+ if (start_tm.tm_mon == end_tm.tm_mon) {
+ e_utf8_strftime (
+ buffer,
+ sizeof (buffer),
+ "%d", &start_tm);
+ e_utf8_strftime (
+ end_buffer,
+ sizeof (end_buffer),
+ _("%d %b %Y"), &end_tm);
+ strcat (buffer, " - ");
+ strcat (buffer, end_buffer);
+ } else {
+ e_utf8_strftime (
+ buffer,
+ sizeof (buffer),
+ _("%d %b"), &start_tm);
+ e_utf8_strftime (
+ end_buffer,
+ sizeof (end_buffer),
+ _("%d %b %Y"), &end_tm);
+ strcat (buffer, " - ");
+ strcat (buffer, end_buffer);
+ }
+ } else {
+ e_utf8_strftime (
+ buffer, sizeof (buffer),
+ _("%d %b %Y"), &start_tm);
+ e_utf8_strftime (
+ end_buffer, sizeof (end_buffer),
+ _("%d %b %Y"), &end_tm);
+ strcat (buffer, " - ");
+ strcat (buffer, end_buffer);
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ e_shell_sidebar_set_secondary_text (shell_sidebar, buffer);
+}
+
+void
+e_cal_shell_view_update_timezone (ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalShellSidebar *cal_shell_sidebar;
+ icaltimezone *timezone;
+ ECalModel *model;
+ GList *clients, *iter;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ model = e_cal_shell_content_get_model (cal_shell_content);
+ timezone = e_cal_model_get_timezone (model);
+
+ cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar;
+ clients = e_cal_shell_sidebar_get_clients (cal_shell_sidebar);
+
+ for (iter = clients; iter != NULL; iter = iter->next) {
+ ECal *client = iter->data;
+
+ if (e_cal_get_load_state (client) == E_CAL_LOAD_LOADED)
+ e_cal_set_default_timezone (client, timezone, NULL);
+ }
+
+ g_list_free (clients);
+}
diff --git a/modules/calendar/e-cal-shell-view-private.h b/modules/calendar/e-cal-shell-view-private.h
new file mode 100644
index 0000000000..690031198e
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view-private.h
@@ -0,0 +1,180 @@
+/*
+ * e-cal-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_CAL_SHELL_VIEW_PRIVATE_H
+#define E_CAL_SHELL_VIEW_PRIVATE_H
+
+#include "e-cal-shell-view.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal-time-util.h>
+#include <libedataserver/e-categories.h>
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-sexp.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/e-dialog-utils.h"
+#include "e-util/e-error.h"
+#include "e-util/e-util.h"
+#include "widgets/misc/e-popup-action.h"
+
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/e-cal-list-view.h"
+#include "calendar/gui/e-cal-model-tasks.h"
+#include "calendar/gui/e-calendar-view.h"
+#include "calendar/gui/e-day-view.h"
+#include "calendar/gui/e-week-view.h"
+#include "calendar/gui/gnome-cal.h"
+#include "calendar/gui/goto.h"
+#include "calendar/gui/print.h"
+#include "calendar/gui/dialogs/calendar-setup.h"
+#include "calendar/gui/dialogs/copy-source-dialog.h"
+#include "calendar/gui/dialogs/event-editor.h"
+#include "calendar/gui/dialogs/memo-editor.h"
+#include "calendar/gui/dialogs/select-source-dialog.h"
+#include "calendar/gui/dialogs/task-editor.h"
+
+#include "e-cal-shell-backend.h"
+#include "e-cal-shell-content.h"
+#include "e-cal-shell-sidebar.h"
+#include "e-cal-shell-view-actions.h"
+
+#define E_CAL_SHELL_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CAL_SHELL_VIEW, ECalShellViewPrivate))
+
+/* 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-calendar-table.etspec"
+
+G_BEGIN_DECLS
+
+/* Filter items are displayed in ascending order.
+ * Non-negative values are reserved for categories. */
+enum {
+ CALENDAR_FILTER_ANY_CATEGORY = -4,
+ CALENDAR_FILTER_UNMATCHED = -3,
+ CALENDAR_FILTER_ACTIVE_APPOINTMENTS = -2,
+ CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS = -1
+};
+
+/* Search items are displayed in ascending order. */
+enum {
+ CALENDAR_SEARCH_SUMMARY_CONTAINS,
+ CALENDAR_SEARCH_DESCRIPTION_CONTAINS,
+ CALENDAR_SEARCH_ANY_FIELD_CONTAINS
+};
+
+struct _ECalShellViewPrivate {
+
+ /* These are just for convenience. */
+ ECalShellBackend *cal_shell_backend;
+ ECalShellContent *cal_shell_content;
+ ECalShellSidebar *cal_shell_sidebar;
+
+ /* The last time explicitly selected by the user. */
+ time_t base_view_time;
+
+ EActivity *calendar_activity;
+ EActivity *memopad_activity;
+ EActivity *taskpad_activity;
+};
+
+void e_cal_shell_view_private_init
+ (ECalShellView *cal_shell_view,
+ EShellViewClass *shell_view_class);
+void e_cal_shell_view_private_constructed
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_private_dispose
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_private_finalize
+ (ECalShellView *cal_shell_view);
+
+/* Private Utilities */
+
+void e_cal_shell_view_actions_init
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_execute_search
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_open_event
+ (ECalShellView *cal_shell_view,
+ ECalModelComponent *comp_data);
+void e_cal_shell_view_set_status_message
+ (ECalShellView *cal_shell_view,
+ const gchar *status_message,
+ gdouble percent);
+void e_cal_shell_view_transfer_item_to
+ (ECalShellView *cal_shell_view,
+ ECalendarViewEvent *event,
+ ECal *destination_client,
+ gboolean remove);
+void e_cal_shell_view_update_sidebar
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_update_search_filter
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_update_timezone
+ (ECalShellView *cal_shell_view);
+
+/* Memo Pad Utilities */
+
+void e_cal_shell_view_memopad_actions_init
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_memopad_actions_update
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_memopad_open_memo
+ (ECalShellView *cal_shell_view,
+ ECalModelComponent *comp_data);
+void e_cal_shell_view_memopad_set_status_message
+ (ECalShellView *cal_shell_view,
+ const gchar *status_message,
+ gdouble percent);
+
+/* Task Pad Utilities */
+
+void e_cal_shell_view_taskpad_actions_init
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_taskpad_actions_update
+ (ECalShellView *cal_shell_view);
+void e_cal_shell_view_taskpad_open_task
+ (ECalShellView *cal_shell_view,
+ ECalModelComponent *comp_data);
+void e_cal_shell_view_taskpad_set_status_message
+ (ECalShellView *cal_shell_view,
+ const gchar *status_message,
+ gdouble percent);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_VIEW_PRIVATE_H */
diff --git a/modules/calendar/e-cal-shell-view-taskpad.c b/modules/calendar/e-cal-shell-view-taskpad.c
new file mode 100644
index 0000000000..965ec47c49
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view-taskpad.c
@@ -0,0 +1,654 @@
+/*
+ * e-cal-shell-view-taskpad.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-cal-shell-view-private.h"
+
+/* Much of this file is based on e-task-shell-view-actions.c. */
+
+static void
+action_calendar_taskpad_assign_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the first selected task. */
+ e_cal_shell_view_taskpad_open_task (cal_shell_view, comp_data);
+
+ /* FIXME Need to actually assign the task. */
+}
+
+static void
+action_calendar_taskpad_clipboard_copy_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ e_calendar_table_copy_clipboard (task_table);
+}
+
+static void
+action_calendar_taskpad_clipboard_cut_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ e_calendar_table_cut_clipboard (task_table);
+}
+
+static void
+action_calendar_taskpad_clipboard_paste_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ e_calendar_table_paste_clipboard (task_table);
+}
+
+static void
+action_calendar_taskpad_delete_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ e_cal_shell_view_taskpad_set_status_message (
+ cal_shell_view, _("Deleting selected tasks..."), -1.0);
+ e_calendar_table_delete_selected (task_table);
+ e_cal_shell_view_taskpad_set_status_message (
+ cal_shell_view, NULL, -1.0);
+}
+
+static void
+action_calendar_taskpad_forward_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only forward the first selected task. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+ itip_send_comp (
+ E_CAL_COMPONENT_METHOD_PUBLISH, comp,
+ comp_data->client, NULL, NULL, NULL, TRUE, FALSE);
+ g_object_unref (comp);
+}
+
+static void
+action_calendar_taskpad_mark_complete_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ GSList *list, *iter;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+ list = e_calendar_table_get_selected (task_table);
+ model = e_calendar_table_get_model (task_table);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ e_cal_model_tasks_mark_comp_complete (
+ E_CAL_MODEL_TASKS (model), comp_data);
+ }
+
+ g_slist_free (list);
+}
+
+static void
+action_calendar_taskpad_mark_incomplete_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ GSList *list, *iter;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+ list = e_calendar_table_get_selected (task_table);
+ model = e_calendar_table_get_model (task_table);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ e_cal_model_tasks_mark_comp_incomplete (
+ E_CAL_MODEL_TASKS (model), comp_data);
+ }
+
+ g_slist_free (list);
+}
+
+static void
+action_calendar_taskpad_new_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ ECal *client;
+ ECalComponent *comp;
+ CompEditor *editor;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ client = comp_data->client;
+ editor = task_editor_new (client, shell, COMP_EDITOR_NEW_ITEM);
+ comp = cal_comp_task_new_with_defaults (client);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (client);
+}
+
+static void
+action_calendar_taskpad_open_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the first selected task. */
+ e_cal_shell_view_taskpad_open_task (cal_shell_view, comp_data);
+}
+
+static void
+action_calendar_taskpad_open_url_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ icalproperty *prop;
+ const gchar *uri;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+
+ /* XXX We only open the URI of the first selected task. */
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ g_return_if_fail (prop == NULL);
+
+ uri = icalproperty_get_url (prop);
+ e_show_uri (GTK_WINDOW (shell_window), uri);
+}
+
+static void
+action_calendar_taskpad_print_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GtkPrintOperationAction print_action;
+ GSList *list;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only print the first selected task. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_cal_component_set_icalcomponent (comp, clone);
+ print_comp (comp, comp_data->client, print_action);
+ g_object_unref (comp);
+}
+
+static void
+action_calendar_taskpad_save_as_cb (GtkAction *action,
+ ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+ gchar *filename;
+ gchar *string;
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ filename = e_file_dialog_save (_("Save as..."), NULL);
+ if (filename == NULL)
+ return;
+
+ string = e_cal_get_component_as_string (
+ comp_data->client, comp_data->icalcomp);
+ if (string == NULL) {
+ g_warning ("Could not convert task to a string");
+ return;
+ }
+
+ e_write_file_uri (filename, string);
+
+ g_free (filename);
+ g_free (string);
+}
+
+static GtkActionEntry calendar_taskpad_entries[] = {
+
+ { "calendar-taskpad-assign",
+ NULL,
+ N_("_Assign Task"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_taskpad_assign_cb) },
+
+ { "calendar-taskpad-clipboard-copy",
+ GTK_STOCK_COPY,
+ NULL,
+ NULL,
+ N_("Copy selected tasks"),
+ G_CALLBACK (action_calendar_taskpad_clipboard_copy_cb) },
+
+ { "calendar-taskpad-clipboard-cut",
+ GTK_STOCK_CUT,
+ NULL,
+ NULL,
+ N_("Cut selected tasks"),
+ G_CALLBACK (action_calendar_taskpad_clipboard_cut_cb) },
+
+ { "calendar-taskpad-clipboard-paste",
+ GTK_STOCK_PASTE,
+ NULL,
+ NULL,
+ N_("Paste tasks from the clipboard"),
+ G_CALLBACK (action_calendar_taskpad_clipboard_paste_cb) },
+
+ { "calendar-taskpad-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete Task"),
+ NULL,
+ N_("Delete selected tasks"),
+ G_CALLBACK (action_calendar_taskpad_delete_cb) },
+
+ { "calendar-taskpad-forward",
+ "mail-forward",
+ N_("_Forward as iCalendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_taskpad_forward_cb) },
+
+ { "calendar-taskpad-mark-complete",
+ NULL,
+ N_("_Mark as Complete"),
+ NULL,
+ N_("Mark selected tasks as complete"),
+ G_CALLBACK (action_calendar_taskpad_mark_complete_cb) },
+
+ { "calendar-taskpad-mark-incomplete",
+ NULL,
+ N_("_Mar_k as Incomplete"),
+ NULL,
+ N_("Mark selected tasks as incomplete"),
+ G_CALLBACK (action_calendar_taskpad_mark_incomplete_cb) },
+
+ { "calendar-taskpad-new",
+ "stock_task",
+ N_("New _Task"),
+ NULL,
+ N_("Create a new task"),
+ G_CALLBACK (action_calendar_taskpad_new_cb) },
+
+ { "calendar-taskpad-open",
+ GTK_STOCK_OPEN,
+ N_("_Open Task"),
+ NULL,
+ N_("View the selected task"),
+ G_CALLBACK (action_calendar_taskpad_open_cb) },
+
+ { "calendar-taskpad-open-url",
+ "applications-internet",
+ N_("Open _Web Page"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_taskpad_open_url_cb) },
+
+ { "calendar-taskpad-save-as",
+ GTK_STOCK_SAVE_AS,
+ N_("_Save as iCalendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_calendar_taskpad_save_as_cb) }
+};
+
+static GtkActionEntry lockdown_printing_entries[] = {
+
+ { "calendar-taskpad-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ NULL,
+ N_("Print the selected task"),
+ G_CALLBACK (action_calendar_taskpad_print_cb) }
+};
+
+void
+e_cal_shell_view_taskpad_actions_init (ECalShellView *cal_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ GtkActionGroup *action_group;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ /* Calendar Actions */
+ action_group = ACTION_GROUP (CALENDAR);
+ gtk_action_group_add_actions (
+ action_group, calendar_taskpad_entries,
+ G_N_ELEMENTS (calendar_taskpad_entries), cal_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), cal_shell_view);
+}
+
+void
+e_cal_shell_view_taskpad_actions_update (ECalShellView *cal_shell_view)
+{
+ ECalShellContent *cal_shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ECalendarTable *task_table;
+ ETable *table;
+ GtkAction *action;
+ GSList *list, *iter;
+ const gchar *label;
+ gboolean assignable = TRUE;
+ gboolean editable = TRUE;
+ gboolean has_url = FALSE;
+ gboolean sensitive;
+ gint n_selected;
+ gint n_complete = 0;
+ gint n_incomplete = 0;
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = cal_shell_view->priv->cal_shell_content;
+ task_table = e_cal_shell_content_get_task_table (cal_shell_content);
+
+ table = e_calendar_table_get_table (task_table);
+ n_selected = e_table_selected_count (table);
+
+ list = e_calendar_table_get_selected (task_table);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ icalproperty *prop;
+ const gchar *cap;
+ gboolean read_only;
+
+ e_cal_is_read_only (comp_data->client, &read_only, NULL);
+ editable &= !read_only;
+
+ cap = CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT;
+ if (e_cal_get_static_capability (comp_data->client, cap))
+ assignable = FALSE;
+
+ cap = CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK;
+ if (e_cal_get_static_capability (comp_data->client, cap))
+ assignable = FALSE;
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ has_url |= (prop != NULL);
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_COMPLETED_PROPERTY);
+ if (prop != NULL)
+ n_complete++;
+ else
+ n_incomplete++;
+ }
+ g_slist_free (list);
+
+ action = ACTION (CALENDAR_TASKPAD_ASSIGN);
+ sensitive = (n_selected == 1) && editable && assignable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_CLIPBOARD_COPY);
+ sensitive = (n_selected > 0);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_CLIPBOARD_CUT);
+ sensitive = (n_selected > 0) && editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_CLIPBOARD_PASTE);
+ sensitive = editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_DELETE);
+ sensitive = (n_selected > 0) && editable;
+ gtk_action_set_sensitive (action, sensitive);
+ label = ngettext ("Delete Task", "Delete Tasks", n_selected);
+ g_object_set (action, "label", label, NULL);
+
+ action = ACTION (CALENDAR_TASKPAD_FORWARD);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_MARK_COMPLETE);
+ sensitive = (n_selected > 0) && editable && (n_incomplete > 0);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_MARK_INCOMPLETE);
+ sensitive = (n_selected > 0) && editable && (n_complete > 0);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_OPEN);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_OPEN_URL);
+ sensitive = (n_selected == 1) && has_url;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_PRINT);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_TASKPAD_SAVE_AS);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+void
+e_cal_shell_view_taskpad_open_task (ECalShellView *cal_shell_view,
+ ECalModelComponent *comp_data)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ icalproperty *prop;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+ g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ editor = comp_editor_find_instance (uid);
+
+ if (editor != NULL)
+ goto exit;
+
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_ATTENDEE_PROPERTY);
+ if (prop != NULL)
+ flags |= COMP_EDITOR_IS_ASSIGNED;
+
+ if (itip_organizer_is_user (comp, comp_data->client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (!e_cal_component_has_attendees (comp))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = task_editor_new (comp_data->client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_ref (comp);
+
+ if (flags & COMP_EDITOR_IS_ASSIGNED)
+ task_editor_show_assignment (TASK_EDITOR (editor));
+
+exit:
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+void
+e_cal_shell_view_taskpad_set_status_message (ECalShellView *cal_shell_view,
+ const gchar *status_message,
+ gdouble percent)
+{
+ EActivity *activity;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+
+ g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view));
+
+ shell_view = E_SHELL_VIEW (cal_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ activity = cal_shell_view->priv->taskpad_activity;
+
+ if (status_message == NULL || *status_message == '\0') {
+ if (activity != NULL) {
+ e_activity_complete (activity);
+ g_object_unref (activity);
+ activity = NULL;
+ }
+
+ } else if (activity == NULL) {
+ activity = e_activity_new (status_message);
+ e_activity_set_percent (activity, percent);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ } else {
+ e_activity_set_percent (activity, percent);
+ e_activity_set_primary_text (activity, status_message);
+ }
+
+ cal_shell_view->priv->taskpad_activity = activity;
+}
diff --git a/modules/calendar/e-cal-shell-view.c b/modules/calendar/e-cal-shell-view.c
new file mode 100644
index 0000000000..d9d21799b9
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view.c
@@ -0,0 +1,219 @@
+/*
+ * e-cal-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-cal-shell-view-private.h"
+
+static gpointer parent_class;
+static GType cal_shell_view_type;
+
+static void
+cal_shell_view_dispose (GObject *object)
+{
+ e_cal_shell_view_private_dispose (E_CAL_SHELL_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+cal_shell_view_finalize (GObject *object)
+{
+ e_cal_shell_view_private_finalize (E_CAL_SHELL_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+cal_shell_view_constructed (GObject *object)
+{
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ e_cal_shell_view_private_constructed (E_CAL_SHELL_VIEW (object));
+}
+
+static void
+cal_shell_view_update_actions (EShellView *shell_view)
+{
+#if 0
+ ECalShellViewPrivate *priv;
+ ECalShellContent *cal_shell_content;
+ ECalShellSidebar *cal_shell_sidebar;
+ EShellWindow *shell_window;
+ GnomeCalendarViewType view_type;
+ GnomeCalendar *calendar;
+ ECalendarView *view;
+ ECalModel *model;
+ ESourceSelector *selector;
+ ESource *source;
+ GtkAction *action;
+ GList *list, *iter;
+ const gchar *uri = NULL;
+ gboolean user_created_source;
+ gboolean editable = TRUE;
+ gboolean recurring = FALSE;
+ gboolean sensitive;
+ gint n_selected;
+
+ priv = E_CAL_SHELL_VIEW_GET_PRIVATE (shell_view);
+
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ cal_shell_content = priv->cal_shell_content;
+ calendar = e_cal_shell_content_get_calendar (cal_shell_content);
+ view_type = gnome_calendar_get_view (calendar);
+ view = gnome_calendar_get_calendar_view (calendar, view_type);
+ model = e_calendar_view_get_model (view);
+
+ cal_shell_sidebar = priv->cal_shell_sidebar;
+ selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
+
+ list = e_calendar_view_get_selected_events (view);
+ n_selected = g_list_length (list);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ gboolean read_only;
+
+ e_cal_is_read_only (comp_data->client, &read_only, NULL);
+ editable &= !read_only;
+
+ if (e_cal_util_component_has_recurrences (comp_data->icalcomp))
+ recurring |= TRUE;
+ else if (e_cal_util_component_is_instance (comp_data->icalcomp))
+ recurring |= TRUE;
+ }
+
+ source = e_source_selector_peek_primary_selection (selector);
+ if (source != NULL)
+ uri = e_source_peek_relative_uri (source);
+ user_created_source = (uri != NULL && strcmp (uri, "system") != 0);
+
+ action = ACTION (CALENDAR_COPY);
+ sensitive = (source != NULL);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_DELETE);
+ sensitive = user_created_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_PROPERTIES);
+ sensitive = (source != NULL);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (CALENDAR_RENAME);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_CLIPBOARD_COPY);
+ sensitive = (n_selected > 0);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_CLIPBOARD_CUT);
+ sensitive = (n_selected > 0) && editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_CLIPBOARD_PASTE);
+ sensitive = editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_DELETE);
+ sensitive = (n_selected > 0) && editable && !recurring;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_DELETE_OCCURRENCE);
+ sensitive = (n_selected > 0) && editable && recurring;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_DELETE_OCCURRENCE_ALL);
+ sensitive = (n_selected > 0) && editable && recurring;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (EVENT_OPEN);
+ sensitive = (n_selected == 1);
+ gtk_action_set_sensitive (action, sensitive);
+#endif
+}
+
+static void
+cal_shell_view_class_init (ECalShellViewClass *class,
+ GTypeModule *type_module)
+{
+ GObjectClass *object_class;
+ EShellViewClass *shell_view_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ECalShellViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = cal_shell_view_dispose;
+ object_class->finalize = cal_shell_view_finalize;
+ object_class->constructed = cal_shell_view_constructed;
+
+ shell_view_class = E_SHELL_VIEW_CLASS (class);
+ shell_view_class->label = _("Calendar");
+ shell_view_class->icon_name = "x-office-calendar";
+ shell_view_class->ui_definition = "evolution-calendars.ui";
+ shell_view_class->ui_manager_id = "org.gnome.evolution.calendars";
+ shell_view_class->search_options = "/calendar-search-options";
+ shell_view_class->search_rules = "caltypes.xml";
+ shell_view_class->new_shell_content = e_cal_shell_content_new;
+ shell_view_class->new_shell_sidebar = e_cal_shell_sidebar_new;
+ shell_view_class->update_actions = cal_shell_view_update_actions;
+}
+
+static void
+cal_shell_view_init (ECalShellView *cal_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ cal_shell_view->priv =
+ E_CAL_SHELL_VIEW_GET_PRIVATE (cal_shell_view);
+
+ e_cal_shell_view_private_init (cal_shell_view, shell_view_class);
+}
+
+GType
+e_cal_shell_view_get_type (void)
+{
+ return cal_shell_view_type;
+}
+
+void
+e_cal_shell_view_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (ECalShellViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) cal_shell_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ type_module,
+ sizeof (ECalShellView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) cal_shell_view_init,
+ NULL /* value_table */
+ };
+
+ cal_shell_view_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_VIEW,
+ "ECalShellView", &type_info, 0);
+}
diff --git a/modules/calendar/e-cal-shell-view.h b/modules/calendar/e-cal-shell-view.h
new file mode 100644
index 0000000000..0cd7382012
--- /dev/null
+++ b/modules/calendar/e-cal-shell-view.h
@@ -0,0 +1,66 @@
+/*
+ * e-cal-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_CAL_SHELL_VIEW_H
+#define E_CAL_SHELL_VIEW_H
+
+#include <shell/e-shell-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_SHELL_VIEW \
+ (e_cal_shell_view_get_type ())
+#define E_CAL_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_SHELL_VIEW, ECalShellView))
+#define E_CAL_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_SHELL_VIEW, ECalShellViewClass))
+#define E_IS_CAL_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_SHELL_VIEW))
+#define E_IS_CAL_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_SHELL_VIEW))
+#define E_CAL_SHELL_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_SHELL_VIEW, ECalShellViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalShellView ECalShellView;
+typedef struct _ECalShellViewClass ECalShellViewClass;
+typedef struct _ECalShellViewPrivate ECalShellViewPrivate;
+
+struct _ECalShellView {
+ EShellView parent;
+ ECalShellViewPrivate *priv;
+};
+
+struct _ECalShellViewClass {
+ EShellViewClass parent_class;
+};
+
+GType e_cal_shell_view_get_type (void);
+void e_cal_shell_view_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_CAL_SHELL_VIEW_H */
diff --git a/modules/calendar/e-memo-shell-backend.c b/modules/calendar/e-memo-shell-backend.c
new file mode 100644
index 0000000000..6c6a36491c
--- /dev/null
+++ b/modules/calendar/e-memo-shell-backend.c
@@ -0,0 +1,653 @@
+/*
+ * e-memo-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-memo-shell-backend.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal.h>
+#include <libedataserver/e-url.h>
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserver/e-source-group.h>
+
+#include "shell/e-shell.h"
+#include "shell/e-shell-backend.h"
+#include "shell/e-shell-window.h"
+
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/dialogs/calendar-setup.h"
+#include "calendar/gui/dialogs/memo-editor.h"
+
+#include "e-memo-shell-migrate.h"
+#include "e-memo-shell-view.h"
+
+#define E_MEMO_SHELL_BACKEND_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendPrivate))
+
+#define WEB_BASE_URI "webcal://"
+#define PERSONAL_RELATIVE_URI "system"
+
+struct _EMemoShellBackendPrivate {
+ ESourceList *source_list;
+};
+
+enum {
+ PROP_0,
+ PROP_SOURCE_LIST
+};
+
+static gpointer parent_class;
+static GType memo_shell_backend_type;
+
+static void
+memo_shell_backend_ensure_sources (EShellBackend *shell_backend)
+{
+ /* XXX This is basically the same algorithm across all modules.
+ * Maybe we could somehow integrate this into EShellBackend? */
+
+ EMemoShellBackendPrivate *priv;
+ ESourceGroup *on_this_computer;
+ ESourceGroup *on_the_web;
+ ESource *personal;
+ EShell *shell;
+ EShellSettings *shell_settings;
+ GSList *groups, *iter;
+ const gchar *data_dir;
+ const gchar *name;
+ gchar *base_uri;
+ gchar *filename;
+
+ on_this_computer = NULL;
+ on_the_web = NULL;
+ personal = NULL;
+
+ priv = E_MEMO_SHELL_BACKEND_GET_PRIVATE (shell_backend);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ if (!e_cal_get_sources (&priv->source_list, E_CAL_SOURCE_TYPE_JOURNAL, NULL)) {
+ g_warning ("Could not get memo 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_the_web == NULL &&
+ strcmp (WEB_BASE_URI, group_base_uri) == 0)
+ on_the_web = 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;
+ GSList *selected;
+ gchar *primary;
+
+ source = e_source_new (name, PERSONAL_RELATIVE_URI);
+ e_source_group_add_source (on_this_computer, source, -1);
+ g_object_unref (source);
+
+ primary = e_shell_settings_get_string (
+ shell_settings, "cal-primary-memo-list");
+
+ selected = calendar_config_get_memos_selected ();
+
+ if (primary == NULL && selected == NULL) {
+ const gchar *uid;
+
+ uid = e_source_peek_uid (source);
+ selected = g_slist_prepend (NULL, g_strdup (uid));
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-memo-list", uid);
+ calendar_config_set_memos_selected (selected);
+ }
+
+ g_slist_foreach (selected, (GFunc) g_free, NULL);
+ g_slist_free (selected);
+ g_free (primary);
+ } else {
+ /* Force the source name to the current locale. */
+ e_source_set_name (personal, name);
+ }
+
+ name = _("On The Web");
+
+ if (on_the_web == NULL) {
+ ESourceGroup *source_group;
+
+ source_group = e_source_group_new (name, WEB_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_the_web, name);
+ }
+
+ g_free (base_uri);
+}
+
+static void
+memo_shell_backend_memo_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+
+ editor = memo_editor_new (cal, shell, flags);
+ comp = cal_comp_memo_new_with_defaults (cal);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+memo_shell_backend_memo_shared_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+ flags |= COMP_EDITOR_IS_SHARED;
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = memo_editor_new (cal, shell, flags);
+ comp = cal_comp_memo_new_with_defaults (cal);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+action_memo_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ ECal *cal = NULL;
+ ECalSourceType source_type;
+ ESourceList *source_list;
+ EShellSettings *shell_settings;
+ EShell *shell;
+ const gchar *action_name;
+ gchar *uid;
+
+ /* This callback is used for both memos and shared memos. */
+
+ source_type = E_CAL_SOURCE_TYPE_JOURNAL;
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ if (!e_cal_get_sources (&source_list, source_type, NULL)) {
+ g_warning ("Could not get memo sources from GConf!");
+ return;
+ }
+
+ uid = e_shell_settings_get_string (
+ shell_settings, "cal-primary-memo-list");
+
+ if (uid != NULL) {
+ ESource *source;
+
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ if (source != NULL)
+ cal = auth_new_cal_from_source (source, source_type);
+ g_free (uid);
+ }
+
+ if (cal == NULL)
+ cal = auth_new_cal_from_default (source_type);
+
+ g_return_if_fail (cal != NULL);
+
+ /* Connect the appropriate signal handler. */
+ action_name = gtk_action_get_name (action);
+ if (strcmp (action_name, "memo-shared-new") == 0)
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (memo_shell_backend_memo_shared_new_cb),
+ shell);
+ else
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (memo_shell_backend_memo_new_cb),
+ shell);
+
+ e_cal_open_async (cal, FALSE);
+}
+
+static void
+action_memo_list_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ calendar_setup_new_memo_list (GTK_WINDOW (shell_window));
+}
+
+static GtkActionEntry item_entries[] = {
+
+ { "memo-new",
+ "stock_insert-note",
+ NC_("New", "Mem_o"),
+ "<Shift><Control>o",
+ N_("Create a new memo"),
+ G_CALLBACK (action_memo_new_cb) },
+
+ { "memo-shared-new",
+ "stock_insert-note",
+ N_("_Shared Memo"),
+ "<Shift><Control>h",
+ N_("Create a new shared memo"),
+ G_CALLBACK (action_memo_new_cb) }
+};
+
+static GtkActionEntry source_entries[] = {
+
+ { "memo-list-new",
+ "stock_notes",
+ NC_("New", "Memo Li_st"),
+ NULL,
+ N_("Create a new memo list"),
+ G_CALLBACK (action_memo_list_new_cb) }
+};
+
+static gboolean
+memo_shell_backend_handle_uri_cb (EShellBackend *shell_backend,
+ const gchar *uri)
+{
+ EShell *shell;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECal *client;
+ ECalComponent *comp;
+ ESource *source;
+ ESourceList *source_list;
+ ECalSourceType source_type;
+ EUri *euri;
+ icalcomponent *icalcomp;
+ const gchar *cp;
+ gchar *source_uid = NULL;
+ gchar *comp_uid = NULL;
+ gchar *comp_rid = NULL;
+ gboolean handled = FALSE;
+ GError *error = NULL;
+
+ source_type = E_CAL_SOURCE_TYPE_JOURNAL;
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ if (strncmp (uri, "memo:", 5) != 0)
+ return FALSE;
+
+ euri = e_uri_new (uri);
+ cp = euri->query;
+ if (cp == NULL)
+ goto exit;
+
+ while (*cp != '\0') {
+ gchar *header;
+ gchar *content;
+ gsize header_len;
+ gsize content_len;
+
+ header_len = strcspn (cp, "=&");
+
+ /* If it's malformed, give up. */
+ if (cp[header_len] != '=')
+ break;
+
+ header = (gchar *) cp;
+ header[header_len] = '\0';
+ cp += header_len + 1;
+
+ content_len = strcspn (cp, "&");
+
+ content = g_strndup (cp, content_len);
+ if (g_ascii_strcasecmp (header, "source-uid") == 0)
+ source_uid = g_strdup (content);
+ else if (g_ascii_strcasecmp (header, "comp-uid") == 0)
+ comp_uid = g_strdup (content);
+ else if (g_ascii_strcasecmp (header, "comp-rid") == 0)
+ comp_rid = g_strdup (content);
+ g_free (content);
+
+ cp += content_len;
+ if (*cp == '&') {
+ cp++;
+ if (strcmp (cp, "amp;") == 0)
+ cp += 4;
+ }
+ }
+
+ if (source_uid == NULL || comp_uid == NULL)
+ goto exit;
+
+ /* URI is valid, so consider it handled. Whether
+ * we successfully open it is another matter... */
+ handled = TRUE;
+
+ if (!e_cal_get_sources (&source_list, source_type, NULL)) {
+ g_printerr ("Could not get memo sources from GConf!\n");
+ goto exit;
+ }
+
+ source = e_source_list_peek_source_by_uid (source_list, source_uid);
+ if (source == NULL) {
+ g_printerr ("No source for UID `%s'\n", source_uid);
+ g_object_unref (source_list);
+ goto exit;
+ }
+
+ client = auth_new_cal_from_source (source, source_type);
+ if (client == NULL || !e_cal_open (client, TRUE, &error)) {
+ g_printerr ("%s\n", error->message);
+ g_object_unref (source_list);
+ g_error_free (error);
+ goto exit;
+ }
+
+ /* XXX Copied from e_memo_shell_view_open_memo().
+ * Clearly a new utility function is needed. */
+
+ editor = comp_editor_find_instance (comp_uid);
+
+ if (editor != NULL)
+ goto present;
+
+ if (!e_cal_get_object (client, comp_uid, comp_rid, &icalcomp, &error)) {
+ g_printerr ("%s\n", error->message);
+ g_object_unref (source_list);
+ g_error_free (error);
+ goto exit;
+ }
+
+ comp = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (comp, icalcomp);
+
+ if (e_cal_component_has_organizer (comp))
+ flags |= COMP_EDITOR_IS_SHARED;
+
+ if (itip_organizer_is_user (comp, client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = memo_editor_new (client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_unref (comp);
+
+present:
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (source_list);
+ g_object_unref (client);
+
+exit:
+ g_free (source_uid);
+ g_free (comp_uid);
+ g_free (comp_rid);
+
+ e_uri_free (euri);
+
+ return handled;
+}
+
+static void
+memo_shell_backend_window_created_cb (EShellBackend *shell_backend,
+ GtkWindow *window)
+{
+ const gchar *module_name;
+
+ if (!E_IS_SHELL_WINDOW (window))
+ return;
+
+ module_name = E_SHELL_BACKEND_GET_CLASS (shell_backend)->name;
+
+ e_shell_window_register_new_item_actions (
+ E_SHELL_WINDOW (window), module_name,
+ item_entries, G_N_ELEMENTS (item_entries));
+
+ e_shell_window_register_new_source_actions (
+ E_SHELL_WINDOW (window), module_name,
+ source_entries, G_N_ELEMENTS (source_entries));
+}
+
+static void
+memo_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_memo_shell_backend_get_source_list (
+ E_MEMO_SHELL_BACKEND (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+memo_shell_backend_dispose (GObject *object)
+{
+ EMemoShellBackendPrivate *priv;
+
+ priv = E_MEMO_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
+memo_shell_backend_constructed (GObject *object)
+{
+ EShell *shell;
+ EShellBackend *shell_backend;
+
+ shell_backend = E_SHELL_BACKEND (object);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ memo_shell_backend_ensure_sources (shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "handle-uri",
+ G_CALLBACK (memo_shell_backend_handle_uri_cb),
+ shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "window-created",
+ G_CALLBACK (memo_shell_backend_window_created_cb),
+ shell_backend);
+}
+
+static void
+memo_shell_backend_class_init (EMemoShellBackendClass *class)
+{
+ GObjectClass *object_class;
+ EShellBackendClass *shell_backend_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMemoShellBackendPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = memo_shell_backend_get_property;
+ object_class->dispose = memo_shell_backend_dispose;
+ object_class->constructed = memo_shell_backend_constructed;
+
+ shell_backend_class = E_SHELL_BACKEND_CLASS (class);
+ shell_backend_class->shell_view_type = E_TYPE_MEMO_SHELL_VIEW;
+ shell_backend_class->name = "memos";
+ shell_backend_class->aliases = "";
+ shell_backend_class->schemes = "memo";
+ shell_backend_class->sort_order = 500;
+ shell_backend_class->start = NULL;
+ shell_backend_class->migrate = e_memo_shell_backend_migrate;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE_LIST,
+ g_param_spec_object (
+ "source-list",
+ _("Source List"),
+ _("The registry of memo lists"),
+ E_TYPE_SOURCE_LIST,
+ G_PARAM_READABLE));
+}
+
+static void
+memo_shell_backend_init (EMemoShellBackend *memo_shell_backend)
+{
+ memo_shell_backend->priv =
+ E_MEMO_SHELL_BACKEND_GET_PRIVATE (memo_shell_backend);
+}
+
+GType
+e_memo_shell_backend_get_type (void)
+{
+ return memo_shell_backend_type;
+}
+
+void
+e_memo_shell_backend_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EMemoShellBackendClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) memo_shell_backend_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMemoShellBackend),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) memo_shell_backend_init,
+ NULL /* value_table */
+ };
+
+ memo_shell_backend_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_BACKEND,
+ "EMemoShellBackend", &type_info, 0);
+}
+
+ESourceList *
+e_memo_shell_backend_get_source_list (EMemoShellBackend *memo_shell_backend)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_BACKEND (memo_shell_backend), NULL);
+
+ return memo_shell_backend->priv->source_list;
+}
diff --git a/modules/calendar/e-memo-shell-backend.h b/modules/calendar/e-memo-shell-backend.h
new file mode 100644
index 0000000000..37fe41a784
--- /dev/null
+++ b/modules/calendar/e-memo-shell-backend.h
@@ -0,0 +1,70 @@
+/*
+ * e-memo-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_MEMO_SHELL_BACKEND_H
+#define E_MEMO_SHELL_BACKEND_H
+
+#include <shell/e-shell-backend.h>
+#include <libedataserver/e-source-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MEMO_SHELL_BACKEND \
+ (e_memo_shell_backend_get_type ())
+#define E_MEMO_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackend))
+#define E_MEMO_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendClass))
+#define E_IS_MEMO_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MEMO_SHELL_BACKEND))
+#define E_IS_MEMO_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MEMO_SHELL_BACKEND))
+#define E_MEMO_SHELL_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMemoShellBackend EMemoShellBackend;
+typedef struct _EMemoShellBackendClass EMemoShellBackendClass;
+typedef struct _EMemoShellBackendPrivate EMemoShellBackendPrivate;
+
+struct _EMemoShellBackend {
+ EShellBackend parent;
+ EMemoShellBackendPrivate *priv;
+};
+
+struct _EMemoShellBackendClass {
+ EShellBackendClass parent_class;
+};
+
+GType e_memo_shell_backend_get_type (void);
+void e_memo_shell_backend_register_type
+ (GTypeModule *type_module);
+ESourceList * e_memo_shell_backend_get_source_list
+ (EMemoShellBackend *memo_shell_backend);
+
+G_END_DECLS
+
+#endif /* E_MEMO_SHELL_BACKEND_H */
diff --git a/modules/calendar/e-memo-shell-content.c b/modules/calendar/e-memo-shell-content.c
new file mode 100644
index 0000000000..2f4e436056
--- /dev/null
+++ b/modules/calendar/e-memo-shell-content.c
@@ -0,0 +1,721 @@
+/*
+ * e-memo-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-memo-shell-content.h"
+
+#include <glib/gi18n.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/menus/gal-view-etable.h"
+#include "widgets/misc/e-paned.h"
+
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/e-cal-model-memos.h"
+#include "calendar/gui/e-memo-table.h"
+
+#define E_MEMO_SHELL_CONTENT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContentPrivate))
+
+#define E_MEMO_TABLE_DEFAULT_STATE \
+ "<?xml version=\"1.0\"?>" \
+ "<ETableState>" \
+ " <column source=\"1\"/>" \
+ " <column source=\"0\"/>" \
+ " <column source=\"2\"/>" \
+ " <grouping/>" \
+ "</ETableState>"
+
+struct _EMemoShellContentPrivate {
+ GtkWidget *paned;
+ GtkWidget *memo_table;
+ GtkWidget *memo_preview;
+
+ ECalModel *memo_model;
+ GalViewInstance *view_instance;
+ GtkOrientation orientation;
+
+ gchar *current_uid;
+
+ guint preview_visible : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_MODEL,
+ PROP_ORIENTATION,
+ PROP_PREVIEW_VISIBLE
+};
+
+enum {
+ TARGET_VCALENDAR
+};
+
+static GtkTargetEntry drag_types[] = {
+ { (gchar *) "text/calendar", 0, TARGET_VCALENDAR },
+ { (gchar *) "text/x-calendar", 0, TARGET_VCALENDAR }
+};
+
+static gpointer parent_class;
+static GType memo_shell_content_type;
+
+static void
+memo_shell_content_display_view_cb (EMemoShellContent *memo_shell_content,
+ GalView *gal_view)
+{
+ EMemoTable *memo_table;
+ ETable *table;
+
+ if (!GAL_IS_VIEW_ETABLE (gal_view))
+ return;
+
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ table = e_memo_table_get_table (memo_table);
+
+ gal_view_etable_attach_table (GAL_VIEW_ETABLE (gal_view), table);
+}
+
+static void
+memo_shell_content_table_foreach_cb (gint model_row,
+ gpointer user_data)
+{
+ ECalModelComponent *comp_data;
+ icalcomponent *clone;
+ icalcomponent *vcal;
+ gchar *string;
+
+ struct {
+ ECalModel *model;
+ GSList *list;
+ } *foreach_data = user_data;
+
+ comp_data = e_cal_model_get_component_at (
+ foreach_data->model, model_row);
+
+ vcal = e_cal_util_new_top_level ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_util_add_timezones_from_component (vcal, comp_data->icalcomp);
+ icalcomponent_add_component (vcal, clone);
+
+ /* String is owned by libical; do not free. */
+ string = icalcomponent_as_ical_string (vcal);
+ if (string != NULL) {
+ ESource *source;
+ const gchar *source_uid;
+
+ source = e_cal_get_source (comp_data->client);
+ source_uid = e_source_peek_uid (source);
+
+ foreach_data->list = g_slist_prepend (
+ foreach_data->list,
+ g_strdup_printf ("%s\n%s", source_uid, string));
+ }
+
+ icalcomponent_free (vcal);
+}
+
+static void
+memo_shell_content_table_drag_data_get_cb (EMemoShellContent *memo_shell_content,
+ gint row,
+ gint col,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ EMemoTable *memo_table;
+ ETable *table;
+
+ struct {
+ ECalModel *model;
+ GSList *list;
+ } foreach_data;
+
+ if (info != TARGET_VCALENDAR)
+ return;
+
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ table = e_memo_table_get_table (memo_table);
+
+ foreach_data.model = e_memo_table_get_model (memo_table);
+ foreach_data.list = NULL;
+
+ e_table_selected_row_foreach (
+ table, memo_shell_content_table_foreach_cb,
+ &foreach_data);
+
+ if (foreach_data.list != NULL) {
+ cal_comp_selection_set_string_list (
+ selection_data, foreach_data.list);
+ g_slist_foreach (foreach_data.list, (GFunc) g_free, NULL);
+ g_slist_free (foreach_data.list);
+ }
+}
+
+static void
+memo_shell_content_table_drag_data_delete_cb (EMemoShellContent *memo_shell_content,
+ gint row,
+ gint col,
+ GdkDragContext *context)
+{
+ /* Moved components are deleted from source immediately when moved,
+ * because some of them can be part of destination source, and we
+ * don't want to delete not-moved memos. There is no such information
+ * which event has been moved and which not, so skip this method. */
+}
+
+static void
+memo_shell_content_cursor_change_cb (EMemoShellContent *memo_shell_content,
+ gint row,
+ ETable *table)
+{
+ ECalComponentPreview *memo_preview;
+ EMemoTable *memo_table;
+ ECalModel *memo_model;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ const gchar *uid;
+
+ memo_model = e_memo_shell_content_get_memo_model (memo_shell_content);
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content);
+
+ if (e_table_selected_count (table) != 1) {
+ e_cal_component_preview_clear (memo_preview);
+ return;
+ }
+
+ row = e_table_get_cursor_row (table);
+ comp_data = e_cal_model_get_component_at (memo_model, row);
+
+ comp = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (
+ comp, icalcomponent_new_clone (comp_data->icalcomp));
+ e_cal_component_preview_display (
+ memo_preview, comp_data->client, comp);
+
+ e_cal_component_get_uid (comp, &uid);
+ g_free (memo_shell_content->priv->current_uid);
+ memo_shell_content->priv->current_uid = g_strdup (uid);
+
+ g_object_unref (comp);
+}
+
+static void
+memo_shell_content_selection_change_cb (EMemoShellContent *memo_shell_content,
+ ETable *table)
+{
+ ECalComponentPreview *memo_preview;
+
+ memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content);
+
+ /* XXX Old code emits a "selection-changed" signal here. */
+
+ if (e_table_selected_count (table) != 1)
+ e_cal_component_preview_clear (memo_preview);
+}
+
+static void
+memo_shell_content_model_row_changed_cb (EMemoShellContent *memo_shell_content,
+ gint row,
+ ETableModel *model)
+{
+ ECalModelComponent *comp_data;
+ EMemoTable *memo_table;
+ ETable *table;
+ const gchar *current_uid;
+ const gchar *uid;
+
+ current_uid = memo_shell_content->priv->current_uid;
+ if (current_uid == NULL)
+ return;
+
+ comp_data = e_cal_model_get_component_at (E_CAL_MODEL (model), row);
+ if (comp_data == NULL)
+ return;
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ if (g_strcmp0 (uid, current_uid) != 0)
+ return;
+
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ table = e_memo_table_get_table (memo_table);
+
+ memo_shell_content_cursor_change_cb (memo_shell_content, 0, table);
+}
+
+static GtkOrientation
+memo_shell_content_get_orientation (EMemoShellContent *memo_shell_content)
+{
+ return memo_shell_content->priv->orientation;
+}
+
+static void
+memo_shell_content_set_orientation (EMemoShellContent *memo_shell_content,
+ GtkOrientation orientation)
+{
+ memo_shell_content->priv->orientation = orientation;
+
+ g_object_notify (G_OBJECT (memo_shell_content), "orientation");
+}
+
+static void
+memo_shell_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ORIENTATION:
+ memo_shell_content_set_orientation (
+ E_MEMO_SHELL_CONTENT (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ e_memo_shell_content_set_preview_visible (
+ E_MEMO_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+memo_shell_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_MODEL:
+ g_value_set_object (
+ value,
+ e_memo_shell_content_get_memo_model (
+ E_MEMO_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_ORIENTATION:
+ g_value_set_enum (
+ value,
+ memo_shell_content_get_orientation (
+ E_MEMO_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ g_value_set_boolean (
+ value,
+ e_memo_shell_content_get_preview_visible (
+ E_MEMO_SHELL_CONTENT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+memo_shell_content_dispose (GObject *object)
+{
+ EMemoShellContentPrivate *priv;
+
+ priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (object);
+
+ if (priv->paned != NULL) {
+ g_object_unref (priv->paned);
+ priv->paned = NULL;
+ }
+
+ if (priv->memo_table != NULL) {
+ g_object_unref (priv->memo_table);
+ priv->memo_table = NULL;
+ }
+
+ if (priv->memo_preview != NULL) {
+ g_object_unref (priv->memo_preview);
+ priv->memo_preview = NULL;
+ }
+
+ if (priv->memo_model != NULL) {
+ g_object_unref (priv->memo_model);
+ priv->memo_model = NULL;
+ }
+
+ if (priv->view_instance != NULL) {
+ g_object_unref (priv->view_instance);
+ priv->view_instance = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+memo_shell_content_finalize (GObject *object)
+{
+ EMemoShellContentPrivate *priv;
+
+ priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (object);
+
+ g_free (priv->current_uid);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+memo_shell_content_constructed (GObject *object)
+{
+ EMemoShellContentPrivate *priv;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellSettings *shell_settings;
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ GalViewInstance *view_instance;
+ icaltimezone *timezone;
+ ETable *table;
+ GConfBridge *bridge;
+ GtkWidget *container;
+ GtkWidget *widget;
+ const gchar *key;
+
+ priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ shell_content = E_SHELL_CONTENT (object);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ priv->memo_model = e_cal_model_memos_new (shell_settings);
+
+ timezone = e_shell_settings_get_pointer (
+ shell_settings, "cal-timezone");
+
+ /* Build content widgets. */
+
+ container = GTK_WIDGET (object);
+
+ widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->paned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "orientation",
+ G_OBJECT (widget), "orientation");
+
+ container = widget;
+
+ widget = e_memo_table_new (shell_view, priv->memo_model);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ priv->memo_table = 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_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "preview-visible",
+ G_OBJECT (widget), "visible");
+
+ container = widget;
+
+ widget = e_cal_component_preview_new ();
+ e_cal_component_preview_set_default_timezone (
+ E_CAL_COMPONENT_PREVIEW (widget), timezone);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->memo_preview = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Configure the memo table. */
+
+ widget = E_MEMO_TABLE (priv->memo_table)->etable;
+ table = e_table_scrolled_get_table (E_TABLE_SCROLLED (widget));
+
+ e_table_set_state (table, E_MEMO_TABLE_DEFAULT_STATE);
+
+ e_table_drag_source_set (
+ table, GDK_BUTTON1_MASK,
+ drag_types, G_N_ELEMENTS (drag_types),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_ASK);
+
+ g_signal_connect_swapped (
+ table, "table-drag-data-get",
+ G_CALLBACK (memo_shell_content_table_drag_data_get_cb),
+ object);
+
+ g_signal_connect_swapped (
+ table, "table-drag-data-delete",
+ G_CALLBACK (memo_shell_content_table_drag_data_delete_cb),
+ object);
+
+ g_signal_connect_swapped (
+ table, "cursor-change",
+ G_CALLBACK (memo_shell_content_cursor_change_cb),
+ object);
+
+ g_signal_connect_swapped (
+ table, "selection-change",
+ G_CALLBACK (memo_shell_content_selection_change_cb),
+ object);
+
+ g_signal_connect_swapped (
+ priv->memo_model, "model-row-changed",
+ G_CALLBACK (memo_shell_content_model_row_changed_cb),
+ object);
+
+ /* Load the view instance. */
+
+ view_instance = e_shell_view_new_view_instance (shell_view, NULL);
+ g_signal_connect_swapped (
+ view_instance, "display-view",
+ G_CALLBACK (memo_shell_content_display_view_cb),
+ object);
+ gal_view_instance_load (view_instance);
+ priv->view_instance = view_instance;
+
+ /* Bind GObject properties to GConf keys. */
+
+ bridge = gconf_bridge_get ();
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/calendar/display/memo_hpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "hposition");
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/calendar/display/memo_vpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "vposition");
+}
+
+static guint32
+memo_shell_content_check_state (EShellContent *shell_content)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ETable *table;
+ GSList *list, *iter;
+ gboolean editable = TRUE;
+ gboolean has_url = FALSE;
+ gint n_selected;
+ guint32 state = 0;
+
+ memo_shell_content = E_MEMO_SHELL_CONTENT (shell_content);
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ table = e_memo_table_get_table (memo_table);
+ n_selected = e_table_selected_count (table);
+
+ list = e_memo_table_get_selected (memo_table);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ icalproperty *prop;
+ gboolean read_only;
+
+ e_cal_is_read_only (comp_data->client, &read_only, NULL);
+ editable &= !read_only;
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ has_url |= (prop != NULL);
+ }
+ g_slist_free (list);
+
+ if (n_selected == 1)
+ state |= E_MEMO_SHELL_CONTENT_SELECTION_SINGLE;
+ if (n_selected > 1)
+ state |= E_MEMO_SHELL_CONTENT_SELECTION_MULTIPLE;
+ if (editable)
+ state |= E_MEMO_SHELL_CONTENT_SELECTION_CAN_EDIT;
+ if (has_url)
+ state |= E_MEMO_SHELL_CONTENT_SELECTION_HAS_URL;
+
+ return state;
+}
+
+static void
+memo_shell_content_class_init (EMemoShellContentClass *class)
+{
+ GObjectClass *object_class;
+ EShellContentClass *shell_content_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMemoShellContentPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = memo_shell_content_set_property;
+ object_class->get_property = memo_shell_content_get_property;
+ object_class->dispose = memo_shell_content_dispose;
+ object_class->finalize = memo_shell_content_finalize;
+ object_class->constructed = memo_shell_content_constructed;
+
+ shell_content_class = E_SHELL_CONTENT_CLASS (class);
+ shell_content_class->check_state = memo_shell_content_check_state;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MODEL,
+ g_param_spec_object (
+ "model",
+ _("Model"),
+ _("The memo table model"),
+ E_TYPE_CAL_MODEL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_VISIBLE,
+ g_param_spec_boolean (
+ "preview-visible",
+ _("Preview is Visible"),
+ _("Whether the preview pane is visible"),
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_override_property (
+ object_class, PROP_ORIENTATION, "orientation");
+}
+
+static void
+memo_shell_content_init (EMemoShellContent *memo_shell_content)
+{
+ memo_shell_content->priv =
+ E_MEMO_SHELL_CONTENT_GET_PRIVATE (memo_shell_content);
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_memo_shell_content_get_type (void)
+{
+ return memo_shell_content_type;
+}
+
+void
+e_memo_shell_content_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EMemoShellContentClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) memo_shell_content_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMemoShellContent),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) memo_shell_content_init,
+ NULL /* value_table */
+ };
+
+ static const GInterfaceInfo orientable_info = {
+ (GInterfaceInitFunc) NULL,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ memo_shell_content_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_CONTENT,
+ "EMemoShellContent", &type_info, 0);
+
+ g_type_module_add_interface (
+ type_module, memo_shell_content_type,
+ GTK_TYPE_ORIENTABLE, &orientable_info);
+}
+
+GtkWidget *
+e_memo_shell_content_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_MEMO_SHELL_CONTENT,
+ "shell-view", shell_view, NULL);
+}
+
+ECalModel *
+e_memo_shell_content_get_memo_model (EMemoShellContent *memo_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL);
+
+ return memo_shell_content->priv->memo_model;
+}
+
+ECalComponentPreview *
+e_memo_shell_content_get_memo_preview (EMemoShellContent *memo_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL);
+
+ return E_CAL_COMPONENT_PREVIEW (
+ memo_shell_content->priv->memo_preview);
+}
+
+EMemoTable *
+e_memo_shell_content_get_memo_table (EMemoShellContent *memo_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL);
+
+ return E_MEMO_TABLE (memo_shell_content->priv->memo_table);
+}
+
+gboolean
+e_memo_shell_content_get_preview_visible (EMemoShellContent *memo_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_CONTENT (memo_shell_content), FALSE);
+
+ return memo_shell_content->priv->preview_visible;
+}
+
+void
+e_memo_shell_content_set_preview_visible (EMemoShellContent *memo_shell_content,
+ gboolean preview_visible)
+{
+ g_return_if_fail (E_IS_MEMO_SHELL_CONTENT (memo_shell_content));
+
+ memo_shell_content->priv->preview_visible = preview_visible;
+
+ g_object_notify (G_OBJECT (memo_shell_content), "preview-visible");
+}
+
+GalViewInstance *
+e_memo_shell_content_get_view_instance (EMemoShellContent *memo_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL);
+
+ return memo_shell_content->priv->view_instance;
+}
diff --git a/modules/calendar/e-memo-shell-content.h b/modules/calendar/e-memo-shell-content.h
new file mode 100644
index 0000000000..ae2710e148
--- /dev/null
+++ b/modules/calendar/e-memo-shell-content.h
@@ -0,0 +1,96 @@
+/*
+ * e-memo-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_MEMO_SHELL_CONTENT_H
+#define E_MEMO_SHELL_CONTENT_H
+
+#include <shell/e-shell-content.h>
+#include <shell/e-shell-view.h>
+
+#include <calendar/gui/e-memo-table.h>
+#include <calendar/gui/e-cal-component-preview.h>
+
+#include <menus/gal-view-instance.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MEMO_SHELL_CONTENT \
+ (e_memo_shell_content_get_type ())
+#define E_MEMO_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContent))
+#define E_MEMO_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContentClass))
+#define E_IS_MEMO_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MEMO_SHELL_CONTENT))
+#define E_IS_MEMO_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MEMO_SHELL_CONTENT))
+#define E_MEMO_SHELL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMemoShellContent EMemoShellContent;
+typedef struct _EMemoShellContentClass EMemoShellContentClass;
+typedef struct _EMemoShellContentPrivate EMemoShellContentPrivate;
+
+enum {
+ E_MEMO_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0,
+ E_MEMO_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1,
+ E_MEMO_SHELL_CONTENT_SELECTION_CAN_EDIT = 1 << 2,
+ E_MEMO_SHELL_CONTENT_SELECTION_HAS_URL = 1 << 3
+};
+
+struct _EMemoShellContent {
+ EShellContent parent;
+ EMemoShellContentPrivate *priv;
+};
+
+struct _EMemoShellContentClass {
+ EShellContentClass parent_class;
+};
+
+GType e_memo_shell_content_get_type (void);
+void e_memo_shell_content_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_memo_shell_content_new(EShellView *shell_view);
+ECalModel * e_memo_shell_content_get_memo_model
+ (EMemoShellContent *memo_shell_conent);
+ECalComponentPreview *
+ e_memo_shell_content_get_memo_preview
+ (EMemoShellContent *memo_shell_content);
+EMemoTable * e_memo_shell_content_get_memo_table
+ (EMemoShellContent *memo_shell_content);
+gboolean e_memo_shell_content_get_preview_visible
+ (EMemoShellContent *memo_shell_content);
+void e_memo_shell_content_set_preview_visible
+ (EMemoShellContent *memo_shell_content,
+ gboolean preview_visible);
+GalViewInstance *
+ e_memo_shell_content_get_view_instance
+ (EMemoShellContent *memo_shell_content);
+
+G_END_DECLS
+
+#endif /* E_MEMO_SHELL_CONTENT_H */
diff --git a/modules/calendar/e-memo-shell-migrate.c b/modules/calendar/e-memo-shell-migrate.c
new file mode 100644
index 0000000000..3bcf3156a5
--- /dev/null
+++ b/modules/calendar/e-memo-shell-migrate.c
@@ -0,0 +1,268 @@
+/*
+ * e-memo-shell-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-memo-shell-migrate.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <camel/camel-url.h>
+#include <libedataserver/e-account.h>
+#include <libedataserver/e-account-list.h>
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-group.h>
+#include <libedataserver/e-source-list.h>
+
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/calendar-config-keys.h"
+#include "shell/e-shell.h"
+
+#define WEBCAL_BASE_URI "webcal://"
+#define PERSONAL_RELATIVE_URI "system"
+#define GROUPWISE_BASE_URI "groupwise://"
+
+static void
+create_memo_sources (EShellBackend *shell_backend,
+ ESourceList *source_list,
+ ESourceGroup **on_this_computer,
+ ESourceGroup **on_the_web,
+ ESource **personal_source)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+ GSList *groups;
+ ESourceGroup *group;
+ gchar *base_uri, *base_uri_proto;
+ const gchar *base_dir;
+
+ *on_this_computer = NULL;
+ *on_the_web = NULL;
+ *personal_source = NULL;
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ base_dir = e_shell_backend_get_config_dir (shell_backend);
+ base_uri = g_build_filename (base_dir, "local", NULL);
+
+ base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL);
+
+ groups = e_source_list_peek_groups (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_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group)))
+ *on_the_web = 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 (source_list, group, -1);
+
+ *on_this_computer = group;
+ }
+
+ if (!*personal_source) {
+ gchar *primary_memo_list;
+
+ /* Create the default Person memo list */
+ ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI);
+ e_source_group_add_source (*on_this_computer, source, -1);
+
+ primary_memo_list = e_shell_settings_get_string (
+ shell_settings, "cal-primary-memo-list");
+
+ if (!primary_memo_list && !calendar_config_get_memos_selected ()) {
+ GSList selected;
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-memo-list",
+ e_source_peek_uid (source));
+
+ selected.data = (gpointer)e_source_peek_uid (source);
+ selected.next = NULL;
+ calendar_config_set_memos_selected (&selected);
+ }
+
+ e_source_set_color_spec (source, "#BECEDD");
+ *personal_source = source;
+ }
+
+ if (!*on_the_web) {
+ /* Create the Webcal source group */
+ group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI);
+ e_source_list_add_group (source_list, group, -1);
+
+ *on_the_web = group;
+ }
+
+ g_free (base_uri_proto);
+ g_free (base_uri);
+}
+
+static gboolean
+is_groupwise_account (EAccount *account)
+{
+ if (account->source->url != NULL) {
+ return g_str_has_prefix (account->source->url, GROUPWISE_BASE_URI);
+ } else {
+ return FALSE;
+ }
+}
+
+static void
+add_gw_esource (ESourceList *source_list, const gchar *group_name, const gchar *source_name, CamelURL *url, GConfClient *client)
+{
+ ESourceGroup *group;
+ ESource *source;
+ GSList *ids, *temp;
+ GError *error = NULL;
+ gchar *relative_uri;
+ const gchar *soap_port;
+ const gchar * use_ssl;
+ const gchar *poa_address;
+ const gchar *offline_sync;
+
+ poa_address = url->host;
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+ soap_port = camel_url_get_param (url, "soap_port");
+
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7191";
+
+ use_ssl = camel_url_get_param (url, "use_ssl");
+ offline_sync = camel_url_get_param (url, "offline_sync");
+
+ group = e_source_group_new (group_name, GROUPWISE_BASE_URI);
+ if (!e_source_list_add_group (source_list, group, -1))
+ return;
+ relative_uri = g_strdup_printf ("%s@%s/", url->user, poa_address);
+
+ source = e_source_new (source_name, relative_uri);
+ e_source_set_property (source, "auth", "1");
+ e_source_set_property (source, "username", url->user);
+ e_source_set_property (source, "port", camel_url_get_param (url, "soap_port"));
+ e_source_set_property (source, "auth-domain", "Groupwise");
+ e_source_set_property (source, "use_ssl", use_ssl);
+ e_source_set_property (source, "offline_sync", offline_sync ? "1" : "0" );
+
+ e_source_set_color_spec (source, "#EEBC60");
+ e_source_group_add_source (group, source, -1);
+
+ ids = gconf_client_get_list (client, CALENDAR_CONFIG_MEMOS_SELECTED_MEMOS, GCONF_VALUE_STRING, &error);
+ if ( error != NULL ) {
+ g_warning("%s (%s) %s\n", G_STRLOC, G_STRFUNC, error->message);
+ g_error_free(error);
+ }
+ ids = g_slist_append (ids, g_strdup (e_source_peek_uid (source)));
+ gconf_client_set_list (client, CALENDAR_CONFIG_MEMOS_SELECTED_MEMOS, GCONF_VALUE_STRING, ids, NULL);
+ temp = ids;
+ for (; temp != NULL; temp = g_slist_next (temp))
+ g_free (temp->data);
+
+ g_slist_free (ids);
+ g_object_unref (source);
+ g_object_unref (group);
+ g_free (relative_uri);
+}
+
+gboolean
+e_memo_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint revision,
+ GError **error)
+{
+ ESourceGroup *on_this_computer = NULL;
+ ESourceGroup *on_the_web = NULL;
+ ESource *personal_source = NULL;
+ ESourceList *source_list = NULL;
+ gboolean retval = FALSE;
+
+ g_object_get (shell_backend, "source-list", &source_list, NULL);
+
+ /* we call this unconditionally now - create_groups either
+ creates the groups/sources or it finds the necessary
+ groups/sources. */
+ create_memo_sources (
+ shell_backend, source_list, &on_this_computer,
+ &on_the_web, &personal_source);
+
+ /* Migration for Gw accounts between versions < 2.8 */
+ if (major == 2 && minor < 8) {
+ EAccountList *al;
+ EAccount *a;
+ CamelURL *url;
+ EIterator *it;
+ GConfClient *gconf_client = gconf_client_get_default ();
+ al = e_account_list_new (gconf_client);
+ for (it = e_list_get_iterator((EList *)al);
+ e_iterator_is_valid(it);
+ e_iterator_next(it)) {
+ a = (EAccount *) e_iterator_get(it);
+ if (!a->enabled || !is_groupwise_account (a))
+ continue;
+ url = camel_url_new (a->source->url, NULL);
+ add_gw_esource (source_list, a->name, _("Notes"), url, gconf_client);
+ camel_url_free (url);
+ }
+ g_object_unref (al);
+ g_object_unref (gconf_client);
+ }
+
+ e_source_list_sync (source_list, NULL);
+ retval = TRUE;
+
+ if (on_this_computer)
+ g_object_unref (on_this_computer);
+ if (on_the_web)
+ g_object_unref (on_the_web);
+ if (personal_source)
+ g_object_unref (personal_source);
+
+ return retval;
+}
diff --git a/modules/calendar/e-memo-shell-migrate.h b/modules/calendar/e-memo-shell-migrate.h
new file mode 100644
index 0000000000..ba163c6950
--- /dev/null
+++ b/modules/calendar/e-memo-shell-migrate.h
@@ -0,0 +1,38 @@
+/*
+ * e-memo-shell-backend-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_MEMO_SHELL_BACKEND_MIGRATE_H
+#define E_MEMO_SHELL_BACKEND_MIGRATE_H
+
+#include <glib.h>
+#include <shell/e-shell-backend.h>
+
+G_BEGIN_DECLS
+
+gboolean e_memo_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_MEMO_SHELL_BACKEND_MIGRATE_H */
diff --git a/modules/calendar/e-memo-shell-sidebar.c b/modules/calendar/e-memo-shell-sidebar.c
new file mode 100644
index 0000000000..33a2226c00
--- /dev/null
+++ b/modules/calendar/e-memo-shell-sidebar.c
@@ -0,0 +1,716 @@
+/*
+ * e-memo-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-memo-shell-sidebar.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/e-error.h"
+#include "e-util/e-util.h"
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/e-memo-list-selector.h"
+#include "calendar/gui/misc.h"
+
+#include "e-memo-shell-view.h"
+#include "e-memo-shell-backend.h"
+
+#define E_MEMO_SHELL_SIDEBAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebarPrivate))
+
+struct _EMemoShellSidebarPrivate {
+ GtkWidget *selector;
+ icaltimezone *timezone;
+
+ /* UID -> Client */
+ GHashTable *client_table;
+};
+
+enum {
+ PROP_0,
+ PROP_SELECTOR
+};
+
+enum {
+ CLIENT_ADDED,
+ CLIENT_REMOVED,
+ STATUS_MESSAGE,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+static GType memo_shell_sidebar_type;
+
+static void
+memo_shell_sidebar_emit_client_added (EMemoShellSidebar *memo_shell_sidebar,
+ ECal *client)
+{
+ guint signal_id = signals[CLIENT_ADDED];
+
+ g_signal_emit (memo_shell_sidebar, signal_id, 0, client);
+}
+
+static void
+memo_shell_sidebar_emit_client_removed (EMemoShellSidebar *memo_shell_sidebar,
+ ECal *client)
+{
+ guint signal_id = signals[CLIENT_REMOVED];
+
+ g_signal_emit (memo_shell_sidebar, signal_id, 0, client);
+}
+
+static void
+memo_shell_sidebar_emit_status_message (EMemoShellSidebar *memo_shell_sidebar,
+ const gchar *status_message)
+{
+ guint signal_id = signals[STATUS_MESSAGE];
+
+ g_signal_emit (memo_shell_sidebar, signal_id, 0, status_message, -1.0);
+}
+
+static void
+memo_shell_sidebar_backend_died_cb (EMemoShellSidebar *memo_shell_sidebar,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ GHashTable *client_table;
+ ESource *source;
+ const gchar *uid;
+
+ client_table = memo_shell_sidebar->priv->client_table;
+
+ shell_sidebar = E_SHELL_SIDEBAR (memo_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ source = e_cal_get_source (client);
+ uid = e_source_peek_uid (source);
+
+ g_object_ref (source);
+
+ g_hash_table_remove (client_table, uid);
+ memo_shell_sidebar_emit_status_message (memo_shell_sidebar, NULL);
+
+ e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:memos-crashed", NULL);
+
+ g_object_unref (source);
+}
+
+static void
+memo_shell_sidebar_backend_error_cb (EMemoShellSidebar *memo_shell_sidebar,
+ const gchar *message,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ GtkWidget *dialog;
+ const gchar *uri;
+ gchar *uri_no_passwd;
+
+ shell_sidebar = E_SHELL_SIDEBAR (memo_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ uri = e_cal_get_uri (client);
+ uri_no_passwd = get_uri_without_password (uri);
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (shell_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Error on %s\n%s"),
+ uri_no_passwd, message);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_free (uri_no_passwd);
+}
+
+static void
+memo_shell_sidebar_client_opened_cb (EMemoShellSidebar *memo_shell_sidebar,
+ ECalendarStatus status,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ ESource *source;
+
+ source = e_cal_get_source (client);
+
+ shell_sidebar = E_SHELL_SIDEBAR (memo_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ if (status == E_CALENDAR_STATUS_AUTHENTICATION_FAILED ||
+ status == E_CALENDAR_STATUS_AUTHENTICATION_REQUIRED)
+ auth_cal_forget_password (client);
+
+ switch (status) {
+ case E_CALENDAR_STATUS_OK:
+ g_signal_handlers_disconnect_matched (
+ client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+ memo_shell_sidebar_client_opened_cb, NULL);
+
+ memo_shell_sidebar_emit_status_message (
+ memo_shell_sidebar, _("Loading memos"));
+ memo_shell_sidebar_emit_client_added (
+ memo_shell_sidebar, client);
+ memo_shell_sidebar_emit_status_message (
+ memo_shell_sidebar, NULL);
+ break;
+
+ case E_CALENDAR_STATUS_AUTHENTICATION_FAILED:
+ e_cal_open_async (client, FALSE);
+ break;
+
+ case E_CALENDAR_STATUS_BUSY:
+ break;
+
+ case E_CALENDAR_STATUS_REPOSITORY_OFFLINE:
+ e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:prompt-no-contents-offline-memos",
+ NULL);
+ break;
+
+ default:
+ memo_shell_sidebar_emit_client_removed (
+ memo_shell_sidebar, client);
+ break;
+ }
+}
+
+static void
+memo_shell_sidebar_row_changed_cb (EMemoShellSidebar *memo_shell_sidebar,
+ GtkTreePath *tree_path,
+ GtkTreeIter *tree_iter,
+ GtkTreeModel *tree_model)
+{
+ ESourceSelector *selector;
+ ESource *source;
+
+ /* XXX ESourceSelector's underlying tree store has only one
+ * column: ESource objects. While we're not supposed to
+ * know this, listening for "row-changed" signals from
+ * the model is easier to deal with than the selector's
+ * "selection-changed" signal, which doesn't tell you
+ * _which_ row changed. */
+
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+ gtk_tree_model_get (tree_model, tree_iter, 0, &source, -1);
+
+ /* XXX This signal gets emitted a lot while the model is being
+ * rebuilt, during which time we won't get a valid ESource.
+ * ESourceSelector should probably block this signal while
+ * rebuilding the model, but we'll be forgiving and not
+ * emit a warning. */
+ if (!E_IS_SOURCE (source))
+ return;
+
+ if (e_source_selector_source_is_selected (selector, source))
+ e_memo_shell_sidebar_add_source (memo_shell_sidebar, source);
+ else
+ e_memo_shell_sidebar_remove_source (memo_shell_sidebar, source);
+}
+
+static void
+memo_shell_sidebar_selection_changed_cb (EMemoShellSidebar *memo_shell_sidebar,
+ ESourceSelector *selector)
+{
+ GSList *list, *iter;
+
+ /* This signal is emitted less frequently than "row-changed",
+ * especially when the model is being rebuilt. So we'll take
+ * it easy on poor GConf. */
+
+ list = e_source_selector_get_selection (selector);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ESource *source = iter->data;
+
+ iter->data = (gpointer) e_source_peek_uid (source);
+ g_object_unref (source);
+ }
+
+ calendar_config_set_memos_selected (list);
+
+ g_slist_free (list);
+}
+
+static void
+memo_shell_sidebar_primary_selection_changed_cb (EMemoShellSidebar *memo_shell_sidebar,
+ ESourceSelector *selector)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ EShellSettings *shell_settings;
+ ESource *source;
+
+ /* XXX ESourceSelector needs a "primary-selection-uid" property
+ * so we can just bind the property with GConfBridge. */
+
+ source = e_source_selector_peek_primary_selection (selector);
+ if (source == NULL)
+ return;
+
+ shell_sidebar = E_SHELL_SIDEBAR (memo_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-memo-list",
+ e_source_peek_uid (source));
+}
+
+static void
+memo_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_memo_shell_sidebar_get_selector (
+ E_MEMO_SHELL_SIDEBAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+memo_shell_sidebar_dispose (GObject *object)
+{
+ EMemoShellSidebarPrivate *priv;
+
+ priv = E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ if (priv->selector != NULL) {
+ g_object_unref (priv->selector);
+ priv->selector = NULL;
+ }
+
+ g_hash_table_remove_all (priv->client_table);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+memo_shell_sidebar_finalize (GObject *object)
+{
+ EMemoShellSidebarPrivate *priv;
+
+ priv = E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->client_table);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+memo_shell_sidebar_constructed (GObject *object)
+{
+ EMemoShellSidebarPrivate *priv;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellSidebar *shell_sidebar;
+ EShellSettings *shell_settings;
+ ESourceSelector *selector;
+ ESourceList *source_list;
+ ESource *source;
+ GtkContainer *container;
+ GtkTreeModel *model;
+ GtkWidget *widget;
+ AtkObject *a11y;
+ GSList *list, *iter;
+ gchar *uid;
+
+ priv = E_MEMO_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);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ source_list = e_memo_shell_backend_get_source_list (
+ E_MEMO_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_memo_list_selector_new (source_list);
+ e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE);
+ gtk_container_add (container, widget);
+ a11y = gtk_widget_get_accessible (widget);
+ atk_object_set_name (a11y, _("Memo List Selector"));
+ priv->selector = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Restore the selector state from the last session. */
+
+ selector = E_SOURCE_SELECTOR (priv->selector);
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+ g_signal_connect_swapped (
+ model, "row-changed",
+ G_CALLBACK (memo_shell_sidebar_row_changed_cb),
+ object);
+
+ source = NULL;
+ uid = e_shell_settings_get_string (
+ shell_settings, "cal-primary-memo-list");
+ if (uid != NULL)
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ if (source == NULL)
+ source = e_source_list_peek_source_any (source_list);
+ if (source != NULL)
+ e_source_selector_set_primary_selection (selector, source);
+ g_free (uid);
+
+ list = calendar_config_get_memos_selected ();
+ for (iter = list; iter != NULL; iter = iter->next) {
+ uid = iter->data;
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ g_free (uid);
+
+ if (source == NULL)
+ continue;
+
+ e_source_selector_select_source (selector, source);
+ }
+ g_slist_free (list);
+
+ /* Listen for subsequent changes to the selector. */
+
+ g_signal_connect_swapped (
+ widget, "selection-changed",
+ G_CALLBACK (memo_shell_sidebar_selection_changed_cb),
+ object);
+
+ g_signal_connect_swapped (
+ widget, "primary-selection-changed",
+ G_CALLBACK (memo_shell_sidebar_primary_selection_changed_cb),
+ object);
+}
+
+static guint32
+memo_shell_sidebar_check_state (EShellSidebar *shell_sidebar)
+{
+ EMemoShellSidebar *memo_shell_sidebar;
+ ESourceSelector *selector;
+ ESource *source;
+ gboolean is_system = FALSE;
+ guint32 state = 0;
+
+ memo_shell_sidebar = E_MEMO_SHELL_SIDEBAR (shell_sidebar);
+ selector = e_memo_shell_sidebar_get_selector (memo_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_MEMO_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE;
+ if (is_system)
+ state |= E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM;
+
+ return state;
+}
+
+static void
+memo_shell_sidebar_client_removed (EMemoShellSidebar *memo_shell_sidebar,
+ ECal *client)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ESource *source;
+ const gchar *uid;
+
+ client_table = memo_shell_sidebar->priv->client_table;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+
+ g_signal_handlers_disconnect_matched (
+ client, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, memo_shell_sidebar);
+
+ source = e_cal_get_source (client);
+ e_source_selector_unselect_source (selector, source);
+
+ uid = e_source_peek_uid (source);
+ g_hash_table_remove (client_table, uid);
+
+ memo_shell_sidebar_emit_status_message (memo_shell_sidebar, NULL);
+}
+
+static void
+memo_shell_sidebar_class_init (EMemoShellSidebarClass *class)
+{
+ GObjectClass *object_class;
+ EShellSidebarClass *shell_sidebar_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMemoShellSidebarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = memo_shell_sidebar_get_property;
+ object_class->dispose = memo_shell_sidebar_dispose;
+ object_class->finalize = memo_shell_sidebar_finalize;
+ object_class->constructed = memo_shell_sidebar_constructed;
+
+ shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class);
+ shell_sidebar_class->check_state = memo_shell_sidebar_check_state;
+
+ class->client_removed = memo_shell_sidebar_client_removed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SELECTOR,
+ g_param_spec_object (
+ "selector",
+ _("Source Selector Widget"),
+ _("This widget displays groups of memo lists"),
+ E_TYPE_SOURCE_SELECTOR,
+ G_PARAM_READABLE));
+
+ signals[CLIENT_ADDED] = g_signal_new (
+ "client-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EMemoShellSidebarClass, client_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_CAL);
+
+ signals[CLIENT_REMOVED] = g_signal_new (
+ "client-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EMemoShellSidebarClass, client_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_CAL);
+
+ signals[STATUS_MESSAGE] = g_signal_new (
+ "status-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMemoShellSidebarClass, status_message),
+ NULL, NULL,
+ e_marshal_VOID__STRING_DOUBLE,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_DOUBLE);
+}
+
+static void
+memo_shell_sidebar_init (EMemoShellSidebar *memo_shell_sidebar)
+{
+ GHashTable *client_table;
+
+ client_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ memo_shell_sidebar->priv =
+ E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (memo_shell_sidebar);
+
+ memo_shell_sidebar->priv->client_table = client_table;
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_memo_shell_sidebar_get_type (void)
+{
+ return memo_shell_sidebar_type;
+}
+
+void
+e_memo_shell_sidebar_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EMemoShellSidebarClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) memo_shell_sidebar_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMemoShellSidebar),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) memo_shell_sidebar_init,
+ NULL /* value_table */
+ };
+
+ memo_shell_sidebar_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_SIDEBAR,
+ "EMemoShellSidebar", &type_info, 0);
+}
+
+GtkWidget *
+e_memo_shell_sidebar_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_MEMO_SHELL_SIDEBAR,
+ "shell-view", shell_view, NULL);
+}
+
+GList *
+e_memo_shell_sidebar_get_clients (EMemoShellSidebar *memo_shell_sidebar)
+{
+ GHashTable *client_table;
+
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar), NULL);
+
+ client_table = memo_shell_sidebar->priv->client_table;
+
+ return g_hash_table_get_values (client_table);
+}
+
+ESourceSelector *
+e_memo_shell_sidebar_get_selector (EMemoShellSidebar *memo_shell_sidebar)
+{
+ g_return_val_if_fail (
+ E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar), NULL);
+
+ return E_SOURCE_SELECTOR (memo_shell_sidebar->priv->selector);
+}
+
+void
+e_memo_shell_sidebar_add_source (EMemoShellSidebar *memo_shell_sidebar,
+ ESource *source)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ECal *client;
+ const gchar *uid;
+ const gchar *uri;
+ gchar *message;
+
+ g_return_if_fail (E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ client_table = memo_shell_sidebar->priv->client_table;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+
+ uid = e_source_peek_uid (source);
+ client = g_hash_table_lookup (client_table, uid);
+
+ if (client != NULL)
+ return;
+
+ client = auth_new_cal_from_source (source, E_CAL_SOURCE_TYPE_JOURNAL);
+ g_return_if_fail (client != NULL);
+
+ g_signal_connect_swapped (
+ client, "backend-died",
+ G_CALLBACK (memo_shell_sidebar_backend_died_cb),
+ memo_shell_sidebar);
+
+ g_signal_connect_swapped (
+ client, "backend-error",
+ G_CALLBACK (memo_shell_sidebar_backend_error_cb),
+ memo_shell_sidebar);
+
+ g_hash_table_insert (client_table, g_strdup (uid), client);
+ e_source_selector_select_source (selector, source);
+
+ uri = e_cal_get_uri (client);
+ message = g_strdup_printf (_("Opening memos at %s"), uri);
+ memo_shell_sidebar_emit_status_message (memo_shell_sidebar, message);
+ g_free (message);
+
+ g_signal_connect_swapped (
+ client, "cal-opened",
+ G_CALLBACK (memo_shell_sidebar_client_opened_cb),
+ memo_shell_sidebar);
+
+ e_cal_open_async (client, FALSE);
+}
+
+void
+e_memo_shell_sidebar_remove_source (EMemoShellSidebar *memo_shell_sidebar,
+ ESource *source)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ECal *client;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ client_table = memo_shell_sidebar->priv->client_table;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+
+ uid = e_source_peek_uid (source);
+ client = g_hash_table_lookup (client_table, uid);
+
+ if (client == NULL)
+ return;
+
+ memo_shell_sidebar_emit_client_removed (memo_shell_sidebar, client);
+}
diff --git a/modules/calendar/e-memo-shell-sidebar.h b/modules/calendar/e-memo-shell-sidebar.h
new file mode 100644
index 0000000000..068d7436bc
--- /dev/null
+++ b/modules/calendar/e-memo-shell-sidebar.h
@@ -0,0 +1,97 @@
+/*
+ * e-memo-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_MEMO_SHELL_SIDEBAR_H
+#define E_MEMO_SHELL_SIDEBAR_H
+
+#include <libecal/e-cal.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_MEMO_SHELL_SIDEBAR \
+ (e_memo_shell_sidebar_get_type ())
+#define E_MEMO_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebar))
+#define E_MEMO_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebarClass))
+#define E_IS_MEMO_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MEMO_SHELL_SIDEBAR))
+#define E_IS_MEMO_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MEMO_SHELL_SIDEBAR))
+#define E_MEMO_SHELL_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMemoShellSidebar EMemoShellSidebar;
+typedef struct _EMemoShellSidebarClass EMemoShellSidebarClass;
+typedef struct _EMemoShellSidebarPrivate EMemoShellSidebarPrivate;
+
+enum {
+ E_MEMO_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0,
+ E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM = 1 << 1
+};
+
+struct _EMemoShellSidebar {
+ EShellSidebar parent;
+ EMemoShellSidebarPrivate *priv;
+};
+
+struct _EMemoShellSidebarClass {
+ EShellSidebarClass parent_class;
+
+ /* Signals */
+ void (*client_added) (EMemoShellSidebar *memo_shell_sidebar,
+ ECal *client);
+ void (*client_removed) (EMemoShellSidebar *memo_shell_sidebar,
+ ECal *client);
+ void (*status_message) (EMemoShellSidebar *memo_shell_sidebar,
+ const gchar *status_message,
+ gdouble percent);
+};
+
+GType e_memo_shell_sidebar_get_type (void);
+void e_memo_shell_sidebar_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_memo_shell_sidebar_new(EShellView *shell_view);
+GList * e_memo_shell_sidebar_get_clients
+ (EMemoShellSidebar *memo_shell_sidebar);
+ESourceSelector *
+ e_memo_shell_sidebar_get_selector
+ (EMemoShellSidebar *memo_shell_sidebar);
+void e_memo_shell_sidebar_add_source
+ (EMemoShellSidebar *memo_shell_sidebar,
+ ESource *source);
+void e_memo_shell_sidebar_remove_source
+ (EMemoShellSidebar *memo_shell_sidebar,
+ ESource *source);
+
+G_END_DECLS
+
+#endif /* E_MEMO_SHELL_SIDEBAR_H */
diff --git a/modules/calendar/e-memo-shell-view-actions.c b/modules/calendar/e-memo-shell-view-actions.c
new file mode 100644
index 0000000000..0161eeb198
--- /dev/null
+++ b/modules/calendar/e-memo-shell-view-actions.c
@@ -0,0 +1,1019 @@
+/*
+ * e-memo-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-memo-shell-view-private.h"
+
+static void
+action_gal_save_custom_view_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EShellView *shell_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 (memo_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ view_instance = e_memo_shell_content_get_view_instance (memo_shell_content);
+ gal_view_instance_save_as (view_instance);
+}
+
+static void
+action_memo_clipboard_copy_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ e_memo_table_copy_clipboard (memo_table);
+}
+
+static void
+action_memo_clipboard_cut_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ e_memo_table_cut_clipboard (memo_table);
+}
+
+static void
+action_memo_clipboard_paste_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ e_memo_table_paste_clipboard (memo_table);
+}
+
+static void
+action_memo_delete_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ ECalComponentPreview *memo_preview;
+ EMemoTable *memo_table;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content);
+
+ e_memo_shell_view_set_status_message (
+ memo_shell_view, _("Deleting selected memos..."), -1.0);
+ e_memo_table_delete_selected (memo_table);
+ e_memo_shell_view_set_status_message (memo_shell_view, NULL, -1.0);
+
+ e_cal_component_preview_clear (memo_preview);
+}
+
+static void
+action_memo_forward_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GSList *list;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only forward the first selected memo. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+ itip_send_comp (
+ E_CAL_COMPONENT_METHOD_PUBLISH, comp,
+ comp_data->client, NULL, NULL, NULL, TRUE, FALSE);
+ g_object_unref (comp);
+}
+
+static void
+action_memo_list_copy_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellSidebar *memo_shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ESourceSelector *selector;
+ ESource *source;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ copy_source_dialog (
+ GTK_WINDOW (shell_window),
+ source, E_CAL_SOURCE_TYPE_JOURNAL);
+}
+
+static void
+action_memo_list_delete_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellBackend *memo_shell_backend;
+ EMemoShellContent *memo_shell_content;
+ EMemoShellSidebar *memo_shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMemoTable *memo_table;
+ ECal *client;
+ ECalModel *model;
+ ESourceSelector *selector;
+ ESourceGroup *source_group;
+ ESourceList *source_list;
+ ESource *source;
+ gint response;
+ gchar *uri;
+ GError *error = NULL;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ memo_shell_backend = memo_shell_view->priv->memo_shell_backend;
+ source_list = e_memo_shell_backend_get_source_list (memo_shell_backend);
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ model = e_memo_table_get_model (memo_table);
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ /* Ask for confirmation. */
+ response = e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:prompt-delete-memo-list",
+ e_source_peek_name (source));
+ if (response != GTK_RESPONSE_YES)
+ return;
+
+ uri = e_source_get_uri (source);
+ client = e_cal_model_get_client_for_uri (model, uri);
+ if (client == NULL)
+ client = e_cal_new_from_uri (uri, E_CAL_SOURCE_TYPE_JOURNAL);
+ g_free (uri);
+
+ g_return_if_fail (client != NULL);
+
+ if (!e_cal_remove (client, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (e_source_selector_source_is_selected (selector, source)) {
+ e_memo_shell_sidebar_remove_source (
+ memo_shell_sidebar, source);
+ e_source_selector_unselect_source (selector, source);
+ }
+
+ source_group = e_source_peek_group (source);
+ e_source_group_remove_source (source_group, source);
+
+ if (!e_source_list_sync (source_list, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+action_memo_list_new_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ calendar_setup_new_memo_list (GTK_WINDOW (shell_window));
+}
+
+static void
+action_memo_list_print_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ETable *table;
+ GtkPrintOperationAction print_action;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ table = e_memo_table_get_table (memo_table);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ print_table (table, _("Print Memos"), _("Memos"), print_action);
+}
+
+static void
+action_memo_list_print_preview_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ETable *table;
+ GtkPrintOperationAction print_action;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ table = e_memo_table_get_table (memo_table);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW;
+ print_table (table, _("Print Memos"), _("Memos"), print_action);
+}
+
+static void
+action_memo_list_properties_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellSidebar *memo_shell_sidebar;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ESource *source;
+ ESourceSelector *selector;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ calendar_setup_edit_memo_list (GTK_WINDOW (shell_window), source);
+}
+
+static void
+action_memo_list_rename_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellSidebar *memo_shell_sidebar;
+ ESourceSelector *selector;
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+
+ e_source_selector_edit_primary_selection (selector);
+}
+
+static void
+action_memo_list_select_one_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellSidebar *memo_shell_sidebar;
+ ESourceSelector *selector;
+ ESource *primary;
+ GSList *list, *iter;
+
+ /* XXX ESourceSelector should provide a function for this. */
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+ primary = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (primary != NULL);
+
+ list = e_source_selector_get_selection (selector);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ESource *source = iter->data;
+
+ if (source == primary)
+ continue;
+
+ e_source_selector_unselect_source (selector, source);
+ }
+ e_source_selector_free_selection (list);
+
+ e_source_selector_select_source (selector, primary);
+}
+
+static void
+action_memo_new_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ ECal *client;
+ ECalComponent *comp;
+ CompEditor *editor;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ client = comp_data->client;
+ editor = memo_editor_new (client, shell, COMP_EDITOR_NEW_ITEM);
+ comp = cal_comp_memo_new_with_defaults (client);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (client);
+}
+
+static void
+action_memo_open_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the first selected memo. */
+ e_memo_shell_view_open_memo (memo_shell_view, comp_data);
+}
+
+static void
+action_memo_open_url_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ icalproperty *prop;
+ const gchar *uri;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the URI of the first selected memo. */
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ g_return_if_fail (prop == NULL);
+
+ uri = icalproperty_get_url (prop);
+ e_show_uri (GTK_WINDOW (shell_window), uri);
+}
+
+static void
+action_memo_preview_cb (GtkToggleAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ gboolean visible;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ visible = gtk_toggle_action_get_active (action);
+ e_memo_shell_content_set_preview_visible (memo_shell_content, visible);
+}
+
+static void
+action_memo_print_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GtkPrintOperationAction print_action;
+ GSList *list;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only print the first selected memo. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_cal_component_set_icalcomponent (comp, clone);
+ print_comp (comp, comp_data->client, print_action);
+ g_object_unref (comp);
+}
+
+static void
+action_memo_save_as_cb (GtkAction *action,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+ gchar *filename;
+ gchar *string;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ list = e_memo_table_get_selected (memo_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ filename = e_file_dialog_save (_("Save as..."), NULL);
+ if (filename == NULL)
+ return;
+
+ /* XXX We only save the first selected memo. */
+ string = e_cal_get_component_as_string (
+ comp_data->client, comp_data->icalcomp);
+ if (string == NULL) {
+ g_warning ("Could not convert memo to a string");
+ return;
+ }
+
+ e_write_file_uri (filename, string);
+
+ g_free (filename);
+ g_free (string);
+}
+
+static void
+action_memo_search_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMemoShellView *memo_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 (memo_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_memo_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ orientable = GTK_ORIENTABLE (memo_shell_content);
+
+ switch (gtk_radio_action_get_current_value (action)) {
+ case 0:
+ orientation = GTK_ORIENTATION_VERTICAL;
+ break;
+ case 1:
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ gtk_orientable_set_orientation (orientable, orientation);
+}
+
+static void
+action_search_execute_cb (GtkAction *action,
+ EMemoShellView *memo_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 (memo_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ e_memo_shell_view_execute_search (memo_shell_view);
+}
+
+static void
+action_search_filter_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMemoShellView *memo_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ gtk_action_activate (ACTION (SEARCH_EXECUTE));
+}
+
+static GtkActionEntry memo_entries[] = {
+
+ { "memo-clipboard-copy",
+ GTK_STOCK_COPY,
+ NULL,
+ NULL,
+ N_("Copy selected memo"),
+ G_CALLBACK (action_memo_clipboard_copy_cb) },
+
+ { "memo-clipboard-cut",
+ GTK_STOCK_CUT,
+ NULL,
+ NULL,
+ N_("Cut selected memo"),
+ G_CALLBACK (action_memo_clipboard_cut_cb) },
+
+ { "memo-clipboard-paste",
+ GTK_STOCK_PASTE,
+ NULL,
+ NULL,
+ N_("Paste memo from the clipboard"),
+ G_CALLBACK (action_memo_clipboard_paste_cb) },
+
+ { "memo-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete Memo"),
+ NULL,
+ N_("Delete selected memos"),
+ G_CALLBACK (action_memo_delete_cb) },
+
+ { "memo-forward",
+ "mail-forward",
+ N_("_Forward as iCalendar..."),
+ "<Control>f",
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_forward_cb) },
+
+ { "memo-list-copy",
+ GTK_STOCK_COPY,
+ N_("_Copy..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_list_copy_cb) },
+
+ { "memo-list-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_list_delete_cb) },
+
+ { "memo-list-new",
+ "stock_notes",
+ N_("_New Memo List"),
+ NULL,
+ N_("Create a new memo list"),
+ G_CALLBACK (action_memo_list_new_cb) },
+
+ { "memo-list-properties",
+ GTK_STOCK_PROPERTIES,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_list_properties_cb) },
+
+ { "memo-list-rename",
+ NULL,
+ N_("_Rename..."),
+ "F2",
+ N_("Rename the selected memo list"),
+ G_CALLBACK (action_memo_list_rename_cb) },
+
+ { "memo-list-select-one",
+ "stock_check-filled",
+ N_("Show _Only This Memo List"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_list_select_one_cb) },
+
+ { "memo-new",
+ "stock_insert-note",
+ N_("New _Memo"),
+ NULL,
+ N_("Create a new memo"),
+ G_CALLBACK (action_memo_new_cb) },
+
+ { "memo-open",
+ GTK_STOCK_OPEN,
+ N_("_Open Memo"),
+ "<Control>o",
+ N_("View the selected memo"),
+ G_CALLBACK (action_memo_open_cb) },
+
+ { "memo-open-url",
+ "applications-internet",
+ N_("Open _Web Page"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_open_url_cb) },
+
+ { "memo-save-as",
+ GTK_STOCK_SAVE_AS,
+ N_("_Save as iCalendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_memo_save_as_cb) },
+
+ /*** Menus ***/
+
+ { "memo-preview-menu",
+ NULL,
+ N_("_Preview"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static EPopupActionEntry memo_popup_entries[] = {
+
+ { "memo-list-popup-copy",
+ NULL,
+ "memo-list-copy" },
+
+ { "memo-list-popup-delete",
+ NULL,
+ "memo-list-delete" },
+
+ { "memo-list-popup-properties",
+ NULL,
+ "memo-list-properties" },
+
+ { "memo-list-popup-rename",
+ NULL,
+ "memo-list-rename" },
+
+ { "memo-list-popup-select-one",
+ NULL,
+ "memo-list-select-one" },
+
+ { "memo-popup-clipboard-copy",
+ NULL,
+ "memo-clipboard-copy" },
+
+ { "memo-popup-clipboard-cut",
+ NULL,
+ "memo-clipboard-cut" },
+
+ { "memo-popup-clipboard-paste",
+ NULL,
+ "memo-clipboard-paste" },
+
+ { "memo-popup-delete",
+ NULL,
+ "memo-delete" },
+
+ { "memo-popup-forward",
+ NULL,
+ "memo-forward" },
+
+ { "memo-popup-open",
+ NULL,
+ "memo-open" },
+
+ { "memo-popup-open-url",
+ NULL,
+ "memo-open-url" },
+
+ { "memo-popup-save-as",
+ NULL,
+ "memo-save-as" }
+};
+
+static GtkToggleActionEntry memo_toggle_entries[] = {
+
+ { "memo-preview",
+ NULL,
+ N_("Memo _Preview"),
+ "<Control>m",
+ N_("Show memo preview pane"),
+ G_CALLBACK (action_memo_preview_cb),
+ TRUE }
+};
+
+static GtkRadioActionEntry memo_view_entries[] = {
+
+ /* This action represents the initial active memo view.
+ * It should not be visible in the UI, nor should it be
+ * possible to switch to it from another shell view. */
+ { "memo-view-initial",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ -1 },
+
+ { "memo-view-classic",
+ NULL,
+ N_("_Classic View"),
+ NULL,
+ N_("Show memo preview below the memo list"),
+ 0 },
+
+ { "memo-view-vertical",
+ NULL,
+ N_("_Vertical View"),
+ NULL,
+ N_("Show memo preview alongside the memo list"),
+ 1 }
+};
+
+static GtkRadioActionEntry memo_filter_entries[] = {
+
+ { "memo-filter-any-category",
+ NULL,
+ N_("Any Category"),
+ NULL,
+ NULL,
+ MEMO_FILTER_ANY_CATEGORY },
+
+ { "memo-filter-unmatched",
+ NULL,
+ N_("Unmatched"),
+ NULL,
+ NULL,
+ MEMO_FILTER_UNMATCHED }
+};
+
+static GtkRadioActionEntry memo_search_entries[] = {
+
+ { "memo-search-any-field-contains",
+ NULL,
+ N_("Any field contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MEMO_SEARCH_ANY_FIELD_CONTAINS },
+
+ { "memo-search-description-contains",
+ NULL,
+ N_("Description contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MEMO_SEARCH_DESCRIPTION_CONTAINS },
+
+ { "memo-search-summary-contains",
+ NULL,
+ N_("Summary contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MEMO_SEARCH_SUMMARY_CONTAINS }
+};
+
+static GtkActionEntry lockdown_printing_entries[] = {
+
+ { "memo-list-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ "<Control>p",
+ N_("Print the list of memos"),
+ G_CALLBACK (action_memo_list_print_cb) },
+
+ { "memo-list-print-preview",
+ GTK_STOCK_PRINT_PREVIEW,
+ NULL,
+ NULL,
+ N_("Preview the list of memos to be printed"),
+ G_CALLBACK (action_memo_list_print_preview_cb) },
+
+ { "memo-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ NULL,
+ N_("Print the selected memo"),
+ G_CALLBACK (action_memo_print_cb) }
+};
+
+static EPopupActionEntry lockdown_printing_popup_entries[] = {
+
+ { "memo-popup-print",
+ NULL,
+ "memo-print" }
+};
+
+void
+e_memo_shell_view_actions_init (EMemoShellView *memo_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 (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ /* Memo Actions */
+ action_group = ACTION_GROUP (MEMOS);
+ gtk_action_group_add_actions (
+ action_group, memo_entries,
+ G_N_ELEMENTS (memo_entries), memo_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, memo_popup_entries,
+ G_N_ELEMENTS (memo_popup_entries));
+ gtk_action_group_add_toggle_actions (
+ action_group, memo_toggle_entries,
+ G_N_ELEMENTS (memo_toggle_entries), memo_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, memo_view_entries,
+ G_N_ELEMENTS (memo_view_entries), -1,
+ G_CALLBACK (action_memo_view_cb), memo_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, memo_search_entries,
+ G_N_ELEMENTS (memo_search_entries),
+ MEMO_SEARCH_SUMMARY_CONTAINS,
+ G_CALLBACK (action_memo_search_cb), memo_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), memo_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 (MEMO_PREVIEW));
+ key = "/apps/evolution/calendar/display/show_memo_preview";
+ gconf_bridge_bind_property (bridge, key, object, "active");
+
+ object = G_OBJECT (ACTION (MEMO_VIEW_VERTICAL));
+ key = "/apps/evolution/calendar/display/memo_layout";
+ gconf_bridge_bind_property (bridge, key, object, "current-value");
+
+ /* Fine tuning. */
+
+ action = ACTION (MEMO_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), memo_shell_view);
+
+ g_signal_connect (
+ ACTION (SEARCH_EXECUTE), "activate",
+ G_CALLBACK (action_search_execute_cb), memo_shell_view);
+}
+
+void
+e_memo_shell_view_update_search_filter (EMemoShellView *memo_shell_view)
+{
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GList *list, *iter;
+ GSList *group;
+ gint ii;
+
+ shell_view = E_SHELL_VIEW (memo_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 (MEMOS_FILTER);
+ e_action_group_remove_all_actions (action_group);
+
+ /* Add the standard filter actions. */
+ gtk_action_group_add_radio_actions (
+ action_group, memo_filter_entries,
+ G_N_ELEMENTS (memo_filter_entries),
+ MEMO_FILTER_ANY_CATEGORY,
+ G_CALLBACK (action_search_filter_cb),
+ memo_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 (
+ "memo-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 = MEMO_FILTER_UNMATCHED;
+ e_shell_content_add_filter_separator_after (shell_content, ii);
+}
diff --git a/modules/calendar/e-memo-shell-view-actions.h b/modules/calendar/e-memo-shell-view-actions.h
new file mode 100644
index 0000000000..d43d0239d9
--- /dev/null
+++ b/modules/calendar/e-memo-shell-view-actions.h
@@ -0,0 +1,91 @@
+/*
+ * e-memo-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_MEMO_SHELL_VIEW_ACTIONS_H
+#define E_MEMO_SHELL_VIEW_ACTIONS_H
+
+#include <shell/e-shell-window-actions.h>
+
+/* Memo Actions */
+#define E_SHELL_WINDOW_ACTION_MEMO_CLIPBOARD_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-clipboard-copy")
+#define E_SHELL_WINDOW_ACTION_MEMO_CLIPBOARD_CUT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-clipboard-cut")
+#define E_SHELL_WINDOW_ACTION_MEMO_CLIPBOARD_PASTE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-clipboard-paste")
+#define E_SHELL_WINDOW_ACTION_MEMO_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-delete")
+#define E_SHELL_WINDOW_ACTION_MEMO_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-forward")
+#define E_SHELL_WINDOW_ACTION_MEMO_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-new")
+#define E_SHELL_WINDOW_ACTION_MEMO_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-open")
+#define E_SHELL_WINDOW_ACTION_MEMO_OPEN_URL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-open-url")
+#define E_SHELL_WINDOW_ACTION_MEMO_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-preview")
+#define E_SHELL_WINDOW_ACTION_MEMO_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-print")
+#define E_SHELL_WINDOW_ACTION_MEMO_SAVE_AS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-save-as")
+#define E_SHELL_WINDOW_ACTION_MEMO_VIEW_CLASSIC(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-view-classic")
+#define E_SHELL_WINDOW_ACTION_MEMO_VIEW_VERTICAL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-view-vertical")
+
+/* Memo List Actions */
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-copy")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-delete")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-new")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-print")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_PRINT_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-print-preview")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_PROPERTIES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-properties")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_RENAME(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-rename")
+#define E_SHELL_WINDOW_ACTION_MEMO_LIST_SELECT_ONE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-list-select-one")
+
+/* Memo Query Actions */
+#define E_SHELL_WINDOW_ACTION_MEMO_FILTER_ANY_CATEGORY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-filter-any-category")
+#define E_SHELL_WINDOW_ACTION_MEMO_FILTER_UNMATCHED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-filter-unmatched")
+#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_ANY_FIELD_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-search-any-field-contains")
+#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_DESCRIPTION_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-search-description-contains")
+#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_SUMMARY_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "memo-search-summary-contains")
+
+/* Action Groups */
+#define E_SHELL_WINDOW_ACTION_GROUP_MEMOS(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "memos")
+#define E_SHELL_WINDOW_ACTION_GROUP_MEMOS_FILTER(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "memos-filter")
+
+#endif /* E_MEMO_SHELL_VIEW_ACTIONS_H */
diff --git a/modules/calendar/e-memo-shell-view-private.c b/modules/calendar/e-memo-shell-view-private.c
new file mode 100644
index 0000000000..da321d5c19
--- /dev/null
+++ b/modules/calendar/e-memo-shell-view-private.c
@@ -0,0 +1,561 @@
+/*
+ * e-memo-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-memo-shell-view-private.h"
+
+#include "widgets/menus/gal-view-factory-etable.h"
+
+static void
+memo_shell_view_table_popup_event_cb (EShellView *shell_view,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/memo-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+}
+
+static void
+memo_shell_view_table_user_created_cb (EMemoShellView *memo_shell_view,
+ EMemoTable *memo_table)
+{
+ EMemoShellSidebar *memo_shell_sidebar;
+ ECalModel *model;
+ ECal *client;
+ ESource *source;
+
+ /* This is the "Click to Add" handler. */
+
+ model = e_memo_table_get_model (memo_table);
+ client = e_cal_model_get_default_client (model);
+ source = e_cal_get_source (client);
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ e_memo_shell_sidebar_add_source (memo_shell_sidebar, source);
+
+ e_cal_model_add_client (model, client);
+}
+
+static void
+memo_shell_view_selector_client_added_cb (EMemoShellView *memo_shell_view,
+ ECal *client)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModel *model;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ model = e_memo_table_get_model (memo_table);
+
+ e_cal_model_add_client (model, client);
+ e_memo_shell_view_update_timezone (memo_shell_view);
+}
+
+static void
+memo_shell_view_selector_client_removed_cb (EMemoShellView *memo_shell_view,
+ ECal *client)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoTable *memo_table;
+ ECalModel *model;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ model = e_memo_table_get_model (memo_table);
+
+ e_cal_model_remove_client (model, client);
+}
+
+static gboolean
+memo_shell_view_selector_popup_event_cb (EShellView *shell_view,
+ ESource *primary_source,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/memo-list-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+
+ return TRUE;
+}
+
+static void
+memo_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 memos");
+ 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);
+
+ gal_view_collection_load (collection);
+}
+
+static void
+memo_shell_view_notify_view_id_cb (EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ GalViewInstance *view_instance;
+ const gchar *view_id;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ view_instance =
+ e_memo_shell_content_get_view_instance (memo_shell_content);
+ view_id = e_shell_view_get_view_id (E_SHELL_VIEW (memo_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_memo_shell_view_private_init (EMemoShellView *memo_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ if (!gal_view_collection_loaded (shell_view_class->view_collection))
+ memo_shell_view_load_view_collection (shell_view_class);
+
+ g_signal_connect (
+ memo_shell_view, "notify::view-id",
+ G_CALLBACK (memo_shell_view_notify_view_id_cb), NULL);
+}
+
+void
+e_memo_shell_view_private_constructed (EMemoShellView *memo_shell_view)
+{
+ EMemoShellViewPrivate *priv = memo_shell_view->priv;
+ EMemoShellContent *memo_shell_content;
+ EMemoShellSidebar *memo_shell_sidebar;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ EMemoTable *memo_table;
+ ECalModel *model;
+ ETable *table;
+ ESourceSelector *selector;
+
+ shell_view = E_SHELL_VIEW (memo_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, "memos");
+ e_shell_window_add_action_group (shell_window, "memos-filter");
+
+ /* Cache these to avoid lots of awkward casting. */
+ priv->memo_shell_backend = g_object_ref (shell_backend);
+ priv->memo_shell_content = g_object_ref (shell_content);
+ priv->memo_shell_sidebar = g_object_ref (shell_sidebar);
+
+ memo_shell_content = E_MEMO_SHELL_CONTENT (shell_content);
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ model = e_memo_table_get_model (memo_table);
+ table = e_memo_table_get_table (memo_table);
+
+ memo_shell_sidebar = E_MEMO_SHELL_SIDEBAR (shell_sidebar);
+ selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar);
+
+ g_signal_connect_swapped (
+ model, "notify::timezone",
+ G_CALLBACK (e_memo_shell_view_update_timezone),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_table, "open-component",
+ G_CALLBACK (e_memo_shell_view_open_memo),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_table, "popup-event",
+ G_CALLBACK (memo_shell_view_table_popup_event_cb),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_table, "status-message",
+ G_CALLBACK (e_memo_shell_view_set_status_message),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_table, "user-created",
+ G_CALLBACK (memo_shell_view_table_user_created_cb),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ model, "model-changed",
+ G_CALLBACK (e_memo_shell_view_update_sidebar),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ model, "model-rows-deleted",
+ G_CALLBACK (e_memo_shell_view_update_sidebar),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ model, "model-rows-inserted",
+ G_CALLBACK (e_memo_shell_view_update_sidebar),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ table, "selection-change",
+ G_CALLBACK (e_memo_shell_view_update_sidebar),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_shell_sidebar, "client-added",
+ G_CALLBACK (memo_shell_view_selector_client_added_cb),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_shell_sidebar, "client-removed",
+ G_CALLBACK (memo_shell_view_selector_client_removed_cb),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ memo_shell_sidebar, "status-message",
+ G_CALLBACK (e_memo_shell_view_set_status_message),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ selector, "popup-event",
+ G_CALLBACK (memo_shell_view_selector_popup_event_cb),
+ memo_shell_view);
+
+ g_signal_connect_swapped (
+ selector, "primary-selection-changed",
+ G_CALLBACK (e_shell_view_update_actions),
+ memo_shell_view);
+
+ e_categories_register_change_listener (
+ G_CALLBACK (e_memo_shell_view_update_search_filter),
+ memo_shell_view);
+
+ e_memo_shell_view_actions_init (memo_shell_view);
+ e_memo_shell_view_update_sidebar (memo_shell_view);
+ e_memo_shell_view_update_search_filter (memo_shell_view);
+ e_memo_shell_view_update_timezone (memo_shell_view);
+
+ e_memo_shell_view_execute_search (memo_shell_view);
+}
+
+void
+e_memo_shell_view_private_dispose (EMemoShellView *memo_shell_view)
+{
+ EMemoShellViewPrivate *priv = memo_shell_view->priv;
+
+ DISPOSE (priv->memo_shell_backend);
+ DISPOSE (priv->memo_shell_content);
+ DISPOSE (priv->memo_shell_sidebar);
+
+ if (memo_shell_view->priv->activity != NULL) {
+ /* XXX Activity is not cancellable. */
+ e_activity_complete (memo_shell_view->priv->activity);
+ g_object_unref (memo_shell_view->priv->activity);
+ memo_shell_view->priv->activity = NULL;
+ }
+}
+
+void
+e_memo_shell_view_private_finalize (EMemoShellView *memo_shell_view)
+{
+ /* XXX Nothing to do? */
+}
+
+void
+e_memo_shell_view_execute_search (EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ GtkRadioAction *action;
+ GString *string;
+ ECalComponentPreview *memo_preview;
+ EMemoTable *memo_table;
+ ECalModel *model;
+ FilterRule *rule;
+ const gchar *format;
+ const gchar *text;
+ gchar *query;
+ gchar *temp;
+ gint value;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ 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 = GTK_RADIO_ACTION (ACTION (MEMO_SEARCH_ANY_FIELD_CONTAINS));
+ value = gtk_radio_action_get_current_value (action);
+
+ if (text == NULL || *text == '\0') {
+ text = "";
+ value = MEMO_SEARCH_SUMMARY_CONTAINS;
+ }
+
+ switch (value) {
+ default:
+ text = "";
+ /* fall through */
+
+ case MEMO_SEARCH_SUMMARY_CONTAINS:
+ format = "(contains? \"summary\" %s)";
+ break;
+
+ case MEMO_SEARCH_DESCRIPTION_CONTAINS:
+ format = "(contains? \"description\" %s)";
+ break;
+
+ case MEMO_SEARCH_ANY_FIELD_CONTAINS:
+ format = "(contains? \"any\" %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 MEMO_FILTER_ANY_CATEGORY:
+ break;
+
+ case MEMO_FILTER_UNMATCHED:
+ temp = g_strdup_printf (
+ "(and (has-categories? #f) %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 (has-categories? \"%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 ECalModel. */
+ rule = filter_rule_new ();
+ e_shell_content_set_search_rule (shell_content, rule);
+ g_object_unref (rule);
+
+ /* Submit the query. */
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+ model = e_memo_table_get_model (memo_table);
+ e_cal_model_set_search_query (model, query);
+ g_free (query);
+
+ memo_preview =
+ e_memo_shell_content_get_memo_preview (memo_shell_content);
+ e_cal_component_preview_clear (memo_preview);
+}
+
+void
+e_memo_shell_view_open_memo (EMemoShellView *memo_shell_view,
+ ECalModelComponent *comp_data)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_MEMO_SHELL_VIEW (memo_shell_view));
+ g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data));
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ editor = comp_editor_find_instance (uid);
+
+ if (editor != NULL)
+ goto exit;
+
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+
+ if (e_cal_component_has_organizer (comp))
+ flags |= COMP_EDITOR_IS_SHARED;
+
+ if (itip_organizer_is_user (comp, comp_data->client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = memo_editor_new (comp_data->client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_unref (comp);
+
+exit:
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+void
+e_memo_shell_view_set_status_message (EMemoShellView *memo_shell_view,
+ const gchar *status_message,
+ gdouble percent)
+{
+ EActivity *activity;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+
+ g_return_if_fail (E_IS_MEMO_SHELL_VIEW (memo_shell_view));
+
+ activity = memo_shell_view->priv->activity;
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ if (status_message == NULL || *status_message == '\0') {
+ if (activity != NULL) {
+ e_activity_complete (activity);
+ g_object_unref (activity);
+ activity = NULL;
+ }
+
+ } else if (activity == NULL) {
+ activity = e_activity_new (status_message);
+ e_activity_set_percent (activity, percent);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ } else {
+ e_activity_set_percent (activity, percent);
+ e_activity_set_primary_text (activity, status_message);
+ }
+
+ memo_shell_view->priv->activity = activity;
+}
+
+void
+e_memo_shell_view_update_sidebar (EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EShellView *shell_view;
+ EShellSidebar *shell_sidebar;
+ EMemoTable *memo_table;
+ ECalModel *model;
+ ETable *table;
+ GString *string;
+ const gchar *format;
+ gint n_rows;
+ gint n_selected;
+
+ shell_view = E_SHELL_VIEW (memo_shell_view);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_table = e_memo_shell_content_get_memo_table (memo_shell_content);
+
+ model = e_memo_table_get_model (memo_table);
+ table = e_memo_table_get_table (memo_table);
+
+ n_rows = e_table_model_row_count (E_TABLE_MODEL (model));
+ n_selected = e_table_selected_count (table);
+
+ string = g_string_sized_new (64);
+
+ format = ngettext ("%d memo", "%d memos", n_rows);
+ g_string_append_printf (string, format, n_rows);
+
+ if (n_selected > 0) {
+ format = _("%d selected");
+ g_string_append_len (string, ", ", 2);
+ g_string_append_printf (string, format, n_selected);
+ }
+
+ e_shell_sidebar_set_secondary_text (shell_sidebar, string->str);
+
+ g_string_free (string, TRUE);
+}
+
+void
+e_memo_shell_view_update_timezone (EMemoShellView *memo_shell_view)
+{
+ EMemoShellContent *memo_shell_content;
+ EMemoShellSidebar *memo_shell_sidebar;
+ ECalComponentPreview *memo_preview;
+ icaltimezone *timezone;
+ ECalModel *model;
+ GList *clients, *iter;
+
+ memo_shell_content = memo_shell_view->priv->memo_shell_content;
+ memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content);
+ model = e_memo_shell_content_get_memo_model (memo_shell_content);
+ timezone = e_cal_model_get_timezone (model);
+
+ memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar;
+ clients = e_memo_shell_sidebar_get_clients (memo_shell_sidebar);
+
+ for (iter = clients; iter != NULL; iter = iter->next) {
+ ECal *client = iter->data;
+
+ if (e_cal_get_load_state (client) == E_CAL_LOAD_LOADED)
+ e_cal_set_default_timezone (client, timezone, NULL);
+ }
+
+ e_cal_component_preview_set_default_timezone (memo_preview, timezone);
+
+ g_list_free (clients);
+}
diff --git a/modules/calendar/e-memo-shell-view-private.h b/modules/calendar/e-memo-shell-view-private.h
new file mode 100644
index 0000000000..c7ece91f68
--- /dev/null
+++ b/modules/calendar/e-memo-shell-view-private.h
@@ -0,0 +1,128 @@
+/*
+ * e-memo-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_MEMO_SHELL_VIEW_PRIVATE_H
+#define E_MEMO_SHELL_VIEW_PRIVATE_H
+
+#include "e-memo-shell-view.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libedataserver/e-categories.h>
+#include <libedataserver/e-sexp.h>
+
+#include "e-util/e-dialog-utils.h"
+#include "e-util/e-error.h"
+#include "e-util/e-util.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/misc/e-popup-action.h"
+
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/e-cal-component-preview.h"
+#include "calendar/gui/e-calendar-selector.h"
+#include "calendar/gui/print.h"
+#include "calendar/gui/dialogs/calendar-setup.h"
+#include "calendar/gui/dialogs/copy-source-dialog.h"
+#include "calendar/gui/dialogs/memo-editor.h"
+
+#include "e-memo-shell-backend.h"
+#include "e-memo-shell-content.h"
+#include "e-memo-shell-sidebar.h"
+#include "e-memo-shell-view-actions.h"
+
+#define E_MEMO_SHELL_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MEMO_SHELL_VIEW, EMemoShellViewPrivate))
+
+/* 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-memo-table.etspec"
+
+G_BEGIN_DECLS
+
+/* Filter items are displayed in ascending order.
+ * Non-negative values are reserved for categories. */
+enum {
+ MEMO_FILTER_ANY_CATEGORY = -2,
+ MEMO_FILTER_UNMATCHED = -1
+};
+
+/* Search items are displayed in ascending order. */
+enum {
+ MEMO_SEARCH_SUMMARY_CONTAINS,
+ MEMO_SEARCH_DESCRIPTION_CONTAINS,
+ MEMO_SEARCH_ANY_FIELD_CONTAINS
+};
+
+struct _EMemoShellViewPrivate {
+
+ /* These are just for convenience. */
+ EMemoShellBackend *memo_shell_backend;
+ EMemoShellContent *memo_shell_content;
+ EMemoShellSidebar *memo_shell_sidebar;
+
+ EActivity *activity;
+};
+
+void e_memo_shell_view_private_init
+ (EMemoShellView *memo_shell_view,
+ EShellViewClass *shell_view_class);
+void e_memo_shell_view_private_constructed
+ (EMemoShellView *memo_shell_view);
+void e_memo_shell_view_private_dispose
+ (EMemoShellView *memo_shell_view);
+void e_memo_shell_view_private_finalize
+ (EMemoShellView *memo_shell_view);
+
+/* Private Utilities */
+
+void e_memo_shell_view_actions_init
+ (EMemoShellView *memo_shell_view);
+void e_memo_shell_view_execute_search
+ (EMemoShellView *memo_shell_view);
+void e_memo_shell_view_open_memo
+ (EMemoShellView *memo_shell_view,
+ ECalModelComponent *comp_data);
+void e_memo_shell_view_set_status_message
+ (EMemoShellView *memo_shell_view,
+ const gchar *status_message,
+ gdouble percent);
+void e_memo_shell_view_update_sidebar
+ (EMemoShellView *memo_shell_view);
+void e_memo_shell_view_update_search_filter
+ (EMemoShellView *memo_shell_view);
+void e_memo_shell_view_update_timezone
+ (EMemoShellView *memo_shell_view);
+
+G_END_DECLS
+
+#endif /* E_MEMO_SHELL_VIEW_PRIVATE_H */
diff --git a/modules/calendar/e-memo-shell-view.c b/modules/calendar/e-memo-shell-view.c
new file mode 100644
index 0000000000..e2964b061c
--- /dev/null
+++ b/modules/calendar/e-memo-shell-view.c
@@ -0,0 +1,222 @@
+/*
+ * e-memo-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-memo-shell-view-private.h"
+
+static gpointer parent_class;
+static GType memo_shell_view_type;
+
+static void
+memo_shell_view_dispose (GObject *object)
+{
+ e_memo_shell_view_private_dispose (E_MEMO_SHELL_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+memo_shell_view_finalize (GObject *object)
+{
+ e_memo_shell_view_private_finalize (E_MEMO_SHELL_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+memo_shell_view_constructed (GObject *object)
+{
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ e_memo_shell_view_private_constructed (E_MEMO_SHELL_VIEW (object));
+}
+
+static void
+memo_shell_view_update_actions (EShellView *shell_view)
+{
+ EMemoShellViewPrivate *priv;
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ GtkAction *action;
+ const gchar *label;
+ gboolean sensitive;
+ guint32 state;
+
+ /* Be descriptive. */
+ gboolean any_memos_selected;
+ gboolean has_primary_source;
+ gboolean multiple_memos_selected;
+ gboolean primary_source_is_system;
+ gboolean selection_has_url;
+ gboolean single_memo_selected;
+ gboolean sources_are_editable;
+
+ priv = E_MEMO_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_memo_selected =
+ (state & E_MEMO_SHELL_CONTENT_SELECTION_SINGLE);
+ multiple_memos_selected =
+ (state & E_MEMO_SHELL_CONTENT_SELECTION_MULTIPLE);
+ sources_are_editable =
+ (state & E_MEMO_SHELL_CONTENT_SELECTION_CAN_EDIT);
+ selection_has_url =
+ (state & E_MEMO_SHELL_CONTENT_SELECTION_HAS_URL);
+
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ state = e_shell_sidebar_check_state (shell_sidebar);
+
+ has_primary_source =
+ (state & E_MEMO_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE);
+ primary_source_is_system =
+ (state & E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM);
+
+ any_memos_selected =
+ (single_memo_selected || multiple_memos_selected);
+
+ action = ACTION (MEMO_CLIPBOARD_COPY);
+ sensitive = any_memos_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_CLIPBOARD_CUT);
+ sensitive = any_memos_selected && sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_CLIPBOARD_PASTE);
+ sensitive = sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_DELETE);
+ sensitive = any_memos_selected && sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+ if (multiple_memos_selected)
+ label = _("Delete Memos");
+ else
+ label = _("Delete Memo");
+ g_object_set (action, "label", label, NULL);
+
+ action = ACTION (MEMO_FORWARD);
+ sensitive = single_memo_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_LIST_COPY);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_LIST_DELETE);
+ sensitive = has_primary_source && !primary_source_is_system;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_LIST_PROPERTIES);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_LIST_RENAME);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_OPEN);
+ sensitive = single_memo_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_OPEN_URL);
+ sensitive = single_memo_selected && selection_has_url;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_PRINT);
+ sensitive = single_memo_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MEMO_SAVE_AS);
+ sensitive = single_memo_selected;
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+memo_shell_view_class_init (EMemoShellViewClass *class,
+ GTypeModule *type_module)
+{
+ GObjectClass *object_class;
+ EShellViewClass *shell_view_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMemoShellViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = memo_shell_view_dispose;
+ object_class->finalize = memo_shell_view_finalize;
+ object_class->constructed = memo_shell_view_constructed;
+
+ shell_view_class = E_SHELL_VIEW_CLASS (class);
+ shell_view_class->label = _("Memos");
+ shell_view_class->icon_name = "evolution-memos";
+ shell_view_class->ui_definition = "evolution-memos.ui";
+ shell_view_class->ui_manager_id = "org.gnome.evolution.memos";
+ shell_view_class->search_options = "/memo-search-options";
+ shell_view_class->search_rules = "memotypes.xml";
+ shell_view_class->new_shell_content = e_memo_shell_content_new;
+ shell_view_class->new_shell_sidebar = e_memo_shell_sidebar_new;
+ shell_view_class->update_actions = memo_shell_view_update_actions;
+}
+
+static void
+memo_shell_view_init (EMemoShellView *memo_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ memo_shell_view->priv =
+ E_MEMO_SHELL_VIEW_GET_PRIVATE (memo_shell_view);
+
+ e_memo_shell_view_private_init (memo_shell_view, shell_view_class);
+}
+
+GType
+e_memo_shell_view_get_type (void)
+{
+ return memo_shell_view_type;
+}
+
+void
+e_memo_shell_view_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EMemoShellViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) memo_shell_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ type_module,
+ sizeof (EMemoShellView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) memo_shell_view_init,
+ NULL /* value_table */
+ };
+
+ memo_shell_view_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_VIEW,
+ "EMemoShellView", &type_info, 0);
+}
diff --git a/modules/calendar/e-memo-shell-view.h b/modules/calendar/e-memo-shell-view.h
new file mode 100644
index 0000000000..686ae734c9
--- /dev/null
+++ b/modules/calendar/e-memo-shell-view.h
@@ -0,0 +1,67 @@
+/*
+ * e-memo-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_MEMO_SHELL_VIEW_H
+#define E_MEMO_SHELL_VIEW_H
+
+#include <shell/e-shell-view.h>
+#include <libedataserver/e-source-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MEMO_SHELL_VIEW \
+ (e_memo_shell_view_get_type ())
+#define E_MEMO_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MEMO_SHELL_VIEW, EMemoShellView))
+#define E_MEMO_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MEMO_SHELL_VIEW, EMemoShellViewClass))
+#define E_IS_MEMO_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MEMO_SHELL_VIEW))
+#define E_IS_MEMO_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MEMO_SHELL_VIEW))
+#define E_MEMO_SHELL_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MEMO_SHELL_VIEW, EMemoShellViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMemoShellView EMemoShellView;
+typedef struct _EMemoShellViewClass EMemoShellViewClass;
+typedef struct _EMemoShellViewPrivate EMemoShellViewPrivate;
+
+struct _EMemoShellView {
+ EShellView parent;
+ EMemoShellViewPrivate *priv;
+};
+
+struct _EMemoShellViewClass {
+ EShellViewClass parent_class;
+};
+
+GType e_memo_shell_view_get_type (void);
+void e_memo_shell_view_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_MEMO_SHELL_VIEW_H */
diff --git a/modules/calendar/e-task-shell-backend.c b/modules/calendar/e-task-shell-backend.c
new file mode 100644
index 0000000000..59a87e653c
--- /dev/null
+++ b/modules/calendar/e-task-shell-backend.c
@@ -0,0 +1,661 @@
+/*
+ * e-task-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-task-shell-backend.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal.h>
+#include <libedataserver/e-url.h>
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserver/e-source-group.h>
+
+#include "shell/e-shell.h"
+#include "shell/e-shell-backend.h"
+#include "shell/e-shell-window.h"
+
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/dialogs/calendar-setup.h"
+#include "calendar/gui/dialogs/task-editor.h"
+
+#include "e-task-shell-content.h"
+#include "e-task-shell-migrate.h"
+#include "e-task-shell-sidebar.h"
+#include "e-task-shell-view.h"
+
+#define E_TASK_SHELL_BACKEND_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackendPrivate))
+
+#define WEB_BASE_URI "webcal://"
+#define PERSONAL_RELATIVE_URI "system"
+
+struct _ETaskShellBackendPrivate {
+ ESourceList *source_list;
+};
+
+enum {
+ PROP_0,
+ PROP_SOURCE_LIST
+};
+
+static gpointer parent_class;
+static GType task_shell_backend_type;
+
+static void
+task_shell_backend_ensure_sources (EShellBackend *shell_backend)
+{
+ /* XXX This is basically the same algorithm across all modules.
+ * Maybe we could somehow integrate this into EShellBackend? */
+
+ ETaskShellBackendPrivate *priv;
+ ESourceGroup *on_this_computer;
+ ESourceGroup *on_the_web;
+ ESource *personal;
+ EShell *shell;
+ EShellSettings *shell_settings;
+ GSList *groups, *iter;
+ const gchar *data_dir;
+ const gchar *name;
+ gchar *base_uri;
+ gchar *filename;
+
+ on_this_computer = NULL;
+ on_the_web = NULL;
+ personal = NULL;
+
+ priv = E_TASK_SHELL_BACKEND_GET_PRIVATE (shell_backend);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ if (!e_cal_get_sources (&priv->source_list, E_CAL_SOURCE_TYPE_TODO, NULL)) {
+ g_warning ("Could not get task 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_the_web == NULL &&
+ strcmp (WEB_BASE_URI, group_base_uri) == 0)
+ on_the_web = 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;
+ GSList *selected;
+ gchar *primary;
+
+ source = e_source_new (name, PERSONAL_RELATIVE_URI);
+ e_source_group_add_source (on_this_computer, source, -1);
+ g_object_unref (source);
+
+ primary = e_shell_settings_get_string (
+ shell_settings, "cal-primary-task-list");
+
+ selected = calendar_config_get_tasks_selected ();
+
+ if (primary == NULL && selected == NULL) {
+ const gchar *uid;
+
+ uid = e_source_peek_uid (source);
+ selected = g_slist_prepend (NULL, g_strdup (uid));
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-task-list", uid);
+ calendar_config_set_tasks_selected (selected);
+ }
+
+ g_slist_foreach (selected, (GFunc) g_free, NULL);
+ g_slist_free (selected);
+ g_free (primary);
+ } else {
+ /* Force the source name to the current locale. */
+ e_source_set_name (personal, name);
+ }
+
+ name = _("On The Web");
+
+ if (on_the_web == NULL) {
+ ESourceGroup *source_group;
+
+ source_group = e_source_group_new (name, WEB_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_the_web, name);
+ }
+
+ g_free (base_uri);
+}
+
+static void
+task_shell_backend_task_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+
+ editor = task_editor_new (cal, shell, flags);
+ comp = cal_comp_task_new_with_defaults (cal);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+task_shell_backend_task_assigned_new_cb (ECal *cal,
+ ECalendarStatus status,
+ EShell *shell)
+{
+ ECalComponent *comp;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+
+ /* XXX Handle errors better. */
+ if (status != E_CALENDAR_STATUS_OK)
+ return;
+
+ flags |= COMP_EDITOR_NEW_ITEM;
+ flags |= COMP_EDITOR_IS_ASSIGNED;
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = task_editor_new (cal, shell, flags);
+ comp = cal_comp_task_new_with_defaults (cal);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (cal);
+}
+
+static void
+action_task_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ ECal *cal = NULL;
+ ECalSourceType source_type;
+ ESourceList *source_list;
+ EShellSettings *shell_settings;
+ EShell *shell;
+ const gchar *action_name;
+ gchar *uid;
+
+ /* This callback is used for both tasks and assigned tasks. */
+
+ source_type = E_CAL_SOURCE_TYPE_TODO;
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ if (!e_cal_get_sources (&source_list, source_type, NULL)) {
+ g_warning ("Could not get task sources from GConf!");
+ return;
+ }
+
+ uid = e_shell_settings_get_string (
+ shell_settings, "cal-primary-task-list");
+
+ if (uid != NULL) {
+ ESource *source;
+
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ if (source != NULL)
+ cal = auth_new_cal_from_source (source, source_type);
+ g_free (uid);
+ }
+
+ if (cal == NULL)
+ cal = auth_new_cal_from_default (source_type);
+
+ g_return_if_fail (cal != NULL);
+
+ /* Connect the appropriate signal handler. */
+ action_name = gtk_action_get_name (action);
+ if (strcmp (action_name, "task-assigned-new") == 0)
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (task_shell_backend_task_assigned_new_cb),
+ shell);
+ else
+ g_signal_connect (
+ cal, "cal-opened",
+ G_CALLBACK (task_shell_backend_task_new_cb),
+ shell);
+
+ e_cal_open_async (cal, FALSE);
+}
+
+static void
+action_task_list_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ calendar_setup_new_task_list (GTK_WINDOW (shell_window));
+}
+
+static GtkActionEntry item_entries[] = {
+
+ { "task-new",
+ "stock_task",
+ NC_("New", "_Task"),
+ "<Shift><Control>t",
+ N_("Create a new task"),
+ G_CALLBACK (action_task_new_cb) },
+
+ { "task-assigned-new",
+ "stock_task",
+ N_("Assigne_d Task"),
+ NULL,
+ N_("Create a new assigned task"),
+ G_CALLBACK (action_task_new_cb) }
+};
+
+static GtkActionEntry source_entries[] = {
+
+ { "task-list-new",
+ "stock_todo",
+ NC_("New", "Tas_k List"),
+ NULL,
+ N_("Create a new task list"),
+ G_CALLBACK (action_task_list_new_cb) }
+};
+
+static gboolean
+task_shell_backend_handle_uri_cb (EShellBackend *shell_backend,
+ const gchar *uri)
+{
+ EShell *shell;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECal *client;
+ ECalComponent *comp;
+ ESource *source;
+ ESourceList *source_list;
+ ECalSourceType source_type;
+ EUri *euri;
+ icalcomponent *icalcomp;
+ icalproperty *icalprop;
+ const gchar *cp;
+ gchar *source_uid = NULL;
+ gchar *comp_uid = NULL;
+ gchar *comp_rid = NULL;
+ gboolean handled = FALSE;
+ GError *error = NULL;
+
+ source_type = E_CAL_SOURCE_TYPE_TODO;
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ if (strncmp (uri, "task:", 5) != 0)
+ return FALSE;
+
+ euri = e_uri_new (uri);
+ cp = euri->query;
+ if (cp == NULL)
+ goto exit;
+
+ while (*cp != '\0') {
+ gchar *header;
+ gchar *content;
+ gsize header_len;
+ gsize content_len;
+
+ header_len = strcspn (cp, "=&");
+
+ /* If it's malformed, give up. */
+ if (cp[header_len] != '=')
+ break;
+
+ header = (gchar *) cp;
+ header[header_len] = '\0';
+ cp += header_len + 1;
+
+ content_len = strcspn (cp, "&");
+
+ content = g_strndup (cp, content_len);
+ if (g_ascii_strcasecmp (header, "source-uid") == 0)
+ source_uid = g_strdup (content);
+ else if (g_ascii_strcasecmp (header, "comp-uid") == 0)
+ comp_uid = g_strdup (content);
+ else if (g_ascii_strcasecmp (header, "comp-rid") == 0)
+ comp_rid = g_strdup (content);
+ g_free (content);
+
+ cp += content_len;
+ if (*cp == '&') {
+ cp++;
+ if (strcmp (cp, "amp;") == 0)
+ cp += 4;
+ }
+ }
+
+ if (source_uid == NULL || comp_uid == NULL)
+ goto exit;
+
+ /* URI is valid, so consider it handled. Whether
+ * we successfully open it is another matter... */
+ handled = TRUE;
+
+ if (!e_cal_get_sources (&source_list, source_type, NULL)) {
+ g_printerr ("Could not get task sources from GConf!\n");
+ goto exit;
+ }
+
+ source = e_source_list_peek_source_by_uid (source_list, source_uid);
+ if (source == NULL) {
+ g_printerr ("No source for UID `%s'\n", source_uid);
+ g_object_unref (source_list);
+ goto exit;
+ }
+
+ client = auth_new_cal_from_source (source, source_type);
+ if (client == NULL || !e_cal_open (client, TRUE, &error)) {
+ g_printerr ("%s\n", error->message);
+ g_object_unref (source_list);
+ g_error_free (error);
+ goto exit;
+ }
+
+ /* XXX Copied from e_task_shell_view_open_task().
+ * Clearly a new utility function is needed. */
+
+ editor = comp_editor_find_instance (comp_uid);
+
+ if (editor != NULL)
+ goto present;
+
+ if (!e_cal_get_object (client, comp_uid, comp_rid, &icalcomp, &error)) {
+ g_printerr ("%s\n", error->message);
+ g_object_unref (source_list);
+ g_error_free (error);
+ goto exit;
+ }
+
+ comp = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (comp, icalcomp);
+
+ icalprop = icalcomponent_get_first_property (
+ icalcomp, ICAL_ATTENDEE_PROPERTY);
+ if (icalprop != NULL)
+ flags |= COMP_EDITOR_IS_ASSIGNED;
+
+ if (itip_organizer_is_user (comp, client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (!e_cal_component_has_attendees (comp))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = task_editor_new (client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_unref (comp);
+
+present:
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (source_list);
+ g_object_unref (client);
+
+exit:
+ g_free (source_uid);
+ g_free (comp_uid);
+ g_free (comp_rid);
+
+ e_uri_free (euri);
+
+ return handled;
+}
+
+static void
+task_shell_backend_window_created_cb (EShellBackend *shell_backend,
+ GtkWindow *window)
+{
+ const gchar *module_name;
+
+ if (!E_IS_SHELL_WINDOW (window))
+ return;
+
+ module_name = E_SHELL_BACKEND_GET_CLASS (shell_backend)->name;
+
+ e_shell_window_register_new_item_actions (
+ E_SHELL_WINDOW (window), module_name,
+ item_entries, G_N_ELEMENTS (item_entries));
+
+ e_shell_window_register_new_source_actions (
+ E_SHELL_WINDOW (window), module_name,
+ source_entries, G_N_ELEMENTS (source_entries));
+}
+
+static void
+task_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_task_shell_backend_get_source_list (
+ E_TASK_SHELL_BACKEND (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+task_shell_backend_dispose (GObject *object)
+{
+ ETaskShellBackendPrivate *priv;
+
+ priv = E_TASK_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
+task_shell_backend_constructed (GObject *object)
+{
+ EShell *shell;
+ EShellBackend *shell_backend;
+
+ shell_backend = E_SHELL_BACKEND (object);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ task_shell_backend_ensure_sources (shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "handle-uri",
+ G_CALLBACK (task_shell_backend_handle_uri_cb),
+ shell_backend);
+
+ g_signal_connect_swapped (
+ shell, "window-created",
+ G_CALLBACK (task_shell_backend_window_created_cb),
+ shell_backend);
+}
+
+static void
+task_shell_backend_class_init (ETaskShellBackendClass *class)
+{
+ GObjectClass *object_class;
+ EShellBackendClass *shell_backend_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ETaskShellBackendPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = task_shell_backend_get_property;
+ object_class->dispose = task_shell_backend_dispose;
+ object_class->constructed = task_shell_backend_constructed;
+
+ shell_backend_class = E_SHELL_BACKEND_CLASS (class);
+ shell_backend_class->shell_view_type = E_TYPE_TASK_SHELL_VIEW;
+ shell_backend_class->name = "tasks";
+ shell_backend_class->aliases = "";
+ shell_backend_class->schemes = "task";
+ shell_backend_class->sort_order = 600;
+ shell_backend_class->start = NULL;
+ shell_backend_class->migrate = e_task_shell_backend_migrate;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE_LIST,
+ g_param_spec_object (
+ "source-list",
+ _("Source List"),
+ _("The registry of task lists"),
+ E_TYPE_SOURCE_LIST,
+ G_PARAM_READABLE));
+}
+
+static void
+task_shell_backend_init (ETaskShellBackend *task_shell_backend)
+{
+ task_shell_backend->priv =
+ E_TASK_SHELL_BACKEND_GET_PRIVATE (task_shell_backend);
+}
+
+GType
+e_task_shell_backend_get_type (void)
+{
+ return task_shell_backend_type;
+}
+
+void
+e_task_shell_backend_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (ETaskShellBackendClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) task_shell_backend_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ETaskShellBackend),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) task_shell_backend_init,
+ NULL /* value_table */
+ };
+
+ task_shell_backend_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_BACKEND,
+ "ETaskShellBackend", &type_info, 0);
+}
+
+ESourceList *
+e_task_shell_backend_get_source_list (ETaskShellBackend *task_shell_backend)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_BACKEND (task_shell_backend), NULL);
+
+ return task_shell_backend->priv->source_list;
+}
diff --git a/modules/calendar/e-task-shell-backend.h b/modules/calendar/e-task-shell-backend.h
new file mode 100644
index 0000000000..63b157ad85
--- /dev/null
+++ b/modules/calendar/e-task-shell-backend.h
@@ -0,0 +1,70 @@
+/*
+ * e-task-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_TASK_SHELL_BACKEND_H
+#define E_TASK_SHELL_BACKEND_H
+
+#include <shell/e-shell-backend.h>
+#include <libedataserver/e-source-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TASK_SHELL_BACKEND \
+ (e_task_shell_backend_get_type ())
+#define E_TASK_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackend))
+#define E_TASK_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackendClass))
+#define E_IS_TASK_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_TASK_SHELL_BACKEND))
+#define E_IS_TASK_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_TASK_SHELL_BACKEND))
+#define E_TASK_SHELL_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETaskShellBackend ETaskShellBackend;
+typedef struct _ETaskShellBackendClass ETaskShellBackendClass;
+typedef struct _ETaskShellBackendPrivate ETaskShellBackendPrivate;
+
+struct _ETaskShellBackend {
+ EShellBackend parent;
+ ETaskShellBackendPrivate *priv;
+};
+
+struct _ETaskShellBackendClass {
+ EShellBackendClass parent_class;
+};
+
+GType e_task_shell_backend_get_type (void);
+void e_task_shell_backend_register_type
+ (GTypeModule *type_module);
+ESourceList * e_task_shell_backend_get_source_list
+ (ETaskShellBackend *task_shell_backend);
+
+G_END_DECLS
+
+#endif /* E_TASK_SHELL_BACKEND_H */
diff --git a/modules/calendar/e-task-shell-content.c b/modules/calendar/e-task-shell-content.c
new file mode 100644
index 0000000000..a095003978
--- /dev/null
+++ b/modules/calendar/e-task-shell-content.c
@@ -0,0 +1,744 @@
+/*
+ * e-task-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-task-shell-content.h"
+
+#include <glib/gi18n.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/menus/gal-view-etable.h"
+#include "widgets/misc/e-paned.h"
+
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/e-cal-model-tasks.h"
+#include "calendar/gui/e-calendar-table.h"
+
+#define E_TASK_SHELL_CONTENT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentPrivate))
+
+#define E_CALENDAR_TABLE_DEFAULT_STATE \
+ "<?xml version=\"1.0\"?>" \
+ "<ETableState>" \
+ " <column source=\"13\"/>" \
+ " <column source=\"14\"/>" \
+ " <column source=\"9\"/>" \
+ " <column source=\"5\"/>" \
+ " <grouping/>" \
+ "</ETableState>"
+
+struct _ETaskShellContentPrivate {
+ GtkWidget *paned;
+ GtkWidget *task_table;
+ GtkWidget *task_preview;
+
+ ECalModel *task_model;
+ GalViewInstance *view_instance;
+ GtkOrientation orientation;
+
+ gchar *current_uid;
+
+ guint preview_visible : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_MODEL,
+ PROP_ORIENTATION,
+ PROP_PREVIEW_VISIBLE
+};
+
+enum {
+ TARGET_VCALENDAR
+};
+
+static GtkTargetEntry drag_types[] = {
+ { (gchar *) "text/calendar", 0, TARGET_VCALENDAR },
+ { (gchar *) "text/x-calendar", 0, TARGET_VCALENDAR }
+};
+
+static gpointer parent_class;
+static GType task_shell_content_type;
+
+static void
+task_shell_content_display_view_cb (ETaskShellContent *task_shell_content,
+ GalView *gal_view)
+{
+ ECalendarTable *task_table;
+ ETable *table;
+
+ if (!GAL_IS_VIEW_ETABLE (gal_view))
+ return;
+
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ table = e_calendar_table_get_table (task_table);
+
+ gal_view_etable_attach_table (GAL_VIEW_ETABLE (gal_view), table);
+}
+
+static void
+task_shell_content_table_foreach_cb (gint model_row,
+ gpointer user_data)
+{
+ ECalModelComponent *comp_data;
+ icalcomponent *clone;
+ icalcomponent *vcal;
+ gchar *string;
+
+ struct {
+ ECalModel *model;
+ GSList *list;
+ } *foreach_data = user_data;
+
+ comp_data = e_cal_model_get_component_at (
+ foreach_data->model, model_row);
+
+ vcal = e_cal_util_new_top_level ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_util_add_timezones_from_component (vcal, comp_data->icalcomp);
+ icalcomponent_add_component (vcal, clone);
+
+ /* String is owned by libical; do not free. */
+ string = icalcomponent_as_ical_string (vcal);
+ if (string != NULL) {
+ ESource *source;
+ const gchar *source_uid;
+
+ source = e_cal_get_source (comp_data->client);
+ source_uid = e_source_peek_uid (source);
+
+ foreach_data->list = g_slist_prepend (
+ foreach_data->list,
+ g_strdup_printf ("%s\n%s", source_uid, string));
+ }
+
+ icalcomponent_free (vcal);
+}
+
+static void
+task_shell_content_table_drag_data_get_cb (ETaskShellContent *task_shell_content,
+ gint row,
+ gint col,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ ECalendarTable *task_table;
+ ETable *table;
+
+ struct {
+ ECalModel *model;
+ GSList *list;
+ } foreach_data;
+
+ if (info != TARGET_VCALENDAR)
+ return;
+
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ table = e_calendar_table_get_table (task_table);
+
+ foreach_data.model = e_calendar_table_get_model (task_table);
+ foreach_data.list = NULL;
+
+ e_table_selected_row_foreach (
+ table, task_shell_content_table_foreach_cb,
+ &foreach_data);
+
+ if (foreach_data.list != NULL) {
+ cal_comp_selection_set_string_list (
+ selection_data, foreach_data.list);
+ g_slist_foreach (foreach_data.list, (GFunc) g_free, NULL);
+ g_slist_free (foreach_data.list);
+ }
+}
+
+static void
+task_shell_content_table_drag_data_delete_cb (ETaskShellContent *task_shell_content,
+ gint row,
+ gint col,
+ GdkDragContext *context)
+{
+ /* Moved components are deleted from source immediately when moved,
+ * because some of them can be part of destination source, and we
+ * don't want to delete not-moved tasks. There is no such information
+ * which event has been moved and which not, so skip this method. */
+}
+
+static void
+task_shell_content_cursor_change_cb (ETaskShellContent *task_shell_content,
+ gint row,
+ ETable *table)
+{
+ ECalComponentPreview *task_preview;
+ ECalendarTable *task_table;
+ ECalModel *task_model;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ const gchar *uid;
+
+ task_model = e_task_shell_content_get_task_model (task_shell_content);
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ task_preview = e_task_shell_content_get_task_preview (task_shell_content);
+
+ if (e_table_selected_count (table) != 1) {
+ e_cal_component_preview_clear (task_preview);
+ return;
+ }
+
+ row = e_table_get_cursor_row (table);
+ comp_data = e_cal_model_get_component_at (task_model, row);
+
+ comp = e_cal_component_new ();
+ e_cal_component_set_icalcomponent (
+ comp, icalcomponent_new_clone (comp_data->icalcomp));
+ e_cal_component_preview_display (
+ task_preview, comp_data->client, comp);
+
+ e_cal_component_get_uid (comp, &uid);
+ g_free (task_shell_content->priv->current_uid);
+ task_shell_content->priv->current_uid = g_strdup (uid);
+
+ g_object_unref (comp);
+}
+
+static void
+task_shell_content_selection_change_cb (ETaskShellContent *task_shell_content,
+ ETable *table)
+{
+ ECalComponentPreview *task_preview;
+
+ task_preview = e_task_shell_content_get_task_preview (task_shell_content);
+
+ if (e_table_selected_count (table) != 1)
+ e_cal_component_preview_clear (task_preview);
+}
+
+static void
+task_shell_content_model_row_changed_cb (ETaskShellContent *task_shell_content,
+ gint row,
+ ETableModel *model)
+{
+ ECalModelComponent *comp_data;
+ ECalendarTable *task_table;
+ ETable *table;
+ const gchar *current_uid;
+ const gchar *uid;
+
+ current_uid = task_shell_content->priv->current_uid;
+ if (current_uid == NULL)
+ return;
+
+ comp_data = e_cal_model_get_component_at (E_CAL_MODEL (model), row);
+ if (comp_data == NULL)
+ return;
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ if (g_strcmp0 (uid, current_uid) != 0)
+ return;
+
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ table = e_calendar_table_get_table (task_table);
+
+ task_shell_content_cursor_change_cb (task_shell_content, 0, table);
+}
+
+static GtkOrientation
+task_shell_content_get_orientation (ETaskShellContent *task_shell_content)
+{
+ return task_shell_content->priv->orientation;
+}
+
+static void
+task_shell_content_set_orientation (ETaskShellContent *task_shell_content,
+ GtkOrientation orientation)
+{
+ task_shell_content->priv->orientation = orientation;
+
+ g_object_notify (G_OBJECT (task_shell_content), "orientation");
+}
+
+static void
+task_shell_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ORIENTATION:
+ task_shell_content_set_orientation (
+ E_TASK_SHELL_CONTENT (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ e_task_shell_content_set_preview_visible (
+ E_TASK_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+task_shell_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_MODEL:
+ g_value_set_object (
+ value,
+ e_task_shell_content_get_task_model (
+ E_TASK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_ORIENTATION:
+ g_value_set_enum (
+ value,
+ task_shell_content_get_orientation (
+ E_TASK_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ g_value_set_boolean (
+ value,
+ e_task_shell_content_get_preview_visible (
+ E_TASK_SHELL_CONTENT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+task_shell_content_dispose (GObject *object)
+{
+ ETaskShellContentPrivate *priv;
+
+ priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (object);
+
+ if (priv->paned != NULL) {
+ g_object_unref (priv->paned);
+ priv->paned = NULL;
+ }
+
+ if (priv->task_table != NULL) {
+ g_object_unref (priv->task_table);
+ priv->task_table = NULL;
+ }
+
+ if (priv->task_preview != NULL) {
+ g_object_unref (priv->task_preview);
+ priv->task_preview = NULL;
+ }
+
+ if (priv->task_model != NULL) {
+ g_object_unref (priv->task_model);
+ priv->task_model = NULL;
+ }
+
+ if (priv->view_instance != NULL) {
+ g_object_unref (priv->view_instance);
+ priv->view_instance = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+task_shell_content_finalize (GObject *object)
+{
+ ETaskShellContentPrivate *priv;
+
+ priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (object);
+
+ g_free (priv->current_uid);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+task_shell_content_constructed (GObject *object)
+{
+ ETaskShellContentPrivate *priv;
+ EShell *shell;
+ EShellSettings *shell_settings;
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GalViewInstance *view_instance;
+ icaltimezone *timezone;
+ ETable *table;
+ GConfBridge *bridge;
+ GtkWidget *container;
+ GtkWidget *widget;
+ const gchar *key;
+
+ priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ shell_content = E_SHELL_CONTENT (object);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ priv->task_model = e_cal_model_tasks_new (shell_settings);
+
+ timezone = e_shell_settings_get_pointer (
+ shell_settings, "cal-timezone");
+
+ /* Build content widgets. */
+
+ container = GTK_WIDGET (object);
+
+ widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->paned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "orientation",
+ G_OBJECT (widget), "orientation");
+
+ container = widget;
+
+ widget = e_calendar_table_new (shell_view, priv->task_model);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ priv->task_table = 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_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "preview-visible",
+ G_OBJECT (widget), "visible");
+
+ container = widget;
+
+ widget = e_cal_component_preview_new ();
+ e_cal_component_preview_set_default_timezone (
+ E_CAL_COMPONENT_PREVIEW (widget), timezone);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->task_preview = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Configure the task table. */
+
+ widget = E_CALENDAR_TABLE (priv->task_table)->etable;
+ table = e_table_scrolled_get_table (E_TABLE_SCROLLED (widget));
+
+ e_table_set_state (table, E_CALENDAR_TABLE_DEFAULT_STATE);
+
+ e_table_drag_source_set (
+ table, GDK_BUTTON1_MASK,
+ drag_types, G_N_ELEMENTS (drag_types),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_ASK);
+
+ g_signal_connect_swapped (
+ table, "table-drag-data-get",
+ G_CALLBACK (task_shell_content_table_drag_data_get_cb),
+ object);
+
+ g_signal_connect_swapped (
+ table, "table-drag-data-delete",
+ G_CALLBACK (task_shell_content_table_drag_data_delete_cb),
+ object);
+
+ g_signal_connect_swapped (
+ table, "cursor-change",
+ G_CALLBACK (task_shell_content_cursor_change_cb),
+ object);
+
+ g_signal_connect_swapped (
+ table, "selection-change",
+ G_CALLBACK (task_shell_content_selection_change_cb),
+ object);
+
+ g_signal_connect_swapped (
+ priv->task_model, "model-row-changed",
+ G_CALLBACK (task_shell_content_model_row_changed_cb),
+ object);
+
+ /* Load the view instance. */
+
+ view_instance = e_shell_view_new_view_instance (shell_view, NULL);
+ g_signal_connect_swapped (
+ view_instance, "display-view",
+ G_CALLBACK (task_shell_content_display_view_cb),
+ object);
+ gal_view_instance_load (view_instance);
+ priv->view_instance = view_instance;
+
+ /* Bind GObject properties to GConf keys. */
+
+ bridge = gconf_bridge_get ();
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/calendar/display/task_hpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "hposition");
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/calendar/display/task_vpane_position";
+ gconf_bridge_bind_property_delayed (bridge, key, object, "vposition");
+}
+
+static guint32
+task_shell_content_check_state (EShellContent *shell_content)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ETable *table;
+ GSList *list, *iter;
+ gboolean assignable = TRUE;
+ gboolean editable = TRUE;
+ gboolean has_url = FALSE;
+ gint n_selected;
+ gint n_complete = 0;
+ gint n_incomplete = 0;
+ guint32 state = 0;
+
+ task_shell_content = E_TASK_SHELL_CONTENT (shell_content);
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ table = e_calendar_table_get_table (task_table);
+ n_selected = e_table_selected_count (table);
+
+ list = e_calendar_table_get_selected (task_table);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ icalproperty *prop;
+ const gchar *cap;
+ gboolean read_only;
+
+ e_cal_is_read_only (comp_data->client, &read_only, NULL);
+ editable &= !read_only;
+
+ cap = CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT;
+ if (e_cal_get_static_capability (comp_data->client, cap))
+ assignable = FALSE;
+
+ cap = CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK;
+ if (e_cal_get_static_capability (comp_data->client, cap))
+ assignable = FALSE;
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ has_url |= (prop != NULL);
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_COMPLETED_PROPERTY);
+ if (prop != NULL)
+ n_complete++;
+ else
+ n_incomplete++;
+ }
+ g_slist_free (list);
+
+ if (n_selected == 1)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_SINGLE;
+ if (n_selected > 1)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_MULTIPLE;
+ if (assignable)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_CAN_ASSIGN;
+ if (editable)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_CAN_EDIT;
+ if (n_complete > 0)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_HAS_COMPLETE;
+ if (n_incomplete > 0)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE;
+ if (has_url)
+ state |= E_TASK_SHELL_CONTENT_SELECTION_HAS_URL;
+
+ return state;
+}
+
+static void
+task_shell_content_class_init (ETaskShellContentClass *class)
+{
+ GObjectClass *object_class;
+ EShellContentClass *shell_content_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ETaskShellContentPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = task_shell_content_set_property;
+ object_class->get_property = task_shell_content_get_property;
+ object_class->dispose = task_shell_content_dispose;
+ object_class->finalize = task_shell_content_finalize;
+ object_class->constructed = task_shell_content_constructed;
+
+ shell_content_class = E_SHELL_CONTENT_CLASS (class);
+ shell_content_class->check_state = task_shell_content_check_state;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MODEL,
+ g_param_spec_object (
+ "model",
+ _("Model"),
+ _("The task table model"),
+ E_TYPE_CAL_MODEL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_VISIBLE,
+ g_param_spec_boolean (
+ "preview-visible",
+ _("Preview is Visible"),
+ _("Whether the preview pane is visible"),
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_override_property (
+ object_class, PROP_ORIENTATION, "orientation");
+}
+
+static void
+task_shell_content_init (ETaskShellContent *task_shell_content)
+{
+ task_shell_content->priv =
+ E_TASK_SHELL_CONTENT_GET_PRIVATE (task_shell_content);
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_task_shell_content_get_type (void)
+{
+ return task_shell_content_type;
+}
+
+void
+e_task_shell_content_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (ETaskShellContentClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) task_shell_content_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ETaskShellContent),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) task_shell_content_init,
+ NULL /* value_table */
+ };
+
+ static const GInterfaceInfo orientable_info = {
+ (GInterfaceInitFunc) NULL,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ task_shell_content_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_CONTENT,
+ "ETaskShellContent", &type_info, 0);
+
+ g_type_module_add_interface (
+ type_module, task_shell_content_type,
+ GTK_TYPE_ORIENTABLE, &orientable_info);
+}
+
+GtkWidget *
+e_task_shell_content_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_TASK_SHELL_CONTENT,
+ "shell-view", shell_view, NULL);
+}
+
+ECalModel *
+e_task_shell_content_get_task_model (ETaskShellContent *task_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
+
+ return task_shell_content->priv->task_model;
+}
+
+ECalComponentPreview *
+e_task_shell_content_get_task_preview (ETaskShellContent *task_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
+
+ return E_CAL_COMPONENT_PREVIEW (
+ task_shell_content->priv->task_preview);
+}
+
+ECalendarTable *
+e_task_shell_content_get_task_table (ETaskShellContent *task_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
+
+ return E_CALENDAR_TABLE (task_shell_content->priv->task_table);
+}
+
+gboolean
+e_task_shell_content_get_preview_visible (ETaskShellContent *task_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_CONTENT (task_shell_content), FALSE);
+
+ return task_shell_content->priv->preview_visible;
+}
+
+void
+e_task_shell_content_set_preview_visible (ETaskShellContent *task_shell_content,
+ gboolean preview_visible)
+{
+ g_return_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content));
+
+ task_shell_content->priv->preview_visible = preview_visible;
+
+ g_object_notify (G_OBJECT (task_shell_content), "preview-visible");
+}
+
+GalViewInstance *
+e_task_shell_content_get_view_instance (ETaskShellContent *task_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
+
+ return task_shell_content->priv->view_instance;
+}
diff --git a/modules/calendar/e-task-shell-content.h b/modules/calendar/e-task-shell-content.h
new file mode 100644
index 0000000000..f5d4fc9665
--- /dev/null
+++ b/modules/calendar/e-task-shell-content.h
@@ -0,0 +1,100 @@
+/*
+ * e-task-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_TASK_SHELL_CONTENT_H
+#define E_TASK_SHELL_CONTENT_H
+
+#include <shell/e-shell-content.h>
+#include <shell/e-shell-view.h>
+
+#include <calendar/gui/e-cal-model.h>
+#include <calendar/gui/e-calendar-table.h>
+#include <calendar/gui/e-cal-component-preview.h>
+
+#include <menus/gal-view-instance.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TASK_SHELL_CONTENT \
+ (e_task_shell_content_get_type ())
+#define E_TASK_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContent))
+#define E_TASK_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentClass))
+#define E_IS_TASK_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_TASK_SHELL_CONTENT))
+#define E_IS_TASK_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_TASK_SHELL_CONTENT))
+#define E_TASK_SHELL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETaskShellContent ETaskShellContent;
+typedef struct _ETaskShellContentClass ETaskShellContentClass;
+typedef struct _ETaskShellContentPrivate ETaskShellContentPrivate;
+
+enum {
+ E_TASK_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0,
+ E_TASK_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1,
+ E_TASK_SHELL_CONTENT_SELECTION_CAN_ASSIGN = 1 << 2,
+ E_TASK_SHELL_CONTENT_SELECTION_CAN_EDIT = 1 << 3,
+ E_TASK_SHELL_CONTENT_SELECTION_HAS_COMPLETE = 1 << 4,
+ E_TASK_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE = 1 << 5,
+ E_TASK_SHELL_CONTENT_SELECTION_HAS_URL = 1 << 6
+};
+
+struct _ETaskShellContent {
+ EShellContent parent;
+ ETaskShellContentPrivate *priv;
+};
+
+struct _ETaskShellContentClass {
+ EShellContentClass parent_class;
+};
+
+GType e_task_shell_content_get_type (void);
+void e_task_shell_content_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_task_shell_content_new(EShellView *shell_view);
+ECalModel * e_task_shell_content_get_task_model
+ (ETaskShellContent *task_shell_content);
+ECalComponentPreview *
+ e_task_shell_content_get_task_preview
+ (ETaskShellContent *task_shell_content);
+ECalendarTable *e_task_shell_content_get_task_table
+ (ETaskShellContent *task_shell_content);
+gboolean e_task_shell_content_get_preview_visible
+ (ETaskShellContent *task_shell_content);
+void e_task_shell_content_set_preview_visible
+ (ETaskShellContent *task_shell_content,
+ gboolean preview_visible);
+GalViewInstance *
+ e_task_shell_content_get_view_instance
+ (ETaskShellContent *task_shell_content);
+
+G_END_DECLS
+
+#endif /* E_TASK_SHELL_CONTENT_H */
diff --git a/modules/calendar/e-task-shell-migrate.c b/modules/calendar/e-task-shell-migrate.c
new file mode 100644
index 0000000000..799298f5a7
--- /dev/null
+++ b/modules/calendar/e-task-shell-migrate.c
@@ -0,0 +1,675 @@
+/*
+ * e-task-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-task-shell-migrate.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <libebackend/e-dbhash.h>
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-group.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserver/e-xml-hash-utils.h>
+#include <libedataserver/e-xml-utils.h>
+
+#include "e-util/e-bconf-map.h"
+#include "e-util/e-folder-map.h"
+#include "e-util/e-util-private.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/calendar-config-keys.h"
+#include "shell/e-shell.h"
+
+#define WEBCAL_BASE_URI "webcal://"
+#define PERSONAL_RELATIVE_URI "system"
+
+static e_gconf_map_t calendar_tasks_map[] = {
+ /* /Calendar/Tasks */
+ { "HideCompletedTasks", "calendar/tasks/hide_completed", E_GCONF_MAP_BOOL },
+ { "HideCompletedTasksUnits", "calendar/tasks/hide_completed_units", E_GCONF_MAP_STRING },
+ { "HideCompletedTasksValue", "calendar/tasks/hide_completed_value", E_GCONF_MAP_INT },
+ { NULL },
+};
+
+static e_gconf_map_t calendar_tasks_colours_map[] = {
+ /* /Calendar/Tasks/Colors */
+ { "TasksDueToday", "calendar/tasks/colors/due_today", E_GCONF_MAP_STRING },
+ { "TasksOverDue", "calendar/tasks/colors/overdue", E_GCONF_MAP_STRING },
+ { "TasksDueToday", "calendar/tasks/colors/due_today", E_GCONF_MAP_STRING },
+ { NULL },
+};
+
+static e_gconf_map_list_t task_remap_list[] = {
+
+ { "/Calendar/Tasks", calendar_tasks_map },
+ { "/Calendar/Tasks/Colors", calendar_tasks_colours_map },
+
+ { NULL },
+};
+
+static GtkWidget *window;
+static GtkLabel *label;
+static GtkProgressBar *progress;
+
+#ifndef G_OS_WIN32
+
+/* No previous versions have been available on Win32, so don't
+ * bother with upgrade support from 1.x on Win32.
+ */
+
+static void
+setup_progress_dialog (void)
+{
+ GtkWidget *vbox, *hbox, *w;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title ((GtkWindow *) window, _("Migrating..."));
+ gtk_window_set_modal ((GtkWindow *) window, TRUE);
+ gtk_container_set_border_width ((GtkContainer *) window, 6);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_container_add ((GtkContainer *) window, vbox);
+
+ w = gtk_label_new (_("The location and hierarchy of the Evolution task "
+ "folders has changed since Evolution 1.x.\n\nPlease be "
+ "patient while Evolution migrates your folders..."));
+
+ gtk_label_set_line_wrap ((GtkLabel *) w, TRUE);
+ gtk_widget_show (w);
+ gtk_box_pack_start ((GtkBox *) vbox, w, TRUE, TRUE, 0);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start ((GtkBox *) vbox, hbox, TRUE, TRUE, 0);
+
+ label = (GtkLabel *) gtk_label_new ("");
+ gtk_widget_show ((GtkWidget *) label);
+ gtk_box_pack_start ((GtkBox *) hbox, (GtkWidget *) label, TRUE, TRUE, 0);
+
+ progress = (GtkProgressBar *) gtk_progress_bar_new ();
+ gtk_widget_show ((GtkWidget *) progress);
+ gtk_box_pack_start ((GtkBox *) hbox, (GtkWidget *) progress, TRUE, TRUE, 0);
+
+ gtk_widget_show (window);
+}
+
+static void
+dialog_close (void)
+{
+ gtk_widget_destroy ((GtkWidget *) window);
+}
+
+static void
+dialog_set_folder_name (const gchar *folder_name)
+{
+ gchar *text;
+
+ text = g_strdup_printf (_("Migrating '%s':"), folder_name);
+ gtk_label_set_text (label, text);
+ g_free (text);
+
+ gtk_progress_bar_set_fraction (progress, 0.0);
+
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+}
+
+static void
+dialog_set_progress (double percent)
+{
+ gchar text[5];
+
+ snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f));
+
+ gtk_progress_bar_set_fraction (progress, percent);
+ gtk_progress_bar_set_text (progress, text);
+
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+}
+
+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)
+{
+ gchar **p = g_strsplit (path, "/", 0);
+ gint i, j, starting_index;
+ gint num_elements;
+ gboolean conflict;
+ GString *s = g_string_new (NULL);
+
+ 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 {
+ 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 gboolean
+migrate_ical (ECal *old_ecal, ECal *new_ecal)
+{
+ GList *l, *objects;
+ gint num_added = 0;
+ gint num_objects;
+ gboolean retval = TRUE;
+
+ /* both ecals are loaded, start the actual migration */
+ if (!e_cal_get_object_list (old_ecal, "#t", &objects, NULL))
+ return FALSE;
+
+ num_objects = g_list_length (objects);
+ for (l = objects; l; l = l->next) {
+ icalcomponent *ical_comp = l->data;
+ GError *error = NULL;
+
+ if (!e_cal_create_object (new_ecal, ical_comp, NULL, &error)) {
+ g_warning ("Migration of object failed: %s", error->message);
+ retval = FALSE;
+ }
+
+ g_clear_error (&error);
+
+ num_added ++;
+ dialog_set_progress ((double)num_added / num_objects);
+ }
+
+ g_list_foreach (objects, (GFunc) icalcomponent_free, NULL);
+ g_list_free (objects);
+
+ return retval;
+}
+
+static gboolean
+migrate_ical_folder_to_source (gchar *old_path, ESource *new_source, ECalSourceType type)
+{
+ ECal *old_ecal = NULL, *new_ecal = NULL;
+ ESource *old_source;
+ ESourceGroup *group;
+ gchar *old_uri = g_strdup_printf ("file://%s", old_path);
+ GError *error = NULL;
+ gboolean retval = FALSE;
+
+ 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 (e_source_peek_name (new_source));
+
+ if (!(old_ecal = e_cal_new (old_source, type))) {
+ g_warning ("could not find a backend for '%s'", e_source_get_uri (old_source));
+ goto finish;
+ }
+ if (!e_cal_open (old_ecal, FALSE, &error)) {
+ g_warning ("failed to load source ecal for migration: '%s' (%s)", error->message,
+ e_source_get_uri (old_source));
+ goto finish;
+ }
+
+ if (!(new_ecal = e_cal_new (new_source, type))) {
+ g_warning ("could not find a backend for '%s'", e_source_get_uri (new_source));
+ goto finish;
+ }
+ if (!e_cal_open (new_ecal, FALSE, &error)) {
+ g_warning ("failed to load destination ecal for migration: '%s' (%s)", error->message,
+ e_source_get_uri (new_source));
+ goto finish;
+ }
+
+ retval = migrate_ical (old_ecal, new_ecal);
+
+finish:
+ g_clear_error (&error);
+ if (old_ecal)
+ g_object_unref (old_ecal);
+ g_object_unref (group);
+ if (new_ecal)
+ g_object_unref (new_ecal);
+ g_free (old_uri);
+
+ return retval;
+}
+
+static gboolean
+migrate_ical_folder (gchar *old_path, ESourceGroup *dest_group, gchar *source_name, ECalSourceType type)
+{
+ ESource *new_source;
+ gboolean retval;
+
+ 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);
+
+ retval = migrate_ical_folder_to_source (old_path, new_source, type);
+
+ g_object_unref (new_source);
+
+ return retval;
+}
+
+#endif /* !G_OS_WIN32 */
+
+#ifndef G_OS_WIN32
+
+static void
+migrate_pilot_db_key (const gchar *key, gpointer user_data)
+{
+ EXmlHash *xmlhash = user_data;
+
+ e_xmlhash_add (xmlhash, key, "");
+}
+
+static void
+migrate_pilot_data (const gchar *component, const gchar *conduit, const gchar *old_path, const gchar *new_path)
+{
+ gchar *changelog, *map;
+ const gchar *dent;
+ const gchar *ext;
+ gchar *filename;
+ GDir *dir;
+
+ if (!(dir = g_dir_open (old_path, 0, NULL)))
+ return;
+
+ map = g_alloca (12 + strlen (conduit));
+ sprintf (map, "pilot-map-%s-", conduit);
+
+ changelog = g_alloca (24 + strlen (conduit));
+ sprintf (changelog, "pilot-sync-evolution-%s-", conduit);
+
+ while ((dent = g_dir_read_name (dir))) {
+ if (!strncmp (dent, map, strlen (map)) &&
+ ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) {
+ /* pilot map file - src and dest file formats are identical */
+ 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, strerror (errno));
+ g_unlink (filename);
+ }
+
+ close (fd0);
+ close (fd1);
+ g_free (filename);
+ } else if (!strncmp (dent, changelog, strlen (changelog)) &&
+ ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db"))) {
+ /* src and dest formats differ, src format is db3 while dest format is xml */
+ EXmlHash *xmlhash;
+ EDbHash *dbhash;
+ struct stat st;
+
+ filename = g_build_filename (old_path, dent, NULL);
+ if (g_stat (filename, &st) == -1) {
+ g_free (filename);
+ continue;
+ }
+
+ dbhash = e_dbhash_new (filename);
+ g_free (filename);
+
+ filename = g_strdup_printf ("%s/%s.ics-%s", new_path, component, dent);
+ if (g_stat (filename, &st) != -1)
+ g_unlink (filename);
+ xmlhash = e_xmlhash_new (filename);
+ g_free (filename);
+
+ e_dbhash_foreach_key (dbhash, migrate_pilot_db_key, xmlhash);
+
+ e_dbhash_destroy (dbhash);
+
+ e_xmlhash_write (xmlhash);
+ e_xmlhash_destroy (xmlhash);
+ }
+ }
+
+ g_dir_close (dir);
+}
+
+#endif
+
+static void
+create_task_sources (EShellBackend *shell_backend,
+ ESourceList *source_list,
+ ESourceGroup **on_this_computer,
+ ESourceGroup **on_the_web,
+ ESource **personal_source)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+ GSList *groups;
+ ESourceGroup *group;
+ gchar *base_uri, *base_uri_proto;
+ const gchar *base_dir;
+
+ *on_this_computer = NULL;
+ *on_the_web = NULL;
+ *personal_source = NULL;
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ base_dir = e_shell_backend_get_config_dir (shell_backend);
+ base_uri = g_build_filename (base_dir, "local", NULL);
+
+ base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL);
+
+ groups = e_source_list_peek_groups (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_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group)))
+ *on_the_web = 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 (source_list, group, -1);
+
+ *on_this_computer = group;
+ }
+
+ if (!*personal_source) {
+ gchar *primary_task_list;
+
+ /* Create the default Person task list */
+ ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI);
+ e_source_group_add_source (*on_this_computer, source, -1);
+
+ primary_task_list = e_shell_settings_get_string (
+ shell_settings, "cal-primary-task-list");
+
+ if (!primary_task_list && !calendar_config_get_tasks_selected ()) {
+ GSList selected;
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-task-list",
+ e_source_peek_uid (source));
+
+ selected.data = (gpointer)e_source_peek_uid (source);
+ selected.next = NULL;
+ calendar_config_set_tasks_selected (&selected);
+ }
+
+ e_source_set_color_spec (source, "#BECEDD");
+ *personal_source = source;
+ }
+
+ if (!*on_the_web) {
+ /* Create the Webcal source group */
+ group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI);
+ e_source_list_add_group (source_list, group, -1);
+
+ *on_the_web = group;
+ }
+
+ g_free (base_uri_proto);
+ g_free (base_uri);
+}
+
+gboolean
+e_task_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error)
+{
+ ESourceGroup *on_this_computer = NULL;
+ ESourceGroup *on_the_web = NULL;
+ ESource *personal_source = NULL;
+ ESourceList *source_list;
+ gboolean retval = FALSE;
+
+ g_object_get (shell_backend, "source-list", &source_list, NULL);
+
+ /* we call this unconditionally now - create_groups either
+ creates the groups/sources or it finds the necessary
+ groups/sources. */
+ create_task_sources (
+ shell_backend, source_list, &on_this_computer,
+ &on_the_web, &personal_source);
+
+#ifndef G_OS_WIN32
+ if (major == 1) {
+ xmlDocPtr config_doc = NULL;
+ gchar *conf_file;
+
+ conf_file = g_build_filename (g_get_home_dir (), "evolution", "config.xmldb", NULL);
+ if (g_file_test (conf_file, G_FILE_TEST_IS_REGULAR))
+ config_doc = e_xml_parse_file (conf_file);
+ g_free (conf_file);
+
+ if (config_doc && minor <= 2) {
+ GConfClient *gconf;
+ gint res = 0;
+
+ /* move bonobo config to gconf */
+ gconf = gconf_client_get_default ();
+
+ res = e_bconf_import (gconf, config_doc, task_remap_list);
+
+ g_object_unref (gconf);
+
+ xmlFreeDoc(config_doc);
+
+ if (res != 0) {
+ g_set_error(error, 0, 0, _("Unable to migrate old settings from evolution/config.xmldb"));
+ goto fail;
+ }
+ }
+
+ if (minor <= 4) {
+ GSList *migration_dirs, *l;
+ gchar *path, *local_task_folder;
+
+ setup_progress_dialog ();
+
+ path = g_build_filename (g_get_home_dir (), "evolution", "local", NULL);
+ migration_dirs = e_folder_map_local_folders (path, "tasks");
+ local_task_folder = g_build_filename (path, "Tasks", NULL);
+ g_free (path);
+
+ if (personal_source)
+ migrate_ical_folder_to_source (local_task_folder, personal_source, E_CAL_SOURCE_TYPE_TODO);
+
+ for (l = migration_dirs; l; l = l->next) {
+ gchar *source_name;
+
+ if (personal_source && !strcmp ((gchar *)l->data, local_task_folder))
+ continue;
+
+ source_name = get_source_name (on_this_computer, (gchar *)l->data);
+
+ if (!migrate_ical_folder (l->data, on_this_computer, source_name, E_CAL_SOURCE_TYPE_TODO)) {
+ /* FIXME: domain/code */
+ g_set_error(error, 0, 0, _("Unable to migrate tasks `%s'"), source_name);
+ g_free(source_name);
+ goto fail;
+ }
+
+ g_free (source_name);
+ }
+
+ g_free (local_task_folder);
+
+ dialog_close ();
+ }
+
+ if (minor < 5 || (minor == 5 && micro <= 10)) {
+ gchar *old_path, *new_path;
+
+ old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Tasks", NULL);
+ new_path = g_build_filename (e_shell_backend_get_config_dir (shell_backend),
+ "local", "system", NULL);
+ migrate_pilot_data ("tasks", "todo", 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 (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);
+ }
+ }
+ }
+ }
+#endif /* !G_OS_WIN32 */
+ e_source_list_sync (source_list, NULL);
+ retval = TRUE;
+fail:
+ if (on_this_computer)
+ g_object_unref (on_this_computer);
+ if (on_the_web)
+ g_object_unref (on_the_web);
+ if (personal_source)
+ g_object_unref (personal_source);
+
+ return retval;
+}
diff --git a/modules/calendar/e-task-shell-migrate.h b/modules/calendar/e-task-shell-migrate.h
new file mode 100644
index 0000000000..4cb91c9f4a
--- /dev/null
+++ b/modules/calendar/e-task-shell-migrate.h
@@ -0,0 +1,38 @@
+/*
+ * e-task-shell-backend-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_TASK_SHELL_BACKEND_MIGRATE_H
+#define E_TASK_SHELL_BACKEND_MIGRATE_H
+
+#include <glib.h>
+#include <shell/e-shell-backend.h>
+
+G_BEGIN_DECLS
+
+gboolean e_task_shell_backend_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_TASK_SHELL_BACKEND_MIGRATE_H */
diff --git a/modules/calendar/e-task-shell-sidebar.c b/modules/calendar/e-task-shell-sidebar.c
new file mode 100644
index 0000000000..6bd7700fcf
--- /dev/null
+++ b/modules/calendar/e-task-shell-sidebar.c
@@ -0,0 +1,714 @@
+/*
+ * e-task-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-task-shell-sidebar.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal.h>
+
+#include "e-util/e-error.h"
+#include "e-util/e-util.h"
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/e-task-list-selector.h"
+#include "calendar/gui/misc.h"
+
+#include "e-task-shell-backend.h"
+#include "e-task-shell-view.h"
+
+#define E_TASK_SHELL_SIDEBAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebarPrivate))
+
+struct _ETaskShellSidebarPrivate {
+ GtkWidget *selector;
+
+ /* UID -> Client */
+ GHashTable *client_table;
+};
+
+enum {
+ PROP_0,
+ PROP_SELECTOR
+};
+
+enum {
+ CLIENT_ADDED,
+ CLIENT_REMOVED,
+ STATUS_MESSAGE,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+static GType task_shell_sidebar_type;
+
+static void
+task_shell_sidebar_emit_client_added (ETaskShellSidebar *task_shell_sidebar,
+ ECal *client)
+{
+ guint signal_id = signals[CLIENT_ADDED];
+
+ g_signal_emit (task_shell_sidebar, signal_id, 0, client);
+}
+
+static void
+task_shell_sidebar_emit_client_removed (ETaskShellSidebar *task_shell_sidebar,
+ ECal *client)
+{
+ guint signal_id = signals[CLIENT_REMOVED];
+
+ g_signal_emit (task_shell_sidebar, signal_id, 0, client);
+}
+
+static void
+task_shell_sidebar_emit_status_message (ETaskShellSidebar *task_shell_sidebar,
+ const gchar *status_message)
+{
+ guint signal_id = signals[STATUS_MESSAGE];
+
+ g_signal_emit (task_shell_sidebar, signal_id, 0, status_message, -1.0);
+}
+
+static void
+task_shell_sidebar_backend_died_cb (ETaskShellSidebar *task_shell_sidebar,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ GHashTable *client_table;
+ ESource *source;
+ const gchar *uid;
+
+ client_table = task_shell_sidebar->priv->client_table;
+
+ shell_sidebar = E_SHELL_SIDEBAR (task_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ source = e_cal_get_source (client);
+ uid = e_source_peek_uid (source);
+
+ g_object_ref (source);
+
+ g_hash_table_remove (client_table, uid);
+ task_shell_sidebar_emit_status_message (task_shell_sidebar, NULL);
+
+ e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:tasks-crashed", NULL);
+
+ g_object_unref (source);
+}
+
+static void
+task_shell_sidebar_backend_error_cb (ETaskShellSidebar *task_shell_sidebar,
+ const gchar *message,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ GtkWidget *dialog;
+ const gchar *uri;
+ gchar *uri_no_passwd;
+
+ shell_sidebar = E_SHELL_SIDEBAR (task_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ uri = e_cal_get_uri (client);
+ uri_no_passwd = get_uri_without_password (uri);
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (shell_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Error on %s\n%s"),
+ uri_no_passwd, message);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_free (uri_no_passwd);
+}
+
+static void
+task_shell_sidebar_client_opened_cb (ETaskShellSidebar *task_shell_sidebar,
+ ECalendarStatus status,
+ ECal *client)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ ESource *source;
+
+ source = e_cal_get_source (client);
+
+ shell_sidebar = E_SHELL_SIDEBAR (task_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ if (status == E_CALENDAR_STATUS_AUTHENTICATION_FAILED ||
+ status == E_CALENDAR_STATUS_AUTHENTICATION_REQUIRED)
+ auth_cal_forget_password (client);
+
+ switch (status) {
+ case E_CALENDAR_STATUS_OK:
+ g_signal_handlers_disconnect_matched (
+ client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+ task_shell_sidebar_client_opened_cb, NULL);
+
+ task_shell_sidebar_emit_status_message (
+ task_shell_sidebar, _("Loading tasks"));
+ task_shell_sidebar_emit_client_added (
+ task_shell_sidebar, client);
+ task_shell_sidebar_emit_status_message (
+ task_shell_sidebar, NULL);
+ break;
+
+ case E_CALENDAR_STATUS_AUTHENTICATION_FAILED:
+ e_cal_open_async (client, FALSE);
+ break;
+
+ case E_CALENDAR_STATUS_BUSY:
+ break;
+
+ case E_CALENDAR_STATUS_REPOSITORY_OFFLINE:
+ e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:prompt-no-contents-offline-tasks",
+ NULL);
+ break;
+
+ default:
+ task_shell_sidebar_emit_client_removed (
+ task_shell_sidebar, client);
+ break;
+ }
+}
+
+static void
+task_shell_sidebar_row_changed_cb (ETaskShellSidebar *task_shell_sidebar,
+ GtkTreePath *tree_path,
+ GtkTreeIter *tree_iter,
+ GtkTreeModel *tree_model)
+{
+ ESourceSelector *selector;
+ ESource *source;
+
+ /* XXX ESourceSelector's underlying tree store has only one
+ * column: ESource objects. While we're not supposed to
+ * know this, listening for "row-changed" signals from
+ * the model is easier to deal with than the selector's
+ * "selection-changed" signal, which doesn't tell you
+ * _which_ row changed. */
+
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+ gtk_tree_model_get (tree_model, tree_iter, 0, &source, -1);
+
+ /* XXX This signal gets emitted a lot while the model is being
+ * rebuilt, during which time we won't get a valid ESource.
+ * ESourceSelector should probably block this signal while
+ * rebuilding the model, but we'll be forgiving and not
+ * emit a warning. */
+ if (!E_IS_SOURCE (source))
+ return;
+
+ if (e_source_selector_source_is_selected (selector, source))
+ e_task_shell_sidebar_add_source (task_shell_sidebar, source);
+ else
+ e_task_shell_sidebar_remove_source (task_shell_sidebar, source);
+}
+
+static void
+task_shell_sidebar_selection_changed_cb (ETaskShellSidebar *task_shell_sidebar,
+ ESourceSelector *selector)
+{
+ GSList *list, *iter;
+
+ /* This signal is emitted less frequently than "row-changed",
+ * especially when the model is being rebuilt. So we'll take
+ * it easy on poor GConf. */
+
+ list = e_source_selector_get_selection (selector);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ESource *source = iter->data;
+
+ iter->data = (gpointer) e_source_peek_uid (source);
+ g_object_unref (source);
+ }
+
+ calendar_config_set_tasks_selected (list);
+
+ g_slist_free (list);
+}
+
+static void
+task_shell_sidebar_primary_selection_changed_cb (ETaskShellSidebar *task_shell_sidebar,
+ ESourceSelector *selector)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSidebar *shell_sidebar;
+ EShellSettings *shell_settings;
+ ESource *source;
+
+ /* XXX ESourceSelector needs a "primary-selection-uid" property
+ * so we can just bind the property with GConfBridge. */
+
+ source = e_source_selector_peek_primary_selection (selector);
+ if (source == NULL)
+ return;
+
+ shell_sidebar = E_SHELL_SIDEBAR (task_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ e_shell_settings_set_string (
+ shell_settings, "cal-primary-task-list",
+ e_source_peek_uid (source));
+}
+
+static void
+task_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_task_shell_sidebar_get_selector (
+ E_TASK_SHELL_SIDEBAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+task_shell_sidebar_dispose (GObject *object)
+{
+ ETaskShellSidebarPrivate *priv;
+
+ priv = E_TASK_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ if (priv->selector != NULL) {
+ g_object_unref (priv->selector);
+ priv->selector = NULL;
+ }
+
+ g_hash_table_remove_all (priv->client_table);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+task_shell_sidebar_finalize (GObject *object)
+{
+ ETaskShellSidebarPrivate *priv;
+
+ priv = E_TASK_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->client_table);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+task_shell_sidebar_constructed (GObject *object)
+{
+ ETaskShellSidebarPrivate *priv;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellSidebar *shell_sidebar;
+ EShellSettings *shell_settings;
+ ESourceSelector *selector;
+ ESourceList *source_list;
+ ESource *source;
+ GtkContainer *container;
+ GtkTreeModel *model;
+ GtkWidget *widget;
+ AtkObject *a11y;
+ GSList *list, *iter;
+ gchar *uid;
+
+ priv = E_TASK_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);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ source_list = e_task_shell_backend_get_source_list (
+ E_TASK_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_task_list_selector_new (source_list);
+ e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE);
+ gtk_container_add (container, widget);
+ a11y = gtk_widget_get_accessible (widget);
+ atk_object_set_name (a11y, _("Task List Selector"));
+ priv->selector = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Restore the selector state from the last session. */
+
+ selector = E_SOURCE_SELECTOR (priv->selector);
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+ g_signal_connect_swapped (
+ model, "row-changed",
+ G_CALLBACK (task_shell_sidebar_row_changed_cb),
+ object);
+
+ source = NULL;
+ uid = e_shell_settings_get_string (
+ shell_settings, "cal-primary-task-list");
+ if (uid != NULL)
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ if (source == NULL)
+ source = e_source_list_peek_source_any (source_list);
+ if (source != NULL)
+ e_source_selector_set_primary_selection (selector, source);
+ g_free (uid);
+
+ list = calendar_config_get_tasks_selected ();
+ for (iter = list; iter != NULL; iter = iter->next) {
+ uid = iter->data;
+ source = e_source_list_peek_source_by_uid (source_list, uid);
+ g_free (uid);
+
+ if (source == NULL)
+ continue;
+
+ e_source_selector_select_source (selector, source);
+ }
+ g_slist_free (list);
+
+ /* Listen for subsequent changes to the selector. */
+
+ g_signal_connect_swapped (
+ widget, "selection-changed",
+ G_CALLBACK (task_shell_sidebar_selection_changed_cb),
+ object);
+
+ g_signal_connect_swapped (
+ widget, "primary-selection-changed",
+ G_CALLBACK (task_shell_sidebar_primary_selection_changed_cb),
+ object);
+}
+
+static guint32
+task_shell_sidebar_check_state (EShellSidebar *shell_sidebar)
+{
+ ETaskShellSidebar *task_shell_sidebar;
+ ESourceSelector *selector;
+ ESource *source;
+ gboolean is_system = FALSE;
+ guint32 state = 0;
+
+ task_shell_sidebar = E_TASK_SHELL_SIDEBAR (shell_sidebar);
+ selector = e_task_shell_sidebar_get_selector (task_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_TASK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE;
+ if (is_system)
+ state |= E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM;
+
+ return state;
+}
+
+static void
+task_shell_sidebar_client_removed (ETaskShellSidebar *task_shell_sidebar,
+ ECal *client)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ESource *source;
+ const gchar *uid;
+
+ client_table = task_shell_sidebar->priv->client_table;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+
+ g_signal_handlers_disconnect_matched (
+ client, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, task_shell_sidebar);
+
+ source = e_cal_get_source (client);
+ e_source_selector_unselect_source (selector, source);
+
+ uid = e_source_peek_uid (source);
+ g_hash_table_remove (client_table, uid);
+
+ task_shell_sidebar_emit_status_message (task_shell_sidebar, NULL);
+}
+
+static void
+task_shell_sidebar_class_init (ETaskShellSidebarClass *class)
+{
+ GObjectClass *object_class;
+ EShellSidebarClass *shell_sidebar_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ETaskShellSidebarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = task_shell_sidebar_get_property;
+ object_class->dispose = task_shell_sidebar_dispose;
+ object_class->finalize = task_shell_sidebar_finalize;
+ object_class->constructed = task_shell_sidebar_constructed;
+
+ shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class);
+ shell_sidebar_class->check_state = task_shell_sidebar_check_state;
+
+ class->client_removed = task_shell_sidebar_client_removed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SELECTOR,
+ g_param_spec_object (
+ "selector",
+ _("Source Selector Widget"),
+ _("This widget displays groups of task lists"),
+ E_TYPE_SOURCE_SELECTOR,
+ G_PARAM_READABLE));
+
+ signals[CLIENT_ADDED] = g_signal_new (
+ "client-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ETaskShellSidebarClass, client_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_CAL);
+
+ signals[CLIENT_REMOVED] = g_signal_new (
+ "client-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ETaskShellSidebarClass, client_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_CAL);
+
+ signals[STATUS_MESSAGE] = g_signal_new (
+ "status-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ETaskShellSidebarClass, status_message),
+ NULL, NULL,
+ e_marshal_VOID__STRING_DOUBLE,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_DOUBLE);
+}
+
+static void
+task_shell_sidebar_init (ETaskShellSidebar *task_shell_sidebar)
+{
+ GHashTable *client_table;
+
+ client_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ task_shell_sidebar->priv =
+ E_TASK_SHELL_SIDEBAR_GET_PRIVATE (task_shell_sidebar);
+
+ task_shell_sidebar->priv->client_table = client_table;
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_task_shell_sidebar_get_type (void)
+{
+ return task_shell_sidebar_type;
+}
+
+void
+e_task_shell_sidebar_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (ETaskShellSidebarClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) task_shell_sidebar_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ETaskShellSidebar),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) task_shell_sidebar_init,
+ NULL /* value_table */
+ };
+
+ task_shell_sidebar_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_SIDEBAR,
+ "ETaskShellSidebar", &type_info, 0);
+}
+
+GtkWidget *
+e_task_shell_sidebar_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_TASK_SHELL_SIDEBAR,
+ "shell-view", shell_view, NULL);
+}
+
+GList *
+e_task_shell_sidebar_get_clients (ETaskShellSidebar *task_shell_sidebar)
+{
+ GHashTable *client_table;
+
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar), NULL);
+
+ client_table = task_shell_sidebar->priv->client_table;
+
+ return g_hash_table_get_values (client_table);
+}
+
+ESourceSelector *
+e_task_shell_sidebar_get_selector (ETaskShellSidebar *task_shell_sidebar)
+{
+ g_return_val_if_fail (
+ E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar), NULL);
+
+ return E_SOURCE_SELECTOR (task_shell_sidebar->priv->selector);
+}
+
+void
+e_task_shell_sidebar_add_source (ETaskShellSidebar *task_shell_sidebar,
+ ESource *source)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ECal *client;
+ const gchar *uid;
+ const gchar *uri;
+ gchar *message;
+
+ g_return_if_fail (E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ client_table = task_shell_sidebar->priv->client_table;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+
+ uid = e_source_peek_uid (source);
+ client = g_hash_table_lookup (client_table, uid);
+
+ if (client != NULL)
+ return;
+
+ client = auth_new_cal_from_source (source, E_CAL_SOURCE_TYPE_TODO);
+ g_return_if_fail (client != NULL);
+
+ g_signal_connect_swapped (
+ client, "backend-died",
+ G_CALLBACK (task_shell_sidebar_backend_died_cb),
+ task_shell_sidebar);
+
+ g_signal_connect_swapped (
+ client, "backend-error",
+ G_CALLBACK (task_shell_sidebar_backend_error_cb),
+ task_shell_sidebar);
+
+ g_hash_table_insert (client_table, g_strdup (uid), client);
+ e_source_selector_select_source (selector, source);
+
+ uri = e_cal_get_uri (client);
+ message = g_strdup_printf (_("Opening tasks at %s"), uri);
+ task_shell_sidebar_emit_status_message (task_shell_sidebar, message);
+ g_free (message);
+
+ g_signal_connect_swapped (
+ client, "cal-opened",
+ G_CALLBACK (task_shell_sidebar_client_opened_cb),
+ task_shell_sidebar);
+
+ e_cal_open_async (client, FALSE);
+}
+
+void
+e_task_shell_sidebar_remove_source (ETaskShellSidebar *task_shell_sidebar,
+ ESource *source)
+{
+ ESourceSelector *selector;
+ GHashTable *client_table;
+ ECal *client;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ client_table = task_shell_sidebar->priv->client_table;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+
+ uid = e_source_peek_uid (source);
+ client = g_hash_table_lookup (client_table, uid);
+
+ if (client == NULL)
+ return;
+
+ task_shell_sidebar_emit_client_removed (task_shell_sidebar, client);
+}
diff --git a/modules/calendar/e-task-shell-sidebar.h b/modules/calendar/e-task-shell-sidebar.h
new file mode 100644
index 0000000000..5d4c74fe11
--- /dev/null
+++ b/modules/calendar/e-task-shell-sidebar.h
@@ -0,0 +1,97 @@
+/*
+ * e-task-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_TASK_SHELL_SIDEBAR_H
+#define E_TASK_SHELL_SIDEBAR_H
+
+#include <libecal/e-cal.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_TASK_SHELL_SIDEBAR \
+ (e_task_shell_sidebar_get_type ())
+#define E_TASK_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebar))
+#define E_TASK_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebarClass))
+#define E_IS_TASK_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_TASK_SHELL_SIDEBAR))
+#define E_IS_TASK_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_TASK_SHELL_SIDEBAR))
+#define E_TASK_SHELL_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETaskShellSidebar ETaskShellSidebar;
+typedef struct _ETaskShellSidebarClass ETaskShellSidebarClass;
+typedef struct _ETaskShellSidebarPrivate ETaskShellSidebarPrivate;
+
+enum {
+ E_TASK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0,
+ E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM = 1 << 1
+};
+
+struct _ETaskShellSidebar {
+ EShellSidebar parent;
+ ETaskShellSidebarPrivate *priv;
+};
+
+struct _ETaskShellSidebarClass {
+ EShellSidebarClass parent_class;
+
+ /* Signals */
+ void (*client_added) (ETaskShellSidebar *task_shell_sidebar,
+ ECal *client);
+ void (*client_removed) (ETaskShellSidebar *task_shell_sidebar,
+ ECal *client);
+ void (*status_message) (ETaskShellSidebar *task_shell_sidebar,
+ const gchar *status_message,
+ gdouble percent);
+};
+
+GType e_task_shell_sidebar_get_type (void);
+void e_task_shell_sidebar_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_task_shell_sidebar_new(EShellView *shell_view);
+GList * e_task_shell_sidebar_get_clients
+ (ETaskShellSidebar *task_shell_sidebar);
+ESourceSelector *
+ e_task_shell_sidebar_get_selector
+ (ETaskShellSidebar *task_shell_sidebar);
+void e_task_shell_sidebar_add_source
+ (ETaskShellSidebar *task_shell_sidebar,
+ ESource *source);
+void e_task_shell_sidebar_remove_source
+ (ETaskShellSidebar *task_shell_sidebar,
+ ESource *source);
+
+G_END_DECLS
+
+#endif /* E_TASK_SHELL_SIDEBAR_H */
diff --git a/modules/calendar/e-task-shell-view-actions.c b/modules/calendar/e-task-shell-view-actions.c
new file mode 100644
index 0000000000..d4fb94c2e5
--- /dev/null
+++ b/modules/calendar/e-task-shell-view-actions.c
@@ -0,0 +1,1222 @@
+/*
+ * e-task-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-task-shell-view-private.h"
+
+static void
+action_gal_save_custom_view_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ EShellView *shell_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 (task_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ view_instance = e_task_shell_content_get_view_instance (task_shell_content);
+ gal_view_instance_save_as (view_instance);
+}
+
+static void
+action_search_execute_cb (GtkAction *action,
+ ETaskShellView *task_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 (task_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ e_task_shell_view_execute_search (task_shell_view);
+}
+
+static void
+action_search_filter_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ETaskShellView *task_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ gtk_action_activate (ACTION (SEARCH_EXECUTE));
+}
+
+static void
+action_task_assign_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the first selected task. */
+ e_task_shell_view_open_task (task_shell_view, comp_data);
+
+ /* FIXME Need to actually assign the task. */
+}
+
+static void
+action_task_clipboard_copy_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ e_calendar_table_copy_clipboard (task_table);
+}
+
+static void
+action_task_clipboard_cut_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ e_calendar_table_cut_clipboard (task_table);
+}
+
+static void
+action_task_clipboard_paste_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ e_calendar_table_paste_clipboard (task_table);
+}
+
+static void
+action_task_delete_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalComponentPreview *task_preview;
+ ECalendarTable *task_table;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ task_preview = e_task_shell_content_get_task_preview (task_shell_content);
+
+ e_task_shell_view_set_status_message (
+ task_shell_view, _("Deleting selected tasks..."), -1.0);
+ e_calendar_table_delete_selected (task_table);
+ e_task_shell_view_set_status_message (task_shell_view, NULL, -1.0);
+
+ e_cal_component_preview_clear (task_preview);
+}
+
+static void
+action_task_forward_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GSList *list;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only forward the first selected task. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+ itip_send_comp (
+ E_CAL_COMPONENT_METHOD_PUBLISH, comp,
+ comp_data->client, NULL, NULL, NULL, TRUE, FALSE);
+ g_object_unref (comp);
+}
+
+static void
+action_task_list_copy_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellSidebar *task_shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ESourceSelector *selector;
+ ESource *source;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ copy_source_dialog (
+ GTK_WINDOW (shell_window),
+ source, E_CAL_SOURCE_TYPE_TODO);
+}
+
+static void
+action_task_list_delete_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellBackend *task_shell_backend;
+ ETaskShellContent *task_shell_content;
+ ETaskShellSidebar *task_shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ECalendarTable *task_table;
+ ECal *client;
+ ECalModel *model;
+ ESourceSelector *selector;
+ ESourceGroup *source_group;
+ ESourceList *source_list;
+ ESource *source;
+ gint response;
+ gchar *uri;
+ GError *error = NULL;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ task_shell_backend = task_shell_view->priv->task_shell_backend;
+ source_list = e_task_shell_backend_get_source_list (task_shell_backend);
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ model = e_calendar_table_get_model (task_table);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ /* Ask for confirmation. */
+ response = e_error_run (
+ GTK_WINDOW (shell_window),
+ "calendar:prompt-delete-task-list",
+ e_source_peek_name (source));
+ if (response != GTK_RESPONSE_YES)
+ return;
+
+ uri = e_source_get_uri (source);
+ client = e_cal_model_get_client_for_uri (model, uri);
+ if (client == NULL)
+ client = e_cal_new_from_uri (uri, E_CAL_SOURCE_TYPE_JOURNAL);
+ g_free (uri);
+
+ g_return_if_fail (client != NULL);
+
+ if (!e_cal_remove (client, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (e_source_selector_source_is_selected (selector, source)) {
+ e_task_shell_sidebar_remove_source (
+ task_shell_sidebar, source);
+ e_source_selector_unselect_source (selector, source);
+ }
+
+ source_group = e_source_peek_group (source);
+ e_source_group_remove_source (source_group, source);
+
+ if (!e_source_list_sync (source_list, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+action_task_list_new_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ calendar_setup_new_task_list (GTK_WINDOW (shell_window));
+}
+
+static void
+action_task_list_print_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ETable *table;
+ GtkPrintOperationAction print_action;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ table = e_calendar_table_get_table (task_table);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ print_table (table, _("Print Tasks"), _("Tasks"), print_action);
+}
+
+static void
+action_task_list_print_preview_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ETable *table;
+ GtkPrintOperationAction print_action;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ table = e_calendar_table_get_table (task_table);
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW;
+ print_table (table, _("Print Tasks"), _("Tasks"), print_action);
+}
+
+static void
+action_task_list_properties_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellSidebar *task_shell_sidebar;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ESource *source;
+ ESourceSelector *selector;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+ source = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ calendar_setup_edit_task_list (GTK_WINDOW (shell_window), source);
+}
+
+static void
+action_task_list_rename_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellSidebar *task_shell_sidebar;
+ ESourceSelector *selector;
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+
+ e_source_selector_edit_primary_selection (selector);
+}
+
+static void
+action_task_list_select_one_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellSidebar *task_shell_sidebar;
+ ESourceSelector *selector;
+ ESource *primary;
+ GSList *list, *iter;
+
+ /* XXX ESourceSelector should provide a function for this. */
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+ primary = e_source_selector_peek_primary_selection (selector);
+ g_return_if_fail (primary != NULL);
+
+ list = e_source_selector_get_selection (selector);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ESource *source = iter->data;
+
+ if (source == primary)
+ continue;
+
+ e_source_selector_unselect_source (selector, source);
+ }
+ e_source_selector_free_selection (list);
+
+ e_source_selector_select_source (selector, primary);
+}
+
+static void
+action_task_mark_complete_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ GSList *list, *iter;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ list = e_calendar_table_get_selected (task_table);
+ model = e_calendar_table_get_model (task_table);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ e_cal_model_tasks_mark_comp_complete (
+ E_CAL_MODEL_TASKS (model), comp_data);
+ }
+
+ g_slist_free (list);
+}
+
+static void
+action_task_mark_incomplete_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ GSList *list, *iter;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ list = e_calendar_table_get_selected (task_table);
+ model = e_calendar_table_get_model (task_table);
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ ECalModelComponent *comp_data = iter->data;
+ e_cal_model_tasks_mark_comp_incomplete (
+ E_CAL_MODEL_TASKS (model), comp_data);
+ }
+
+ g_slist_free (list);
+}
+
+static void
+action_task_new_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ ECal *client;
+ ECalComponent *comp;
+ CompEditor *editor;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ client = comp_data->client;
+ editor = task_editor_new (client, shell, COMP_EDITOR_NEW_ITEM);
+ comp = cal_comp_task_new_with_defaults (client);
+ comp_editor_edit_comp (editor, comp);
+
+ gtk_window_present (GTK_WINDOW (editor));
+
+ g_object_unref (comp);
+ g_object_unref (client);
+}
+
+static void
+action_task_open_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only open the first selected task. */
+ e_task_shell_view_open_task (task_shell_view, comp_data);
+}
+
+static void
+action_task_open_url_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ icalproperty *prop;
+ const gchar *uri;
+ GSList *list;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+
+ /* XXX We only open the URI of the first selected task. */
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_URL_PROPERTY);
+ g_return_if_fail (prop == NULL);
+
+ uri = icalproperty_get_url (prop);
+ e_show_uri (GTK_WINDOW (shell_window), uri);
+}
+
+static void
+action_task_preview_cb (GtkToggleAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ gboolean visible;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ visible = gtk_toggle_action_get_active (action);
+ e_task_shell_content_set_preview_visible (task_shell_content, visible);
+}
+
+static void
+action_task_print_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ GtkPrintOperationAction print_action;
+ GSList *list;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ /* XXX We only print the first selected task. */
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_cal_component_set_icalcomponent (comp, clone);
+ print_comp (comp, comp_data->client, print_action);
+ g_object_unref (comp);
+}
+
+static void
+action_task_purge_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ GtkWidget *dialog;
+ GtkWidget *widget;
+ gboolean active;
+ gint response;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ if (!e_task_shell_view_get_confirm_purge (task_shell_view))
+ goto purge;
+
+ /* XXX This needs reworked. The dialog looks like ass. */
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (shell_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_YES_NO,
+ "%s", _("This operation will permanently erase all tasks "
+ "marked as completed. If you continue, you will not be able "
+ "to recover these tasks.\n\nReally erase these tasks?"));
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
+
+ widget = gtk_check_button_new_with_label (_("Do not ask me again"));
+ gtk_box_pack_start (
+ GTK_BOX (GTK_DIALOG (dialog)->vbox), widget, TRUE, TRUE, 6);
+ gtk_widget_show (widget);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ gtk_widget_destroy (dialog);
+
+ if (response != GTK_RESPONSE_YES)
+ return;
+
+ if (active)
+ e_task_shell_view_set_confirm_purge (task_shell_view, FALSE);
+
+purge:
+
+ /* FIXME KILL-BONOBO */
+ ;
+}
+
+static void
+action_task_save_as_cb (GtkAction *action,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModelComponent *comp_data;
+ GSList *list;
+ gchar *filename;
+ gchar *string;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ list = e_calendar_table_get_selected (task_table);
+ g_return_if_fail (list != NULL);
+ comp_data = list->data;
+ g_slist_free (list);
+
+ filename = e_file_dialog_save (_("Save as..."), NULL);
+ if (filename == NULL)
+ return;
+
+ string = e_cal_get_component_as_string (
+ comp_data->client, comp_data->icalcomp);
+ if (string == NULL) {
+ g_warning ("Could not convert task to a string");
+ return;
+ }
+
+ e_write_file_uri (filename, string);
+
+ g_free (filename);
+ g_free (string);
+}
+
+static void
+action_task_search_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ETaskShellView *task_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 (task_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_task_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ orientable = GTK_ORIENTABLE (task_shell_content);
+
+ switch (gtk_radio_action_get_current_value (action)) {
+ case 0:
+ orientation = GTK_ORIENTATION_VERTICAL;
+ break;
+ case 1:
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ gtk_orientable_set_orientation (orientable, orientation);
+}
+
+static GtkActionEntry task_entries[] = {
+
+ { "task-assign",
+ NULL,
+ N_("_Assign Task"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_assign_cb) },
+
+ { "task-clipboard-copy",
+ GTK_STOCK_COPY,
+ NULL,
+ NULL,
+ N_("Copy selected tasks"),
+ G_CALLBACK (action_task_clipboard_copy_cb) },
+
+ { "task-clipboard-cut",
+ GTK_STOCK_CUT,
+ NULL,
+ NULL,
+ N_("Cut selected tasks"),
+ G_CALLBACK (action_task_clipboard_cut_cb) },
+
+ { "task-clipboard-paste",
+ GTK_STOCK_PASTE,
+ NULL,
+ NULL,
+ N_("Paste tasks from the clipboard"),
+ G_CALLBACK (action_task_clipboard_paste_cb) },
+
+ { "task-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete Task"),
+ NULL,
+ N_("Delete selected tasks"),
+ G_CALLBACK (action_task_delete_cb) },
+
+ { "task-forward",
+ "mail-forward",
+ N_("_Forward as iCalendar..."),
+ "<Control>f",
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_forward_cb) },
+
+ { "task-list-copy",
+ GTK_STOCK_COPY,
+ N_("Copy..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_list_copy_cb) },
+
+ { "task-list-delete",
+ GTK_STOCK_DELETE,
+ N_("_Delete"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_list_delete_cb) },
+
+ { "task-list-new",
+ "stock_todo",
+ N_("_New Task List"),
+ NULL,
+ N_("Create a new task list"),
+ G_CALLBACK (action_task_list_new_cb) },
+
+ { "task-list-properties",
+ GTK_STOCK_PROPERTIES,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_list_properties_cb) },
+
+ { "task-list-rename",
+ NULL,
+ N_("_Rename..."),
+ "F2",
+ N_("Rename the selected task list"),
+ G_CALLBACK (action_task_list_rename_cb) },
+
+ { "task-list-select-one",
+ "stock_check-filled",
+ N_("Show _Only This Task List"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_list_select_one_cb) },
+
+ { "task-mark-complete",
+ NULL,
+ N_("_Mark as Complete"),
+ "<Control>k",
+ N_("Mark selected tasks as complete"),
+ G_CALLBACK (action_task_mark_complete_cb) },
+
+ { "task-mark-incomplete",
+ NULL,
+ N_("Mar_k as Incomplete"),
+ NULL,
+ N_("Mark selected tasks as incomplete"),
+ G_CALLBACK (action_task_mark_incomplete_cb) },
+
+ { "task-new",
+ "stock_task",
+ N_("New _Task"),
+ NULL,
+ N_("Create a new task"),
+ G_CALLBACK (action_task_new_cb) },
+
+ { "task-open",
+ GTK_STOCK_OPEN,
+ N_("_Open Task"),
+ "<Control>o",
+ N_("View the selected task"),
+ G_CALLBACK (action_task_open_cb) },
+
+ { "task-open-url",
+ "applications-internet",
+ N_("Open _Web Page"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_open_url_cb) },
+
+ { "task-purge",
+ NULL,
+ N_("Purg_e"),
+ "<Control>e",
+ N_("Delete completed tasks"),
+ G_CALLBACK (action_task_purge_cb) },
+
+ { "task-save-as",
+ GTK_STOCK_SAVE_AS,
+ N_("_Save as iCalendar..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_task_save_as_cb) },
+
+ /*** Menus ***/
+
+ { "task-actions-menu",
+ NULL,
+ N_("_Actions"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "task-preview-menu",
+ NULL,
+ N_("_Preview"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static EPopupActionEntry task_popup_entries[] = {
+
+ { "task-list-popup-copy",
+ NULL,
+ "task-list-copy" },
+
+ { "task-list-popup-delete",
+ NULL,
+ "task-list-delete" },
+
+ { "task-list-popup-properties",
+ NULL,
+ "task-list-properties" },
+
+ { "task-list-popup-rename",
+ NULL,
+ "task-list-rename" },
+
+ { "task-list-popup-select-one",
+ NULL,
+ "task-list-select-one" },
+
+ { "task-popup-assign",
+ NULL,
+ "task-assign" },
+
+ { "task-popup-clipboard-copy",
+ NULL,
+ "task-clipboard-copy" },
+
+ { "task-popup-clipboard-cut",
+ NULL,
+ "task-clipboard-cut" },
+
+ { "task-popup-clipboard-paste",
+ NULL,
+ "task-clipboard-paste" },
+
+ { "task-popup-delete",
+ NULL,
+ "task-delete" },
+
+ { "task-popup-forward",
+ NULL,
+ "task-forward" },
+
+ { "task-popup-mark-complete",
+ NULL,
+ "task-mark-complete" },
+
+ { "task-popup-mark-incomplete",
+ NULL,
+ "task-mark-incomplete" },
+
+ { "task-popup-open",
+ NULL,
+ "task-open" },
+
+ { "task-popup-open-url",
+ NULL,
+ "task-open-url" },
+
+ { "task-popup-save-as",
+ NULL,
+ "task-save-as" },
+};
+
+static GtkToggleActionEntry task_toggle_entries[] = {
+
+ { "task-preview",
+ NULL,
+ N_("Task _Preview"),
+ "<Control>m",
+ N_("Show task preview pane"),
+ G_CALLBACK (action_task_preview_cb),
+ TRUE }
+};
+
+static GtkRadioActionEntry task_view_entries[] = {
+
+ /* This action represents the inital active memo view.
+ * It should not be visible in the UI, nor should it be
+ * possible to switch to it from another shell view. */
+ { "task-view-initial",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ -1 },
+
+ { "task-view-classic",
+ NULL,
+ N_("_Classic View"),
+ NULL,
+ N_("Show task preview below the task list"),
+ 0 },
+
+ { "task-view-vertical",
+ NULL,
+ N_("_Vertical View"),
+ NULL,
+ N_("Show task preview alongside the task list"),
+ 1 }
+};
+
+static GtkRadioActionEntry task_filter_entries[] = {
+
+ { "task-filter-active-tasks",
+ NULL,
+ N_("Active Tasks"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_ACTIVE_TASKS },
+
+ { "task-filter-any-category",
+ NULL,
+ N_("Any Category"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_ANY_CATEGORY },
+
+ { "task-filter-completed-tasks",
+ NULL,
+ N_("Completed Tasks"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_COMPLETED_TASKS },
+
+ { "task-filter-next-7-days-tasks",
+ NULL,
+ N_("Next 7 Days' Tasks"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_NEXT_7_DAYS_TASKS },
+
+ { "task-filter-overdue-tasks",
+ NULL,
+ N_("Overdue Tasks"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_OVERDUE_TASKS },
+
+ { "task-filter-tasks-with-attachments",
+ NULL,
+ N_("Tasks with Attachments"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_TASKS_WITH_ATTACHMENTS },
+
+ { "task-filter-unmatched",
+ NULL,
+ N_("Unmatched"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_FILTER_UNMATCHED }
+};
+
+static GtkRadioActionEntry task_search_entries[] = {
+
+ { "task-search-any-field-contains",
+ NULL,
+ N_("Any field contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_SEARCH_ANY_FIELD_CONTAINS },
+
+ { "task-search-description-contains",
+ NULL,
+ N_("Description contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_SEARCH_DESCRIPTION_CONTAINS },
+
+ { "task-search-summary-contains",
+ NULL,
+ N_("Summary contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ TASK_SEARCH_SUMMARY_CONTAINS }
+};
+
+static GtkActionEntry lockdown_printing_entries[] = {
+
+ { "task-list-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ "<Control>p",
+ N_("Print the list of tasks"),
+ G_CALLBACK (action_task_list_print_cb) },
+
+ { "task-list-print-preview",
+ GTK_STOCK_PRINT_PREVIEW,
+ NULL,
+ NULL,
+ N_("Preview the list of tasks to be printed"),
+ G_CALLBACK (action_task_list_print_preview_cb) },
+
+ { "task-print",
+ GTK_STOCK_PRINT,
+ NULL,
+ NULL,
+ N_("Print the selected task"),
+ G_CALLBACK (action_task_print_cb) }
+};
+
+static EPopupActionEntry lockdown_printing_popup_entries[] = {
+
+ { "task-popup-print",
+ NULL,
+ "task-print" }
+};
+
+void
+e_task_shell_view_actions_init (ETaskShellView *task_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 (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ /* Task Actions */
+ action_group = ACTION_GROUP (TASKS);
+ gtk_action_group_add_actions (
+ action_group, task_entries,
+ G_N_ELEMENTS (task_entries), task_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, task_popup_entries,
+ G_N_ELEMENTS (task_popup_entries));
+ gtk_action_group_add_toggle_actions (
+ action_group, task_toggle_entries,
+ G_N_ELEMENTS (task_toggle_entries), task_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, task_view_entries,
+ G_N_ELEMENTS (task_view_entries), -1,
+ G_CALLBACK (action_task_view_cb), task_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, task_search_entries,
+ G_N_ELEMENTS (task_search_entries),
+ TASK_SEARCH_SUMMARY_CONTAINS,
+ G_CALLBACK (action_task_search_cb), task_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), task_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 (TASK_PREVIEW));
+ key = "/apps/evolution/calendar/display/show_task_preview";
+ gconf_bridge_bind_property (bridge, key, object, "active");
+
+ object = G_OBJECT (ACTION (TASK_VIEW_VERTICAL));
+ key = "/apps/evolution/calendar/display/task_layout";
+ gconf_bridge_bind_property (bridge, key, object, "current-value");
+
+ /* Fine tuning. */
+
+ action = ACTION (TASK_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), task_shell_view);
+
+ g_signal_connect (
+ ACTION (SEARCH_EXECUTE), "activate",
+ G_CALLBACK (action_search_execute_cb), task_shell_view);
+}
+
+void
+e_task_shell_view_update_search_filter (ETaskShellView *task_shell_view)
+{
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GList *list, *iter;
+ GSList *group;
+ gint ii;
+
+ shell_view = E_SHELL_VIEW (task_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 (TASKS_FILTER);
+ e_action_group_remove_all_actions (action_group);
+
+ /* Add the standard filter actions. */
+ gtk_action_group_add_radio_actions (
+ action_group, task_filter_entries,
+ G_N_ELEMENTS (task_filter_entries),
+ TASK_FILTER_ANY_CATEGORY,
+ G_CALLBACK (action_search_filter_cb),
+ task_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 (
+ "task-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 = TASK_FILTER_UNMATCHED;
+ e_shell_content_add_filter_separator_after (shell_content, ii);
+
+ ii = TASK_FILTER_TASKS_WITH_ATTACHMENTS;
+ e_shell_content_add_filter_separator_after (shell_content, ii);
+}
diff --git a/modules/calendar/e-task-shell-view-actions.h b/modules/calendar/e-task-shell-view-actions.h
new file mode 100644
index 0000000000..daa70c36da
--- /dev/null
+++ b/modules/calendar/e-task-shell-view-actions.h
@@ -0,0 +1,109 @@
+/*
+ * e-task-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_TASK_SHELL_VIEW_ACTIONS_H
+#define E_TASK_SHELL_VIEW_ACTIONS_H
+
+#include <shell/e-shell-window-actions.h>
+
+/* Task Actions */
+#define E_SHELL_WINDOW_ACTION_TASK_ASSIGN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-assign")
+#define E_SHELL_WINDOW_ACTION_TASK_CLIPBOARD_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-clipboard-copy")
+#define E_SHELL_WINDOW_ACTION_TASK_CLIPBOARD_CUT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-clipboard-cut")
+#define E_SHELL_WINDOW_ACTION_TASK_CLIPBOARD_PASTE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-clipboard-paste")
+#define E_SHELL_WINDOW_ACTION_TASK_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-delete")
+#define E_SHELL_WINDOW_ACTION_TASK_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-forward")
+#define E_SHELL_WINDOW_ACTION_TASK_MARK_COMPLETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-mark-complete")
+#define E_SHELL_WINDOW_ACTION_TASK_MARK_INCOMPLETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-mark-incomplete")
+#define E_SHELL_WINDOW_ACTION_TASK_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-new")
+#define E_SHELL_WINDOW_ACTION_TASK_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-open")
+#define E_SHELL_WINDOW_ACTION_TASK_OPEN_URL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-open-url")
+#define E_SHELL_WINDOW_ACTION_TASK_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-preview")
+#define E_SHELL_WINDOW_ACTION_TASK_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-print")
+#define E_SHELL_WINDOW_ACTION_TASK_PURGE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-purge")
+#define E_SHELL_WINDOW_ACTION_TASK_SAVE_AS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-save-as")
+#define E_SHELL_WINDOW_ACTION_TASK_VIEW_CLASSIC(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-view-classic")
+#define E_SHELL_WINDOW_ACTION_TASK_VIEW_VERTICAL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-view-vertical")
+
+/* Task List Actions */
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-copy")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-delete")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-new")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-print")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_PRINT_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-print-preview")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_PROPERTIES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-properties")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_RENAME(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-rename")
+#define E_SHELL_WINDOW_ACTION_TASK_LIST_SELECT_ONE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-list-select-one")
+
+/* Task Query Actions */
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_ACTIVE_TASKS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-active-tasks")
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_ANY_CATEGORY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-any-category")
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_COMPLETED_TASKS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-completed-tasks")
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_NEXT_7_DAYS_TASKS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-next-7-days-tasks")
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_OVERDUE_TASKS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-overdue-tasks")
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_TASKS_WITH_ATTACHMENTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-tasks-with-attachments")
+#define E_SHELL_WINDOW_ACTION_TASK_FILTER_UNMATCHED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-filter-unmatched")
+#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_ANY_FIELD_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-search-any-field-contains")
+#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_DESCRIPTION_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-search-description-contains")
+#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_SUMMARY_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "task-search-summary-contains")
+
+/* Action Groups */
+#define E_SHELL_WINDOW_ACTION_GROUP_TASKS(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "tasks")
+#define E_SHELL_WINDOW_ACTION_GROUP_TASKS_FILTER(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "tasks-filter")
+
+#endif /* E_TASK_SHELL_VIEW_ACTIONS_H */
diff --git a/modules/calendar/e-task-shell-view-private.c b/modules/calendar/e-task-shell-view-private.c
new file mode 100644
index 0000000000..a0ca5ee978
--- /dev/null
+++ b/modules/calendar/e-task-shell-view-private.c
@@ -0,0 +1,726 @@
+/*
+ * e-task-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-task-shell-view-private.h"
+
+#include "widgets/menus/gal-view-factory-etable.h"
+
+static void
+task_shell_view_process_completed_tasks (ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ETaskShellSidebar *task_shell_sidebar;
+ ECalendarTable *task_table;
+ GList *clients;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ clients = e_task_shell_sidebar_get_clients (task_shell_sidebar);
+
+ e_calendar_table_process_completed_tasks (task_table, clients, TRUE);
+
+ /* Search query takes whether to show completed tasks into account,
+ * so if the preference has changed we need to update the query. */
+ e_task_shell_view_execute_search (task_shell_view);
+
+ g_list_free (clients);
+}
+
+static void
+task_shell_view_table_popup_event_cb (EShellView *shell_view,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/task-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+}
+
+static void
+task_shell_view_table_user_created_cb (ETaskShellView *task_shell_view,
+ ECalendarTable *task_table)
+{
+ ETaskShellSidebar *task_shell_sidebar;
+ ECalModel *model;
+ ECal *client;
+ ESource *source;
+
+ /* This is the "Click to Add" handler. */
+
+ model = e_calendar_table_get_model (task_table);
+ client = e_cal_model_get_default_client (model);
+ source = e_cal_get_source (client);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ e_task_shell_sidebar_add_source (task_shell_sidebar, source);
+
+ e_cal_model_add_client (model, client);
+}
+
+static void
+task_shell_view_selector_client_added_cb (ETaskShellView *task_shell_view,
+ ECal *client)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModel *model;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ model = e_calendar_table_get_model (task_table);
+
+ e_cal_model_add_client (model, client);
+ e_task_shell_view_update_timezone (task_shell_view);
+}
+
+static void
+task_shell_view_selector_client_removed_cb (ETaskShellView *task_shell_view,
+ ECal *client)
+{
+ ETaskShellContent *task_shell_content;
+ ECalendarTable *task_table;
+ ECalModel *model;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ model = e_calendar_table_get_model (task_table);
+
+ e_cal_model_remove_client (model, client);
+}
+
+static gboolean
+task_shell_view_selector_popup_event_cb (EShellView *shell_view,
+ ESource *primary_source,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/task-list-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+
+ return TRUE;
+}
+
+static gboolean
+task_shell_view_update_timeout_cb (ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ETaskShellSidebar *task_shell_sidebar;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ GList *clients;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ model = e_calendar_table_get_model (task_table);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ clients = e_task_shell_sidebar_get_clients (task_shell_sidebar);
+
+ e_calendar_table_process_completed_tasks (task_table, clients, FALSE);
+ e_cal_model_tasks_update_due_tasks (E_CAL_MODEL_TASKS (model));
+
+ g_list_free (clients);
+
+ return TRUE;
+}
+
+static void
+task_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 tasks");
+ 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);
+
+ gal_view_collection_load (collection);
+}
+
+static void
+task_shell_view_notify_view_id_cb (ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ GalViewInstance *view_instance;
+ const gchar *view_id;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ view_instance =
+ e_task_shell_content_get_view_instance (task_shell_content);
+ view_id = e_shell_view_get_view_id (E_SHELL_VIEW (task_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_task_shell_view_private_init (ETaskShellView *task_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ if (!gal_view_collection_loaded (shell_view_class->view_collection))
+ task_shell_view_load_view_collection (shell_view_class);
+
+ g_signal_connect (
+ task_shell_view, "notify::view-id",
+ G_CALLBACK (task_shell_view_notify_view_id_cb), NULL);
+}
+
+void
+e_task_shell_view_private_constructed (ETaskShellView *task_shell_view)
+{
+ ETaskShellViewPrivate *priv = task_shell_view->priv;
+ ETaskShellContent *task_shell_content;
+ ETaskShellSidebar *task_shell_sidebar;
+ EShell *shell;
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ EShellSettings *shell_settings;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ ETable *table;
+ ESourceSelector *selector;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ e_shell_window_add_action_group (shell_window, "tasks");
+ e_shell_window_add_action_group (shell_window, "tasks-filter");
+
+ /* Cache these to avoid lots of awkward casting. */
+ priv->task_shell_backend = g_object_ref (shell_backend);
+ priv->task_shell_content = g_object_ref (shell_content);
+ priv->task_shell_sidebar = g_object_ref (shell_sidebar);
+
+ task_shell_content = E_TASK_SHELL_CONTENT (shell_content);
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ model = e_calendar_table_get_model (task_table);
+ table = e_calendar_table_get_table (task_table);
+
+ task_shell_sidebar = E_TASK_SHELL_SIDEBAR (shell_sidebar);
+ selector = e_task_shell_sidebar_get_selector (task_shell_sidebar);
+
+ g_signal_connect_swapped (
+ model, "notify::timezone",
+ G_CALLBACK (e_task_shell_view_update_timezone),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_table, "open-component",
+ G_CALLBACK (e_task_shell_view_open_task),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_table, "popup-event",
+ G_CALLBACK (task_shell_view_table_popup_event_cb),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_table, "status-message",
+ G_CALLBACK (e_task_shell_view_set_status_message),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_table, "user-created",
+ G_CALLBACK (task_shell_view_table_user_created_cb),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ model, "model-changed",
+ G_CALLBACK (e_task_shell_view_update_sidebar),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ model, "model-rows-deleted",
+ G_CALLBACK (e_task_shell_view_update_sidebar),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ model, "model-rows-inserted",
+ G_CALLBACK (e_task_shell_view_update_sidebar),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ table, "selection-change",
+ G_CALLBACK (e_task_shell_view_update_sidebar),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_shell_sidebar, "client-added",
+ G_CALLBACK (task_shell_view_selector_client_added_cb),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_shell_sidebar, "client-removed",
+ G_CALLBACK (task_shell_view_selector_client_removed_cb),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ task_shell_sidebar, "status-message",
+ G_CALLBACK (e_task_shell_view_set_status_message),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ selector, "popup-event",
+ G_CALLBACK (task_shell_view_selector_popup_event_cb),
+ task_shell_view);
+
+ g_signal_connect_swapped (
+ selector, "primary-selection-changed",
+ G_CALLBACK (e_shell_view_update_actions),
+ task_shell_view);
+
+ e_categories_register_change_listener (
+ G_CALLBACK (e_task_shell_view_update_search_filter),
+ task_shell_view);
+
+ task_shell_view_update_timeout_cb (task_shell_view);
+ priv->update_timeout = g_timeout_add_full (
+ G_PRIORITY_LOW, 60000, (GSourceFunc)
+ task_shell_view_update_timeout_cb,
+ task_shell_view, NULL);
+
+ /* Listen for configuration changes. */
+
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "cal-confirm-purge",
+ G_OBJECT (task_shell_view), "confirm-purge");
+
+ /* Hide Completed Tasks (enable/units/value) */
+ g_signal_connect_swapped (
+ shell_settings, "notify::cal-hide-completed-tasks",
+ G_CALLBACK (task_shell_view_process_completed_tasks),
+ task_shell_view);
+ g_signal_connect_swapped (
+ shell_settings, "notify::cal-hide-completed-tasks-units",
+ G_CALLBACK (task_shell_view_process_completed_tasks),
+ task_shell_view);
+ g_signal_connect_swapped (
+ shell_settings, "notify::cal-hide-completed-tasks-value",
+ G_CALLBACK (task_shell_view_process_completed_tasks),
+ task_shell_view);
+
+ e_task_shell_view_actions_init (task_shell_view);
+ e_task_shell_view_update_sidebar (task_shell_view);
+ e_task_shell_view_update_search_filter (task_shell_view);
+ e_task_shell_view_update_timezone (task_shell_view);
+
+ e_task_shell_view_execute_search (task_shell_view);
+}
+
+void
+e_task_shell_view_private_dispose (ETaskShellView *task_shell_view)
+{
+ ETaskShellViewPrivate *priv = task_shell_view->priv;
+
+ DISPOSE (priv->task_shell_backend);
+ DISPOSE (priv->task_shell_content);
+ DISPOSE (priv->task_shell_sidebar);
+
+ if (task_shell_view->priv->activity != NULL) {
+ /* XXX Activity is no cancellable. */
+ e_activity_complete (task_shell_view->priv->activity);
+ g_object_unref (task_shell_view->priv->activity);
+ task_shell_view->priv->activity = NULL;
+ }
+
+ if (priv->update_timeout > 0) {
+ g_source_remove (priv->update_timeout);
+ priv->update_timeout = 0;
+ }
+}
+
+void
+e_task_shell_view_private_finalize (ETaskShellView *task_shell_view)
+{
+ /* XXX Nothing to do? */
+}
+
+void
+e_task_shell_view_execute_search (ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ GtkRadioAction *action;
+ GString *string;
+ ECalComponentPreview *task_preview;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ FilterRule *rule;
+ const gchar *format;
+ const gchar *text;
+ time_t start_range;
+ time_t end_range;
+ gchar *start, *end;
+ gchar *query;
+ gchar *temp;
+ gint value;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ 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 = GTK_RADIO_ACTION (ACTION (TASK_SEARCH_ANY_FIELD_CONTAINS));
+ value = gtk_radio_action_get_current_value (action);
+
+ if (text == NULL || *text == '\0') {
+ text = "";
+ value = TASK_SEARCH_SUMMARY_CONTAINS;
+ }
+
+ switch (value) {
+ default:
+ text = "";
+ /* fall through */
+
+ case TASK_SEARCH_SUMMARY_CONTAINS:
+ format = "(contains? \"summary\" %s)";
+ break;
+
+ case TASK_SEARCH_DESCRIPTION_CONTAINS:
+ format = "(contains? \"description\" %s)";
+ break;
+
+ case TASK_SEARCH_ANY_FIELD_CONTAINS:
+ format = "(contains? \"any\" %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 TASK_FILTER_ANY_CATEGORY:
+ break;
+
+ case TASK_FILTER_UNMATCHED:
+ temp = g_strdup_printf (
+ "(and (has-categories? #f) %s)", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case TASK_FILTER_NEXT_7_DAYS_TASKS:
+ start_range = time (NULL);
+ end_range = time_add_day (start_range, 7);
+ start = isodate_from_time_t (start_range);
+ end = isodate_from_time_t (end_range);
+
+ temp = g_strdup_printf (
+ "(and %s (due-in-time-range? "
+ "(make-time \"%s\") (make-time \"%s\")))",
+ query, start, end);
+ g_free (query);
+ query = temp;
+ break;
+
+ case TASK_FILTER_ACTIVE_TASKS:
+ start_range = time (NULL);
+ end_range = time_add_day (start_range, 365);
+ start = isodate_from_time_t (start_range);
+ end = isodate_from_time_t (end_range);
+
+ temp = g_strdup_printf (
+ "(and %s (due-in-time-range? "
+ "(make-time \"%s\") (make-time \"%s\")) "
+ "(not (is-completed?)))",
+ query, start, end);
+ g_free (query);
+ query = temp;
+ break;
+
+ case TASK_FILTER_OVERDUE_TASKS:
+ start_range = 0;
+ end_range = time (NULL);
+ start = isodate_from_time_t (start_range);
+ end = isodate_from_time_t (end_range);
+
+ temp = g_strdup_printf (
+ "(and %s (due-in-time-range? "
+ "(make-time \"%s\") (make-time \"%s\")) "
+ "(not (is-completed?)))",
+ query, start, end);
+ g_free (query);
+ query = temp;
+ break;
+
+ case TASK_FILTER_COMPLETED_TASKS:
+ temp = g_strdup_printf (
+ "(and (is-completed?) %s)", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case TASK_FILTER_TASKS_WITH_ATTACHMENTS:
+ temp = g_strdup_printf (
+ "(and (has-attachments?) %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 (has-categories? \"%s\") %s)",
+ category_name, query);
+ g_free (query);
+ query = temp;
+ break;
+ }
+ }
+
+ /* Honor the user's preference to hide completed tasks. */
+ temp = calendar_config_get_hide_completed_tasks_sexp (FALSE);
+ if (temp != NULL) {
+ gchar *temp2;
+
+ temp2 = g_strdup_printf ("(and %s %s)", temp, query);
+ g_free (query);
+ g_free (temp);
+ query = temp2;
+ }
+
+ /* XXX This is wrong. We need to programmatically construct a
+ * FilterRule, tell it to build code, and pass the resulting
+ * expression string to ECalModel. */
+ rule = filter_rule_new ();
+ e_shell_content_set_search_rule (shell_content, rule);
+ g_object_unref (rule);
+
+ /* Submit the query. */
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+ model = e_calendar_table_get_model (task_table);
+ e_cal_model_set_search_query (model, query);
+ g_free (query);
+
+ task_preview =
+ e_task_shell_content_get_task_preview (task_shell_content);
+ e_cal_component_preview_clear (task_preview);
+}
+
+void
+e_task_shell_view_open_task (ETaskShellView *task_shell_view,
+ ECalModelComponent *comp_data)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ CompEditor *editor;
+ CompEditorFlags flags = 0;
+ ECalComponent *comp;
+ icalcomponent *clone;
+ icalproperty *prop;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view));
+ g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data));
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ uid = icalcomponent_get_uid (comp_data->icalcomp);
+ editor = comp_editor_find_instance (uid);
+
+ if (editor != NULL)
+ goto exit;
+
+ comp = e_cal_component_new ();
+ clone = icalcomponent_new_clone (comp_data->icalcomp);
+ e_cal_component_set_icalcomponent (comp, clone);
+
+ prop = icalcomponent_get_first_property (
+ comp_data->icalcomp, ICAL_ATTENDEE_PROPERTY);
+ if (prop != NULL)
+ flags |= COMP_EDITOR_IS_ASSIGNED;
+
+ if (itip_organizer_is_user (comp, comp_data->client))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ if (!e_cal_component_has_attendees (comp))
+ flags |= COMP_EDITOR_USER_ORG;
+
+ editor = task_editor_new (comp_data->client, shell, flags);
+ comp_editor_edit_comp (editor, comp);
+
+ g_object_ref (comp);
+
+ if (flags & COMP_EDITOR_IS_ASSIGNED)
+ task_editor_show_assignment (TASK_EDITOR (editor));
+
+exit:
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+void
+e_task_shell_view_set_status_message (ETaskShellView *task_shell_view,
+ const gchar *status_message,
+ gdouble percent)
+{
+ EActivity *activity;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+
+ g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view));
+
+ activity = task_shell_view->priv->activity;
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ if (status_message == NULL || *status_message == '\0') {
+ if (activity != NULL) {
+ e_activity_complete (activity);
+ g_object_unref (activity);
+ activity = NULL;
+ }
+
+ } else if (activity == NULL) {
+ activity = e_activity_new (status_message);
+ e_activity_set_percent (activity, percent);
+ e_shell_backend_add_activity (shell_backend, activity);
+
+ } else {
+ e_activity_set_percent (activity, percent);
+ e_activity_set_primary_text (activity, status_message);
+ }
+
+ task_shell_view->priv->activity = activity;
+}
+
+void
+e_task_shell_view_update_sidebar (ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ EShellView *shell_view;
+ EShellSidebar *shell_sidebar;
+ ECalendarTable *task_table;
+ ECalModel *model;
+ ETable *table;
+ GString *string;
+ const gchar *format;
+ gint n_rows;
+ gint n_selected;
+
+ shell_view = E_SHELL_VIEW (task_shell_view);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_table = e_task_shell_content_get_task_table (task_shell_content);
+
+ model = e_calendar_table_get_model (task_table);
+ table = e_calendar_table_get_table (task_table);
+
+ n_rows = e_table_model_row_count (E_TABLE_MODEL (model));
+ n_selected = e_table_selected_count (table);
+
+ string = g_string_sized_new (64);
+
+ format = ngettext ("%d task", "%d tasks", n_rows);
+ g_string_append_printf (string, format, n_rows);
+
+ if (n_selected > 0) {
+ format = _("%d selected");
+ g_string_append_len (string, ", ", 2);
+ g_string_append_printf (string, format, n_selected);
+ }
+
+ e_shell_sidebar_set_secondary_text (shell_sidebar, string->str);
+
+ g_string_free (string, TRUE);
+}
+
+void
+e_task_shell_view_update_timezone (ETaskShellView *task_shell_view)
+{
+ ETaskShellContent *task_shell_content;
+ ETaskShellSidebar *task_shell_sidebar;
+ ECalComponentPreview *task_preview;
+ icaltimezone *timezone;
+ ECalModel *model;
+ GList *clients, *iter;
+
+ task_shell_content = task_shell_view->priv->task_shell_content;
+ task_preview = e_task_shell_content_get_task_preview (task_shell_content);
+ model = e_task_shell_content_get_task_model (task_shell_content);
+ timezone = e_cal_model_get_timezone (model);
+
+ task_shell_sidebar = task_shell_view->priv->task_shell_sidebar;
+ clients = e_task_shell_sidebar_get_clients (task_shell_sidebar);
+
+ for (iter = clients; iter != NULL; iter = iter->next) {
+ ECal *client = iter->data;
+
+ if (e_cal_get_load_state (client) == E_CAL_LOAD_LOADED)
+ e_cal_set_default_timezone (client, timezone, NULL);
+ }
+
+ e_cal_component_preview_set_default_timezone (task_preview, timezone);
+
+ g_list_free (clients);
+}
diff --git a/modules/calendar/e-task-shell-view-private.h b/modules/calendar/e-task-shell-view-private.h
new file mode 100644
index 0000000000..e612069bed
--- /dev/null
+++ b/modules/calendar/e-task-shell-view-private.h
@@ -0,0 +1,141 @@
+/*
+ * e-task-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_TASK_SHELL_VIEW_PRIVATE_H
+#define E_TASK_SHELL_VIEW_PRIVATE_H
+
+#include "e-task-shell-view.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <libecal/e-cal-time-util.h>
+#include <libedataserver/e-categories.h>
+#include <libedataserver/e-sexp.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/e-dialog-utils.h"
+#include "e-util/e-error.h"
+#include "e-util/e-util.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/misc/e-popup-action.h"
+
+#include "calendar/common/authentication.h"
+#include "calendar/gui/calendar-config.h"
+#include "calendar/gui/comp-util.h"
+#include "calendar/gui/e-cal-component-preview.h"
+#include "calendar/gui/e-cal-model-tasks.h"
+#include "calendar/gui/e-calendar-selector.h"
+#include "calendar/gui/print.h"
+#include "calendar/gui/dialogs/calendar-setup.h"
+#include "calendar/gui/dialogs/copy-source-dialog.h"
+#include "calendar/gui/dialogs/task-editor.h"
+
+#include "e-task-shell-backend.h"
+#include "e-task-shell-content.h"
+#include "e-task-shell-sidebar.h"
+#include "e-task-shell-view-actions.h"
+
+#define E_TASK_SHELL_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TASK_SHELL_VIEW, ETaskShellViewPrivate))
+
+/* 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-calendar-table.etspec"
+
+G_BEGIN_DECLS
+
+/* Filter items are displayed in ascending order.
+ * Non-negative values are reserved for categories. */
+enum {
+ TASK_FILTER_ANY_CATEGORY = -7,
+ TASK_FILTER_UNMATCHED = -6,
+ TASK_FILTER_NEXT_7_DAYS_TASKS = -5,
+ TASK_FILTER_ACTIVE_TASKS = -4,
+ TASK_FILTER_OVERDUE_TASKS = -3,
+ TASK_FILTER_COMPLETED_TASKS = -2,
+ TASK_FILTER_TASKS_WITH_ATTACHMENTS = -1
+};
+
+/* Search items are displayed in ascending order. */
+enum {
+ TASK_SEARCH_SUMMARY_CONTAINS,
+ TASK_SEARCH_DESCRIPTION_CONTAINS,
+ TASK_SEARCH_ANY_FIELD_CONTAINS
+};
+
+struct _ETaskShellViewPrivate {
+
+ /* These are just for convenience. */
+ ETaskShellBackend *task_shell_backend;
+ ETaskShellContent *task_shell_content;
+ ETaskShellSidebar *task_shell_sidebar;
+
+ EActivity *activity;
+ guint update_timeout;
+
+ guint confirm_purge : 1;
+};
+
+void e_task_shell_view_private_init
+ (ETaskShellView *task_shell_view,
+ EShellViewClass *shell_view_class);
+void e_task_shell_view_private_constructed
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_private_dispose
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_private_finalize
+ (ETaskShellView *task_shell_view);
+
+/* Private Utilities */
+
+void e_task_shell_view_actions_init
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_execute_search
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_open_task
+ (ETaskShellView *task_shell_view,
+ ECalModelComponent *comp_data);
+void e_task_shell_view_set_status_message
+ (ETaskShellView *task_shell_view,
+ const gchar *status_message,
+ gdouble percent);
+void e_task_shell_view_update_sidebar
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_update_search_filter
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_update_timezone
+ (ETaskShellView *task_shell_view);
+
+G_END_DECLS
+
+#endif /* E_TASK_SHELL_VIEW_PRIVATE_H */
diff --git a/modules/calendar/e-task-shell-view.c b/modules/calendar/e-task-shell-view.c
new file mode 100644
index 0000000000..b1298eaa29
--- /dev/null
+++ b/modules/calendar/e-task-shell-view.c
@@ -0,0 +1,325 @@
+/*
+ * e-task-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-task-shell-view-private.h"
+
+enum {
+ PROP_0,
+ PROP_CONFIRM_PURGE
+};
+
+static gpointer parent_class;
+static GType task_shell_view_type;
+
+static void
+task_shell_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONFIRM_PURGE:
+ e_task_shell_view_set_confirm_purge (
+ E_TASK_SHELL_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+task_shell_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONFIRM_PURGE:
+ g_value_set_boolean (
+ value, e_task_shell_view_get_confirm_purge (
+ E_TASK_SHELL_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+task_shell_view_dispose (GObject *object)
+{
+ e_task_shell_view_private_dispose (E_TASK_SHELL_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+task_shell_view_finalize (GObject *object)
+{
+ e_task_shell_view_private_finalize (E_TASK_SHELL_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+task_shell_view_constructed (GObject *object)
+{
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ e_task_shell_view_private_constructed (E_TASK_SHELL_VIEW (object));
+}
+
+static void
+task_shell_view_update_actions (EShellView *shell_view)
+{
+ ETaskShellViewPrivate *priv;
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ GtkAction *action;
+ const gchar *label;
+ gboolean sensitive;
+ guint32 state;
+
+ /* Be descriptive. */
+ gboolean any_tasks_selected;
+ gboolean has_primary_source;
+ gboolean multiple_tasks_selected;
+ gboolean primary_source_is_system;
+ gboolean selection_has_url;
+ gboolean selection_is_assignable;
+ gboolean single_task_selected;
+ gboolean some_tasks_complete;
+ gboolean some_tasks_incomplete;
+ gboolean sources_are_editable;
+
+ priv = E_TASK_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_task_selected =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_SINGLE);
+ multiple_tasks_selected =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_MULTIPLE);
+ selection_is_assignable =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_CAN_ASSIGN);
+ sources_are_editable =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_CAN_EDIT);
+ some_tasks_complete =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_HAS_COMPLETE);
+ some_tasks_incomplete =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE);
+ selection_has_url =
+ (state & E_TASK_SHELL_CONTENT_SELECTION_HAS_URL);
+
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ state = e_shell_sidebar_check_state (shell_sidebar);
+
+ has_primary_source =
+ (state & E_TASK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE);
+ primary_source_is_system =
+ (state & E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_SYSTEM);
+
+ any_tasks_selected =
+ (single_task_selected || multiple_tasks_selected);
+
+ action = ACTION (TASK_ASSIGN);
+ sensitive =
+ single_task_selected && sources_are_editable &&
+ selection_is_assignable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_CLIPBOARD_COPY);
+ sensitive = any_tasks_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_CLIPBOARD_CUT);
+ sensitive = any_tasks_selected && sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_CLIPBOARD_PASTE);
+ sensitive = sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_DELETE);
+ sensitive = any_tasks_selected && sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+ if (multiple_tasks_selected)
+ label = _("Delete Tasks");
+ else
+ label = _("Delete Task");
+ g_object_set (action, "label", label, NULL);
+
+ action = ACTION (TASK_FORWARD);
+ sensitive = single_task_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_LIST_COPY);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_LIST_DELETE);
+ sensitive = has_primary_source && !primary_source_is_system;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_LIST_PROPERTIES);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_LIST_RENAME);
+ sensitive = has_primary_source;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_MARK_COMPLETE);
+ sensitive =
+ any_tasks_selected &&
+ sources_are_editable &&
+ some_tasks_incomplete;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_MARK_INCOMPLETE);
+ sensitive =
+ any_tasks_selected &&
+ sources_are_editable &&
+ some_tasks_complete;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_OPEN);
+ sensitive = single_task_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_OPEN_URL);
+ sensitive = single_task_selected && selection_has_url;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_PRINT);
+ sensitive = single_task_selected;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_PURGE);
+ sensitive = sources_are_editable;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (TASK_SAVE_AS);
+ sensitive = single_task_selected;
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+task_shell_view_class_init (ETaskShellViewClass *class,
+ GTypeModule *type_module)
+{
+ GObjectClass *object_class;
+ EShellViewClass *shell_view_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ETaskShellViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = task_shell_view_set_property;
+ object_class->get_property = task_shell_view_get_property;
+ object_class->dispose = task_shell_view_dispose;
+ object_class->finalize = task_shell_view_finalize;
+ object_class->constructed = task_shell_view_constructed;
+
+ shell_view_class = E_SHELL_VIEW_CLASS (class);
+ shell_view_class->label = _("Tasks");
+ shell_view_class->icon_name = "evolution-tasks";
+ shell_view_class->ui_definition = "evolution-tasks.ui";
+ shell_view_class->ui_manager_id = "org.gnome.evolution.tasks";
+ shell_view_class->search_options = "/task-search-options";
+ shell_view_class->search_rules = "tasktypes.xml";
+ shell_view_class->new_shell_content = e_task_shell_content_new;
+ shell_view_class->new_shell_sidebar = e_task_shell_sidebar_new;
+ shell_view_class->update_actions = task_shell_view_update_actions;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONFIRM_PURGE,
+ g_param_spec_boolean (
+ "confirm-purge",
+ "Confirm Purge",
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+}
+
+static void
+task_shell_view_init (ETaskShellView *task_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ task_shell_view->priv =
+ E_TASK_SHELL_VIEW_GET_PRIVATE (task_shell_view);
+
+ e_task_shell_view_private_init (task_shell_view, shell_view_class);
+}
+
+GType
+e_task_shell_view_get_type (void)
+{
+ return task_shell_view_type;
+}
+
+void
+e_task_shell_view_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (ETaskShellViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) task_shell_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ type_module,
+ sizeof (ETaskShellView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) task_shell_view_init,
+ NULL /* value_table */
+ };
+
+ task_shell_view_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_VIEW,
+ "ETaskShellView", &type_info, 0);
+}
+
+gboolean
+e_task_shell_view_get_confirm_purge (ETaskShellView *task_shell_view)
+{
+ g_return_val_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view), FALSE);
+
+ return task_shell_view->priv->confirm_purge;
+}
+
+void
+e_task_shell_view_set_confirm_purge (ETaskShellView *task_shell_view,
+ gboolean confirm_purge)
+{
+ g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view));
+
+ task_shell_view->priv->confirm_purge = confirm_purge;
+
+ g_object_notify (G_OBJECT (task_shell_view), "confirm-purge");
+}
diff --git a/modules/calendar/e-task-shell-view.h b/modules/calendar/e-task-shell-view.h
new file mode 100644
index 0000000000..853d90cac1
--- /dev/null
+++ b/modules/calendar/e-task-shell-view.h
@@ -0,0 +1,72 @@
+/*
+ * e-task-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_TASK_SHELL_VIEW_H
+#define E_TASK_SHELL_VIEW_H
+
+#include <shell/e-shell-view.h>
+#include <libedataserver/e-source-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TASK_SHELL_VIEW \
+ (e_task_shell_view_get_type ())
+#define E_TASK_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_TASK_SHELL_VIEW, ETaskShellView))
+#define E_TASK_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_TASK_SHELL_VIEW, ETaskShellViewClass))
+#define E_IS_TASK_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_TASK_SHELL_VIEW))
+#define E_IS_TASK_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_TASK_SHELL_VIEW))
+#define E_TASK_SHELL_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_TASK_SHELL_VIEW, ETaskShellViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETaskShellView ETaskShellView;
+typedef struct _ETaskShellViewClass ETaskShellViewClass;
+typedef struct _ETaskShellViewPrivate ETaskShellViewPrivate;
+
+struct _ETaskShellView {
+ EShellView parent;
+ ETaskShellViewPrivate *priv;
+};
+
+struct _ETaskShellViewClass {
+ EShellViewClass parent_class;
+};
+
+GType e_task_shell_view_get_type (void);
+void e_task_shell_view_register_type (GTypeModule *type_module);
+gboolean e_task_shell_view_get_confirm_purge
+ (ETaskShellView *task_shell_view);
+void e_task_shell_view_set_confirm_purge
+ (ETaskShellView *task_shell_view,
+ gboolean confirm_purge);
+
+G_END_DECLS
+
+#endif /* E_TASK_SHELL_VIEW_H */
diff --git a/modules/calendar/evolution-module-calendar.c b/modules/calendar/evolution-module-calendar.c
new file mode 100644
index 0000000000..f72e8a97e4
--- /dev/null
+++ b/modules/calendar/evolution-module-calendar.c
@@ -0,0 +1,75 @@
+/*
+ * evolution-module-calendar.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-cal-attachment-handler.h"
+
+#include "e-cal-config-hook.h"
+#include "e-cal-event-hook.h"
+
+#include "e-cal-shell-backend.h"
+#include "e-cal-shell-content.h"
+#include "e-cal-shell-sidebar.h"
+#include "e-cal-shell-view.h"
+
+#include "e-memo-shell-backend.h"
+#include "e-memo-shell-content.h"
+#include "e-memo-shell-sidebar.h"
+#include "e-memo-shell-view.h"
+
+#include "e-task-shell-backend.h"
+#include "e-task-shell-content.h"
+#include "e-task-shell-sidebar.h"
+#include "e-task-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_cal_attachment_handler_register_type (type_module);
+
+ e_cal_config_hook_register_type (type_module);
+ e_cal_event_hook_register_type (type_module);
+
+ e_cal_shell_backend_register_type (type_module);
+ e_cal_shell_content_register_type (type_module);
+ e_cal_shell_sidebar_register_type (type_module);
+ e_cal_shell_view_register_type (type_module);
+
+ e_memo_shell_backend_register_type (type_module);
+ e_memo_shell_content_register_type (type_module);
+ e_memo_shell_sidebar_register_type (type_module);
+ e_memo_shell_view_register_type (type_module);
+
+ e_task_shell_backend_register_type (type_module);
+ e_task_shell_content_register_type (type_module);
+ e_task_shell_sidebar_register_type (type_module);
+ e_task_shell_view_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/mail/Makefile.am b/modules/mail/Makefile.am
new file mode 100644
index 0000000000..462161843c
--- /dev/null
+++ b/modules/mail/Makefile.am
@@ -0,0 +1,60 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/mail \
+ -I$(top_srcdir)/widgets \
+ $(EVOLUTION_MAIL_CFLAGS) \
+ -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \
+ -DEVOLUTION_GLADEDIR=\""$(gladedir)"\" \
+ -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \
+ -DG_LOG_DOMAIN=\"evolution-module-mail\"
+
+module_LTLIBRARIES = libevolution-module-mail.la
+
+libevolution_module_mail_la_SOURCES = \
+ evolution-module-mail.c \
+ e-mail-attachment-handler.c \
+ e-mail-attachment-handler.h \
+ e-mail-config-hook.c \
+ e-mail-config-hook.h \
+ e-mail-event-hook.c \
+ e-mail-event-hook.h \
+ e-mail-junk-hook.c \
+ e-mail-junk-hook.h \
+ e-mail-shell-backend.c \
+ e-mail-shell-backend.h \
+ e-mail-shell-content.c \
+ e-mail-shell-content.h \
+ e-mail-shell-migrate.c \
+ e-mail-shell-migrate.h \
+ e-mail-shell-settings.c \
+ e-mail-shell-settings.h \
+ e-mail-shell-sidebar.c \
+ e-mail-shell-sidebar.h \
+ e-mail-shell-view.c \
+ e-mail-shell-view.h \
+ e-mail-shell-view-actions.c \
+ e-mail-shell-view-actions.h \
+ e-mail-shell-view-private.c \
+ e-mail-shell-view-private.h \
+ em-account-prefs.c \
+ em-account-prefs.h \
+ em-composer-prefs.c \
+ em-composer-prefs.h \
+ em-mailer-prefs.c \
+ em-mailer-prefs.h \
+ em-network-prefs.c \
+ em-network-prefs.h
+
+libevolution_module_mail_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/shell/libeshell.la \
+ $(top_builddir)/composer/libcomposer.la \
+ $(top_builddir)/widgets/table/libetable.la \
+ $(top_builddir)/widgets/text/libetext.la \
+ $(top_builddir)/widgets/misc/libemiscwidgets.la \
+ $(top_builddir)/mail/importers/libevolution-mail-importers.la
+
+libevolution_module_mail_la_LDFLAGS = \
+ -avoid-version -module $(NO_UNDEFINED)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/mail/e-mail-attachment-handler.c b/modules/mail/e-mail-attachment-handler.c
new file mode 100644
index 0000000000..91fe5d3e5f
--- /dev/null
+++ b/modules/mail/e-mail-attachment-handler.c
@@ -0,0 +1,525 @@
+/*
+ * e-mail-attachment-handler.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-mail-attachment-handler.h"
+
+#include <glib/gi18n.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-stream-mem.h>
+
+#include "e-util/e-error.h"
+#include "mail/em-composer-utils.h"
+#include "mail/mail-tools.h"
+
+#define E_MAIL_ATTACHMENT_HANDLER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_ATTACHMENT_HANDLER, EMailAttachmentHandlerPrivate))
+
+struct _EMailAttachmentHandlerPrivate {
+ gint placeholder;
+};
+
+static gpointer parent_class;
+static GType mail_attachment_handler_type;
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <placeholder name='custom-actions'>"
+" <menuitem action='mail-reply-sender'/>"
+" <menuitem action='mail-reply-all'/>"
+" <menuitem action='mail-forward'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+/* Note: Do not use the info field. */
+static GtkTargetEntry target_table[] = {
+ { (gchar *) "message/rfc822", 0, 0 },
+ { (gchar *) "x-uid-list", 0, 0 }
+};
+
+static void
+mail_attachment_handler_forward (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachment *attachment;
+ CamelMimePart *mime_part;
+ CamelDataWrapper *wrapper;
+ GList *selected;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ attachment = E_ATTACHMENT (selected->data);
+ mime_part = e_attachment_get_mime_part (attachment);
+ wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+
+ em_utils_forward_message (CAMEL_MIME_MESSAGE (wrapper), NULL);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+mail_attachment_handler_reply_all (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachment *attachment;
+ CamelMimePart *mime_part;
+ CamelDataWrapper *wrapper;
+ GList *selected;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ attachment = E_ATTACHMENT (selected->data);
+ mime_part = e_attachment_get_mime_part (attachment);
+ wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+
+ em_utils_reply_to_message (
+ NULL, NULL, CAMEL_MIME_MESSAGE (wrapper),
+ REPLY_MODE_ALL, NULL);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+mail_attachment_handler_reply_sender (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachment *attachment;
+ CamelMimePart *mime_part;
+ CamelDataWrapper *wrapper;
+ GList *selected;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+
+ attachment = E_ATTACHMENT (selected->data);
+ mime_part = e_attachment_get_mime_part (attachment);
+ wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+
+ em_utils_reply_to_message (
+ NULL, NULL, CAMEL_MIME_MESSAGE (wrapper),
+ REPLY_MODE_SENDER, NULL);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+ { "mail-forward",
+ "mail-forward",
+ N_("_Forward"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (mail_attachment_handler_forward) },
+
+ { "mail-reply-all",
+ "mail-reply-all",
+ N_("Reply to _All"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (mail_attachment_handler_reply_all) },
+
+ { "mail-reply-sender",
+ "mail-reply-sender",
+ N_("_Reply to Sender"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (mail_attachment_handler_reply_sender) }
+};
+
+static void
+mail_attachment_handler_message_rfc822 (EAttachmentView *view,
+ GdkDragContext *drag_context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ static GdkAtom atom = GDK_NONE;
+ EAttachmentStore *store;
+ EAttachment *attachment;
+ CamelMimeMessage *message;
+ CamelDataWrapper *wrapper;
+ CamelStream *stream;
+ const gchar *data;
+ gboolean success = FALSE;
+ gpointer parent;
+ gint length;
+
+ if (G_UNLIKELY (atom == GDK_NONE))
+ atom = gdk_atom_intern_static_string ("message/rfc822");
+
+ if (gtk_selection_data_get_target (selection_data) != atom)
+ return;
+
+ g_signal_stop_emission_by_name (view, "drag-data-received");
+
+ data = (const gchar *) gtk_selection_data_get_data (selection_data);
+ length = gtk_selection_data_get_length (selection_data);
+
+ stream = camel_stream_mem_new ();
+ camel_stream_write (stream, data, length);
+ camel_stream_reset (stream);
+
+ message = camel_mime_message_new ();
+ wrapper = CAMEL_DATA_WRAPPER (message);
+
+ if (camel_data_wrapper_construct_from_stream (wrapper, stream) == -1)
+ goto exit;
+
+ store = e_attachment_view_get_store (view);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ attachment = e_attachment_new_for_message (message);
+ e_attachment_store_add_attachment (store, attachment);
+ e_attachment_load_async (
+ attachment, (GAsyncReadyCallback)
+ e_attachment_load_handle_error, parent);
+ g_object_unref (attachment);
+
+ success = TRUE;
+
+exit:
+ camel_object_unref (message);
+ camel_object_unref (stream);
+
+ gtk_drag_finish (drag_context, success, FALSE, time);
+}
+
+static void
+mail_attachment_handler_x_uid_list (EAttachmentView *view,
+ GdkDragContext *drag_context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ static GdkAtom atom = GDK_NONE;
+ CamelException ex = CAMEL_EXCEPTION_INITIALISER;
+ CamelDataWrapper *wrapper;
+ CamelMimeMessage *message;
+ CamelMultipart *multipart;
+ CamelMimePart *mime_part;
+ CamelFolder *folder = NULL;
+ EAttachment *attachment;
+ EAttachmentStore *store;
+ GPtrArray *uids;
+ const gchar *data;
+ const gchar *cp, *end;
+ gchar *description;
+ gpointer parent;
+ gint length;
+ guint ii;
+
+ if (G_UNLIKELY (atom == GDK_NONE))
+ atom = gdk_atom_intern_static_string ("x-uid-list");
+
+ if (gtk_selection_data_get_target (selection_data) != atom)
+ return;
+
+ store = e_attachment_view_get_store (view);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ uids = g_ptr_array_new ();
+
+ data = (const gchar *) gtk_selection_data_get_data (selection_data);
+ length = gtk_selection_data_get_length (selection_data);
+
+ /* The UID list is delimited by NUL characters.
+ * Brilliant. So we can't use g_strsplit(). */
+
+ cp = data;
+ end = data + length;
+
+ while (cp < end) {
+ const gchar *start = cp;
+
+ while (cp < end && *cp != '\0')
+ cp++;
+
+ /* Skip the first string. */
+ if (start > data)
+ g_ptr_array_add (uids, g_strndup (start, cp - start));
+
+ cp++;
+ }
+
+ if (uids->len == 0)
+ goto exit;
+
+ /* The first string is the folder URI. */
+ folder = mail_tool_uri_to_folder (data, 0, &ex);
+ if (folder == NULL)
+ goto exit;
+
+ /* Handle one message. */
+ if (uids->len == 1) {
+ message = camel_folder_get_message (
+ folder, uids->pdata[0], &ex);
+ if (message == NULL)
+ goto exit;
+
+ attachment = e_attachment_new_for_message (message);
+ e_attachment_store_add_attachment (store, attachment);
+ e_attachment_load_async (
+ attachment, (GAsyncReadyCallback)
+ e_attachment_load_handle_error, parent);
+ g_object_unref (attachment);
+
+ camel_object_unref (message);
+ goto exit;
+ }
+
+ /* Build a multipart/digest message out of the UIDs. */
+
+ multipart = camel_multipart_new ();
+ wrapper = CAMEL_DATA_WRAPPER (multipart);
+ camel_data_wrapper_set_mime_type (wrapper, "multipart/digest");
+ camel_multipart_set_boundary (multipart, NULL);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ message = camel_folder_get_message (
+ folder, uids->pdata[ii], &ex);
+ if (message == NULL) {
+ camel_object_unref (multipart);
+ goto exit;
+ }
+
+ mime_part = camel_mime_part_new ();
+ wrapper = CAMEL_DATA_WRAPPER (message);
+ camel_mime_part_set_disposition (mime_part, "inline");
+ camel_medium_set_content_object (
+ CAMEL_MEDIUM (mime_part), wrapper);
+ camel_mime_part_set_content_type (mime_part, "message/rfc822");
+ camel_multipart_add_part (multipart, mime_part);
+ camel_object_unref (mime_part);
+
+ camel_object_unref (message);
+ }
+
+ mime_part = camel_mime_part_new ();
+ wrapper = CAMEL_DATA_WRAPPER (multipart);
+ camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper);
+
+ /* Translators: This is only for multiple messages. */
+ description = g_strdup_printf (_("%d attached messages"), uids->len);
+ camel_mime_part_set_description (mime_part, description);
+ g_free (description);
+
+ attachment = e_attachment_new ();
+ e_attachment_set_mime_part (attachment, mime_part);
+ e_attachment_store_add_attachment (store, attachment);
+ e_attachment_load_async (
+ attachment, (GAsyncReadyCallback)
+ e_attachment_load_handle_error, parent);
+ g_object_unref (attachment);
+
+ camel_object_unref (mime_part);
+ camel_object_unref (multipart);
+
+exit:
+ if (camel_exception_is_set (&ex)) {
+ gchar *folder_name;
+
+ if (folder != NULL)
+ camel_object_get (
+ folder, NULL, CAMEL_FOLDER_NAME,
+ &folder_name, NULL);
+ else
+ folder_name = g_strdup (data);
+
+ e_error_run (
+ parent, "mail-composer:attach-nomessages",
+ folder_name, camel_exception_get_description (&ex),
+ NULL);
+
+ if (folder != NULL)
+ camel_object_free (
+ folder, CAMEL_FOLDER_NAME, folder_name);
+ else
+ g_free (folder_name);
+
+ camel_exception_clear (&ex);
+ }
+
+ if (folder != NULL)
+ camel_object_unref (folder);
+
+ g_ptr_array_free (uids, TRUE);
+
+ g_signal_stop_emission_by_name (view, "drag-data-received");
+}
+
+static void
+mail_attachment_handler_update_actions (EAttachmentView *view)
+{
+ EAttachment *attachment;
+ CamelMimePart *mime_part;
+ CamelDataWrapper *wrapper;
+ GtkActionGroup *action_group;
+ GList *selected;
+ gboolean visible = FALSE;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+
+ if (g_list_length (selected) != 1)
+ goto exit;
+
+ attachment = E_ATTACHMENT (selected->data);
+ mime_part = e_attachment_get_mime_part (attachment);
+
+ if (!CAMEL_IS_MIME_PART (mime_part))
+ goto exit;
+
+ wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+
+ visible = CAMEL_IS_MIME_MESSAGE (wrapper);
+
+exit:
+ action_group = e_attachment_view_get_action_group (view, "mail");
+ gtk_action_group_set_visible (action_group, visible);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+mail_attachment_handler_constructed (GObject *object)
+{
+ EAttachmentHandler *handler;
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GError *error = NULL;
+
+ handler = E_ATTACHMENT_HANDLER (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ view = e_attachment_handler_get_view (handler);
+
+ action_group = e_attachment_view_add_action_group (view, "mail");
+ gtk_action_group_add_actions (
+ action_group, standard_entries,
+ G_N_ELEMENTS (standard_entries), view);
+
+ ui_manager = e_attachment_view_get_ui_manager (view);
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_signal_connect (
+ view, "update-actions",
+ G_CALLBACK (mail_attachment_handler_update_actions),
+ NULL);
+
+ g_signal_connect (
+ view, "drag-data-received",
+ G_CALLBACK (mail_attachment_handler_message_rfc822),
+ NULL);
+
+ g_signal_connect (
+ view, "drag-data-received",
+ G_CALLBACK (mail_attachment_handler_x_uid_list),
+ NULL);
+}
+
+static GdkDragAction
+mail_attachment_handler_get_drag_actions (EAttachmentHandler *handler)
+{
+ return GDK_ACTION_COPY;
+}
+
+static const GtkTargetEntry *
+mail_attachment_handler_get_target_table (EAttachmentHandler *handler,
+ guint *n_targets)
+{
+ if (n_targets != NULL)
+ *n_targets = G_N_ELEMENTS (target_table);
+
+ return target_table;
+}
+
+static void
+mail_attachment_handler_class_init (EMailAttachmentHandlerClass *class)
+{
+ GObjectClass *object_class;
+ EAttachmentHandlerClass *handler_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMailAttachmentHandlerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = mail_attachment_handler_constructed;
+
+ handler_class = E_ATTACHMENT_HANDLER_CLASS (class);
+ handler_class->get_drag_actions = mail_attachment_handler_get_drag_actions;
+ handler_class->get_target_table = mail_attachment_handler_get_target_table;
+}
+
+static void
+mail_attachment_handler_init (EMailAttachmentHandler *handler)
+{
+ handler->priv = E_MAIL_ATTACHMENT_HANDLER_GET_PRIVATE (handler);
+}
+
+GType
+e_mail_attachment_handler_get_type (void)
+{
+ return mail_attachment_handler_type;
+}
+
+void
+e_mail_attachment_handler_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EMailAttachmentHandlerClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_attachment_handler_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMailAttachmentHandler),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) mail_attachment_handler_init,
+ NULL /* value_table */
+ };
+
+ mail_attachment_handler_type = g_type_module_register_type (
+ type_module, E_TYPE_ATTACHMENT_HANDLER,
+ "EMailAttachmentHandler", &type_info, 0);
+}
diff --git a/modules/mail/e-mail-attachment-handler.h b/modules/mail/e-mail-attachment-handler.h
new file mode 100644
index 0000000000..13032b488c
--- /dev/null
+++ b/modules/mail/e-mail-attachment-handler.h
@@ -0,0 +1,67 @@
+/*
+ * e-mail-attachment-handler.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_MAIL_ATTACHMENT_HANDLER_H
+#define E_MAIL_ATTACHMENT_HANDLER_H
+
+#include <widgets/misc/e-attachment-handler.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_ATTACHMENT_HANDLER \
+ (e_mail_attachment_handler_get_type ())
+#define E_MAIL_ATTACHMENT_HANDLER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_ATTACHMENT_HANDLER, EMailAttachmentHandler))
+#define E_MAIL_ATTACHMENT_HANDLER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_ATTACHMENT_HANDLER, EMailAttachmentHandlerClass))
+#define E_IS_MAIL_ATTACHMENT_HANDLER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_ATTACHMENT_HANDLER))
+#define E_IS_MAIL_ATTACHMENT_HANDLER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_ATTACHMENT_HANDLER))
+#define E_MAIL_ATTACHMENT_HANDLER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_ATTACHMENT_HANDLER, EMailAttachmentHandlerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailAttachmentHandler EMailAttachmentHandler;
+typedef struct _EMailAttachmentHandlerClass EMailAttachmentHandlerClass;
+typedef struct _EMailAttachmentHandlerPrivate EMailAttachmentHandlerPrivate;
+
+struct _EMailAttachmentHandler {
+ EAttachmentHandler parent;
+ EMailAttachmentHandlerPrivate *priv;
+};
+
+struct _EMailAttachmentHandlerClass {
+ EAttachmentHandlerClass parent_class;
+};
+
+GType e_mail_attachment_handler_get_type (void);
+void e_mail_attachment_handler_register_type
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_MAIL_ATTACHMENT_HANDLER_H */
diff --git a/modules/mail/e-mail-config-hook.c b/modules/mail/e-mail-config-hook.c
new file mode 100644
index 0000000000..4b37a6b171
--- /dev/null
+++ b/modules/mail/e-mail-config-hook.c
@@ -0,0 +1,69 @@
+/*
+ * e-mail-config-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-mail-config-hook.h"
+
+#include "e-util/e-config.h"
+#include "mail/em-config.h"
+
+static const EConfigHookTargetMask no_masks[] = {
+ { NULL }
+};
+
+static const EConfigHookTargetMap targets[] = {
+ { "folder", EM_CONFIG_TARGET_FOLDER, no_masks },
+ { "prefs", EM_CONFIG_TARGET_PREFS, no_masks },
+ { "account", EM_CONFIG_TARGET_ACCOUNT, no_masks },
+ { NULL }
+};
+
+static void
+mail_config_hook_class_init (EPluginHookClass *class)
+{
+ gint ii;
+
+ class->id = "org.gnome.evolution.mail.config:1.0";
+
+ for (ii = 0; targets[ii].type != NULL; ii++)
+ e_config_hook_class_add_target_map (
+ (EConfigHookClass *) class, &targets[ii]);
+}
+
+void
+e_mail_config_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EConfigHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_config_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EConfigHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ g_type_module_register_type (
+ type_module, e_config_hook_get_type (),
+ "EMailConfigHook", &type_info, 0);
+}
diff --git a/modules/mail/e-mail-config-hook.h b/modules/mail/e-mail-config-hook.h
new file mode 100644
index 0000000000..e54ad81815
--- /dev/null
+++ b/modules/mail/e-mail-config-hook.h
@@ -0,0 +1,33 @@
+/*
+ * e-mail-config-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_MAIL_CONFIG_HOOK_H
+#define E_MAIL_CONFIG_HOOK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_mail_config_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_MAIL_CONFIG_HOOK_H */
diff --git a/modules/mail/e-mail-event-hook.c b/modules/mail/e-mail-event-hook.c
new file mode 100644
index 0000000000..a6be6d294f
--- /dev/null
+++ b/modules/mail/e-mail-event-hook.c
@@ -0,0 +1,93 @@
+/*
+ * e-mail-event-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-mail-event-hook.h"
+
+#include "e-util/e-event.h"
+#include "mail/em-event.h"
+
+static const EEventHookTargetMask folder_masks[] = {
+ { "newmail", EM_EVENT_FOLDER_NEWMAIL },
+ { NULL }
+};
+
+static const EEventHookTargetMask composer_masks[] = {
+ { "sendoption", EM_EVENT_COMPOSER_SEND_OPTION },
+ { NULL }
+};
+
+static const EEventHookTargetMask message_masks[] = {
+ { "replyall", EM_EVENT_MESSAGE_REPLY_ALL },
+ { "reply", EM_EVENT_MESSAGE_REPLY },
+ { NULL }
+};
+
+static const EEventHookTargetMask send_receive_masks[] = {
+ { "sendreceive", EM_EVENT_SEND_RECEIVE },
+ { NULL }
+};
+
+static const EEventHookTargetMask custom_icon_masks[] = {
+ { "customicon", EM_EVENT_CUSTOM_ICON },
+ { NULL }
+};
+
+static const EEventHookTargetMap targets[] = {
+ { "folder", EM_EVENT_TARGET_FOLDER, folder_masks },
+ { "message", EM_EVENT_TARGET_MESSAGE, message_masks },
+ { "composer", EM_EVENT_TARGET_COMPOSER, composer_masks },
+ { "sendreceive", EM_EVENT_TARGET_SEND_RECEIVE, send_receive_masks },
+ { "customicon", EM_EVENT_TARGET_CUSTOM_ICON, custom_icon_masks },
+ { NULL }
+};
+
+static void
+mail_event_hook_class_init (EPluginHookClass *class)
+{
+ gint ii;
+
+ class->id = "org.gnome.evolution.mail.events:1.0";
+
+ for (ii = 0; targets[ii].type != NULL; ii++)
+ e_event_hook_class_add_target_map (
+ (EEventHookClass *) class, &targets[ii]);
+}
+
+void
+e_mail_event_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EEventHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_event_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EEventHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ g_type_module_register_type (
+ type_module, e_event_hook_get_type (),
+ "EMailEventHook", &type_info, 0);
+}
diff --git a/modules/mail/e-mail-event-hook.h b/modules/mail/e-mail-event-hook.h
new file mode 100644
index 0000000000..9cad43a0c2
--- /dev/null
+++ b/modules/mail/e-mail-event-hook.h
@@ -0,0 +1,33 @@
+/*
+ * e-mail-event-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_MAIL_EVENT_HOOK_H
+#define E_MAIL_EVENT_HOOK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_mail_event_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_MAIL_EVENT_HOOK_H */
diff --git a/modules/mail/e-mail-junk-hook.c b/modules/mail/e-mail-junk-hook.c
new file mode 100644
index 0000000000..2687223af9
--- /dev/null
+++ b/modules/mail/e-mail-junk-hook.c
@@ -0,0 +1,323 @@
+/*
+ * e-mail-junk-hook.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-mail-junk-hook.h"
+
+#include <glib/gi18n.h>
+#include <camel/camel-junk-plugin.h>
+
+#include "e-util/e-error.h"
+#include "mail/em-junk.h"
+#include "mail/em-utils.h"
+#include "mail/mail-session.h"
+
+#define E_MAIL_JUNK_HOOK_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookPrivate))
+
+struct _EMailJunkHookPrivate {
+ EMJunkInterface interface;
+};
+
+struct ErrorData {
+ const gchar *error_message;
+ GError *error;
+};
+
+static gpointer parent_class;
+static GType mail_junk_hook_type;
+
+static gboolean
+mail_junk_hook_idle_cb (struct ErrorData *data)
+{
+ GtkWidget *widget;
+
+ widget = e_error_new (
+ NULL, data->error_message, data->error->message, NULL);
+ em_utils_show_error_silent (widget);
+
+ g_error_free (data->error);
+ g_slice_free (struct ErrorData, data);
+
+ return FALSE;
+}
+
+static void
+mail_junk_hook_error (const gchar *error_message,
+ GError *error)
+{
+ struct ErrorData *data;
+
+ g_return_if_fail (error != NULL);
+
+ data = g_slice_new (struct ErrorData);
+ data->error_message = error_message;
+ data->error = error;
+
+ g_idle_add ((GSourceFunc) mail_junk_hook_idle_cb, data);
+}
+
+static const gchar *
+mail_junk_hook_get_name (CamelJunkPlugin *junk_plugin)
+{
+ EMJunkInterface *interface;
+
+ interface = (EMJunkInterface *) junk_plugin;
+
+ if (!interface->hook->plugin->enabled)
+ return _("None");
+
+ return interface->hook->plugin->name;
+}
+
+static void
+mail_junk_hook_plugin_init (CamelJunkPlugin *junk_plugin)
+{
+ EMJunkInterface *interface;
+ EPluginClass *class;
+
+ interface = (EMJunkInterface *) junk_plugin;
+
+ class = E_PLUGIN_GET_CLASS (interface->hook->plugin);
+ g_return_if_fail (class->enable != NULL);
+
+ class->enable (interface->hook->plugin, 1);
+}
+
+static gboolean
+mail_junk_hook_check_junk (CamelJunkPlugin *junk_plugin,
+ CamelMimeMessage *mime_message)
+{
+ EMJunkTarget target = { mime_message, NULL };
+ EMJunkInterface *interface;
+ gpointer result;
+
+ interface = (EMJunkInterface *) junk_plugin;
+
+ if (!interface->hook->plugin->enabled)
+ return FALSE;
+
+ result = e_plugin_invoke (
+ interface->hook->plugin,
+ interface->check_junk, &target);
+
+ if (target.error != NULL)
+ mail_junk_hook_error ("mail:junk-check-error", target.error);
+
+ return (result != NULL);
+}
+
+static void
+mail_junk_hook_report_junk (CamelJunkPlugin *junk_plugin,
+ CamelMimeMessage *mime_message)
+{
+ EMJunkTarget target = { mime_message, NULL };
+ EMJunkInterface *interface;
+
+ interface = (EMJunkInterface *) junk_plugin;
+
+ if (!interface->hook->plugin->enabled)
+ return;
+
+ e_plugin_invoke (
+ interface->hook->plugin,
+ interface->report_junk, &target);
+
+ if (target.error != NULL)
+ mail_junk_hook_error ("mail:junk-report-error", target.error);
+}
+
+static void
+mail_junk_hook_report_notjunk (CamelJunkPlugin *junk_plugin,
+ CamelMimeMessage *mime_message)
+{
+ EMJunkTarget target = { mime_message, NULL };
+ EMJunkInterface *interface;
+
+ interface = (EMJunkInterface *) junk_plugin;
+
+ if (!interface->hook->plugin->enabled)
+ return;
+
+ e_plugin_invoke (
+ interface->hook->plugin,
+ interface->report_notjunk, &target);
+
+ if (target.error != NULL)
+ mail_junk_hook_error (
+ "mail:junk-not-report-error", target.error);
+}
+
+static void
+mail_junk_hook_commit_reports (CamelJunkPlugin *junk_plugin)
+{
+ EMJunkInterface *interface;
+
+ interface = (EMJunkInterface *) junk_plugin;
+
+ if (!interface->hook->plugin->enabled)
+ return;
+
+ e_plugin_invoke (
+ interface->hook->plugin,
+ interface->commit_reports, NULL);
+}
+
+static void
+mail_junk_hook_finalize (GObject *object)
+{
+ EMailJunkHookPrivate *priv;
+
+ priv = E_MAIL_JUNK_HOOK_GET_PRIVATE (object);
+
+ g_free (priv->interface.check_junk);
+ g_free (priv->interface.report_junk);
+ g_free (priv->interface.report_notjunk);
+ g_free (priv->interface.commit_reports);
+ g_free (priv->interface.validate_binary);
+ g_free (priv->interface.plugin_name);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint
+mail_junk_hook_construct (EPluginHook *hook,
+ EPlugin *plugin,
+ xmlNodePtr node)
+{
+ EMailJunkHookPrivate *priv;
+ gchar *property;
+
+ priv = E_MAIL_JUNK_HOOK_GET_PRIVATE (hook);
+
+ /* Chain up to parent's construct() method. */
+ if (E_PLUGIN_HOOK_CLASS (parent_class)->construct (hook, plugin, node) == -1)
+ return -1;
+
+ if (!plugin->enabled)
+ return -1;
+
+ node = xmlFirstElementChild (node);
+
+ if (node == NULL)
+ return -1;
+
+ if (g_strcmp0 ((gchar *) node->name, "interface") != 0)
+ return -1;
+
+ property = e_plugin_xml_prop (node, "check_junk");
+ priv->interface.check_junk = property;
+
+ property = e_plugin_xml_prop (node, "report_junk");
+ priv->interface.report_junk = property;
+
+ property = e_plugin_xml_prop (node, "report_non_junk");
+ priv->interface.report_notjunk = property;
+
+ property = e_plugin_xml_prop (node, "commit_reports");
+ priv->interface.commit_reports = property;
+
+ property = e_plugin_xml_prop (node, "validate_binary");
+ priv->interface.validate_binary = property;
+
+ property = e_plugin_xml_prop (node, "name");
+ priv->interface.plugin_name = property;
+
+ if (priv->interface.check_junk == NULL)
+ return -1;
+
+ if (priv->interface.report_junk == NULL)
+ return -1;
+
+ if (priv->interface.report_notjunk == NULL)
+ return -1;
+
+ if (priv->interface.commit_reports == NULL)
+ return -1;
+
+ mail_session_add_junk_plugin (
+ priv->interface.plugin_name, &priv->interface.camel);
+
+ return 0;
+}
+
+static void
+mail_junk_hook_class_init (EMailJunkHookClass *class)
+{
+ GObjectClass *object_class;
+ EPluginHookClass *plugin_hook_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMailJunkHookPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mail_junk_hook_finalize;
+
+ plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
+ plugin_hook_class->construct = mail_junk_hook_construct;
+ plugin_hook_class->id = "org.gnome.evolution.mail.junk:1.0";
+}
+
+static void
+mail_junk_hook_init (EMailJunkHook *mail_junk_hook)
+{
+ EMJunkInterface *interface;
+
+ mail_junk_hook->priv = E_MAIL_JUNK_HOOK_GET_PRIVATE (mail_junk_hook);
+
+ interface = &mail_junk_hook->priv->interface;
+ interface->camel.get_name = mail_junk_hook_get_name;
+ interface->camel.api_version = 1;
+ interface->camel.check_junk = mail_junk_hook_check_junk;
+ interface->camel.report_junk = mail_junk_hook_report_junk;
+ interface->camel.report_notjunk = mail_junk_hook_report_notjunk;
+ interface->camel.commit_reports = mail_junk_hook_commit_reports;
+ interface->camel.init = mail_junk_hook_plugin_init;
+ interface->hook = E_PLUGIN_HOOK (mail_junk_hook);
+}
+
+GType
+e_mail_junk_hook_get_type (void)
+{
+ return mail_junk_hook_type;
+}
+
+void
+e_mail_junk_hook_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EMailJunkHookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_junk_hook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMailJunkHook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) mail_junk_hook_init,
+ NULL /* value_table */
+ };
+
+ mail_junk_hook_type = g_type_module_register_type (
+ type_module, E_TYPE_PLUGIN_HOOK,
+ "EMailJunkHook", &type_info, 0);
+}
diff --git a/modules/mail/e-mail-junk-hook.h b/modules/mail/e-mail-junk-hook.h
new file mode 100644
index 0000000000..f5882e66b3
--- /dev/null
+++ b/modules/mail/e-mail-junk-hook.h
@@ -0,0 +1,66 @@
+/*
+ * e-mail-junk-hook.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_MAIL_JUNK_HOOK_H
+#define E_MAIL_JUNK_HOOK_H
+
+#include <e-util/e-plugin.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_JUNK_HOOK \
+ (e_mail_junk_hook_get_type ())
+#define E_MAIL_JUNK_HOOK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHook))
+#define E_MAIL_JUNK_HOOK_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookClass))
+#define E_IS_MAIL_JUNK_HOOK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_JUNK_HOOK))
+#define E_IS_MAIL_JUNK_HOOK_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_JUNK_HOOK))
+#define E_MAIL_JUNK_HOOK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailJunkHook EMailJunkHook;
+typedef struct _EMailJunkHookClass EMailJunkHookClass;
+typedef struct _EMailJunkHookPrivate EMailJunkHookPrivate;
+
+struct _EMailJunkHook {
+ EPluginHook parent;
+ EMailJunkHookPrivate *priv;
+};
+
+struct _EMailJunkHookClass {
+ EPluginHookClass parent_class;
+};
+
+GType e_mail_junk_hook_get_type (void);
+void e_mail_junk_hook_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_MAIL_JUNK_HOOK_H */
diff --git a/modules/mail/e-mail-shell-backend.c b/modules/mail/e-mail-shell-backend.c
new file mode 100644
index 0000000000..fd1e7263c4
--- /dev/null
+++ b/modules/mail/e-mail-shell-backend.c
@@ -0,0 +1,987 @@
+/*
+ * e-mail-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-mail-shell-backend.h"
+
+#include <glib/gi18n.h>
+#include <camel/camel-disco-store.h>
+#include <camel/camel-offline-store.h>
+#include <camel/camel-session.h>
+#include <camel/camel-url.h>
+
+#include "e-util/e-account-utils.h"
+#include "e-util/e-binding.h"
+#include "e-util/e-error.h"
+#include "e-util/e-import.h"
+#include "e-util/e-util.h"
+#include "shell/e-shell.h"
+#include "shell/e-shell-window.h"
+#include "composer/e-msg-composer.h"
+#include "widgets/misc/e-preferences-window.h"
+
+#include "e-mail-shell-migrate.h"
+#include "e-mail-shell-settings.h"
+#include "e-mail-shell-sidebar.h"
+#include "e-mail-shell-view.h"
+
+#include "e-mail-browser.h"
+#include "e-mail-local.h"
+#include "e-mail-reader.h"
+#include "e-mail-store.h"
+#include "em-account-editor.h"
+#include "em-account-prefs.h"
+#include "em-composer-prefs.h"
+#include "em-composer-utils.h"
+#include "em-folder-utils.h"
+#include "em-format-hook.h"
+#include "em-format-html-display.h"
+#include "em-mailer-prefs.h"
+#include "em-network-prefs.h"
+#include "em-utils.h"
+#include "mail-config.h"
+#include "mail-ops.h"
+#include "mail-send-recv.h"
+#include "mail-session.h"
+#include "mail-vfolder.h"
+#include "importers/mail-importer.h"
+
+#define E_MAIL_SHELL_BACKEND_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SHELL_BACKEND, EMailShellBackendPrivate))
+
+#define BACKEND_NAME "mail"
+#define QUIT_POLL_INTERVAL 1 /* seconds */
+
+struct _EMailShellBackendPrivate {
+ gint mail_sync_in_progress;
+ guint mail_sync_timeout_source_id;
+};
+
+static gpointer parent_class;
+static GType mail_shell_backend_type;
+
+extern gint camel_application_is_exiting;
+
+static void
+mail_shell_backend_init_importers (void)
+{
+ EImportClass *import_class;
+ EImportImporter *importer;
+
+ import_class = g_type_class_ref (e_import_get_type ());
+
+ importer = mbox_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = elm_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+
+ importer = pine_importer_peek ();
+ e_import_class_add_importer (import_class, importer, NULL, NULL);
+}
+
+static void
+mail_shell_backend_mail_icon_cb (EShellWindow *shell_window,
+ const gchar *icon_name)
+{
+ GtkAction *action;
+
+ action = e_shell_window_get_shell_view_action (
+ shell_window, BACKEND_NAME);
+ g_object_set (action, "icon-name", icon_name, NULL);
+}
+
+static void
+action_mail_folder_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ EMFolderTree *folder_tree = NULL;
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellSidebar *shell_sidebar;
+ EShellView *shell_view;
+ const gchar *view_name;
+
+ /* Take care not to unnecessarily load the mail shell view. */
+ view_name = e_shell_window_get_active_view (shell_window);
+ if (g_strcmp0 (view_name, BACKEND_NAME) != 0)
+ goto exit;
+
+ shell_view = e_shell_window_get_shell_view (shell_window, view_name);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+
+ mail_shell_sidebar = E_MAIL_SHELL_SIDEBAR (shell_sidebar);
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+exit:
+ em_folder_utils_create_folder (
+ NULL, folder_tree, GTK_WINDOW (shell_window));
+}
+
+static void
+action_mail_message_new_cb (GtkAction *action,
+ EShellWindow *shell_window)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellSidebar *shell_sidebar;
+ EShellView *shell_view;
+ EMFolderTree *folder_tree;
+ const gchar *view_name;
+ gchar *uri = NULL;
+
+ if (!em_utils_check_user_can_send_mail ())
+ return;
+
+ /* Take care not to unnecessarily load the mail shell view. */
+ view_name = e_shell_window_get_active_view (shell_window);
+ if (g_strcmp0 (view_name, BACKEND_NAME) != 0)
+ goto exit;
+
+ shell_view = e_shell_window_get_shell_view (shell_window, view_name);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+
+ mail_shell_sidebar = E_MAIL_SHELL_SIDEBAR (shell_sidebar);
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ uri = em_folder_tree_get_selected_uri (folder_tree);
+
+exit:
+ em_utils_compose_new_message (uri);
+
+ g_free (uri);
+}
+
+static GtkActionEntry item_entries[] = {
+
+ { "mail-message-new",
+ "mail-message-new",
+ NC_("New", "_Mail Message"),
+ "<Shift><Control>m",
+ N_("Compose a new mail message"),
+ G_CALLBACK (action_mail_message_new_cb) }
+};
+
+static GtkActionEntry source_entries[] = {
+
+ { "mail-folder-new",
+ "folder-new",
+ NC_("New", "Mail _Folder"),
+ NULL,
+ N_("Create a new mail folder"),
+ G_CALLBACK (action_mail_folder_new_cb) }
+};
+
+static void
+mail_shell_backend_init_preferences (EShell *shell)
+{
+ EAccountList *account_list;
+ GtkWidget *preferences_window;
+
+ account_list = e_get_account_list ();
+ preferences_window = e_shell_get_preferences_window (shell);
+
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "mail-accounts",
+ "preferences-mail-accounts",
+ _("Mail Accounts"),
+ em_account_prefs_new (account_list),
+ 100);
+
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "mail",
+ "preferences-mail",
+ _("Mail Preferences"),
+ em_mailer_prefs_new (shell),
+ 300);
+
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "composer",
+ "preferences-composer",
+ _("Composer Preferences"),
+ em_composer_prefs_new (shell),
+ 400);
+
+ e_preferences_window_add_page (
+ E_PREFERENCES_WINDOW (preferences_window),
+ "system-network-proxy",
+ "preferences-system-network-proxy",
+ _("Network Preferences"),
+ em_network_prefs_new (),
+ 500);
+}
+
+static void
+mail_shell_backend_sync_store_done_cb (CamelStore *store,
+ gpointer user_data)
+{
+ EMailShellBackend *mail_shell_backend = user_data;
+
+ mail_shell_backend->priv->mail_sync_in_progress--;
+}
+
+static void
+mail_shell_backend_sync_store_cb (CamelStore *store,
+ EMailShellBackend *mail_shell_backend)
+{
+ if (!camel_application_is_exiting) {
+ mail_shell_backend->priv->mail_sync_in_progress++;
+ mail_sync_store (
+ store, FALSE,
+ mail_shell_backend_sync_store_done_cb,
+ mail_shell_backend);
+ }
+}
+
+static gboolean
+mail_shell_backend_mail_sync (EMailShellBackend *mail_shell_backend)
+{
+ if (camel_application_is_exiting)
+ return FALSE;
+
+ if (mail_shell_backend->priv->mail_sync_in_progress)
+ goto exit;
+
+ if (session == NULL || !camel_session_is_online (session))
+ goto exit;
+
+ e_mail_store_foreach (
+ (GHFunc) mail_shell_backend_sync_store_cb,
+ mail_shell_backend);
+
+exit:
+ return !camel_application_is_exiting;
+}
+
+static void
+mail_shell_backend_notify_online_cb (EShell *shell,
+ GParamSpec *pspec,
+ EShellBackend *shell_backend)
+{
+ gboolean online;
+
+ online = e_shell_get_online (shell);
+ camel_session_set_online (session, online);
+}
+
+static void
+mail_shell_backend_handle_email_uri_cb (gchar *folder_uri,
+ CamelFolder *folder,
+ gpointer user_data)
+{
+ EShellBackend *shell_backend = user_data;
+ CamelURL *url = user_data;
+ const gchar *forward;
+ const gchar *reply;
+ const gchar *uid;
+
+ if (folder == NULL) {
+ g_warning ("Could not open folder '%s'", folder_uri);
+ goto exit;
+ }
+
+ forward = camel_url_get_param (url, "forward");
+ reply = camel_url_get_param (url, "reply");
+ uid = camel_url_get_param (url, "uid");
+
+ if (reply != NULL) {
+ gint mode;
+
+ if (g_strcmp0 (reply, "all") == 0)
+ mode = REPLY_MODE_ALL;
+ else if (g_strcmp0 (reply, "list") == 0)
+ mode = REPLY_MODE_LIST;
+ else
+ mode = REPLY_MODE_SENDER;
+
+ em_utils_reply_to_message (folder, uid, NULL, mode, NULL);
+
+ } else if (forward != NULL) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (uids, g_strdup (uid));
+
+ if (g_strcmp0 (forward, "attached") == 0)
+ em_utils_forward_attached (folder, uids, folder_uri);
+ else if (g_strcmp0 (forward, "inline") == 0)
+ em_utils_forward_inline (folder, uids, folder_uri);
+ else if (g_strcmp0 (forward, "quoted") == 0)
+ em_utils_forward_quoted (folder, uids, folder_uri);
+ else
+ em_utils_forward_messages (folder, uids, folder_uri);
+
+ } else {
+ GtkWidget *browser;
+
+ /* FIXME Should pass in the shell module. */
+ browser = e_mail_browser_new (shell_backend);
+ e_mail_reader_set_folder (
+ E_MAIL_READER (browser), folder, folder_uri);
+ e_mail_reader_set_message (E_MAIL_READER (browser), uid);
+ gtk_widget_show (browser);
+ }
+
+exit:
+ camel_url_free (url);
+}
+
+static gboolean
+mail_shell_backend_handle_uri_cb (EShell *shell,
+ const gchar *uri,
+ EMailShellBackend *mail_shell_backend)
+{
+ gboolean handled = TRUE;
+
+ if (g_str_has_prefix (uri, "mailto:")) {
+ if (em_utils_check_user_can_send_mail ())
+ em_utils_compose_new_message_with_mailto (uri, NULL);
+
+ } else if (g_str_has_prefix (uri, "email:")) {
+ CamelURL *url;
+
+ url = camel_url_new (uri, NULL);
+ if (camel_url_get_param (url, "uid") != NULL) {
+ gchar *curi = em_uri_to_camel (uri);
+
+ mail_get_folder (
+ curi, 0,
+ mail_shell_backend_handle_email_uri_cb,
+ mail_shell_backend, mail_msg_unordered_push);
+ g_free (curi);
+
+ } else {
+ g_warning ("Email URI's must include a uid parameter");
+ camel_url_free (url);
+ }
+ } else
+ handled = FALSE;
+
+ return handled;
+}
+
+/* Helper for mail_shell_backend_prepare_for_[off|on]line_cb() */
+static void
+mail_shell_store_line_transition_done_cb (CamelStore *store,
+ gpointer user_data)
+{
+ EActivity *activity = user_data;
+
+ g_object_unref (activity);
+}
+
+/* Helper for mail_shell_backend_prepare_for_offline_cb() */
+static void
+mail_shell_store_prepare_for_offline_cb (CamelService *service,
+ gpointer unused,
+ EActivity *activity)
+{
+ if (CAMEL_IS_DISCO_STORE (service) || CAMEL_IS_OFFLINE_STORE (service))
+ mail_store_set_offline (
+ CAMEL_STORE (service), TRUE,
+ mail_shell_store_line_transition_done_cb,
+ g_object_ref (activity));
+}
+
+static void
+mail_shell_backend_prepare_for_offline_cb (EShell *shell,
+ EActivity *activity,
+ EMailShellBackend *mail_shell_backend)
+{
+ GList *watched_windows;
+ GtkWidget *parent = NULL;
+ gboolean synchronize = FALSE;
+
+ watched_windows = e_shell_get_watched_windows (shell);
+ if (watched_windows != NULL)
+ parent = GTK_WIDGET (watched_windows->data);
+
+ if (e_shell_get_network_available (shell))
+ synchronize = em_utils_prompt_user (
+ GTK_WINDOW (parent),
+ "/apps/evolution/mail/prompts/quick_offline",
+ "mail:ask-quick-offline", NULL);
+
+ if (!synchronize) {
+ mail_cancel_all ();
+ camel_session_set_network_state (session, FALSE);
+ }
+
+ e_mail_store_foreach (
+ (GHFunc) mail_shell_store_prepare_for_offline_cb, activity);
+}
+
+/* Helper for mail_shell_backend_prepare_for_online_cb() */
+static void
+mail_shell_store_prepare_for_online_cb (CamelService *service,
+ gpointer unused,
+ EActivity *activity)
+{
+ if (CAMEL_IS_DISCO_STORE (service) || CAMEL_IS_OFFLINE_STORE (service))
+ mail_store_set_offline (
+ CAMEL_STORE (service), FALSE,
+ mail_shell_store_line_transition_done_cb,
+ g_object_ref (activity));
+}
+
+static void
+mail_shell_backend_prepare_for_online_cb (EShell *shell,
+ EActivity *activity,
+ EMailShellBackend *mail_shell_backend)
+{
+ camel_session_set_online (session, TRUE);
+
+ e_mail_store_foreach (
+ (GHFunc) mail_shell_store_prepare_for_online_cb, activity);
+}
+
+/* Helper for mail_shell_backend_prepare_for_quit_cb() */
+static void
+mail_shell_backend_empty_junk (CamelStore *store,
+ gpointer opaque_store_info,
+ EMailShellBackend *mail_shell_backend)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+ guint32 flags;
+ guint32 mask;
+ guint ii;
+
+ folder = camel_store_get_junk (store, NULL);
+ if (folder == NULL)
+ return;
+
+ uids = camel_folder_get_uids (folder);
+ flags = mask = CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN;
+
+ camel_folder_freeze (folder);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ const gchar *uid = uids->pdata[ii];
+ camel_folder_set_message_flags (folder, uid, flags, mask);
+ }
+
+ camel_folder_thaw (folder);
+
+ camel_folder_free_uids (folder, uids);
+}
+
+/* Helper for mail_shell_backend_final_sync() */
+static void
+mail_shell_backend_final_sync_done_cb (CamelStore *store,
+ gpointer user_data)
+{
+ g_object_unref (E_ACTIVITY (user_data));
+}
+
+/* Helper for mail_shell_backend_prepare_for_quit_cb() */
+static void
+mail_shell_backend_final_sync (CamelStore *store,
+ gpointer opaque_store_info,
+ gpointer user_data)
+{
+ struct {
+ EActivity *activity;
+ gboolean empty_trash;
+ } *sync_data = user_data;
+
+ /* Reffing the activity delays quitting; the reference count
+ * acts like a counting semaphore. */
+ mail_sync_store (
+ store, sync_data->empty_trash,
+ mail_shell_backend_final_sync_done_cb,
+ g_object_ref (sync_data->activity));
+}
+
+/* Helper for mail_shell_backend_prepare_for_quit_cb() */
+static gboolean
+mail_shell_backend_poll_to_quit (EActivity *activity)
+{
+ return mail_msg_active ((guint) -1);
+}
+
+/* Helper for mail_shell_backend_prepare_for_quit_cb() */
+static void
+mail_shell_backend_ready_to_quit (EActivity *activity)
+{
+ mail_session_shutdown ();
+ g_object_unref (activity);
+}
+
+static void
+mail_shell_backend_prepare_for_quit_cb (EShell *shell,
+ EActivity *activity,
+ EMailShellBackend *mail_shell_backend)
+{
+ EShellSettings *shell_settings;
+ EAccountList *account_list;
+ GConfClient *client;
+ const gchar *key;
+ gboolean empty_junk;
+ gboolean empty_trash;
+ gint empty_date;
+ gint empty_days;
+ gint now;
+ GError *error = NULL;
+
+ struct {
+ EActivity *activity;
+ gboolean empty_trash;
+ } sync_data;
+
+ client = e_shell_get_gconf_client (shell);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ camel_application_is_exiting = TRUE;
+ now = time (NULL) / 60 / 60 / 24;
+
+ account_list = e_get_account_list ();
+ e_account_list_prune_proxies (account_list);
+
+ mail_vfolder_shutdown ();
+
+ empty_junk = e_shell_settings_get_boolean (
+ shell_settings, "mail-empty-junk-on-exit");
+
+ empty_trash = e_shell_settings_get_boolean (
+ shell_settings, "mail-empty-trash-on-exit");
+
+ /* XXX No EShellSettings properties for these keys. */
+
+ empty_date = empty_days = 0;
+
+ if (empty_junk) {
+ key = "/apps/evolution/mail/junk/empty_on_exit_days";
+ empty_days = gconf_client_get_int (client, key, &error);
+ if (error == NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ empty_trash = FALSE;
+ }
+ }
+
+ if (empty_junk) {
+ key = "/apps/evolution/mail/junk/empty_date";
+ empty_date = gconf_client_get_int (client, key, &error);
+ if (error == NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ empty_trash = FALSE;
+ }
+ }
+
+ empty_junk &= (empty_days = 0) || (empty_date + empty_days <= now);
+
+ if (empty_junk) {
+ e_mail_store_foreach (
+ (GHFunc) mail_shell_backend_empty_junk,
+ mail_shell_backend);
+
+ key = "/apps/evolution/mail/junk/empty_date";
+ gconf_client_set_int (client, key, now, NULL);
+ }
+
+ empty_date = empty_days = 0;
+
+ if (empty_trash) {
+ key = "/apps/evolution/mail/trash/empty_on_exit_days";
+ empty_days = gconf_client_get_int (client, key, &error);
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ empty_trash = FALSE;
+ }
+ }
+
+ if (empty_trash) {
+ key = "/apps/evolution/mail/trash/empty_date";
+ empty_date = gconf_client_get_int (client, key, &error);
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ empty_trash = FALSE;
+ }
+ }
+
+ empty_trash &= (empty_days == 0) || (empty_date + empty_days <= now);
+
+ sync_data.activity = activity;
+ sync_data.empty_trash = empty_trash;
+
+ e_mail_store_foreach (
+ (GHFunc) mail_shell_backend_final_sync, &sync_data);
+
+ if (empty_trash) {
+ key = "/apps/evolution/mail/trash/empty_date";
+ gconf_client_set_int (client, key, now, NULL);
+ }
+
+ /* Cancel all activities. */
+ mail_cancel_all ();
+
+ /* Now we poll until all activities are actually cancelled.
+ * Reffing the activity delays quitting; the reference count
+ * acts like a counting semaphore. */
+ if (mail_msg_active ((guint) -1))
+ g_timeout_add_seconds_full (
+ G_PRIORITY_DEFAULT, QUIT_POLL_INTERVAL,
+ (GSourceFunc) mail_shell_backend_poll_to_quit,
+ g_object_ref (activity),
+ (GDestroyNotify) mail_shell_backend_ready_to_quit);
+ else
+ mail_shell_backend_ready_to_quit (g_object_ref (activity));
+}
+
+static void
+mail_shell_backend_quit_requested_cb (EShell *shell,
+ EShellBackend *shell_backend)
+{
+ CamelFolder *folder;
+ GList *watched_windows;
+ GtkWindow *parent = NULL;
+ guint32 unsent;
+ gint response;
+
+ /* We can quit immediately if offline. */
+ if (!camel_session_is_online (session))
+ return;
+
+ /* Check Outbox for any unsent messages. */
+
+ folder = e_mail_local_get_folder (E_MAIL_FOLDER_OUTBOX);
+ if (folder == NULL)
+ return;
+
+ if (camel_object_get (
+ folder, NULL, CAMEL_FOLDER_VISIBLE, &unsent, 0) != 0)
+ return;
+
+ if (unsent == 0)
+ return;
+
+ /* Try to find a parent window for the dialog.
+ * First list item is what's currently focused. */
+ watched_windows = e_shell_get_watched_windows (shell);
+ if (watched_windows != NULL)
+ parent = GTK_WINDOW (watched_windows->data);
+ response = e_error_run (parent, "mail:exit-unsaved", NULL);
+
+ if (response == GTK_RESPONSE_YES)
+ return;
+
+ e_shell_cancel_quit (shell);
+}
+
+static void
+mail_shell_backend_send_receive_cb (EShell *shell,
+ GtkWindow *parent,
+ EShellBackend *shell_backend)
+{
+ em_utils_clear_get_password_canceled_accounts_flag ();
+ mail_send_receive (parent);
+}
+
+static void
+mail_shell_backend_window_weak_notify_cb (EShell *shell,
+ GObject *where_the_object_was)
+{
+ g_signal_handlers_disconnect_by_func (
+ shell, mail_shell_backend_mail_icon_cb,
+ where_the_object_was);
+}
+
+static void
+mail_shell_backend_window_created_cb (EShell *shell,
+ GtkWindow *window,
+ EShellBackend *shell_backend)
+{
+ EShellSettings *shell_settings;
+ static gboolean first_time = TRUE;
+ const gchar *backend_name;
+
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ /* This applies to both the composer and signature editor. */
+ if (GTKHTML_IS_EDITOR (window)) {
+ GList *spell_languages;
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "composer-inline-spelling",
+ G_OBJECT (window), "inline-spelling");
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "composer-magic-links",
+ G_OBJECT (window), "magic-links");
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "composer-magic-smileys",
+ G_OBJECT (window), "magic-smileys");
+
+ spell_languages = e_load_spell_languages ();
+ gtkhtml_editor_set_spell_languages (
+ GTKHTML_EDITOR (window), spell_languages);
+ g_list_free (spell_languages);
+ }
+
+ if (E_IS_MSG_COMPOSER (window)) {
+ /* Integrate the new composer into the mail module. */
+ em_configure_new_composer (E_MSG_COMPOSER (window));
+ return;
+ }
+
+ 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));
+
+ g_signal_connect_swapped (
+ shell, "event::mail-icon",
+ G_CALLBACK (mail_shell_backend_mail_icon_cb), window);
+
+ g_object_weak_ref (
+ G_OBJECT (window), (GWeakNotify)
+ mail_shell_backend_window_weak_notify_cb, shell);
+
+ if (first_time) {
+ g_signal_connect (
+ window, "map-event",
+ G_CALLBACK (e_msg_composer_check_autosave), NULL);
+ first_time = FALSE;
+ }
+}
+
+static void
+mail_shell_backend_constructed (GObject *object)
+{
+ EMailShellBackendPrivate *priv;
+ EShell *shell;
+ EShellBackend *shell_backend;
+ const gchar *data_dir;
+
+ priv = E_MAIL_SHELL_BACKEND_GET_PRIVATE (object);
+
+ shell_backend = E_SHELL_BACKEND (object);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ /* This also initializes Camel, so it needs to happen early. */
+ mail_session_init (shell_backend);
+
+ /* Register format types for EMFormatHook. */
+ em_format_hook_register_type (em_format_get_type ());
+ em_format_hook_register_type (em_format_html_get_type ());
+ em_format_hook_register_type (em_format_html_display_get_type ());
+
+ /* Register plugin hook types. */
+ em_format_hook_get_type ();
+
+ mail_shell_backend_init_importers ();
+
+ g_signal_connect (
+ shell, "notify::online",
+ G_CALLBACK (mail_shell_backend_notify_online_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "handle-uri",
+ G_CALLBACK (mail_shell_backend_handle_uri_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "prepare-for-offline",
+ G_CALLBACK (mail_shell_backend_prepare_for_offline_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "prepare-for-online",
+ G_CALLBACK (mail_shell_backend_prepare_for_online_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "prepare-for-quit",
+ G_CALLBACK (mail_shell_backend_prepare_for_quit_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "quit-requested",
+ G_CALLBACK (mail_shell_backend_quit_requested_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "send-receive",
+ G_CALLBACK (mail_shell_backend_send_receive_cb),
+ shell_backend);
+
+ g_signal_connect (
+ shell, "window-created",
+ G_CALLBACK (mail_shell_backend_window_created_cb),
+ shell_backend);
+
+ mail_config_init ();
+ mail_msg_init ();
+
+ data_dir = e_shell_backend_get_data_dir (shell_backend);
+ e_mail_store_init (data_dir);
+
+ /* Initialize settings before initializing preferences,
+ * since the preferences bind to the shell settings. */
+ e_mail_shell_settings_init (shell);
+ mail_shell_backend_init_preferences (shell);
+}
+
+static void
+mail_shell_backend_start (EShellBackend *shell_backend)
+{
+ EMailShellBackendPrivate *priv;
+ EShell *shell;
+ EShellSettings *shell_settings;
+ gboolean enable_search_folders;
+
+ priv = E_MAIL_SHELL_BACKEND_GET_PRIVATE (shell_backend);
+
+ shell = e_shell_backend_get_shell (shell_backend);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ /* XXX Do we really still need this flag? */
+ mail_session_set_interactive (TRUE);
+
+ enable_search_folders = e_shell_settings_get_boolean (
+ shell_settings, "mail-enable-search-folders");
+ if (enable_search_folders)
+ vfolder_load_storage ();
+
+ mail_autoreceive_init (shell_backend, session);
+
+ if (g_getenv ("CAMEL_FLUSH_CHANGES") != NULL)
+ priv->mail_sync_timeout_source_id = g_timeout_add_seconds (
+ mail_config_get_sync_timeout (),
+ (GSourceFunc) mail_shell_backend_mail_sync,
+ shell_backend);
+}
+
+static void
+mail_shell_backend_class_init (EMailShellBackendClass *class)
+{
+ GObjectClass *object_class;
+ EShellBackendClass *shell_backend_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMailShellBackendPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = mail_shell_backend_constructed;
+
+ shell_backend_class = E_SHELL_BACKEND_CLASS (class);
+ shell_backend_class->shell_view_type = E_TYPE_MAIL_SHELL_VIEW;
+ shell_backend_class->name = BACKEND_NAME;
+ shell_backend_class->aliases = "";
+ shell_backend_class->schemes = "mailto:email";
+ shell_backend_class->sort_order = 200;
+ shell_backend_class->start = mail_shell_backend_start;
+ shell_backend_class->migrate = e_mail_shell_migrate;
+}
+
+static void
+mail_shell_backend_init (EMailShellBackend *mail_shell_backend)
+{
+ mail_shell_backend->priv =
+ E_MAIL_SHELL_BACKEND_GET_PRIVATE (mail_shell_backend);
+}
+
+GType
+e_mail_shell_backend_get_type (void)
+{
+ return mail_shell_backend_type;
+}
+
+void
+e_mail_shell_backend_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EMailShellBackendClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_shell_backend_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMailShellBackend),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) mail_shell_backend_init,
+ NULL /* value_table */
+ };
+
+ mail_shell_backend_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_BACKEND,
+ "EMailShellBackend", &type_info, 0);
+}
+
+/******************* Code below here belongs elsewhere. *******************/
+
+#include "filter/filter-option.h"
+#include "shell/e-shell-settings.h"
+#include "mail/e-mail-label-list-store.h"
+
+GSList *
+e_mail_labels_get_filter_options (void)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+ EMailLabelListStore *list_store;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GSList *list = NULL;
+ gboolean valid;
+
+ shell = e_shell_get_default ();
+ shell_settings = e_shell_get_shell_settings (shell);
+ list_store = e_shell_settings_get_object (
+ shell_settings, "mail-label-list-store");
+
+ model = GTK_TREE_MODEL (list_store);
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid) {
+ struct _filter_option *option;
+ gchar *name, *tag;
+
+ name = e_mail_label_list_store_get_name (list_store, &iter);
+ tag = e_mail_label_list_store_get_tag (list_store, &iter);
+
+ option = g_new0 (struct _filter_option, 1);
+ option->title = e_str_without_underscores (name);
+ option->value = tag; /* takes ownership */
+
+ g_free (name);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ g_object_unref (list_store);
+
+ return list;
+}
diff --git a/modules/mail/e-mail-shell-backend.h b/modules/mail/e-mail-shell-backend.h
new file mode 100644
index 0000000000..73ef10e75b
--- /dev/null
+++ b/modules/mail/e-mail-shell-backend.h
@@ -0,0 +1,75 @@
+/*
+ * e-mail-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_MAIL_SHELL_BACKEND_H
+#define E_MAIL_SHELL_BACKEND_H
+
+#include <shell/e-shell-backend.h>
+
+#include <camel/camel-folder.h>
+#include <camel/camel-store.h>
+#include <e-util/e-signature-list.h>
+#include <libedataserver/e-account-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SHELL_BACKEND \
+ (e_mail_shell_backend_get_type ())
+#define E_MAIL_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_SHELL_BACKEND, EMailShellBackend))
+#define E_MAIL_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_SHELL_BACKEND, EMailShellBackendClass))
+#define E_IS_MAIL_SHELL_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_SHELL_BACKEND))
+#define E_IS_MAIL_SHELL_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_SHELL_BACKEND))
+#define E_MAIL_SHELL_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_SHELL_BACKEND, EMailShellBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailShellBackend EMailShellBackend;
+typedef struct _EMailShellBackendClass EMailShellBackendClass;
+typedef struct _EMailShellBackendPrivate EMailShellBackendPrivate;
+
+struct _EMailShellBackend {
+ EShellBackend parent;
+ EMailShellBackendPrivate *priv;
+};
+
+struct _EMailShellBackendClass {
+ EShellBackendClass parent_class;
+};
+
+GType e_mail_shell_backend_get_type (void);
+void e_mail_shell_backend_register_type
+ (GTypeModule *type_module);
+
+/* XXX Find a better place for this function. */
+GSList * e_mail_labels_get_filter_options(void);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_BACKEND_H */
diff --git a/modules/mail/e-mail-shell-content.c b/modules/mail/e-mail-shell-content.c
new file mode 100644
index 0000000000..20c9df7a29
--- /dev/null
+++ b/modules/mail/e-mail-shell-content.c
@@ -0,0 +1,1029 @@
+/*
+ * e-mail-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-mail-shell-content.h"
+
+#include <glib/gi18n.h>
+#include <camel/camel-store.h>
+#include <libedataserver/e-data-server-util.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "widgets/menus/gal-view-etable.h"
+#include "widgets/menus/gal-view-instance.h"
+#include "widgets/misc/e-paned.h"
+
+#include "em-search-context.h"
+#include "em-utils.h"
+#include "mail-config.h"
+#include "mail-ops.h"
+
+#include "e-mail-reader.h"
+#include "e-mail-search-bar.h"
+#include "e-mail-shell-backend.h"
+#include "e-mail-shell-view-actions.h"
+
+#define E_MAIL_SHELL_CONTENT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SHELL_CONTENT, EMailShellContentPrivate))
+
+#define STATE_KEY_SCROLLBAR_POSITION "ScrollbarPosition"
+#define STATE_KEY_SELECTED_MESSAGE "SelectedMessage"
+
+struct _EMailShellContentPrivate {
+ GtkWidget *paned;
+ GtkWidget *message_list;
+ GtkWidget *search_bar;
+
+ EMFormatHTMLDisplay *html_display;
+ GalViewInstance *view_instance;
+ GtkOrientation orientation;
+
+ /* ETable scrolling hack */
+ gdouble default_scrollbar_position;
+
+ guint paned_binding_id;
+ guint scroll_timeout_id;
+
+ /* Signal handler IDs */
+ guint message_list_built_id;
+ guint message_list_scrolled_id;
+
+ guint preview_visible : 1;
+ guint suppress_message_selection : 1;
+ guint show_deleted : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_PREVIEW_VISIBLE,
+ PROP_SHOW_DELETED
+};
+
+static gpointer parent_class;
+static GType mail_shell_content_type;
+
+static void
+mail_shell_content_etree_unfreeze (MessageList *message_list,
+ GdkEvent *event)
+{
+ ETableItem *item;
+ GObject *object;
+
+ item = e_tree_get_item (message_list->tree);
+ object = G_OBJECT (((GnomeCanvasItem *) item)->canvas);
+
+ g_object_set_data (object, "freeze-cursor", 0);
+}
+
+static void
+mail_shell_content_message_list_scrolled_cb (EMailShellContent *mail_shell_content,
+ MessageList *message_list)
+{
+ EShellContent *shell_content;
+ EShellView *shell_view;
+ GKeyFile *key_file;
+ const gchar *folder_uri;
+ const gchar *key;
+ gchar *group_name;
+ gdouble position;
+
+ /* Save the scrollbar position for the current folder. */
+
+ folder_uri = message_list->folder_uri;
+
+ if (folder_uri == NULL)
+ return;
+
+ shell_content = E_SHELL_CONTENT (mail_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ key = STATE_KEY_SCROLLBAR_POSITION;
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+ position = message_list_get_scrollbar_position (message_list);
+
+ g_key_file_set_double (key_file, group_name, key, position);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+}
+
+static gboolean
+mail_shell_content_scroll_timeout_cb (EMailShellContent *mail_shell_content)
+{
+ EMailShellContentPrivate *priv = mail_shell_content->priv;
+ EShellContent *shell_content;
+ EShellView *shell_view;
+ MessageList *message_list;
+ EMailReader *reader;
+ GKeyFile *key_file;
+ const gchar *folder_uri;
+ const gchar *key;
+ gchar *group_name;
+
+ /* Initialize the scrollbar position for the current folder
+ * and setup a callback to handle scrollbar position changes. */
+
+ shell_content = E_SHELL_CONTENT (mail_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ reader = E_MAIL_READER (mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+
+ if (folder_uri == NULL)
+ goto skip;
+
+ /* Restore the message list scrollbar position. */
+
+ key = STATE_KEY_SCROLLBAR_POSITION;
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+
+ if (g_key_file_has_key (key_file, group_name, key, NULL)) {
+ gdouble position;
+
+ position = g_key_file_get_double (
+ key_file, group_name, key, NULL);
+ message_list_set_scrollbar_position (message_list, position);
+ }
+
+ g_free (group_name);
+
+skip:
+ priv->message_list_scrolled_id = g_signal_connect_swapped (
+ message_list, "message-list-scrolled",
+ G_CALLBACK (mail_shell_content_message_list_scrolled_cb),
+ mail_shell_content);
+
+ priv->scroll_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+mail_shell_content_message_list_built_cb (EMailShellContent *mail_shell_content,
+ MessageList *message_list)
+{
+ EMailShellContentPrivate *priv = mail_shell_content->priv;
+ EShellContent *shell_content;
+ EShellView *shell_view;
+ GtkScrolledWindow *scrolled_window;
+ GtkWidget *vscrollbar;
+ GKeyFile *key_file;
+ gchar *uid;
+
+ g_signal_handler_disconnect (
+ message_list, priv->message_list_built_id);
+ priv->message_list_built_id = 0;
+
+ shell_content = E_SHELL_CONTENT (mail_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ if (message_list->cursor_uid != NULL)
+ uid = NULL;
+
+ else if (message_list->folder_uri == NULL)
+ uid = NULL;
+
+ else if (mail_shell_content->priv->suppress_message_selection)
+ uid = NULL;
+
+ else {
+ const gchar *folder_uri;
+ const gchar *key;
+ gchar *group_name;
+
+ key = STATE_KEY_SELECTED_MESSAGE;
+ folder_uri = message_list->folder_uri;
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+ uid = g_key_file_get_string (key_file, group_name, key, NULL);
+ g_free (group_name);
+ }
+
+ if (uid != NULL) {
+ CamelFolder *folder;
+ CamelMessageInfo *info;
+
+ folder = message_list->folder;
+ info = camel_folder_get_message_info (folder, uid);
+ if (info != NULL) {
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_content);
+ e_mail_reader_set_message (reader, uid);
+ camel_folder_free_message_info (folder, info);
+ }
+
+ g_free (uid);
+ }
+
+ /* FIXME This is a gross workaround for an ETable bug that I can't
+ * fix (Ximian bug #55303).
+ *
+ * Since e_canvas_item_region_show_relay() uses a timeout,
+ * we have to use a timeout of the same interval but a lower
+ * priority. */
+ priv->scroll_timeout_id = g_timeout_add_full (
+ G_PRIORITY_LOW, 250, (GSourceFunc)
+ mail_shell_content_scroll_timeout_cb,
+ mail_shell_content, NULL);
+
+ /* FIXME This is another ugly hack to hide a side-effect of the
+ * previous workaround. */
+ scrolled_window = GTK_SCROLLED_WINDOW (message_list);
+ vscrollbar = gtk_scrolled_window_get_vscrollbar (scrolled_window);
+ g_signal_connect_swapped (
+ vscrollbar, "button-press-event",
+ G_CALLBACK (mail_shell_content_etree_unfreeze),
+ message_list);
+}
+
+static void
+mail_shell_content_display_view_cb (EMailShellContent *mail_shell_content,
+ GalView *gal_view)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+
+ reader = E_MAIL_READER (mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ if (GAL_IS_VIEW_ETABLE (gal_view))
+ gal_view_etable_attach_tree (
+ GAL_VIEW_ETABLE (gal_view), message_list->tree);
+}
+
+static void
+mail_shell_content_message_selected_cb (EMailShellContent *mail_shell_content,
+ const gchar *message_uid,
+ MessageList *message_list)
+{
+ EShellContent *shell_content;
+ EShellView *shell_view;
+ GKeyFile *key_file;
+ const gchar *folder_uri;
+ const gchar *key;
+ gchar *group_name;
+
+ folder_uri = message_list->folder_uri;
+
+ /* This also gets triggered when selecting a store name on
+ * the sidebar such as "On This Computer", in which case
+ * 'folder_uri' will be NULL. */
+ if (folder_uri == NULL)
+ return;
+
+ shell_content = E_SHELL_CONTENT (mail_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ key = STATE_KEY_SELECTED_MESSAGE;
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+
+ if (message_uid != NULL)
+ g_key_file_set_string (key_file, group_name, key, message_uid);
+ else
+ g_key_file_remove_key (key_file, group_name, key, NULL);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+}
+
+static GtkOrientation
+mail_shell_content_get_orientation (EMailShellContent *mail_shell_content)
+{
+ return mail_shell_content->priv->orientation;
+}
+
+static void
+mail_shell_content_set_orientation (EMailShellContent *mail_shell_content,
+ GtkOrientation orientation)
+{
+ mail_shell_content->priv->orientation = orientation;
+
+ g_object_notify (G_OBJECT (mail_shell_content), "orientation");
+
+ e_mail_shell_content_update_view_instance (mail_shell_content);
+}
+
+static void
+mail_shell_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ORIENTATION:
+ mail_shell_content_set_orientation (
+ E_MAIL_SHELL_CONTENT (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ e_mail_shell_content_set_preview_visible (
+ E_MAIL_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_SHOW_DELETED:
+ e_mail_shell_content_set_show_deleted (
+ E_MAIL_SHELL_CONTENT (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_shell_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ORIENTATION:
+ g_value_set_enum (
+ value,
+ mail_shell_content_get_orientation (
+ E_MAIL_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_PREVIEW_VISIBLE:
+ g_value_set_boolean (
+ value,
+ e_mail_shell_content_get_preview_visible (
+ E_MAIL_SHELL_CONTENT (object)));
+ return;
+
+ case PROP_SHOW_DELETED:
+ g_value_set_boolean (
+ value,
+ e_mail_shell_content_get_show_deleted (
+ E_MAIL_SHELL_CONTENT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_shell_content_dispose (GObject *object)
+{
+ EMailShellContentPrivate *priv;
+
+ priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (object);
+
+ if (priv->paned != NULL) {
+ g_object_unref (priv->paned);
+ priv->paned = NULL;
+ }
+
+ if (priv->message_list != NULL) {
+ g_object_unref (priv->message_list);
+ priv->message_list = NULL;
+ }
+
+ if (priv->search_bar != NULL) {
+ g_object_unref (priv->search_bar);
+ priv->search_bar = NULL;
+ }
+
+ if (priv->html_display != NULL) {
+ g_object_unref (priv->html_display);
+ priv->html_display = NULL;
+ }
+
+ if (priv->view_instance != NULL) {
+ g_object_unref (priv->view_instance);
+ priv->view_instance = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+mail_shell_content_constructed (GObject *object)
+{
+ EMailShellContentPrivate *priv;
+ EShellContent *shell_content;
+ EShellBackend *shell_backend;
+ EShellView *shell_view;
+ EShellViewClass *shell_view_class;
+ EMailReader *reader;
+ MessageList *message_list;
+ GConfBridge *bridge;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkHTML *html;
+ GalViewCollection *view_collection;
+ const gchar *key;
+
+ priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (object);
+ priv->html_display = em_format_html_display_new ();
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ shell_content = E_SHELL_CONTENT (object);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_view_class = E_SHELL_VIEW_GET_CLASS (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ view_collection = shell_view_class->view_collection;
+
+ html = EM_FORMAT_HTML (priv->html_display)->html;
+
+ /* Build content widgets. */
+
+ container = GTK_WIDGET (object);
+
+ widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ priv->paned = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "orientation",
+ G_OBJECT (widget), "orientation");
+
+ container = widget;
+
+ widget = message_list_new (shell_backend);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ priv->message_list = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_vbox_new (FALSE, 1);
+ gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (object), "preview-visible",
+ G_OBJECT (widget), "visible");
+
+ container = 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_container_add (GTK_CONTAINER (widget), GTK_WIDGET (html));
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (html));
+ gtk_widget_show (widget);
+
+ widget = e_mail_search_bar_new (html);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ priv->search_bar = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (em_format_redraw), priv->html_display);
+
+ /* Load the view instance. */
+
+ e_mail_shell_content_update_view_instance (
+ E_MAIL_SHELL_CONTENT (shell_content));
+
+ /* Bind GObject properties to GConf keys. */
+
+ bridge = gconf_bridge_get ();
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/mail/display/hpaned_size";
+ gconf_bridge_bind_property (bridge, key, object, "hposition");
+
+ object = G_OBJECT (priv->paned);
+ key = "/apps/evolution/mail/display/paned_size";
+ gconf_bridge_bind_property (bridge, key, object, "vposition");
+
+ object = G_OBJECT (shell_content);
+ key = "/apps/evolution/mail/display/show_deleted";
+ gconf_bridge_bind_property (bridge, key, object, "show-deleted");
+
+ /* Message list customizations. */
+
+ reader = E_MAIL_READER (shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ g_signal_connect_swapped (
+ message_list, "message-selected",
+ G_CALLBACK (mail_shell_content_message_selected_cb),
+ shell_content);
+}
+
+static guint32
+mail_shell_content_check_state (EShellContent *shell_content)
+{
+ return e_mail_reader_check_state (E_MAIL_READER (shell_content));
+}
+
+static GtkActionGroup *
+mail_shell_content_get_action_group (EMailReader *reader)
+{
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+
+ shell_content = E_SHELL_CONTENT (reader);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ return E_SHELL_WINDOW_ACTION_GROUP_MAIL (shell_window);
+}
+
+static gboolean
+mail_shell_content_get_hide_deleted (EMailReader *reader)
+{
+ EMailShellContent *mail_shell_content;
+
+ mail_shell_content = E_MAIL_SHELL_CONTENT (reader);
+
+ return !e_mail_shell_content_get_show_deleted (mail_shell_content);
+}
+
+static EMFormatHTMLDisplay *
+mail_shell_content_get_html_display (EMailReader *reader)
+{
+ EMailShellContentPrivate *priv;
+
+ priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (reader);
+
+ return priv->html_display;
+}
+
+static MessageList *
+mail_shell_content_get_message_list (EMailReader *reader)
+{
+ EMailShellContentPrivate *priv;
+
+ priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (reader);
+
+ return MESSAGE_LIST (priv->message_list);
+}
+
+static EShellBackend *
+mail_shell_content_get_shell_backend (EMailReader *reader)
+{
+ EShellContent *shell_content;
+ EShellView *shell_view;
+
+ shell_content = E_SHELL_CONTENT (reader);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+
+ return e_shell_view_get_shell_backend (shell_view);
+}
+
+static GtkWindow *
+mail_shell_content_get_window (EMailReader *reader)
+{
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+
+ shell_content = E_SHELL_CONTENT (reader);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ return GTK_WINDOW (shell_window);
+}
+
+static void
+mail_shell_content_set_folder (EMailReader *reader,
+ CamelFolder *folder,
+ const gchar *folder_uri)
+{
+ EMailShellContentPrivate *priv;
+ EMailReaderIface *default_iface;
+ MessageList *message_list;
+ gboolean different_folder;
+
+ priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (reader);
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_freeze (message_list);
+
+ different_folder =
+ message_list->folder != NULL &&
+ folder != message_list->folder;
+
+ /* Chain up to interface's default set_folder() method. */
+ default_iface = g_type_default_interface_peek (E_TYPE_MAIL_READER);
+ default_iface->set_folder (reader, folder, folder_uri);
+
+ if (folder == NULL)
+ goto exit;
+
+ mail_refresh_folder (folder, NULL, NULL);
+
+ /* This function gets triggered several times at startup,
+ * so we don't want to reset the message suppression state
+ * unless we're actually switching to a different folder. */
+ if (different_folder)
+ priv->suppress_message_selection = FALSE;
+
+ /* This is a one-time-only callback. */
+ if (message_list->cursor_uid == NULL && priv->message_list_built_id == 0)
+ priv->message_list_built_id = g_signal_connect_swapped (
+ message_list, "message-list-built",
+ G_CALLBACK (mail_shell_content_message_list_built_cb),
+ reader);
+
+exit:
+ message_list_thaw (message_list);
+}
+
+static void
+mail_shell_content_show_search_bar (EMailReader *reader)
+{
+ EMailShellContentPrivate *priv;
+
+ priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (reader);
+
+ gtk_widget_show (priv->search_bar);
+}
+
+static void
+mail_shell_content_class_init (EMailShellContentClass *class)
+{
+ GObjectClass *object_class;
+ EShellContentClass *shell_content_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMailShellContentPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_shell_content_set_property;
+ object_class->get_property = mail_shell_content_get_property;
+ object_class->dispose = mail_shell_content_dispose;
+ object_class->constructed = mail_shell_content_constructed;
+
+ shell_content_class = E_SHELL_CONTENT_CLASS (class);
+ shell_content_class->new_search_context = em_search_context_new;
+ shell_content_class->check_state = mail_shell_content_check_state;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_VISIBLE,
+ g_param_spec_boolean (
+ "preview-visible",
+ _("Preview is Visible"),
+ _("Whether the preview pane is visible"),
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SHOW_DELETED,
+ g_param_spec_boolean (
+ "show-deleted",
+ "Show Deleted",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_override_property (
+ object_class, PROP_ORIENTATION, "orientation");
+}
+
+static void
+mail_shell_content_reader_init (EMailReaderIface *iface)
+{
+ iface->get_action_group = mail_shell_content_get_action_group;
+ iface->get_hide_deleted = mail_shell_content_get_hide_deleted;
+ iface->get_html_display = mail_shell_content_get_html_display;
+ iface->get_message_list = mail_shell_content_get_message_list;
+ iface->get_shell_backend = mail_shell_content_get_shell_backend;
+ iface->get_window = mail_shell_content_get_window;
+ iface->set_folder = mail_shell_content_set_folder;
+ iface->show_search_bar = mail_shell_content_show_search_bar;
+}
+
+static void
+mail_shell_content_init (EMailShellContent *mail_shell_content)
+{
+ mail_shell_content->priv =
+ E_MAIL_SHELL_CONTENT_GET_PRIVATE (mail_shell_content);
+
+ mail_shell_content->priv->preview_visible = TRUE;
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_mail_shell_content_get_type (void)
+{
+ return mail_shell_content_type;
+}
+
+void
+e_mail_shell_content_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EMailShellContentClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_shell_content_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMailShellContent),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) mail_shell_content_init,
+ NULL /* value_table */
+ };
+
+ static const GInterfaceInfo orientable_info = {
+ (GInterfaceInitFunc) NULL,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ static const GInterfaceInfo reader_info = {
+ (GInterfaceInitFunc) mail_shell_content_reader_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ mail_shell_content_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_CONTENT,
+ "EMailShellContent", &type_info, 0);
+
+ g_type_module_add_interface (
+ type_module, mail_shell_content_type,
+ GTK_TYPE_ORIENTABLE, &orientable_info);
+
+ g_type_module_add_interface (
+ type_module, mail_shell_content_type,
+ E_TYPE_MAIL_READER, &reader_info);
+}
+
+GtkWidget *
+e_mail_shell_content_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_MAIL_SHELL_CONTENT,
+ "shell-view", shell_view, NULL);
+}
+
+gboolean
+e_mail_shell_content_get_preview_visible (EMailShellContent *mail_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SHELL_CONTENT (mail_shell_content), FALSE);
+
+ return mail_shell_content->priv->preview_visible;
+}
+
+void
+e_mail_shell_content_set_preview_visible (EMailShellContent *mail_shell_content,
+ gboolean preview_visible)
+{
+ g_return_if_fail (E_IS_MAIL_SHELL_CONTENT (mail_shell_content));
+
+ if (preview_visible == mail_shell_content->priv->preview_visible)
+ return;
+
+ /* If we're showing the preview, tell EMailReader to reload the
+ * selected message. This should force it to download the full
+ * message if necessary, so we don't get an empty preview. */
+ if (preview_visible) {
+ EMailReader *reader;
+ MessageList *message_list;
+
+ reader = E_MAIL_READER (mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ e_mail_reader_set_message (reader, message_list->cursor_uid);
+ }
+
+ mail_shell_content->priv->preview_visible = preview_visible;
+
+ g_object_notify (G_OBJECT (mail_shell_content), "preview-visible");
+}
+
+gboolean
+e_mail_shell_content_get_show_deleted (EMailShellContent *mail_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SHELL_CONTENT (mail_shell_content), FALSE);
+
+ return mail_shell_content->priv->show_deleted;
+}
+
+void
+e_mail_shell_content_set_show_deleted (EMailShellContent *mail_shell_content,
+ gboolean show_deleted)
+{
+ g_return_if_fail (E_IS_MAIL_SHELL_CONTENT (mail_shell_content));
+
+ mail_shell_content->priv->show_deleted = show_deleted;
+
+ g_object_notify (G_OBJECT (mail_shell_content), "show-deleted");
+}
+
+GalViewInstance *
+e_mail_shell_content_get_view_instance (EMailShellContent *mail_shell_content)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SHELL_CONTENT (mail_shell_content), NULL);
+
+ return mail_shell_content->priv->view_instance;
+}
+
+void
+e_mail_shell_content_set_search_strings (EMailShellContent *mail_shell_content,
+ GSList *search_strings)
+{
+ EMailSearchBar *search_bar;
+ ESearchingTokenizer *tokenizer;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_CONTENT (mail_shell_content));
+
+ search_bar = E_MAIL_SEARCH_BAR (mail_shell_content->priv->search_bar);
+ tokenizer = e_mail_search_bar_get_tokenizer (search_bar);
+
+ e_searching_tokenizer_set_secondary_case_sensitivity (tokenizer, FALSE);
+ e_searching_tokenizer_set_secondary_search_string (tokenizer, NULL);
+
+ while (search_strings != NULL) {
+ e_searching_tokenizer_add_secondary_search_string (
+ tokenizer, search_strings->data);
+ search_strings = g_slist_next (search_strings);
+ }
+
+ e_mail_search_bar_changed (search_bar);
+}
+
+void
+e_mail_shell_content_update_view_instance (EMailShellContent *mail_shell_content)
+{
+ EMailReader *reader;
+ EShellContent *shell_content;
+ EShellView *shell_view;
+ EShellViewClass *shell_view_class;
+ GalViewCollection *view_collection;
+ GalViewInstance *view_instance;
+ MessageList *message_list;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+ gboolean outgoing_folder;
+ gboolean show_vertical_view;
+ gchar *view_id;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_CONTENT (mail_shell_content));
+
+ shell_content = E_SHELL_CONTENT (mail_shell_content);
+ shell_view = e_shell_content_get_shell_view (shell_content);
+ shell_view_class = E_SHELL_VIEW_GET_CLASS (shell_view);
+ view_collection = shell_view_class->view_collection;
+
+ reader = E_MAIL_READER (mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ /* If no folder is selected, return silently. */
+ if (message_list->folder == NULL)
+ return;
+
+ /* If we have a folder, we should also have a URI. */
+ g_return_if_fail (message_list->folder_uri != NULL);
+
+ if (mail_shell_content->priv->view_instance != NULL) {
+ g_object_unref (mail_shell_content->priv->view_instance);
+ mail_shell_content->priv->view_instance = NULL;
+ }
+
+ view_id = mail_config_folder_to_safe_url (message_list->folder);
+ view_instance = e_shell_view_new_view_instance (shell_view, view_id);
+ mail_shell_content->priv->view_instance = view_instance;
+
+ orientable = GTK_ORIENTABLE (mail_shell_content);
+ orientation = gtk_orientable_get_orientation (orientable);
+ show_vertical_view = (orientation == GTK_ORIENTATION_HORIZONTAL);
+
+ if (show_vertical_view) {
+ gchar *filename;
+ gchar *safe_view_id;
+
+ /* Force the view instance into vertical view. */
+
+ g_free (view_instance->custom_filename);
+ g_free (view_instance->current_view_filename);
+
+ safe_view_id = g_strdup (view_id);
+ e_filename_make_safe (safe_view_id);
+
+ filename = g_strdup_printf (
+ "custom_wide_view-%s.xml", safe_view_id);
+ view_instance->custom_filename = g_build_filename (
+ view_collection->local_dir, filename, NULL);
+ g_free (filename);
+
+ filename = g_strdup_printf (
+ "current_wide_view-%s.xml", safe_view_id);
+ view_instance->current_view_filename = g_build_filename (
+ view_collection->local_dir, filename, NULL);
+ g_free (filename);
+
+ g_free (safe_view_id);
+ }
+
+ g_free (view_id);
+
+ outgoing_folder =
+ em_utils_folder_is_drafts (
+ message_list->folder, message_list->folder_uri) ||
+ em_utils_folder_is_outbox (
+ message_list->folder, message_list->folder_uri) ||
+ em_utils_folder_is_sent (
+ message_list->folder, message_list->folder_uri);
+
+ if (outgoing_folder) {
+ if (show_vertical_view)
+ gal_view_instance_set_default_view (
+ view_instance, "Wide_View_Sent");
+ else
+ gal_view_instance_set_default_view (
+ view_instance, "As_Sent_Folder");
+ } else if (show_vertical_view) {
+ gal_view_instance_set_default_view (
+ view_instance, "Wide_View_Normal");
+ }
+
+ gal_view_instance_load (view_instance);
+
+ if (!gal_view_instance_exists (view_instance)) {
+ gchar *state_filename;
+
+ state_filename = mail_config_folder_to_cachename (
+ message_list->folder, "et-header-");
+
+ if (g_file_test (state_filename, G_FILE_TEST_IS_REGULAR)) {
+ ETableSpecification *spec;
+ ETableState *state;
+ GalView *view;
+ gchar *spec_filename;
+
+ spec = e_table_specification_new ();
+ spec_filename = g_build_filename (
+ EVOLUTION_ETSPECDIR,
+ "message-list.etspec",
+ NULL);
+ e_table_specification_load_from_file (
+ spec, spec_filename);
+ g_free (spec_filename);
+
+ state = e_table_state_new ();
+ view = gal_view_etable_new (spec, "");
+
+ e_table_state_load_from_file (
+ state, state_filename);
+ gal_view_etable_set_state (
+ GAL_VIEW_ETABLE (view), state);
+ gal_view_instance_set_custom_view (
+ view_instance, view);
+
+ g_object_unref (state);
+ g_object_unref (view);
+ g_object_unref (spec);
+ }
+
+ g_free (state_filename);
+ }
+
+ g_signal_connect (
+ view_instance, "display-view",
+ G_CALLBACK (mail_shell_content_display_view_cb),
+ mail_shell_content);
+
+ mail_shell_content_display_view_cb (
+ mail_shell_content,
+ gal_view_instance_get_current_view (view_instance));
+}
diff --git a/modules/mail/e-mail-shell-content.h b/modules/mail/e-mail-shell-content.h
new file mode 100644
index 0000000000..88714f8f95
--- /dev/null
+++ b/modules/mail/e-mail-shell-content.h
@@ -0,0 +1,89 @@
+/*
+ * e-mail-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_MAIL_SHELL_CONTENT_H
+#define E_MAIL_SHELL_CONTENT_H
+
+#include <shell/e-shell-content.h>
+#include <shell/e-shell-view.h>
+
+#include <mail/em-format-html-display.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SHELL_CONTENT \
+ (e_mail_shell_content_get_type ())
+#define E_MAIL_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_SHELL_CONTENT, EMailShellContent))
+#define E_MAIL_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_SHELL_CONTENT, EMailShellContentClass))
+#define E_IS_MAIL_SHELL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_SHELL_CONTENT))
+#define E_IS_MAIL_SHELL_CONTENT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_SHELL_CONTENT))
+#define E_MAIL_SHELL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_SHELL_CONTENT, EMailShellContentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailShellContent EMailShellContent;
+typedef struct _EMailShellContentClass EMailShellContentClass;
+typedef struct _EMailShellContentPrivate EMailShellContentPrivate;
+
+struct _EMailShellContent {
+ EShellContent parent;
+ EMailShellContentPrivate *priv;
+};
+
+struct _EMailShellContentClass {
+ EShellContentClass parent_class;
+};
+
+GType e_mail_shell_content_get_type (void);
+void e_mail_shell_content_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_mail_shell_content_new(EShellView *shell_view);
+gboolean e_mail_shell_content_get_preview_visible
+ (EMailShellContent *mail_shell_content);
+void e_mail_shell_content_set_preview_visible
+ (EMailShellContent *mail_shell_content,
+ gboolean preview_visible);
+gboolean e_mail_shell_content_get_show_deleted
+ (EMailShellContent *mail_shell_content);
+void e_mail_shell_content_set_show_deleted
+ (EMailShellContent *mail_shell_content,
+ gboolean show_deleted);
+GalViewInstance *
+ e_mail_shell_content_get_view_instance
+ (EMailShellContent *mail_shell_content);
+void e_mail_shell_content_set_search_strings
+ (EMailShellContent *mail_shell_content,
+ GSList *search_strings);
+void e_mail_shell_content_update_view_instance
+ (EMailShellContent *mail_shell_content);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_CONTENT_H */
diff --git a/modules/mail/e-mail-shell-migrate.c b/modules/mail/e-mail-shell-migrate.c
new file mode 100644
index 0000000000..7176d4fba7
--- /dev/null
+++ b/modules/mail/e-mail-shell-migrate.c
@@ -0,0 +1,3100 @@
+/*
+ * e-mail-shell-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-mail-shell-migrate.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <utime.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <regex.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <gtk/gtk.h>
+
+#include <gconf/gconf-client.h>
+
+#include <camel/camel.h>
+#include <camel/camel-store.h>
+#include <camel/camel-session.h>
+#include <camel/camel-file-utils.h>
+#include <camel/camel-disco-folder.h>
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <e-util/e-util.h>
+#include <libedataserver/e-xml-utils.h>
+#include <libedataserver/e-data-server-util.h>
+#include <e-util/e-xml-utils.h>
+
+#include "e-util/e-account-utils.h"
+#include "e-util/e-bconf-map.h"
+#include "e-util/e-error.h"
+#include "e-util/e-util-private.h"
+#include "e-util/e-plugin.h"
+#include "e-util/e-signature-utils.h"
+
+#include "e-mail-shell-backend.h"
+#include "shell/e-shell-migrate.h"
+
+#include "e-mail-store.h"
+#include "mail-config.h"
+#include "em-utils.h"
+
+#define d(x) x
+
+#ifndef G_OS_WIN32
+/* No versions previous to 2.8 or thereabouts have been available on
+ * Windows, so don't bother with upgrade support from earlier versions
+ * on Win32. Do try to support upgrades from 2.12 and later to the
+ * current version.
+ */
+
+/* upgrade helper functions */
+static xmlDocPtr
+emm_load_xml (const gchar *dirname, const gchar *filename)
+{
+ xmlDocPtr doc;
+ struct stat st;
+ gchar *path;
+
+ path = g_strdup_printf ("%s/%s", dirname, filename);
+ if (stat (path, &st) == -1 || !(doc = xmlParseFile (path))) {
+ g_free (path);
+ return NULL;
+ }
+
+ g_free (path);
+
+ return doc;
+}
+
+static gint
+emm_save_xml (xmlDocPtr doc, const gchar *dirname, const gchar *filename)
+{
+ gchar *path;
+ gint retval;
+
+ path = g_strdup_printf ("%s/%s", dirname, filename);
+ retval = e_xml_save_file (path, doc);
+ g_free (path);
+
+ return retval;
+}
+
+static xmlNodePtr
+xml_find_node (xmlNodePtr parent, const gchar *name)
+{
+ xmlNodePtr node;
+
+ node = parent->children;
+ while (node != NULL) {
+ if (node->name && !strcmp ((gchar *)node->name, name))
+ return node;
+
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+static void
+upgrade_xml_uris (xmlDocPtr doc, gchar * (* upgrade_uri) (const gchar *uri))
+{
+ xmlNodePtr root, node;
+ gchar *uri, *new;
+
+ if (!doc || !(root = xmlDocGetRootElement (doc)))
+ return;
+
+ if (!root->name || strcmp ((gchar *)root->name, "filteroptions") != 0) {
+ /* root node is not <filteroptions>, nothing to upgrade */
+ return;
+ }
+
+ if (!(node = xml_find_node (root, "ruleset"))) {
+ /* no ruleset node, nothing to upgrade */
+ return;
+ }
+
+ node = node->children;
+ while (node != NULL) {
+ if (node->name && !strcmp ((gchar *)node->name, "rule")) {
+ xmlNodePtr actionset, part, val, n;
+
+ if ((actionset = xml_find_node (node, "actionset"))) {
+ /* filters.xml */
+ part = actionset->children;
+ while (part != NULL) {
+ if (part->name && !strcmp ((gchar *)part->name, "part")) {
+ val = part->children;
+ while (val != NULL) {
+ if (val->name && !strcmp ((gchar *)val->name, "value")) {
+ gchar *type;
+
+ type = (gchar *)xmlGetProp (val, (const guchar *)"type");
+ if (type && !strcmp ((gchar *)type, "folder")) {
+ if ((n = xml_find_node (val, "folder"))) {
+ uri = (gchar *)xmlGetProp (n, (const guchar *)"uri");
+ new = upgrade_uri (uri);
+ xmlFree (uri);
+
+ xmlSetProp (n, (const guchar *)"uri", (guchar *)new);
+ g_free (new);
+ }
+ }
+
+ xmlFree (type);
+ }
+
+ val = val->next;
+ }
+ }
+
+ part = part->next;
+ }
+ } else if ((actionset = xml_find_node (node, "sources"))) {
+ /* vfolders.xml */
+ n = actionset->children;
+ while (n != NULL) {
+ if (n->name && !strcmp ((gchar *)n->name, "folder")) {
+ uri = (gchar *)xmlGetProp (n, (const guchar *)"uri");
+ new = upgrade_uri (uri);
+ xmlFree (uri);
+
+ xmlSetProp (n, (const guchar *)"uri", (guchar *)new);
+ g_free (new);
+ }
+
+ n = n->next;
+ }
+ }
+ }
+
+ node = node->next;
+ }
+}
+
+/* 1.0 upgrade functions & data */
+
+/* as much info as we have on a given account */
+struct _account_info_1_0 {
+ gchar *name;
+ gchar *uri;
+ gchar *base_uri;
+ union {
+ struct {
+ /* for imap */
+ gchar *namespace;
+ gchar *namespace_full;
+ guint32 capabilities;
+ GHashTable *folders;
+ gchar dir_sep;
+ } imap;
+ } u;
+};
+
+struct _imap_folder_info_1_0 {
+ gchar *folder;
+ /* encoded? decoded? canonicalised? */
+ gchar dir_sep;
+};
+
+static GHashTable *accounts_1_0 = NULL;
+static GHashTable *accounts_name_1_0 = NULL;
+
+static void
+imap_folder_info_1_0_free (struct _imap_folder_info_1_0 *fi)
+{
+ g_free(fi->folder);
+ g_free(fi);
+}
+
+static void
+account_info_1_0_free (struct _account_info_1_0 *ai)
+{
+ g_free(ai->name);
+ g_free(ai->uri);
+ g_free(ai->base_uri);
+ g_free(ai->u.imap.namespace);
+ g_free(ai->u.imap.namespace_full);
+ g_hash_table_destroy(ai->u.imap.folders);
+ g_free(ai);
+}
+
+static gchar *
+get_base_uri(const gchar *val)
+{
+ const gchar *tmp;
+
+ tmp = strchr(val, ':');
+ if (tmp) {
+ tmp++;
+ if (strncmp(tmp, "//", 2) == 0)
+ tmp += 2;
+ tmp = strchr(tmp, '/');
+ }
+
+ if (tmp)
+ return g_strndup(val, tmp-val);
+ else
+ return g_strdup(val);
+}
+
+static gchar *
+upgrade_xml_uris_1_0 (const gchar *uri)
+{
+ gchar *out = NULL;
+
+ /* upgrades camel uri's */
+ if (strncmp (uri, "imap:", 5) == 0) {
+ gchar *base_uri, dir_sep, *folder, *p;
+ struct _account_info_1_0 *ai;
+
+ /* add namespace, canonicalise dir_sep to / */
+ base_uri = get_base_uri (uri);
+ ai = g_hash_table_lookup (accounts_1_0, base_uri);
+
+ if (ai == NULL) {
+ g_free (base_uri);
+ return NULL;
+ }
+
+ dir_sep = ai->u.imap.dir_sep;
+ if (dir_sep == 0) {
+ /* no dir_sep listed, try get it from the namespace, if set */
+ if (ai->u.imap.namespace != NULL) {
+ p = ai->u.imap.namespace;
+ while ((dir_sep = *p++)) {
+ if (dir_sep < '0'
+ || (dir_sep > '9' && dir_sep < 'A')
+ || (dir_sep > 'Z' && dir_sep < 'a')
+ || (dir_sep > 'z')) {
+ break;
+ }
+ p++;
+ }
+ }
+
+ /* give up ... */
+ if (dir_sep == 0) {
+ g_free (base_uri);
+ return NULL;
+ }
+ }
+
+ folder = g_strdup (uri + strlen (base_uri) + 1);
+
+ /* Add the namespace before the mailbox name, unless the mailbox is INBOX */
+ if (ai->u.imap.namespace && strcmp ((gchar *)folder, "INBOX") != 0)
+ out = g_strdup_printf ("%s/%s/%s", base_uri, ai->u.imap.namespace, folder);
+ else
+ out = g_strdup_printf ("%s/%s", base_uri, folder);
+
+ p = out;
+ while (*p) {
+ if (*p == dir_sep)
+ *p = '/';
+ p++;
+ }
+
+ g_free (folder);
+ g_free (base_uri);
+ } else if (strncmp (uri, "exchange:", 9) == 0) {
+ gchar *base_uri, *folder, *p;
+
+ /* exchange://user@host/exchange/ * -> exchange://user@host/personal/ * */
+ /* Any url encoding (%xx) in the folder name is also removed */
+ base_uri = get_base_uri (uri);
+ uri += strlen (base_uri) + 1;
+ if (strncmp (uri, "exchange/", 9) == 0) {
+ folder = e_bconf_url_decode (uri + 9);
+ p = strchr (folder, '/');
+ out = g_strdup_printf ("%s/personal%s", base_uri, p ? p : "/");
+ g_free (folder);
+ }
+ } else if (strncmp (uri, "exchanget:", 10) == 0) {
+ /* these should be converted in the accounts table when it is loaded */
+ g_warning ("exchanget: uri not converted: '%s'", uri);
+ }
+
+ return out;
+}
+
+static gchar *
+parse_lsub (const gchar *lsub, gchar *dir_sep)
+{
+ static gint comp;
+ static regex_t pat;
+ regmatch_t match[3];
+ const gchar *m = "^\\* LSUB \\([^)]*\\) \"?([^\" ]+)\"? \"?(.*)\"?$";
+
+ if (!comp) {
+ if (regcomp (&pat, m, REG_EXTENDED|REG_ICASE) == -1) {
+ g_warning ("reg comp '%s' failed: %s", m, g_strerror (errno));
+ return NULL;
+ }
+ comp = 1;
+ }
+
+ if (regexec (&pat, lsub, 3, match, 0) == 0) {
+ if (match[1].rm_so != -1 && match[2].rm_so != -1) {
+ if (dir_sep)
+ *dir_sep = (match[1].rm_eo - match[1].rm_so == 1) ? lsub[match[1].rm_so] : 0;
+ return g_strndup (lsub + match[2].rm_so, match[2].rm_eo - match[2].rm_so);
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+read_imap_storeinfo (struct _account_info_1_0 *si)
+{
+ FILE *storeinfo;
+ guint32 tmp;
+ gchar *buf, *folder, dir_sep, *path, *name, *p;
+ struct _imap_folder_info_1_0 *fi;
+
+ si->u.imap.folders = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) imap_folder_info_1_0_free);
+
+ /* get details from uri first */
+ name = strstr (si->uri, ";override_namespace");
+ if (name) {
+ name = strstr (si->uri, ";namespace=");
+ if (name) {
+ gchar *end;
+
+ name += strlen (";namespace=");
+ if (*name == '\"') {
+ name++;
+ end = strchr (name, '\"');
+ } else {
+ end = strchr (name, ';');
+ }
+
+ if (end) {
+ /* try get the dir_sep from the namespace */
+ si->u.imap.namespace = g_strndup (name, end-name);
+
+ p = si->u.imap.namespace;
+ while ((dir_sep = *p++)) {
+ if (dir_sep < '0'
+ || (dir_sep > '9' && dir_sep < 'A')
+ || (dir_sep > 'Z' && dir_sep < 'a')
+ || (dir_sep > 'z')) {
+ si->u.imap.dir_sep = dir_sep;
+ break;
+ }
+ p++;
+ }
+ }
+ }
+ }
+
+ /* now load storeinfo if it exists */
+ path = g_build_filename (g_get_home_dir (), "evolution", "mail", "imap", si->base_uri + 7, "storeinfo", NULL);
+ storeinfo = fopen (path, "r");
+ g_free (path);
+ if (storeinfo == NULL) {
+ g_warning ("could not find imap store info '%s'", path);
+ return FALSE;
+ }
+
+ /* ignore version */
+ camel_file_util_decode_uint32 (storeinfo, &tmp);
+ camel_file_util_decode_uint32 (storeinfo, &si->u.imap.capabilities);
+ g_free (si->u.imap.namespace);
+ camel_file_util_decode_string (storeinfo, &si->u.imap.namespace);
+ camel_file_util_decode_uint32 (storeinfo, &tmp);
+ si->u.imap.dir_sep = tmp;
+ /* strip trailing dir_sep or / */
+ if (si->u.imap.namespace
+ && (si->u.imap.namespace[strlen (si->u.imap.namespace) - 1] == si->u.imap.dir_sep
+ || si->u.imap.namespace[strlen (si->u.imap.namespace) - 1] == '/')) {
+ si->u.imap.namespace[strlen (si->u.imap.namespace) - 1] = 0;
+ }
+
+ d(printf ("namespace '%s' dir_sep '%c'\n", si->u.imap.namespace, si->u.imap.dir_sep ? si->u.imap.dir_sep : '?'));
+
+ while (camel_file_util_decode_string (storeinfo, &buf) == 0) {
+ folder = parse_lsub (buf, &dir_sep);
+ if (folder) {
+ fi = g_new0 (struct _imap_folder_info_1_0, 1);
+ fi->folder = folder;
+ fi->dir_sep = dir_sep;
+#if d(!)0
+ printf (" add folder '%s' ", folder);
+ if (dir_sep)
+ printf ("'%c'\n", dir_sep);
+ else
+ printf ("NIL\n");
+#endif
+ g_hash_table_insert (si->u.imap.folders, fi->folder, fi);
+ } else {
+ g_warning ("Could not parse LIST result '%s'\n", buf);
+ }
+ }
+
+ fclose (storeinfo);
+
+ return TRUE;
+}
+
+static gboolean
+load_accounts_1_0 (xmlDocPtr doc)
+{
+ xmlNodePtr source;
+ gchar *val, *tmp;
+ gint count = 0, i;
+ gchar key[32];
+
+ if (!(source = e_bconf_get_path (doc, "/Mail/Accounts")))
+ return TRUE;
+
+ if ((val = e_bconf_get_value (source, "num"))) {
+ count = atoi (val);
+ xmlFree (val);
+ }
+
+ /* load account upgrade info for each account */
+ for (i = 0; i < count; i++) {
+ struct _account_info_1_0 *ai;
+ gchar *rawuri;
+
+ sprintf (key, "source_url_%d", i);
+ if (!(rawuri = e_bconf_get_value (source, key)))
+ continue;
+
+ ai = g_malloc0 (sizeof (struct _account_info_1_0));
+ ai->uri = e_bconf_hex_decode (rawuri);
+ ai->base_uri = get_base_uri (ai->uri);
+ sprintf (key, "account_name_%d", i);
+ ai->name = e_bconf_get_string (source, key);
+
+ d(printf("load account '%s'\n", ai->uri));
+
+ if (!strncmp (ai->uri, "imap:", 5)) {
+ read_imap_storeinfo (ai);
+ } else if (!strncmp (ai->uri, "exchange:", 9)) {
+ xmlNodePtr node;
+
+ d(printf (" upgrade exchange account\n"));
+ /* small hack, poke the source_url into the transport_url for exchanget: transports
+ - this will be picked up later in the conversion */
+ sprintf (key, "transport_url_%d", i);
+ node = e_bconf_get_entry (source, key);
+ if (node && (val = (gchar *)xmlGetProp (node, (const guchar *)"value"))) {
+ tmp = e_bconf_hex_decode (val);
+ xmlFree (val);
+ if (strncmp (tmp, "exchanget:", 10) == 0)
+ xmlSetProp (node, (const guchar *)"value", (guchar *)rawuri);
+ g_free (tmp);
+ } else {
+ d(printf (" couldn't find transport uri?\n"));
+ }
+ }
+ xmlFree (rawuri);
+
+ g_hash_table_insert (accounts_1_0, ai->base_uri, ai);
+ if (ai->name)
+ g_hash_table_insert (accounts_name_1_0, ai->name, ai);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+em_migrate_1_0 (const gchar *data_dir, xmlDocPtr config_xmldb, xmlDocPtr filters, xmlDocPtr vfolders, GError **error)
+{
+ accounts_1_0 = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) account_info_1_0_free);
+ accounts_name_1_0 = g_hash_table_new (g_str_hash, g_str_equal);
+ load_accounts_1_0 (config_xmldb);
+
+ upgrade_xml_uris(filters, upgrade_xml_uris_1_0);
+ upgrade_xml_uris(vfolders, upgrade_xml_uris_1_0);
+
+ g_hash_table_destroy (accounts_1_0);
+ g_hash_table_destroy (accounts_name_1_0);
+
+ return TRUE;
+}
+
+/* 1.2 upgrade functions */
+static gboolean
+is_xml1encoded (const gchar *txt)
+{
+ const guchar *p;
+ gint isxml1 = FALSE;
+ gint is8bit = FALSE;
+
+ p = (const guchar *)txt;
+ while (*p) {
+ if (p[0] == '\\' && p[1] == 'U' && p[2] == '+'
+ && isxdigit (p[3]) && isxdigit (p[4]) && isxdigit (p[5]) && isxdigit (p[6])
+ && p[7] == '\\') {
+ isxml1 = TRUE;
+ p+=7;
+ } else if (p[0] >= 0x80)
+ is8bit = TRUE;
+ p++;
+ }
+
+ /* check for invalid utf8 that needs cleaning */
+ if (is8bit && !isxml1)
+ isxml1 = !g_utf8_validate (txt, -1, NULL);
+
+ return isxml1;
+}
+
+static gchar *
+decode_xml1 (const gchar *txt)
+{
+ GString *out = g_string_new ("");
+ const guchar *p;
+ gchar *res;
+
+ /* convert:
+ \U+XXXX\ -> utf8
+ 8 bit characters -> utf8 (iso-8859-1) */
+
+ p = (const guchar *) txt;
+ while (*p) {
+ if (p[0] > 0x80
+ || (p[0] == '\\' && p[1] == 'U' && p[2] == '+'
+ && isxdigit (p[3]) && isxdigit (p[4]) && isxdigit (p[5]) && isxdigit (p[6])
+ && p[7] == '\\')) {
+ gchar utf8[8];
+ gunichar u;
+
+ if (p[0] == '\\') {
+ memcpy (utf8, p + 3, 4);
+ utf8[4] = 0;
+ u = strtoul (utf8, NULL, 16);
+ p+=7;
+ } else
+ u = p[0];
+ utf8[g_unichar_to_utf8 (u, utf8)] = 0;
+ g_string_append (out, utf8);
+ } else {
+ g_string_append_c (out, *p);
+ }
+ p++;
+ }
+
+ res = out->str;
+ g_string_free (out, FALSE);
+
+ return res;
+}
+
+static gchar *
+utf8_reencode (const gchar *txt)
+{
+ GString *out = g_string_new ("");
+ gchar *p;
+ gchar *res;
+
+ /* convert:
+ libxml1 8 bit utf8 converted to xml entities byte-by-byte chars -> utf8 */
+
+ p = (gchar *)txt;
+
+ while (*p) {
+ g_string_append_c (out, (gchar)g_utf8_get_char ((const gchar *)p));
+ p = (gchar *)g_utf8_next_char (p);
+ }
+
+ res = out->str;
+ if (g_utf8_validate (res, -1, NULL)) {
+ g_string_free (out, FALSE);
+ return res;
+ } else {
+ g_string_free (out, TRUE);
+ return g_strdup (txt);
+ }
+}
+
+static gboolean
+upgrade_xml_1_2_rec (xmlNodePtr node)
+{
+ const gchar *value_tags[] = { "string", "address", "regex", "file", "command", NULL };
+ const gchar *rule_tags[] = { "title", NULL };
+ const gchar *item_props[] = { "name", NULL };
+ struct {
+ const gchar *name;
+ const gchar **tags;
+ const gchar **props;
+ } tags[] = {
+ { "value", value_tags, NULL },
+ { "rule", rule_tags, NULL },
+ { "item", NULL, item_props },
+ { 0 },
+ };
+ xmlNodePtr work;
+ gint i,j;
+ gchar *txt, *tmp;
+
+ /* upgrades the content of a node, if the node has a specific parent/node name */
+
+ for (i = 0; tags[i].name; i++) {
+ if (!strcmp ((gchar *)node->name, tags[i].name)) {
+ if (tags[i].tags != NULL) {
+ work = node->children;
+ while (work) {
+ for (j = 0; tags[i].tags[j]; j++) {
+ if (!strcmp ((gchar *)work->name, tags[i].tags[j])) {
+ txt = (gchar *)xmlNodeGetContent (work);
+ if (is_xml1encoded (txt)) {
+ tmp = decode_xml1 (txt);
+ d(printf ("upgrading xml node %s/%s '%s' -> '%s'\n",
+ tags[i].name, tags[i].tags[j], txt, tmp));
+ xmlNodeSetContent (work, (guchar *)tmp);
+ g_free (tmp);
+ }
+ xmlFree (txt);
+ }
+ }
+ work = work->next;
+ }
+ break;
+ }
+
+ if (tags[i].props != NULL) {
+ for (j = 0; tags[i].props[j]; j++) {
+ txt = (gchar *)xmlGetProp (node, (guchar *)tags[i].props[j]);
+ tmp = utf8_reencode (txt);
+ d(printf ("upgrading xml property %s on node %s '%s' -> '%s'\n",
+ tags[i].props[j], tags[i].name, txt, tmp));
+ xmlSetProp (node, (const guchar *)tags[i].props[j], (guchar *)tmp);
+ g_free (tmp);
+ xmlFree (txt);
+ }
+ }
+ }
+ }
+
+ node = node->children;
+ while (node) {
+ upgrade_xml_1_2_rec (node);
+ node = node->next;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+em_upgrade_xml_1_2 (xmlDocPtr doc)
+{
+ xmlNodePtr root;
+
+ if (!doc || !(root = xmlDocGetRootElement (doc)))
+ return TRUE;
+
+ return upgrade_xml_1_2_rec (root);
+}
+
+/* ********************************************************************** */
+/* Tables for converting flat bonobo conf -> gconf xml blob */
+/* ********************************************************************** */
+
+/* Mail/Accounts/ * */
+static e_bconf_map_t cc_map[] = {
+ { "account_always_cc_%i", "always", E_BCONF_MAP_BOOL },
+ { "account_always_cc_addrs_%i", "recipients", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+static e_bconf_map_t bcc_map[] = {
+ { "account_always_cc_%i", "always", E_BCONF_MAP_BOOL },
+ { "account_always_bcc_addrs_%i", "recipients", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+static e_bconf_map_t pgp_map[] = {
+ { "account_pgp_encrypt_to_self_%i", "encrypt-to-self", E_BCONF_MAP_BOOL },
+ { "account_pgp_always_trust_%i", "always-trust", E_BCONF_MAP_BOOL },
+ { "account_pgp_always_sign_%i", "always-sign", E_BCONF_MAP_BOOL },
+ { "account_pgp_no_imip_sign_%i", "no-imip-sign", E_BCONF_MAP_BOOL },
+ { "account_pgp_key_%i", "key-id", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+static e_bconf_map_t smime_map[] = {
+ { "account_smime_encrypt_to_self_%i", "encrypt-to-self", E_BCONF_MAP_BOOL },
+ { "account_smime_always_sign_%i", "always-sign", E_BCONF_MAP_BOOL },
+ { "account_smime_key_%i", "key-id", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+static e_bconf_map_t identity_sig_map[] = {
+ { "identity_autogenerated_signature_%i", "auto", E_BCONF_MAP_BOOL },
+ { "identity_def_signature_%i", "default", E_BCONF_MAP_LONG },
+ { NULL },
+};
+
+static e_bconf_map_t identity_map[] = {
+ { "identity_name_%i", "name", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { "identity_address_%i", "addr-spec", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { "identity_reply_to_%i", "reply-to", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { "identity_organization_%i", "organization", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL, "signature", E_BCONF_MAP_CHILD, identity_sig_map },
+ { NULL },
+};
+
+static e_bconf_map_t source_map[] = {
+ { "source_save_passwd_%i", "save-passwd", E_BCONF_MAP_BOOL },
+ { "source_keep_on_server_%i", "keep-on-server", E_BCONF_MAP_BOOL },
+ { "source_auto_check_%i", "auto-check", E_BCONF_MAP_BOOL },
+ { "source_auto_check_time_%i", "auto-check-timeout", E_BCONF_MAP_LONG },
+ { "source_url_%i", "url", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+static e_bconf_map_t transport_map[] = {
+ { "transport_save_passwd_%i", "save-passwd", E_BCONF_MAP_BOOL },
+ { "transport_url_%i", "url", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+static e_bconf_map_t account_map[] = {
+ { "account_name_%i", "name", E_BCONF_MAP_STRING },
+ { "source_enabled_%i", "enabled", E_BCONF_MAP_BOOL },
+ { NULL, "identity", E_BCONF_MAP_CHILD, identity_map },
+ { NULL, "source", E_BCONF_MAP_CHILD, source_map },
+ { NULL, "transport", E_BCONF_MAP_CHILD, transport_map },
+ { "account_drafts_folder_uri_%i", "drafts-folder", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { "account_sent_folder_uri_%i", "sent-folder", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL, "auto-cc", E_BCONF_MAP_CHILD, cc_map },
+ { NULL, "auto-bcc", E_BCONF_MAP_CHILD, bcc_map },
+ { NULL, "pgp", E_BCONF_MAP_CHILD, pgp_map },
+ { NULL, "smime", E_BCONF_MAP_CHILD, smime_map },
+ { NULL },
+};
+
+/* /Mail/Signatures/ * */
+static e_bconf_map_t signature_format_map[] = {
+ { "text/plain", },
+ { "text/html", },
+ { NULL }
+};
+
+static e_bconf_map_t signature_map[] = {
+ { "name_%i", "name", E_BCONF_MAP_STRING },
+ { "html_%i", "format", E_BCONF_MAP_ENUM, signature_format_map },
+ { "filename_%i", "filename", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { "script_%i", "script", E_BCONF_MAP_STRING|E_BCONF_MAP_CONTENT },
+ { NULL },
+};
+
+/* ********************************************************************** */
+/* Tables for bonobo conf -> gconf conversion */
+/* ********************************************************************** */
+
+static e_gconf_map_t mail_accounts_map[] = {
+ /* /Mail/Accounts - most entries are processed via the xml blob routine */
+ /* This also works because the initial uid mapping is 1:1 with the list order */
+ { "default_account", "mail/default_account", E_GCONF_MAP_SIMPLESTRING },
+ { 0 },
+};
+
+static e_gconf_map_t mail_display_map[] = {
+ /* /Mail/Display */
+ { "side_bar_search", "mail/display/side_bar_search", E_GCONF_MAP_BOOL },
+ { "thread_list", "mail/display/thread_list", E_GCONF_MAP_BOOL },
+ { "thread_subject", "mail/display/thread_subject", E_GCONF_MAP_BOOL },
+ { "hide_deleted", "mail/display/show_deleted", E_GCONF_MAP_BOOLNOT },
+ { "preview_pane", "mail/display/show_preview", E_GCONF_MAP_BOOL },
+ { "paned_size", "mail/display/paned_size", E_GCONF_MAP_INT },
+ { "seen_timeout", "mail/display/mark_seen_timeout", E_GCONF_MAP_INT },
+ { "do_seen_timeout", "mail/display/mark_seen", E_GCONF_MAP_BOOL },
+ { "http_images", "mail/display/load_http_images", E_GCONF_MAP_INT },
+ { "citation_highlight", "mail/display/mark_citations", E_GCONF_MAP_BOOL },
+ { "citation_color", "mail/display/citation_colour", E_GCONF_MAP_COLOUR },
+ { 0 },
+};
+
+static e_gconf_map_t mail_format_map[] = {
+ /* /Mail/Format */
+ { "message_display_style", "mail/display/message_style", E_GCONF_MAP_INT },
+ { "send_html", "mail/composer/send_html", E_GCONF_MAP_BOOL },
+ { "default_reply_style", "mail/format/reply_style", E_GCONF_MAP_INT },
+ { "default_forward_style", "mail/format/forward_style", E_GCONF_MAP_INT },
+ { "default_charset", "mail/composer/charset", E_GCONF_MAP_STRING },
+ { "confirm_unwanted_html", "mail/prompts/unwanted_html", E_GCONF_MAP_BOOL },
+ { 0 },
+};
+
+static e_gconf_map_t mail_trash_map[] = {
+ /* /Mail/Trash */
+ { "empty_on_exit", "mail/trash/empty_on_exit", E_GCONF_MAP_BOOL },
+ { 0 },
+};
+
+static e_gconf_map_t mail_prompts_map[] = {
+ /* /Mail/Prompts */
+ { "confirm_expunge", "mail/prompts/expunge", E_GCONF_MAP_BOOL },
+ { "empty_subject", "mail/prompts/empty_subject", E_GCONF_MAP_BOOL },
+ { "only_bcc", "mail/prompts/only_bcc", E_GCONF_MAP_BOOL },
+ { 0 }
+};
+
+static e_gconf_map_t mail_filters_map[] = {
+ /* /Mail/Filters */
+ { "log", "mail/filters/log", E_GCONF_MAP_BOOL },
+ { "log_path", "mail/filters/logfile", E_GCONF_MAP_STRING },
+ { 0 }
+};
+
+static e_gconf_map_t mail_notify_map[] = {
+ /* /Mail/Notify */
+ { "new_mail_notification", "mail/notify/type", E_GCONF_MAP_INT },
+ { "new_mail_notification_sound_file", "mail/notify/sound", E_GCONF_MAP_STRING },
+ { 0 }
+};
+
+static e_gconf_map_t mail_filesel_map[] = {
+ /* /Mail/Filesel */
+ { "last_filesel_dir", "mail/save_dir", E_GCONF_MAP_STRING },
+ { 0 }
+};
+
+static e_gconf_map_t mail_composer_map[] = {
+ /* /Mail/Composer */
+ { "ViewFrom", "mail/composer/view/From", E_GCONF_MAP_BOOL },
+ { "ViewReplyTo", "mail/composer/view/ReplyTo", E_GCONF_MAP_BOOL },
+ { "ViewCC", "mail/composer/view/Cc", E_GCONF_MAP_BOOL },
+ { "ViewBCC", "mail/composer/view/Bcc", E_GCONF_MAP_BOOL },
+ { "ViewSubject", "mail/composer/view/Subject", E_GCONF_MAP_BOOL },
+ { 0 },
+};
+
+/* ********************************************************************** */
+
+static e_gconf_map_t importer_elm_map[] = {
+ /* /Importer/Elm */
+ { "mail", "importer/elm/mail", E_GCONF_MAP_BOOL },
+ { "mail-imported", "importer/elm/mail-imported", E_GCONF_MAP_BOOL },
+ { 0 },
+};
+
+static e_gconf_map_t importer_pine_map[] = {
+ /* /Importer/Pine */
+ { "mail", "importer/elm/mail", E_GCONF_MAP_BOOL },
+ { "address", "importer/elm/address", E_GCONF_MAP_BOOL },
+ { 0 },
+};
+
+static e_gconf_map_t importer_netscape_map[] = {
+ /* /Importer/Netscape */
+ { "mail", "importer/netscape/mail", E_GCONF_MAP_BOOL },
+ { "settings", "importer/netscape/settings", E_GCONF_MAP_BOOL },
+ { "filters", "importer/netscape/filters", E_GCONF_MAP_BOOL },
+ { 0 },
+};
+
+/* ********************************************************************** */
+
+static e_gconf_map_list_t gconf_remap_list[] = {
+ { "/Mail/Accounts", mail_accounts_map },
+ { "/Mail/Display", mail_display_map },
+ { "/Mail/Format", mail_format_map },
+ { "/Mail/Trash", mail_trash_map },
+ { "/Mail/Prompts", mail_prompts_map },
+ { "/Mail/Filters", mail_filters_map },
+ { "/Mail/Notify", mail_notify_map },
+ { "/Mail/Filesel", mail_filesel_map },
+ { "/Mail/Composer", mail_composer_map },
+
+ { "/Importer/Elm", importer_elm_map },
+ { "/Importer/Pine", importer_pine_map },
+ { "/Importer/Netscape", importer_netscape_map },
+
+ { 0 },
+};
+
+static struct {
+ const gchar *label;
+ const gchar *colour;
+} label_default[5] = {
+ { N_("Important"), "#EF2929" }, /* red */
+ { N_("Work"), "#F57900" }, /* orange */
+ { N_("Personal"), "#4E9A06" }, /* green */
+ { N_("To Do"), "#3465A4" }, /* blue */
+ { N_("Later"), "#75507B" } /* purple */
+};
+
+/* remaps mail config from bconf to gconf */
+static gboolean
+bconf_import(GConfClient *gconf, xmlDocPtr config_xmldb)
+{
+ xmlNodePtr source;
+ gchar labx[16], colx[16];
+ gchar *val, *lab, *col;
+ GSList *list, *l;
+ gint i;
+
+ e_bconf_import(gconf, config_xmldb, gconf_remap_list);
+
+ /* Labels:
+ label string + label colour as integer
+ -> label string:# colour as hex */
+ source = e_bconf_get_path(config_xmldb, "/Mail/Labels");
+ if (source) {
+ list = NULL;
+ for (i = 0; i < 5; i++) {
+ sprintf(labx, "label_%d", i);
+ sprintf(colx, "color_%d", i);
+ lab = e_bconf_get_string(source, labx);
+ if ((col = e_bconf_get_value(source, colx))) {
+ sprintf(colx, "#%06x", atoi(col) & 0xffffff);
+ g_free(col);
+ } else
+ strcpy(colx, label_default[i].colour);
+
+ val = g_strdup_printf("%s:%s", lab ? lab : label_default[i].label, colx);
+ list = g_slist_append(list, val);
+ g_free(lab);
+ }
+
+ gconf_client_set_list(gconf, "/apps/evolution/mail/labels", GCONF_VALUE_STRING, list, NULL);
+ while (list) {
+ l = list->next;
+ g_free(list->data);
+ g_slist_free_1(list);
+ list = l;
+ }
+ } else {
+ g_warning("could not find /Mail/Labels in old config database, skipping");
+ }
+
+ /* Accounts: The flat bonobo-config structure is remapped to a list of xml blobs. Upgrades as necessary */
+ e_bconf_import_xml_blob(gconf, config_xmldb, account_map, "/Mail/Accounts",
+ "/apps/evolution/mail/accounts", "account", "uid");
+
+ /* Same for signatures */
+ e_bconf_import_xml_blob(gconf, config_xmldb, signature_map, "/Mail/Signatures",
+ "/apps/evolution/mail/signatures", "signature", NULL);
+
+ return TRUE;
+}
+
+static gboolean
+em_migrate_1_2(const gchar *data_dir, xmlDocPtr config_xmldb, xmlDocPtr filters, xmlDocPtr vfolders, GError **error)
+{
+ GConfClient *gconf;
+
+ gconf = gconf_client_get_default();
+ bconf_import(gconf, config_xmldb);
+ g_object_unref(gconf);
+
+ em_upgrade_xml_1_2(filters);
+ em_upgrade_xml_1_2(vfolders);
+
+ return TRUE;
+}
+
+/* 1.4 upgrade functions */
+
+#define EM_MIGRATE_SESSION_TYPE (em_migrate_session_get_type ())
+#define EM_MIGRATE_SESSION(obj) (CAMEL_CHECK_CAST((obj), EM_MIGRATE_SESSION_TYPE, EMMigrateSession))
+#define EM_MIGRATE_SESSION_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), EM_MIGRATE_SESSION_TYPE, EMMigrateSessionClass))
+#define EM_MIGRATE_IS_SESSION(o) (CAMEL_CHECK_TYPE((o), EM_MIGRATE_SESSION_TYPE))
+
+typedef struct _EMMigrateSession {
+ CamelSession parent_object;
+
+ CamelStore *store; /* new folder tree store */
+ gchar *srcdir; /* old folder tree path */
+} EMMigrateSession;
+
+typedef struct _EMMigrateSessionClass {
+ CamelSessionClass parent_class;
+
+} EMMigrateSessionClass;
+
+static CamelType em_migrate_session_get_type (void);
+static CamelSession *em_migrate_session_new (const gchar *path);
+
+static void
+class_init (EMMigrateSessionClass *klass)
+{
+ ;
+}
+
+static CamelType
+em_migrate_session_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (
+ camel_session_get_type (),
+ "EMMigrateSession",
+ sizeof (EMMigrateSession),
+ sizeof (EMMigrateSessionClass),
+ (CamelObjectClassInitFunc) class_init,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ return type;
+}
+
+static CamelSession *
+em_migrate_session_new (const gchar *path)
+{
+ CamelSession *session;
+
+ session = CAMEL_SESSION (camel_object_new (EM_MIGRATE_SESSION_TYPE));
+
+ camel_session_construct (session, path);
+
+ return session;
+}
+
+#endif /* !G_OS_WIN32 */
+
+static GtkWidget *window;
+static GtkLabel *label;
+static GtkProgressBar *progress;
+
+static void
+em_migrate_setup_progress_dialog (const gchar *title, const gchar *desc)
+{
+ GtkWidget *vbox, *hbox, *w;
+ gchar *markup;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title ((GtkWindow *) window, _("Migrating..."));
+ gtk_window_set_modal ((GtkWindow *) window, TRUE);
+ gtk_container_set_border_width ((GtkContainer *) window, 6);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_container_add ((GtkContainer *) window, vbox);
+
+ w = gtk_label_new (desc);
+
+ gtk_label_set_line_wrap ((GtkLabel *) w, TRUE);
+ gtk_widget_show (w);
+ gtk_box_pack_start ((GtkBox *) vbox, w, TRUE, TRUE, 0);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start ((GtkBox *) vbox, hbox, TRUE, TRUE, 0);
+
+ label = (GtkLabel *) gtk_label_new ("");
+ gtk_widget_show ((GtkWidget *) label);
+ gtk_box_pack_start ((GtkBox *) hbox, (GtkWidget *) label, TRUE, TRUE, 0);
+
+ progress = (GtkProgressBar *) gtk_progress_bar_new ();
+ gtk_widget_show ((GtkWidget *) progress);
+ gtk_box_pack_start ((GtkBox *) hbox, (GtkWidget *) progress, TRUE, TRUE, 0);
+
+ /* Prepare the message */
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+ w = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
+ markup = g_strconcat ("<big><b>", title ? title : _("Migration"), "</b></big>", NULL);
+ gtk_label_set_markup (GTK_LABEL (w), markup);
+ gtk_box_pack_start (GTK_BOX (vbox), w, TRUE, TRUE, 0);
+ g_free (markup);
+
+ w = gtk_label_new (desc);
+ gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox), w, TRUE, TRUE, 0);
+
+ /* Progress bar */
+ w = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), w, TRUE, TRUE, 0);
+
+ label = GTK_LABEL (gtk_label_new (""));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
+ gtk_label_set_line_wrap (label, TRUE);
+ gtk_widget_show (GTK_WIDGET (label));
+ gtk_box_pack_start (GTK_BOX (w), GTK_WIDGET (label), TRUE, TRUE, 0);
+
+ progress = GTK_PROGRESS_BAR (gtk_progress_bar_new ());
+ gtk_widget_show (GTK_WIDGET (progress));
+ gtk_box_pack_start (GTK_BOX (w), GTK_WIDGET (progress), TRUE, TRUE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), hbox);
+ gtk_widget_show_all (hbox);
+ gtk_widget_show (window);
+}
+
+static void
+em_migrate_close_progress_dialog (void)
+{
+ gtk_widget_destroy ((GtkWidget *) window);
+}
+
+static void
+em_migrate_set_folder_name (const gchar *folder_name)
+{
+ gchar *text;
+
+ text = g_strdup_printf (_("Migrating '%s':"), folder_name);
+ gtk_label_set_text (label, text);
+ g_free (text);
+
+ gtk_progress_bar_set_fraction (progress, 0.0);
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+}
+
+static void
+em_migrate_set_progress (double percent)
+{
+ gchar text[5];
+
+ snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f));
+
+ gtk_progress_bar_set_fraction (progress, percent);
+ gtk_progress_bar_set_text (progress, text);
+
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+}
+
+#ifndef G_OS_WIN32
+
+static gboolean
+is_mail_folder (const gchar *metadata)
+{
+ xmlNodePtr node;
+ xmlDocPtr doc;
+ gchar *type;
+
+ if (!(doc = xmlParseFile (metadata))) {
+ g_warning ("Cannot parse `%s'", metadata);
+ return FALSE;
+ }
+
+ if (!(node = xmlDocGetRootElement (doc))) {
+ g_warning ("`%s' corrupt: document contains no root node", metadata);
+ xmlFreeDoc (doc);
+ return FALSE;
+ }
+
+ if (!node->name || strcmp ((gchar *)node->name, "efolder") != 0) {
+ g_warning ("`%s' corrupt: root node is not 'efolder'", metadata);
+ xmlFreeDoc (doc);
+ return FALSE;
+ }
+
+ node = node->children;
+ while (node != NULL) {
+ if (node->name && !strcmp ((gchar *)node->name, "type")) {
+ type = (gchar *)xmlNodeGetContent (node);
+ if (!strcmp ((gchar *)type, "mail")) {
+ xmlFreeDoc (doc);
+ xmlFree (type);
+
+ return TRUE;
+ }
+
+ xmlFree (type);
+
+ break;
+ }
+
+ node = node->next;
+ }
+
+ xmlFreeDoc (doc);
+
+ return FALSE;
+}
+
+static gboolean
+get_local_et_expanded (const gchar *dirname)
+{
+ xmlNodePtr node;
+ xmlDocPtr doc;
+ struct stat st;
+ gchar *buf, *p;
+ gint thread_list;
+
+ buf = g_strdup_printf ("%s/evolution/config/file:%s", g_get_home_dir (), dirname);
+ p = buf + strlen (g_get_home_dir ()) + strlen ("/evolution/config/file:");
+ e_filename_make_safe (p);
+
+ if (stat (buf, &st) == -1) {
+ g_free (buf);
+ return FALSE;
+ }
+
+ if (!(doc = xmlParseFile (buf))) {
+ g_free (buf);
+ return FALSE;
+ }
+
+ g_free (buf);
+
+ if (!(node = xmlDocGetRootElement (doc)) || strcmp ((gchar *)node->name, "expanded_state") != 0) {
+ xmlFreeDoc (doc);
+ return FALSE;
+ }
+
+ if (!(buf = (gchar *)xmlGetProp (node, (const guchar *)"default"))) {
+ xmlFreeDoc (doc);
+ return FALSE;
+ }
+
+ thread_list = strcmp (buf, "0") == 0 ? 0 : 1;
+ xmlFree (buf);
+
+ xmlFreeDoc (doc);
+
+ return thread_list;
+}
+
+static gchar *
+get_local_store_uri (const gchar *dirname, gchar **namep, gint *indexp)
+{
+ gchar *name, *protocol, *metadata, *tmp;
+ gint index;
+ struct stat st;
+ xmlNodePtr node;
+ xmlDocPtr doc;
+
+ metadata = g_build_filename(dirname, "local-metadata.xml", NULL);
+
+ /* in 1.4, any errors are treated as defaults, this function cannot fail */
+
+ /* defaults */
+ name = (gchar *) "mbox";
+ protocol = (gchar *) "mbox";
+ index = TRUE;
+
+ if (stat (metadata, &st) == -1 || !S_ISREG (st.st_mode))
+ goto nofile;
+
+ doc = xmlParseFile(metadata);
+ if (doc == NULL)
+ goto nofile;
+
+ node = doc->children;
+ if (strcmp((gchar *)node->name, "folderinfo"))
+ goto dodefault;
+
+ for (node = node->children; node; node = node->next) {
+ if (node->name && !strcmp ((gchar *)node->name, "folder")) {
+ tmp = (gchar *)xmlGetProp (node, (const guchar *)"type");
+ if (tmp) {
+ protocol = alloca(strlen(tmp)+1);
+ strcpy(protocol, tmp);
+ xmlFree(tmp);
+ }
+ tmp = (gchar *)xmlGetProp (node, (const guchar *)"name");
+ if (tmp) {
+ name = alloca(strlen(tmp)+1);
+ strcpy(name, tmp);
+ xmlFree(tmp);
+ }
+ tmp = (gchar *)xmlGetProp (node, (const guchar *)"index");
+ if (tmp) {
+ index = atoi(tmp);
+ xmlFree(tmp);
+ }
+ }
+ }
+dodefault:
+ xmlFreeDoc (doc);
+nofile:
+ g_free(metadata);
+
+ *namep = g_strdup(name);
+ *indexp = index;
+
+ return g_strdup_printf("%s:%s", protocol, dirname);
+}
+
+#endif /* !G_OS_WIN32 */
+
+enum {
+ CP_UNIQUE = 0,
+ CP_OVERWRITE,
+ CP_APPEND
+};
+
+static gint open_flags[3] = {
+ O_WRONLY | O_CREAT | O_TRUNC,
+ O_WRONLY | O_CREAT | O_TRUNC,
+ O_WRONLY | O_CREAT | O_APPEND,
+};
+
+static gboolean
+cp (const gchar *src, const gchar *dest, gboolean show_progress, gint mode)
+{
+ guchar readbuf[65536];
+ gssize nread, nwritten;
+ gint errnosav, readfd, writefd;
+ gsize total = 0;
+ struct stat st;
+ struct utimbuf ut;
+
+ /* if the dest file exists and has content, abort - we don't
+ * want to corrupt their existing data */
+ if (g_stat (dest, &st) == 0 && st.st_size > 0 && mode == CP_UNIQUE) {
+ errno = EEXIST;
+ return FALSE;
+ }
+
+ if (g_stat (src, &st) == -1
+ || (readfd = g_open (src, O_RDONLY | O_BINARY, 0)) == -1)
+ return FALSE;
+
+ if ((writefd = g_open (dest, open_flags[mode] | O_BINARY, 0666)) == -1) {
+ errnosav = errno;
+ close (readfd);
+ errno = errnosav;
+ return FALSE;
+ }
+
+ do {
+ do {
+ nread = read (readfd, readbuf, sizeof (readbuf));
+ } while (nread == -1 && errno == EINTR);
+
+ if (nread == 0)
+ break;
+ else if (nread < 0)
+ goto exception;
+
+ do {
+ nwritten = write (writefd, readbuf, nread);
+ } while (nwritten == -1 && errno == EINTR);
+
+ if (nwritten < nread)
+ goto exception;
+
+ total += nwritten;
+ if (show_progress)
+ em_migrate_set_progress (((double) total) / ((double) st.st_size));
+ } while (total < st.st_size);
+
+ if (fsync (writefd) == -1)
+ goto exception;
+
+ close (readfd);
+ if (close (writefd) == -1)
+ goto failclose;
+
+ ut.actime = st.st_atime;
+ ut.modtime = st.st_mtime;
+ utime (dest, &ut);
+ chmod (dest, st.st_mode);
+
+ return TRUE;
+
+ exception:
+
+ errnosav = errno;
+ close (readfd);
+ close (writefd);
+ errno = errnosav;
+
+ failclose:
+
+ errnosav = errno;
+ unlink (dest);
+ errno = errnosav;
+
+ return FALSE;
+}
+
+#ifndef G_OS_WIN32
+
+static gboolean
+cp_r (const gchar *src, const gchar *dest, const gchar *pattern, gint mode)
+{
+ GString *srcpath, *destpath;
+ struct dirent *dent;
+ gsize slen, dlen;
+ struct stat st;
+ DIR *dir;
+
+ if (g_mkdir_with_parents (dest, 0777) == -1)
+ return FALSE;
+
+ if (!(dir = opendir (src)))
+ return FALSE;
+
+ srcpath = g_string_new (src);
+ g_string_append_c (srcpath, '/');
+ slen = srcpath->len;
+
+ destpath = g_string_new (dest);
+ g_string_append_c (destpath, '/');
+ dlen = destpath->len;
+
+ while ((dent = readdir (dir))) {
+ if (!strcmp (dent->d_name, ".") || !strcmp (dent->d_name, ".."))
+ continue;
+
+ g_string_truncate (srcpath, slen);
+ g_string_truncate (destpath, dlen);
+
+ g_string_append (srcpath, dent->d_name);
+ g_string_append (destpath, dent->d_name);
+
+ if (stat (srcpath->str, &st) == -1)
+ continue;
+
+ if (S_ISDIR (st.st_mode)) {
+ cp_r (srcpath->str, destpath->str, pattern, mode);
+ } else if (!pattern || !strcmp (dent->d_name, pattern)) {
+ cp (srcpath->str, destpath->str, FALSE, mode);
+ }
+ }
+
+ closedir (dir);
+
+ g_string_free (destpath, TRUE);
+ g_string_free (srcpath, TRUE);
+
+ return TRUE;
+}
+
+static void
+mbox_build_filename (GString *path, const gchar *toplevel_dir, const gchar *full_name)
+{
+ const gchar *start, *inptr = full_name;
+ gint subdirs = 0;
+
+ while (*inptr != '\0') {
+ if (*inptr == '/')
+ subdirs++;
+ inptr++;
+ }
+
+ g_string_assign(path, toplevel_dir);
+ g_string_append_c (path, '/');
+
+ inptr = full_name;
+ while (*inptr != '\0') {
+ start = inptr;
+ while (*inptr != '/' && *inptr != '\0')
+ inptr++;
+
+ g_string_append_len (path, start, inptr - start);
+
+ if (*inptr == '/') {
+ g_string_append (path, ".sbd/");
+ inptr++;
+
+ /* strip extranaeous '/'s */
+ while (*inptr == '/')
+ inptr++;
+ }
+ }
+}
+
+static gboolean
+em_migrate_folder(EMMigrateSession *session, const gchar *dirname, const gchar *full_name, GError **error)
+{
+ CamelFolder *old_folder = NULL, *new_folder = NULL;
+ CamelStore *local_store = NULL;
+ CamelException ex;
+ gchar *name, *uri;
+ GPtrArray *uids;
+ struct stat st;
+ gboolean thread_list;
+ gint index, i;
+ GString *src, *dest;
+ gboolean success = FALSE;
+
+ camel_exception_init (&ex);
+
+ src = g_string_new("");
+
+ g_string_printf(src, "%s/folder-metadata.xml", dirname);
+ if (stat (src->str, &st) == -1
+ || !S_ISREG (st.st_mode)
+ || !is_mail_folder(src->str)) {
+ /* Not an evolution mail folder */
+ g_string_free(src, TRUE);
+ return TRUE;
+ }
+
+ dest = g_string_new("");
+ uri = get_local_store_uri(dirname, &name, &index);
+ em_migrate_set_folder_name (full_name);
+ thread_list = get_local_et_expanded (dirname);
+
+ /* Manually copy local mbox files, its much faster */
+ if (!strncmp (uri, "mbox:", 5)) {
+ static const gchar *meta_ext[] = { ".summary", ".ibex.index", ".ibex.index.data" };
+ gsize slen, dlen;
+ FILE *fp;
+ gchar *p;
+ gint mode;
+
+ g_string_printf (src, "%s/%s", uri + 5, name);
+ mbox_build_filename (dest, ((CamelService *)session->store)->url->path, full_name);
+ p = strrchr (dest->str, '/');
+ *p = '\0';
+
+ slen = src->len;
+ dlen = dest->len;
+
+ if (g_mkdir_with_parents (dest->str, 0777) == -1 && errno != EEXIST) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to create new folder `%s': %s"),
+ dest->str, g_strerror (errno));
+ goto fatal;
+ }
+
+ *p = '/';
+ mode = CP_UNIQUE;
+ retry_copy:
+ if (!cp (src->str, dest->str, TRUE, mode)) {
+ if (errno == EEXIST) {
+ gint save = errno;
+
+ switch (e_error_run(NULL, "mail:ask-migrate-existing", src->str, dest->str, NULL)) {
+ case GTK_RESPONSE_ACCEPT:
+ mode = CP_OVERWRITE;
+ goto retry_copy;
+ case GTK_RESPONSE_OK:
+ mode = CP_APPEND;
+ goto retry_copy;
+ case GTK_RESPONSE_REJECT:
+ goto ignore;
+ }
+
+ errno = save;
+ }
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to copy folder `%s' to `%s': %s"),
+ src->str, dest->str, g_strerror (errno));
+ goto fatal;
+ }
+ ignore:
+
+ /* create a .cmeta file specifying to index and/or thread the folder */
+ g_string_truncate (dest, dlen);
+ g_string_append (dest, ".cmeta");
+ if ((fp = fopen (dest->str, "w")) != NULL) {
+ gint fd = fileno (fp);
+
+ /* write the magic string */
+ if (fwrite ("CLMD", 4, 1, fp) != 1)
+ goto cmeta_err;
+
+ /* write the version (1) */
+ if (camel_file_util_encode_uint32 (fp, 1) == -1)
+ goto cmeta_err;
+
+ /* write the meta count */
+ if (camel_file_util_encode_uint32 (fp, thread_list ? 1 : 0) == -1)
+ goto cmeta_err;
+
+ if (!thread_list) {
+ if (camel_file_util_encode_string (fp, "evolution:thread_list") == -1)
+ goto cmeta_err;
+
+ if (camel_file_util_encode_string (fp, !thread_list ? "1" : "0") == -1)
+ goto cmeta_err;
+ }
+
+ /* write the prop count (only prop is the index prop) */
+ if (camel_file_util_encode_uint32 (fp, 1) == -1)
+ goto cmeta_err;
+
+ /* write the index prop tag (== CAMEL_FOLDER_ARG_LAST|CAMEL_ARG_BOO) */
+ if (camel_file_util_encode_uint32 (fp, CAMEL_FOLDER_ARG_LAST|CAMEL_ARG_BOO) == -1)
+ goto cmeta_err;
+
+ /* write the index prop value */
+ if (camel_file_util_encode_uint32 (fp, 1) == -1)
+ goto cmeta_err;
+
+ fflush (fp);
+
+ if (fsync (fd) == -1) {
+ cmeta_err:
+ fclose (fp);
+ unlink (dest->str);
+ } else {
+ fclose (fp);
+ }
+ }
+
+ /* copy over the metadata files */
+ for (i = 0; i < sizeof(meta_ext)/sizeof(meta_ext[0]); i++) {
+ g_string_truncate (src, slen);
+ g_string_truncate (dest, dlen);
+
+ g_string_append (src, meta_ext[i]);
+ g_string_append (dest, meta_ext[i]);
+ cp (src->str, dest->str, FALSE, CP_OVERWRITE);
+ }
+ } else {
+ guint32 flags = CAMEL_STORE_FOLDER_CREATE;
+
+ if (!(local_store = camel_session_get_store ((CamelSession *) session, uri, &ex))
+ || !(old_folder = camel_store_get_folder (local_store, name, 0, &ex)))
+ goto fatal;
+
+ flags |= (index ? CAMEL_STORE_FOLDER_BODY_INDEX : 0);
+ if (!(new_folder = camel_store_get_folder (session->store, full_name, flags, &ex)))
+ goto fatal;
+
+ if (!thread_list) {
+ camel_object_meta_set (new_folder, "evolution:thread_list", !thread_list ? "1" : "0");
+ camel_object_state_write (new_folder);
+ }
+
+ uids = camel_folder_get_uids (old_folder);
+ for (i = 0; i < uids->len; i++) {
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+
+ if (!(info = camel_folder_get_message_info (old_folder, uids->pdata[i])))
+ continue;
+
+ if (!(message = camel_folder_get_message (old_folder, uids->pdata[i], &ex))) {
+ camel_folder_free_message_info (old_folder, info);
+ camel_folder_free_uids (old_folder, uids);
+ goto fatal;
+ }
+
+ camel_folder_append_message (new_folder, message, info, NULL, &ex);
+ camel_folder_free_message_info (old_folder, info);
+ camel_object_unref (message);
+
+ if (camel_exception_is_set (&ex))
+ break;
+
+ em_migrate_set_progress (((double) i + 1) / ((double) uids->len));
+ }
+
+ camel_folder_free_uids (old_folder, uids);
+
+ if (camel_exception_is_set (&ex))
+ goto fatal;
+ }
+ success = TRUE;
+fatal:
+ g_free (uri);
+ g_free (name);
+ g_string_free(src, TRUE);
+ g_string_free(dest, TRUE);
+ if (local_store)
+ camel_object_unref(local_store);
+ if (old_folder)
+ camel_object_unref(old_folder);
+ if (new_folder)
+ camel_object_unref(new_folder);
+
+ if (camel_exception_is_set (&ex)) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ "%s", camel_exception_get_description (&ex));
+ camel_exception_clear (&ex);
+ }
+
+ return success;
+}
+
+static gboolean
+em_migrate_dir (EMMigrateSession *session, const gchar *dirname, const gchar *full_name, GError **error)
+{
+ gchar *path;
+ DIR *dir;
+ struct stat st;
+ struct dirent *dent;
+ gboolean success = TRUE;
+
+ if (!em_migrate_folder(session, dirname, full_name, error))
+ return FALSE;
+
+ /* no subfolders, not readable, don't care */
+ path = g_strdup_printf ("%s/subfolders", dirname);
+ if (stat (path, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_free (path);
+ return TRUE;
+ }
+
+ if (!(dir = opendir (path))) {
+ g_free (path);
+ return TRUE;
+ }
+
+ while (success && (dent = readdir (dir))) {
+ gchar *full_path;
+ gchar *name;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ full_path = g_strdup_printf ("%s/%s", path, dent->d_name);
+ if (stat (full_path, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_free (full_path);
+ continue;
+ }
+
+ name = g_strdup_printf ("%s/%s", full_name, dent->d_name);
+ success = em_migrate_dir (session, full_path, name, error);
+ g_free (full_path);
+ g_free (name);
+ }
+
+ closedir (dir);
+
+ g_free (path);
+
+ return success;
+}
+
+static gboolean
+em_migrate_local_folders_1_4 (EMMigrateSession *session, GError **error)
+{
+ struct dirent *dent;
+ struct stat st;
+ DIR *dir;
+ gboolean success = TRUE;
+
+ if (!(dir = opendir (session->srcdir))) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to scan for existing mailboxes at "
+ "`%s': %s"), session->srcdir, g_strerror (errno));
+ return FALSE;
+ }
+
+ em_migrate_setup_progress_dialog (
+ _("Migrating Folders"),
+ _("The location and hierarchy of the Evolution mailbox "
+ "folders has changed since Evolution 1.x.\n\nPlease be "
+ "patient while Evolution migrates your folders..."));
+
+ while (success && (dent = readdir (dir))) {
+ gchar *full_path;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ full_path = g_strdup_printf ("%s/%s", session->srcdir, dent->d_name);
+ if (stat (full_path, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_free (full_path);
+ continue;
+ }
+
+ success = em_migrate_dir (session, full_path, dent->d_name, error);
+ g_free (full_path);
+ }
+
+ closedir (dir);
+
+ em_migrate_close_progress_dialog ();
+
+ return success;
+}
+
+static gchar *
+upgrade_xml_uris_1_4 (const gchar *uri)
+{
+ gchar *path, *prefix, *p;
+ CamelURL *url;
+
+ if (!strncmp (uri, "file:", 5)) {
+ url = camel_url_new (uri, NULL);
+ camel_url_set_protocol (url, "email");
+ camel_url_set_user (url, "local");
+ camel_url_set_host (url, "local");
+
+ prefix = g_build_filename (g_get_home_dir (), "evolution", "local", NULL);
+ if (strncmp (url->path, prefix, strlen (prefix)) != 0) {
+ /* uri is busticated - user probably copied from another user's home directory */
+ camel_url_free (url);
+ g_free (prefix);
+
+ return g_strdup (uri);
+ }
+ path = g_strdup (url->path + strlen (prefix));
+ g_free (prefix);
+
+ /* modify the path in-place */
+ p = path + strlen (path) - 12;
+ while (p > path) {
+ if (!strncmp (p, "/subfolders/", 12))
+ memmove (p, p + 11, strlen (p + 11) + 1);
+
+ p--;
+ }
+
+ camel_url_set_path (url, path);
+ g_free (path);
+
+ path = camel_url_to_string (url, 0);
+ camel_url_free (url);
+
+ return path;
+ } else {
+ return em_uri_from_camel (uri);
+ }
+}
+
+static void
+upgrade_vfolder_sources_1_4 (xmlDocPtr doc)
+{
+ xmlNodePtr root, node;
+
+ if (!doc || !(root = xmlDocGetRootElement (doc)))
+ return;
+
+ if (!root->name || strcmp ((gchar *)root->name, "filteroptions") != 0) {
+ /* root node is not <filteroptions>, nothing to upgrade */
+ return;
+ }
+
+ if (!(node = xml_find_node (root, "ruleset"))) {
+ /* no ruleset node, nothing to upgrade */
+ return;
+ }
+
+ node = node->children;
+ while (node != NULL) {
+ if (node->name && !strcmp ((gchar *)node->name, "rule")) {
+ xmlNodePtr sources;
+ gchar *src;
+
+ if (!(src = (gchar *)xmlGetProp (node, (const guchar *)"source")))
+ src = (gchar *)xmlStrdup ((const guchar *)"local"); /* default to all local folders? */
+
+ xmlSetProp (node, (const guchar *)"source", (const guchar *)"incoming");
+
+ if (!(sources = xml_find_node (node, "sources")))
+ sources = xmlNewChild (node, NULL, (const guchar *)"sources", NULL);
+
+ xmlSetProp (sources, (const guchar *)"with", (guchar *)src);
+ xmlFree (src);
+ }
+
+ node = node->next;
+ }
+}
+
+static gchar *
+get_nth_sig (gint id)
+{
+ ESignatureList *list;
+ ESignature *sig;
+ EIterator *iter;
+ gchar *uid = NULL;
+ gint i = 0;
+
+ list = e_get_signature_list ();
+ iter = e_list_get_iterator ((EList *) list);
+
+ while (e_iterator_is_valid (iter) && i < id) {
+ e_iterator_next (iter);
+ i++;
+ }
+
+ if (i == id && e_iterator_is_valid (iter)) {
+ sig = (ESignature *) e_iterator_get (iter);
+ uid = g_strdup (e_signature_get_uid (sig));
+ }
+
+ g_object_unref (iter);
+
+ return uid;
+}
+
+static void
+em_upgrade_accounts_1_4 (void)
+{
+ EAccountList *accounts;
+ EIterator *iter;
+
+ if (!(accounts = e_get_account_list ()))
+ return;
+
+ iter = e_list_get_iterator ((EList *) accounts);
+ while (e_iterator_is_valid (iter)) {
+ EAccount *account = (EAccount *) e_iterator_get (iter);
+ gchar *url;
+
+ if (account->drafts_folder_uri) {
+ url = upgrade_xml_uris_1_4 (account->drafts_folder_uri);
+ g_free (account->drafts_folder_uri);
+ account->drafts_folder_uri = url;
+ }
+
+ if (account->sent_folder_uri) {
+ url = upgrade_xml_uris_1_4 (account->sent_folder_uri);
+ g_free (account->sent_folder_uri);
+ account->sent_folder_uri = url;
+ }
+
+ if (account->id->sig_uid && !strncmp (account->id->sig_uid, "::", 2)) {
+ gint sig_id;
+
+ sig_id = strtol (account->id->sig_uid + 2, NULL, 10);
+ g_free (account->id->sig_uid);
+ account->id->sig_uid = get_nth_sig (sig_id);
+ }
+
+ e_iterator_next (iter);
+ }
+
+ g_object_unref (iter);
+
+ e_account_list_save (accounts);
+}
+
+static gboolean
+em_migrate_pop_uid_caches_1_4 (const gchar *data_dir, GError **error)
+{
+ GString *oldpath, *newpath;
+ struct dirent *dent;
+ gsize olen, nlen;
+ gchar *cache_dir;
+ DIR *dir;
+ gboolean success = TRUE;
+
+ /* Sigh, too many unique strings to translate, for cases which shouldn't ever happen */
+
+ /* open the old cache dir */
+ cache_dir = g_build_filename (g_get_home_dir (), "evolution", "mail", "pop3", NULL);
+ if (!(dir = opendir (cache_dir))) {
+ if (errno == ENOENT) {
+ g_free(cache_dir);
+ return TRUE;
+ }
+
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to open old POP keep-on-server data "
+ "`%s': %s"), cache_dir, g_strerror (errno));
+ g_free (cache_dir);
+ return FALSE;
+ }
+
+ oldpath = g_string_new (cache_dir);
+ g_string_append_c (oldpath, '/');
+ olen = oldpath->len;
+ g_free (cache_dir);
+
+ cache_dir = g_build_filename (data_dir, "pop", NULL);
+ if (g_mkdir_with_parents (cache_dir, 0777) == -1) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to create POP3 keep-on-server data "
+ "directory `%s': %s"), cache_dir,
+ g_strerror (errno));
+ g_string_free (oldpath, TRUE);
+ g_free (cache_dir);
+ closedir (dir);
+ return FALSE;
+ }
+
+ newpath = g_string_new (cache_dir);
+ g_string_append_c (newpath, '/');
+ nlen = newpath->len;
+ g_free (cache_dir);
+
+ while (success && (dent = readdir (dir))) {
+ if (strncmp (dent->d_name, "cache-pop:__", 12) != 0)
+ continue;
+
+ g_string_truncate (oldpath, olen);
+ g_string_truncate (newpath, nlen);
+
+ g_string_append (oldpath, dent->d_name);
+ g_string_append (newpath, dent->d_name + 12);
+
+ /* strip the trailing '_' */
+ g_string_truncate (newpath, newpath->len - 1);
+
+ if (g_mkdir_with_parents (newpath->str, 0777) == -1
+ || !cp(oldpath->str, (g_string_append(newpath, "/uid-cache"))->str, FALSE, CP_UNIQUE)) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to copy POP3 keep-on-server data "
+ "`%s': %s"), oldpath->str,
+ g_strerror (errno));
+ success = FALSE;
+ }
+
+ }
+
+ g_string_free (oldpath, TRUE);
+ g_string_free (newpath, TRUE);
+
+ closedir (dir);
+
+ return success;
+}
+
+static gboolean
+em_migrate_imap_caches_1_4 (const gchar *data_dir, GError **error)
+{
+ gchar *src, *dest;
+ struct stat st;
+
+ src = g_build_filename (g_get_home_dir (), "evolution", "mail", "imap", NULL);
+ if (stat (src, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_free (src);
+ return TRUE;
+ }
+
+ dest = g_build_filename (data_dir, "imap", NULL);
+
+ /* we don't care if this fails, it's only a cache... */
+ cp_r (src, dest, "summary", CP_OVERWRITE);
+
+ g_free (dest);
+ g_free (src);
+
+ return TRUE;
+}
+
+static gboolean
+em_migrate_folder_expand_state_1_4 (const gchar *data_dir, GError **error)
+{
+ GString *srcpath, *destpath;
+ gsize slen, dlen, rlen;
+ gchar *evo14_mbox_root;
+ struct dirent *dent;
+ struct stat st;
+ DIR *dir;
+
+ srcpath = g_string_new (g_get_home_dir ());
+ g_string_append (srcpath, "/evolution/config");
+ if (stat (srcpath->str, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_string_free (srcpath, TRUE);
+ return TRUE;
+ }
+
+ destpath = g_string_new (data_dir);
+ g_string_append (destpath, "/config");
+ if (g_mkdir_with_parents (destpath->str, 0777) == -1 || !(dir = opendir (srcpath->str))) {
+ g_string_free (destpath, TRUE);
+ g_string_free (srcpath, TRUE);
+ return TRUE;
+ }
+
+ g_string_append (srcpath, "/et-expanded-");
+ slen = srcpath->len;
+ g_string_append (destpath, "/et-expanded-");
+ dlen = destpath->len;
+
+ evo14_mbox_root = g_build_filename (g_get_home_dir (), "evolution", "local", NULL);
+ e_filename_make_safe (evo14_mbox_root);
+ rlen = strlen (evo14_mbox_root);
+ evo14_mbox_root = g_realloc (evo14_mbox_root, rlen + 2);
+ evo14_mbox_root[rlen++] = '_';
+ evo14_mbox_root[rlen] = '\0';
+
+ while ((dent = readdir (dir))) {
+ gchar *full_name, *inptr, *buf = NULL;
+ const gchar *filename;
+ GString *new;
+
+ if (strncmp (dent->d_name, "et-expanded-", 12) != 0)
+ continue;
+
+ if (!strncmp (dent->d_name + 12, "file:", 5)) {
+ /* need to munge the filename */
+ inptr = dent->d_name + 17;
+
+ if (!strncmp (inptr, evo14_mbox_root, rlen)) {
+ /* this should always be the case afaik... */
+ inptr += rlen;
+ new = g_string_new ("mbox:");
+ g_string_append_printf (new, "%s/local#", data_dir);
+
+ full_name = g_strdup (inptr);
+ inptr = full_name + strlen (full_name) - 12;
+ while (inptr > full_name) {
+ if (!strncmp (inptr, "_subfolders_", 12))
+ memmove (inptr, inptr + 11, strlen (inptr + 11) + 1);
+
+ inptr--;
+ }
+
+ g_string_append (new, full_name);
+ g_free (full_name);
+
+ filename = buf = new->str;
+ g_string_free (new, FALSE);
+ e_filename_make_safe (buf);
+ } else {
+ /* but just in case... */
+ filename = dent->d_name + 12;
+ }
+ } else {
+ /* no munging needed */
+ filename = dent->d_name + 12;
+ }
+
+ g_string_append (srcpath, dent->d_name + 12);
+ g_string_append (destpath, filename);
+ g_free (buf);
+
+ cp (srcpath->str, destpath->str, FALSE, CP_UNIQUE);
+
+ g_string_truncate (srcpath, slen);
+ g_string_truncate (destpath, dlen);
+ }
+
+ closedir (dir);
+
+ g_free (evo14_mbox_root);
+ g_string_free (destpath, TRUE);
+ g_string_free (srcpath, TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+em_migrate_folder_view_settings_1_4 (const gchar *data_dir, GError **error)
+{
+ GString *srcpath, *destpath;
+ gsize slen, dlen, rlen;
+ gchar *evo14_mbox_root;
+ struct dirent *dent;
+ struct stat st;
+ DIR *dir;
+
+ srcpath = g_string_new (g_get_home_dir ());
+ g_string_append (srcpath, "/evolution/views/mail");
+ if (stat (srcpath->str, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_string_free (srcpath, TRUE);
+ return TRUE;
+ }
+
+ destpath = g_string_new (data_dir);
+ g_string_append (destpath, "/views");
+ if (g_mkdir_with_parents (destpath->str, 0777) == -1 || !(dir = opendir (srcpath->str))) {
+ g_string_free (destpath, TRUE);
+ g_string_free (srcpath, TRUE);
+ return TRUE;
+ }
+
+ g_string_append_c (srcpath, '/');
+ slen = srcpath->len;
+ g_string_append_c (destpath, '/');
+ dlen = destpath->len;
+
+ evo14_mbox_root = g_build_filename (g_get_home_dir (), "evolution", "local", NULL);
+ e_filename_make_safe (evo14_mbox_root);
+ rlen = strlen (evo14_mbox_root);
+ evo14_mbox_root = g_realloc (evo14_mbox_root, rlen + 2);
+ evo14_mbox_root[rlen++] = '_';
+ evo14_mbox_root[rlen] = '\0';
+
+ while ((dent = readdir (dir))) {
+ gchar *full_name, *inptr, *buf = NULL;
+ const gchar *filename, *ext;
+ gsize prelen = 0;
+ GString *new;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ if (!(ext = strrchr (dent->d_name, '.')))
+ continue;
+
+ if (!strcmp (ext, ".galview") || !strcmp ((gchar *)dent->d_name, "galview.xml")) {
+ /* just copy the file */
+ filename = dent->d_name;
+ goto copy;
+ } else if (strcmp (ext, ".xml") != 0) {
+ continue;
+ }
+
+ if (!strncmp ((const gchar *)dent->d_name, "current_view-", 13)) {
+ prelen = 13;
+ } else if (!strncmp ((const gchar *)dent->d_name, "custom_view-", 12)) {
+ prelen = 12;
+ } else {
+ /* huh? wtf is this file? */
+ continue;
+ }
+
+ if (!strncmp (dent->d_name + prelen, "file:", 5)) {
+ /* need to munge the filename */
+ inptr = dent->d_name + prelen + 5;
+
+ if (!strncmp (inptr, evo14_mbox_root, rlen)) {
+ /* this should always be the case afaik... */
+ inptr += rlen;
+ new = g_string_new ("mbox:");
+ g_string_append_printf (new, "%s/local#", data_dir);
+
+ full_name = g_strdup (inptr);
+ inptr = full_name + strlen (full_name) - 12;
+ while (inptr > full_name) {
+ if (!strncmp (inptr, "_subfolders_", 12))
+ memmove (inptr, inptr + 11, strlen (inptr + 11) + 1);
+
+ inptr--;
+ }
+
+ g_string_append (new, full_name);
+ g_free (full_name);
+
+ filename = buf = new->str;
+ g_string_free (new, FALSE);
+ e_filename_make_safe (buf);
+ } else {
+ /* but just in case... */
+ filename = dent->d_name + prelen;
+ }
+ } else {
+ /* no munging needed */
+ filename = dent->d_name + prelen;
+ }
+
+ copy:
+ g_string_append (srcpath, dent->d_name);
+ if (prelen > 0)
+ g_string_append_len (destpath, dent->d_name, prelen);
+ g_string_append (destpath, filename);
+ g_free (buf);
+
+ cp (srcpath->str, destpath->str, FALSE, CP_UNIQUE);
+
+ g_string_truncate (srcpath, slen);
+ g_string_truncate (destpath, dlen);
+ }
+
+ closedir (dir);
+
+ g_free (evo14_mbox_root);
+ g_string_free (destpath, TRUE);
+ g_string_free (srcpath, TRUE);
+
+ return TRUE;
+}
+
+#define SUBFOLDER_DIR_NAME "subfolders"
+#define SUBFOLDER_DIR_NAME_LEN 10
+
+static gchar *
+e_path_to_physical (const gchar *prefix, const gchar *vpath)
+{
+ const gchar *p, *newp;
+ gchar *dp;
+ gchar *ppath;
+ gint ppath_len;
+ gint prefix_len;
+
+ while (*vpath == '/')
+ vpath++;
+ if (!prefix)
+ prefix = "";
+
+ /* Calculate the length of the real path. */
+ ppath_len = strlen (vpath);
+ ppath_len++; /* For the ending zero. */
+
+ prefix_len = strlen (prefix);
+ ppath_len += prefix_len;
+ ppath_len++; /* For the separating slash. */
+
+ /* Take account of the fact that we need to translate every
+ * separator into `subfolders/'.
+ */
+ p = vpath;
+ while (1) {
+ newp = strchr (p, '/');
+ if (newp == NULL)
+ break;
+
+ ppath_len += SUBFOLDER_DIR_NAME_LEN;
+ ppath_len++; /* For the separating slash. */
+
+ /* Skip consecutive slashes. */
+ while (*newp == '/')
+ newp++;
+
+ p = newp;
+ };
+
+ ppath = g_malloc (ppath_len);
+ dp = ppath;
+
+ memcpy (dp, prefix, prefix_len);
+ dp += prefix_len;
+ *(dp++) = '/';
+
+ /* Copy the mangled path. */
+ p = vpath;
+ while (1) {
+ newp = strchr (p, '/');
+ if (newp == NULL) {
+ strcpy (dp, p);
+ break;
+ }
+
+ memcpy (dp, p, newp - p + 1); /* `+ 1' to copy the slash too. */
+ dp += newp - p + 1;
+
+ memcpy (dp, SUBFOLDER_DIR_NAME, SUBFOLDER_DIR_NAME_LEN);
+ dp += SUBFOLDER_DIR_NAME_LEN;
+
+ *(dp++) = '/';
+
+ /* Skip consecutive slashes. */
+ while (*newp == '/')
+ newp++;
+
+ p = newp;
+ }
+
+ return ppath;
+}
+
+static gboolean
+em_migrate_imap_cmeta_1_4(const gchar *data_dir, GError **error)
+{
+ GConfClient *gconf;
+ GSList *paths, *p;
+ EAccountList *accounts;
+ const EAccount *account;
+
+ if (!(accounts = e_get_account_list ()))
+ return TRUE;
+
+ gconf = gconf_client_get_default();
+ paths = gconf_client_get_list(gconf, "/apps/evolution/shell/offline/folder_paths", GCONF_VALUE_STRING, NULL);
+ for (p = paths;p;p = g_slist_next(p)) {
+ gchar *name, *path;
+
+ name = p->data;
+ if (*name)
+ name++;
+ path = strchr(name, '/');
+ if (path) {
+ *path++ = 0;
+ account = e_account_list_find(accounts, E_ACCOUNT_FIND_NAME, name);
+ if (account && !strncmp(account->source->url, "imap:", 5)) {
+ CamelURL *url = camel_url_new(account->source->url, NULL);
+
+ if (url) {
+ gchar *dir, *base;
+
+ base = g_strdup_printf("%s/imap/%s@%s/folders",
+ data_dir,
+ url->user?url->user:"",
+ url->host?url->host:"");
+
+ dir = e_path_to_physical(base, path);
+ if (g_mkdir_with_parents(dir, 0777) == 0) {
+ gchar *cmeta;
+ FILE *fp;
+
+ cmeta = g_build_filename(dir, "cmeta", NULL);
+ fp = fopen(cmeta, "w");
+ if (fp) {
+ /* header/version */
+ fwrite("CLMD", 4, 1, fp);
+ camel_file_util_encode_uint32(fp, 1);
+ /* meta count, do we have any metadata? */
+ camel_file_util_encode_uint32(fp, 0);
+ /* prop count */
+ camel_file_util_encode_uint32(fp, 1);
+ /* sync offline property */
+ camel_file_util_encode_uint32(fp, CAMEL_DISCO_FOLDER_OFFLINE_SYNC);
+ camel_file_util_encode_uint32(fp, 1);
+ fclose(fp);
+ } else {
+ g_warning("couldn't create imap folder cmeta file '%s'", cmeta);
+ }
+ g_free(cmeta);
+ } else {
+ g_warning("couldn't create imap folder directory '%s'", dir);
+ }
+ g_free(dir);
+ g_free(base);
+ camel_url_free(url);
+ }
+ } else
+ g_warning("can't find offline folder '%s' '%s'", name, path);
+ }
+ g_free(p->data);
+ }
+ g_slist_free(paths);
+ g_object_unref(gconf);
+
+ /* we couldn't care less if this doesn't work */
+
+ return TRUE;
+}
+
+static void
+remove_system_searches(xmlDocPtr searches)
+{
+ xmlNodePtr node;
+
+ /* in pre 2.0, system searches were stored in the user
+ * searches.xml file with the source set to 'demand'. In 2.0+
+ * the system searches are stored in the system
+ * searchtypes.xml file instead */
+
+ node = xmlDocGetRootElement(searches);
+ if (!node->name || strcmp((gchar *)node->name, "filteroptions"))
+ return;
+
+ if (!(node = xml_find_node(node, "ruleset")))
+ return;
+
+ node = node->children;
+ while (node != NULL) {
+ xmlNodePtr nnode = node->next;
+
+ if (node->name && !strcmp ((gchar *)node->name, "rule")) {
+ gchar *src;
+
+ src = (gchar *)xmlGetProp(node, (guchar *)"source");
+ if (src && !strcmp((gchar *)src, "demand")) {
+ xmlUnlinkNode(node);
+ xmlFreeNodeList(node);
+ }
+ xmlFree (src);
+ }
+
+ node = nnode;
+ }
+}
+
+static gboolean
+em_migrate_1_4 (const gchar *data_dir, xmlDocPtr filters, xmlDocPtr vfolders, GError **error)
+{
+ EMMigrateSession *session;
+ CamelException lex;
+ struct stat st;
+ gchar *path;
+ xmlDocPtr searches;
+
+ camel_init (data_dir, TRUE);
+ camel_provider_init();
+ session = (EMMigrateSession *) em_migrate_session_new (data_dir);
+
+ session->srcdir = g_build_filename (g_get_home_dir (), "evolution", "local", NULL);
+
+ path = g_strdup_printf ("mbox:%s/.evolution/mail/local", g_get_home_dir ());
+ if (stat (path + 5, &st) == -1) {
+ if (errno != ENOENT || g_mkdir_with_parents (path + 5, 0777) == -1) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Failed to create local mail storage "
+ "`%s': %s"), path + 5, g_strerror (errno));
+ g_free (session->srcdir);
+ camel_object_unref (session);
+ g_free (path);
+ return FALSE;
+ }
+ }
+
+ camel_exception_init (&lex);
+ if (!(session->store = camel_session_get_store ((CamelSession *) session, path, &lex))) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Failed to create local mail storage `%s': %s"),
+ path, lex.desc);
+ g_free (session->srcdir);
+ camel_object_unref (session);
+ camel_exception_clear (&lex);
+ g_free (path);
+ return FALSE;
+ }
+ g_free (path);
+
+ if (!em_migrate_local_folders_1_4 (session, error))
+ return FALSE;
+
+ camel_object_unref (session->store);
+ g_free (session->srcdir);
+
+ camel_object_unref (session);
+
+ em_upgrade_accounts_1_4();
+
+ upgrade_xml_uris(filters, upgrade_xml_uris_1_4);
+ upgrade_vfolder_sources_1_4(vfolders);
+ upgrade_xml_uris(vfolders, upgrade_xml_uris_1_4);
+
+ path = g_build_filename(g_get_home_dir(), "evolution", NULL);
+ searches = emm_load_xml(path, "searches.xml");
+ g_free(path);
+ if (searches) {
+ remove_system_searches(searches);
+ emm_save_xml(searches, data_dir, "searches.xml");
+ xmlFreeDoc(searches);
+ }
+
+ if (!em_migrate_pop_uid_caches_1_4 (data_dir, error))
+ return FALSE;
+
+ /* these are non-fatal */
+ em_migrate_imap_caches_1_4 (data_dir, error);
+ g_clear_error (error);
+ em_migrate_folder_expand_state_1_4 (data_dir, error);
+ g_clear_error (error);
+ em_migrate_folder_view_settings_1_4 (data_dir, error);
+ g_clear_error (error);
+ em_migrate_imap_cmeta_1_4 (data_dir, error);
+ g_clear_error (error);
+
+ return TRUE;
+}
+
+static void
+em_update_accounts_2_11 (void)
+{
+ EAccountList *accounts;
+ EIterator *iter;
+ gboolean changed = FALSE;
+
+ if (!(accounts = e_get_account_list ()))
+ return;
+
+ iter = e_list_get_iterator ((EList *) accounts);
+ while (e_iterator_is_valid (iter)) {
+ EAccount *account = (EAccount *) e_iterator_get (iter);
+
+ if (g_str_has_prefix (account->source->url, "spool://")) {
+ if (g_file_test (account->source->url + 8, G_FILE_TEST_IS_DIR)) {
+ gchar *str = g_strdup_printf ("spooldir://%s", account->source->url + 8);
+
+ g_free (account->source->url);
+ account->source->url = str;
+ changed = TRUE;
+ }
+ }
+
+ e_iterator_next (iter);
+ }
+
+ g_object_unref (iter);
+
+ if (changed)
+ e_account_list_save (accounts);
+}
+
+#endif /* !G_OS_WIN32 */
+
+static gboolean
+emm_setup_initial(const gchar *data_dir)
+{
+ GDir *dir;
+ const gchar *d;
+ gchar *local = NULL, *base;
+ const gchar * const *language_names;
+
+ /* special-case - this means brand new install of evolution */
+ /* FIXME: create default folders and stuff... */
+
+ d(printf("Setting up initial mail tree\n"));
+
+ base = g_build_filename(data_dir, "local", NULL);
+ if (g_mkdir_with_parents(base, 0777) == -1 && errno != EEXIST) {
+ g_free(base);
+ return FALSE;
+ }
+
+ /* e.g. try en-AU then en, etc */
+ language_names = g_get_language_names ();
+ while (*language_names != NULL) {
+ local = g_build_filename (
+ EVOLUTION_PRIVDATADIR, "default",
+ *language_names, "mail", "local", NULL);
+ if (g_file_test (local, G_FILE_TEST_EXISTS))
+ break;
+ g_free (local);
+ language_names++;
+ }
+
+ /* Make sure we found one. */
+ g_return_val_if_fail (*language_names != NULL, FALSE);
+
+ dir = g_dir_open(local, 0, NULL);
+ if (dir) {
+ while ((d = g_dir_read_name(dir))) {
+ gchar *src, *dest;
+
+ src = g_build_filename(local, d, NULL);
+ dest = g_build_filename(base, d, NULL);
+
+ cp(src, dest, FALSE, CP_UNIQUE);
+ g_free(dest);
+ g_free(src);
+ }
+ g_dir_close(dir);
+ }
+
+ g_free(local);
+ g_free(base);
+
+ return TRUE;
+}
+
+static gboolean
+is_in_plugs_list (GSList *list, const gchar *value)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ if (l->data && !strcmp (l->data, value))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * em_update_message_notify_settings_2_21
+ * DBus plugin and sound email notification was merged to mail-notification plugin,
+ * so move these options to new locations.
+ */
+static void
+em_update_message_notify_settings_2_21 (void)
+{
+ GConfClient *client;
+ GConfValue *is_key;
+ gboolean dbus, status;
+ GSList *list;
+ gchar *str;
+ gint val;
+
+ client = gconf_client_get_default ();
+
+ is_key = gconf_client_get (client, "/apps/evolution/eplugin/mail-notification/dbus-enabled", NULL);
+ if (is_key) {
+ /* already migrated, so do not migrate again */
+ gconf_value_free (is_key);
+ g_object_unref (client);
+
+ return;
+ }
+
+ gconf_client_set_bool (client, "/apps/evolution/eplugin/mail-notification/status-blink-icon",
+ gconf_client_get_bool (client, "/apps/evolution/mail/notification/blink-status-icon", NULL), NULL);
+ gconf_client_set_bool (client, "/apps/evolution/eplugin/mail-notification/status-notification",
+ gconf_client_get_bool (client, "/apps/evolution/mail/notification/notification", NULL), NULL);
+
+ list = gconf_client_get_list (client, "/apps/evolution/eplugin/disabled", GCONF_VALUE_STRING, NULL);
+ dbus = !is_in_plugs_list (list, "org.gnome.evolution.new_mail_notify");
+ status = !is_in_plugs_list (list, "org.gnome.evolution.mail_notification");
+
+ gconf_client_set_bool (client, "/apps/evolution/eplugin/mail-notification/dbus-enabled", dbus, NULL);
+ gconf_client_set_bool (client, "/apps/evolution/eplugin/mail-notification/status-enabled", status, NULL);
+
+ if (!status) {
+ /* enable this plugin, because it holds all those other things */
+ GSList *plugins, *l;
+
+ plugins = e_plugin_list_plugins ();
+
+ for (l = plugins; l; l = l->next) {
+ EPlugin *p = l->data;
+
+ if (p && p->id && !strcmp (p->id, "org.gnome.evolution.mail_notification")) {
+ e_plugin_enable (p, 1);
+ break;
+ }
+ }
+
+ g_slist_foreach (plugins, (GFunc)g_object_unref, NULL);
+ g_slist_free (plugins);
+ }
+
+ g_slist_foreach (list, (GFunc) g_free, NULL);
+ g_slist_free (list);
+
+ val = gconf_client_get_int (client, "/apps/evolution/mail/notify/type", NULL);
+ gconf_client_set_bool (client, "/apps/evolution/eplugin/mail-notification/sound-enabled", val == 1 || val == 2, NULL);
+ gconf_client_set_bool (client, "/apps/evolution/eplugin/mail-notification/sound-beep", val == 0 || val == 1, NULL);
+
+ str = gconf_client_get_string (client, "/apps/evolution/mail/notify/sound", NULL);
+ gconf_client_set_string (client, "/apps/evolution/eplugin/mail-notification/sound-file", str ? str : "", NULL);
+ g_free (str);
+
+ g_object_unref (client);
+}
+
+/* fixing typo in SpamAssassin name */
+static void
+em_update_sa_junk_setting_2_23 (void)
+{
+ GConfClient *client;
+ GConfValue *key;
+
+ client = gconf_client_get_default ();
+
+ key = gconf_client_get (client, "/apps/evolution/mail/junk/default_plugin", NULL);
+ if (key) {
+ const gchar *str = gconf_value_get_string (key);
+
+ if (str && strcmp (str, "Spamassasin") == 0)
+ gconf_client_set_string (client, "/apps/evolution/mail/junk/default_plugin", "SpamAssassin", NULL);
+
+ gconf_value_free (key);
+ g_object_unref (client);
+
+ return;
+ }
+
+ g_object_unref (client);
+}
+
+static gboolean
+update_progress_in_main_thread (double *progress)
+{
+ em_migrate_set_progress (*progress);
+ return FALSE;
+}
+
+static void
+migrate_folders(CamelStore *store, gboolean is_local, CamelFolderInfo *fi, const gchar *acc, CamelException *ex, gboolean *done, gint *nth_folder, gint total_folders)
+{
+ CamelFolder *folder;
+
+ while (fi) {
+ double progress;
+ gchar *tmp;
+
+ *nth_folder = *nth_folder + 1;
+
+ tmp = g_strdup_printf ("%s/%s", acc, fi->full_name);
+ em_migrate_set_folder_name (tmp);
+ g_free (tmp);
+
+ progress = (double) (*nth_folder) / total_folders;
+ g_idle_add ((GSourceFunc) update_progress_in_main_thread, &progress);
+
+ if (is_local)
+ folder = camel_store_get_folder (store, fi->full_name, CAMEL_STORE_IS_MIGRATING, ex);
+ else
+ folder = camel_store_get_folder (store, fi->full_name, 0, ex);
+
+ if (folder != NULL)
+ camel_folder_summary_migrate_infos (folder->summary);
+ migrate_folders(store, is_local, fi->child, acc, ex, done, nth_folder, total_folders);
+ fi = fi->next;
+ }
+
+ if ((*nth_folder) == (total_folders - 1))
+ *done = TRUE;
+}
+
+/* This could be in CamelStore.ch */
+static void
+count_folders (CamelFolderInfo *fi, gint *count)
+{
+ while (fi) {
+ *count = *count + 1;
+ count_folders (fi->child, count);
+ fi = fi->next;
+ }
+}
+
+static CamelStore *
+setup_local_store (EShellBackend *shell_backend,
+ EMMigrateSession *session)
+{
+ CamelURL *url;
+ const gchar *data_dir;
+ gchar *tmp;
+ CamelStore *store;
+
+ url = camel_url_new("mbox:", NULL);
+ data_dir = e_shell_backend_get_data_dir (shell_backend);
+ tmp = g_build_filename (data_dir, "local", NULL);
+ camel_url_set_path(url, tmp);
+ g_free(tmp);
+ tmp = camel_url_to_string(url, 0);
+ store = (CamelStore *)camel_session_get_service(CAMEL_SESSION (session), tmp, CAMEL_PROVIDER_STORE, NULL);
+ g_free(tmp);
+
+ return store;
+}
+
+struct migrate_folders_to_db_structure {
+ gchar *account_name;
+ CamelException ex;
+ CamelStore *store;
+ CamelFolderInfo *info;
+ gboolean done;
+ gboolean is_local_store;
+};
+
+static void
+migrate_folders_to_db_thread (struct migrate_folders_to_db_structure *migrate_dbs)
+{
+ gint num_of_folders = 0, nth_folder = 0;
+ count_folders (migrate_dbs->info, &num_of_folders);
+ migrate_folders (migrate_dbs->store, migrate_dbs->is_local_store, migrate_dbs->info,
+ migrate_dbs->account_name, &(migrate_dbs->ex), &(migrate_dbs->done),
+ &nth_folder, num_of_folders);
+}
+
+static void
+migrate_to_db (EShellBackend *shell_backend)
+{
+ EMMigrateSession *session;
+ EAccountList *accounts;
+ EIterator *iter;
+ gint i=0, len;
+ CamelStore *store = NULL;
+ CamelFolderInfo *info;
+ const gchar *data_dir;
+
+ if (!(accounts = e_get_account_list ()))
+ return;
+
+ iter = e_list_get_iterator ((EList *) accounts);
+ len = e_list_length ((EList *) accounts);
+
+ data_dir = e_shell_backend_get_data_dir (shell_backend);
+ session = (EMMigrateSession *) em_migrate_session_new (data_dir);
+ camel_session_set_online ((CamelSession *) session, FALSE);
+ em_migrate_setup_progress_dialog (
+ _("Migrating Folders"),
+ _("The summary format of the Evolution mailbox "
+ "folders has been moved to SQLite since Evolution 2.24.\n\nPlease be "
+ "patient while Evolution migrates your folders..."));
+
+ em_migrate_set_progress ( (double)i/(len+1));
+ store = setup_local_store (shell_backend, session);
+ info = camel_store_get_folder_info (store, NULL, CAMEL_STORE_FOLDER_INFO_RECURSIVE|CAMEL_STORE_FOLDER_INFO_FAST|CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, NULL);
+ if (info) {
+ GThread *thread;
+ struct migrate_folders_to_db_structure migrate_dbs;
+
+ if (g_str_has_suffix (((CamelService *)store)->url->path, ".evolution/mail/local"))
+ migrate_dbs.is_local_store = TRUE;
+ else
+ migrate_dbs.is_local_store = FALSE;
+ camel_exception_init (&migrate_dbs.ex);
+ migrate_dbs.account_name = _("On This Computer");
+ migrate_dbs.info = info;
+ migrate_dbs.store = store;
+ migrate_dbs.done = FALSE;
+
+ thread = g_thread_create ((GThreadFunc) migrate_folders_to_db_thread, &migrate_dbs, TRUE, NULL);
+ while (!migrate_dbs.done)
+ g_main_context_iteration (NULL, TRUE);
+ }
+ i++;
+ em_migrate_set_progress ( (double)i/(len+1));
+
+ while (e_iterator_is_valid (iter)) {
+ EAccount *account = (EAccount *) e_iterator_get (iter);
+ EAccountService *service;
+ const gchar *name;
+
+ service = account->source;
+ name = account->name;
+ em_migrate_set_progress ( (double)i/(len+1));
+ if (account->enabled
+ && service->url != NULL
+ && service->url[0]
+ && strncmp(service->url, "mbox:", 5) != 0) {
+
+ CamelException ex;
+
+ camel_exception_init (&ex);
+ e_mail_store_add_by_uri (service->url, name);
+
+ store = (CamelStore *) camel_session_get_service (CAMEL_SESSION (session), service->url, CAMEL_PROVIDER_STORE, &ex);
+ info = camel_store_get_folder_info (store, NULL, CAMEL_STORE_FOLDER_INFO_RECURSIVE|CAMEL_STORE_FOLDER_INFO_FAST|CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, &ex);
+ if (info) {
+ GThread *thread;
+ struct migrate_folders_to_db_structure migrate_dbs;
+
+ migrate_dbs.ex = ex;
+ migrate_dbs.account_name = account->name;
+ migrate_dbs.info = info;
+ migrate_dbs.store = store;
+ migrate_dbs.done = FALSE;
+
+ thread = g_thread_create ((GThreadFunc) migrate_folders_to_db_thread, &migrate_dbs, TRUE, NULL);
+ while (!migrate_dbs.done)
+ g_main_context_iteration (NULL, TRUE);
+ } else
+ printf("%s:%s: failed to get folder infos \n", G_STRLOC, G_STRFUNC);
+ camel_exception_clear(&ex);
+
+ }
+ i++;
+ e_iterator_next (iter);
+
+ }
+
+ //camel_session_set_online ((CamelSession *) session, TRUE);
+
+ g_object_unref (iter);
+ em_migrate_close_progress_dialog ();
+
+ g_object_unref (session);
+}
+
+gboolean
+e_mail_shell_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error)
+{
+ struct stat st;
+ const gchar *data_dir;
+ gchar *path;
+
+ /* make sure ~/.evolution/mail exists */
+ data_dir = e_shell_backend_get_data_dir (shell_backend);
+ if (g_stat (data_dir, &st) == -1) {
+ if (errno != ENOENT || g_mkdir_with_parents (data_dir, 0777) == -1) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to create local mail folders at "
+ "`%s': %s"), data_dir, g_strerror (errno));
+ return FALSE;
+ }
+ }
+
+ if (major == 0)
+ return emm_setup_initial (data_dir);
+
+ if (major == 1 && minor < 5) {
+#ifndef G_OS_WIN32
+ xmlDocPtr config_xmldb = NULL, filters, vfolders;
+
+ path = g_build_filename (g_get_home_dir (), "evolution", NULL);
+ if (minor <= 2 && !(config_xmldb = emm_load_xml (path, "config.xmldb"))) {
+ g_set_error (
+ error, E_SHELL_MIGRATE_ERROR,
+ E_SHELL_MIGRATE_ERROR_FAILED,
+ _("Unable to read settings from previous "
+ "Evolution install, `evolution/config.xmldb' "
+ "does not exist or is corrupt."));
+ return FALSE;
+ }
+ filters = emm_load_xml (path, "filters.xml");
+ vfolders = emm_load_xml (path, "vfolders.xml");
+ g_free (path);
+
+ if (minor == 0) {
+ if (!em_migrate_1_0 (data_dir, config_xmldb, filters, vfolders, error)) {
+ xmlFreeDoc (config_xmldb);
+ xmlFreeDoc (filters);
+ xmlFreeDoc (vfolders);
+ return FALSE;
+ }
+ }
+
+ if (minor <= 2) {
+ if (!em_migrate_1_2 (data_dir, config_xmldb, filters, vfolders, error)) {
+ xmlFreeDoc (config_xmldb);
+ xmlFreeDoc (filters);
+ xmlFreeDoc (vfolders);
+ return FALSE;
+ }
+
+ xmlFreeDoc (config_xmldb);
+ }
+
+ if (minor <= 4) {
+ if (!em_migrate_1_4 (data_dir, filters, vfolders, error)) {
+ xmlFreeDoc (filters);
+ xmlFreeDoc (vfolders);
+ return FALSE;
+ }
+ }
+
+ if (filters) {
+ emm_save_xml (filters, path, "filters.xml");
+ xmlFreeDoc (filters);
+ }
+
+ if (vfolders) {
+ emm_save_xml (vfolders, path, "vfolders.xml");
+ xmlFreeDoc (vfolders);
+ }
+
+ g_free (path);
+#else
+ g_error ("Upgrading from ancient versions not supported on Windows");
+#endif
+ }
+
+ if (major < 2 || (major == 2 && minor < 12)) {
+#ifndef G_OS_WIN32
+ em_update_accounts_2_11 ();
+#else
+ g_error ("Upgrading from ancient versions not supported on Windows");
+#endif
+ }
+
+ if (major < 2 || (major == 2 && minor < 22))
+ em_update_message_notify_settings_2_21 ();
+
+ if (major < 2 || (major == 2 && minor < 24)) {
+ em_update_sa_junk_setting_2_23 ();
+ migrate_to_db (shell_backend);
+ }
+
+ return TRUE;
+}
diff --git a/modules/mail/e-mail-shell-migrate.h b/modules/mail/e-mail-shell-migrate.h
new file mode 100644
index 0000000000..8f3057ec0d
--- /dev/null
+++ b/modules/mail/e-mail-shell-migrate.h
@@ -0,0 +1,38 @@
+/*
+ * e-mail-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_MAIL_SHELL_BACKEND_MIGRATE_H
+#define E_MAIL_SHELL_BACKEND_MIGRATE_H
+
+#include <glib.h>
+#include <shell/e-shell-backend.h>
+
+G_BEGIN_DECLS
+
+gboolean e_mail_shell_migrate (EShellBackend *shell_backend,
+ gint major,
+ gint minor,
+ gint micro,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_BACKEND_MIGRATE_H */
diff --git a/modules/mail/e-mail-shell-settings.c b/modules/mail/e-mail-shell-settings.c
new file mode 100644
index 0000000000..907c5fb643
--- /dev/null
+++ b/modules/mail/e-mail-shell-settings.c
@@ -0,0 +1,229 @@
+/*
+ * e-mail-shell-settings.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-mail-shell-settings.h"
+
+#include <gconf/gconf-client.h>
+#include <libedataserver/e-account-list.h>
+
+#include "e-util/e-signature-list.h"
+#include "mail/e-mail-label-list-store.h"
+#include "mail/mail-session.h"
+
+void
+e_mail_shell_settings_init (EShell *shell)
+{
+ EShellSettings *shell_settings;
+ gpointer object;
+
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ /*** Global Objects ***/
+
+ e_shell_settings_install_property (
+ g_param_spec_object (
+ "mail-label-list-store",
+ NULL,
+ NULL,
+ E_TYPE_MAIL_LABEL_LIST_STORE,
+ G_PARAM_READWRITE));
+
+ object = e_mail_label_list_store_new ();
+ e_shell_settings_set_object (
+ shell_settings, "mail-label-list-store", object);
+ g_object_unref (object);
+
+ e_shell_settings_install_property (
+ g_param_spec_pointer (
+ "mail-session",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ camel_object_ref (session);
+ e_shell_settings_set_pointer (
+ shell_settings, "mail-session", session);
+
+ /*** Mail Preferences ***/
+
+ e_shell_settings_install_property_for_key (
+ "mail-address-compress",
+ "/apps/evolution/mail/display/address_compress");
+
+ e_shell_settings_install_property_for_key (
+ "mail-address-count",
+ "/apps/evolution/mail/display/address_count");
+
+ e_shell_settings_install_property_for_key (
+ "mail-charset",
+ "/apps/evolution/mail/display/charset");
+
+ e_shell_settings_install_property_for_key (
+ "mail-check-for-junk",
+ "/apps/evolution/mail/junk/check_incoming");
+
+ e_shell_settings_install_property_for_key (
+ "mail-citation-color",
+ "/apps/evolution/mail/display/citation_colour");
+
+ e_shell_settings_install_property_for_key (
+ "mail-confirm-expunge",
+ "/apps/evolution/mail/prompts/expunge");
+
+ e_shell_settings_install_property_for_key (
+ "mail-confirm-unwanted-html",
+ "/apps/evolution/mail/prompts/unwanted_html");
+
+ e_shell_settings_install_property_for_key (
+ "mail-empty-junk-on-exit",
+ "/apps/evolution/mail/junk/empty_on_exit");
+
+ e_shell_settings_install_property_for_key (
+ "mail-empty-trash-on-exit",
+ "/apps/evolution/mail/trash/empty_on_exit");
+
+ e_shell_settings_install_property_for_key (
+ "mail-enable-search-folders",
+ "/apps/evolution/mail/display/enable_vfolders");
+
+ e_shell_settings_install_property_for_key (
+ "mail-font-monospace",
+ "/apps/evolution/mail/display/fonts/monospace");
+
+ e_shell_settings_install_property_for_key (
+ "mail-font-variable",
+ "/apps/evolution/mail/display/fonts/variable");
+
+ e_shell_settings_install_property_for_key (
+ "mail-force-message-limit",
+ "/apps/evolution/mail/display/force_message_limit");
+
+ /* This value corresponds to MailConfigForwardStyle enum. */
+ e_shell_settings_install_property_for_key (
+ "mail-forward-style",
+ "/apps/evolution/mail/format/forward_style");
+
+ /* This value corresponds to MailConfigHTTPMode enum. */
+ e_shell_settings_install_property_for_key (
+ "mail-image-loading-policy",
+ "/apps/evolution/mail/display/load_http_images");
+
+ e_shell_settings_install_property_for_key (
+ "mail-magic-spacebar",
+ "/apps/evolution/mail/display/magic_spacebar");
+
+ e_shell_settings_install_property_for_key (
+ "mail-mark-citations",
+ "/apps/evolution/mail/display/mark_citations");
+
+ e_shell_settings_install_property_for_key (
+ "mail-mark-seen",
+ "/apps/evolution/mail/display/mark_seen");
+
+ e_shell_settings_install_property_for_key (
+ "mail-mark-seen-timeout",
+ "/apps/evolution/mail/display/mark_seen_timeout");
+
+ e_shell_settings_install_property_for_key (
+ "mail-message-text-part-limit",
+ "/apps/evolution/mail/display/message_text_part_limit");
+
+ e_shell_settings_install_property_for_key (
+ "mail-only-local-photos",
+ "/apps/evolution/mail/display/photo_local");
+
+ e_shell_settings_install_property_for_key (
+ "mail-prompt-delete-in-vfolder",
+ "/apps/evolution/mail/prompts/delete_in_vfolder");
+
+ /* This value corresponds to MailConfigReplyStyle enum,
+ * but the ordering of the combo box items in preferences
+ * has changed. We use transformation functions there. */
+ e_shell_settings_install_property_for_key (
+ "mail-reply-style",
+ "/apps/evolution/mail/format/reply_style");
+
+ e_shell_settings_install_property_for_key (
+ "mail-show-animated-images",
+ "/apps/evolution/mail/display/animated_images");
+
+ e_shell_settings_install_property_for_key (
+ "mail-show-sender-photo",
+ "/apps/evolution/mail/display/sender_photo");
+
+ e_shell_settings_install_property_for_key (
+ "mail-side-bar-search",
+ "/apps/evolution/mail/display/side_bar_search");
+
+ e_shell_settings_install_property_for_key (
+ "mail-use-custom-fonts",
+ "/apps/evolution/mail/display/fonts/use_custom");
+
+ /*** Composer Preferences ***/
+
+ e_shell_settings_install_property_for_key (
+ "composer-charset",
+ "/apps/evolution/mail/composer/charset");
+
+ e_shell_settings_install_property_for_key (
+ "composer-format-html",
+ "/apps/evolution/mail/composer/send_html");
+
+ e_shell_settings_install_property_for_key (
+ "composer-inline-spelling",
+ "/apps/evolution/mail/composer/inline_spelling");
+
+ e_shell_settings_install_property_for_key (
+ "composer-magic-links",
+ "/apps/evolution/mail/composer/magic_links");
+
+ e_shell_settings_install_property_for_key (
+ "composer-magic-smileys",
+ "/apps/evolution/mail/composer/magic_smileys");
+
+ e_shell_settings_install_property_for_key (
+ "composer-outlook-filenames",
+ "/apps/evolution/mail/composer/outlook_filenames");
+
+ e_shell_settings_install_property_for_key (
+ "composer-prompt-only-bcc",
+ "/apps/evolution/mail/prompts/only_bcc");
+
+ e_shell_settings_install_property_for_key (
+ "composer-prompt-empty-subject",
+ "/apps/evolution/mail/prompts/empty_subject");
+
+ e_shell_settings_install_property_for_key (
+ "composer-reply-start-bottom",
+ "/apps/evolution/mail/composer/reply_start_bottom");
+
+ e_shell_settings_install_property_for_key (
+ "composer-request-receipt",
+ "/apps/evolution/mail/composer/request_receipt");
+
+ e_shell_settings_install_property_for_key (
+ "composer-spell-color",
+ "/apps/evolution/mail/composer/spell_color");
+
+ e_shell_settings_install_property_for_key (
+ "composer-top-signature",
+ "/apps/evolution/mail/composer/top_signature");
+}
diff --git a/modules/mail/e-mail-shell-settings.h b/modules/mail/e-mail-shell-settings.h
new file mode 100644
index 0000000000..4267fd8a60
--- /dev/null
+++ b/modules/mail/e-mail-shell-settings.h
@@ -0,0 +1,33 @@
+/*
+ * e-mail-shell-settings.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_MAIL_SHELL_SETTINGS_H
+#define E_MAIL_SHELL_SETTINGS_H
+
+#include <shell/e-shell.h>
+
+G_BEGIN_DECLS
+
+void e_mail_shell_settings_init (EShell *shell);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_SETTINGS_H */
diff --git a/modules/mail/e-mail-shell-sidebar.c b/modules/mail/e-mail-shell-sidebar.c
new file mode 100644
index 0000000000..0f8e4f5783
--- /dev/null
+++ b/modules/mail/e-mail-shell-sidebar.c
@@ -0,0 +1,657 @@
+/*
+ * e-mail-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-mail-shell-sidebar.h"
+
+#include <string.h>
+#include <camel/camel.h>
+
+#include "e-util/e-binding.h"
+
+#include "em-utils.h"
+#include "em-folder-utils.h"
+
+#include "e-mail-local.h"
+#include "e-mail-store.h"
+
+#define E_MAIL_SHELL_SIDEBAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SHELL_SIDEBAR, EMailShellSidebarPrivate))
+
+#define STATE_KEY_EXPANDED "Expanded"
+
+struct _EMailShellSidebarPrivate {
+ GtkWidget *folder_tree;
+};
+
+enum {
+ PROP_0,
+ PROP_FOLDER_TREE
+};
+
+static gpointer parent_class;
+static GType mail_shell_sidebar_type;
+
+static void
+mail_shell_sidebar_restore_state (EMailShellSidebar *mail_shell_sidebar)
+{
+ EShellView *shell_view;
+ EShellSidebar *shell_sidebar;
+ EMFolderTree *folder_tree;
+ GtkTreeModel *tree_model;
+ GtkTreeView *tree_view;
+ GtkTreeIter iter;
+ GKeyFile *key_file;
+ gboolean valid;
+ gchar *selected;
+ gchar **groups;
+ gint ii;
+
+ shell_sidebar = E_SHELL_SIDEBAR (mail_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ tree_view = GTK_TREE_VIEW (folder_tree);
+ tree_model = gtk_tree_view_get_model (tree_view);
+
+ /* Restore selected folder. */
+
+ selected = g_key_file_get_string (
+ key_file, "Folder Tree", "Selected", NULL);
+ if (selected != NULL) {
+ em_folder_tree_set_selected (folder_tree, selected, FALSE);
+ g_free (selected);
+ }
+
+ /* Set the initial folder tree expanded state in two stages:
+ *
+ * 1) Iterate over the "Store" and "Folder" state file groups
+ * and apply the "Expanded" keys where possible.
+ *
+ * 2) Iterate over the top-level nodes in the folder tree
+ * (these are all stores) and expand those that have no
+ * corresponding "Expanded" key in the state file. This
+ * ensures that new stores are expanded by default.
+ */
+
+ /* Stage 1 */
+
+ groups = g_key_file_get_groups (key_file, NULL);
+
+ for (ii = 0; groups[ii] != NULL; ii++) {
+ GtkTreeRowReference *reference;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ const gchar *group_name = groups[ii];
+ const gchar *key = STATE_KEY_EXPANDED;
+ const gchar *uri;
+ gboolean expanded;
+
+ if (g_str_has_prefix (group_name, "Store ")) {
+ uri = group_name + 6;
+ expanded = TRUE;
+ } else if (g_str_has_prefix (group_name, "Folder ")) {
+ uri = group_name + 7;
+ expanded = FALSE;
+ } else
+ continue;
+
+ if (g_key_file_has_key (key_file, group_name, key, NULL))
+ expanded = g_key_file_get_boolean (
+ key_file, group_name, key, NULL);
+
+ if (!expanded)
+ continue;
+
+ reference = em_folder_tree_model_lookup_uri (
+ EM_FOLDER_TREE_MODEL (tree_model), uri);
+ if (reference == NULL)
+ continue;
+
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_model_get_iter (tree_model, &iter, path);
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+ gtk_tree_path_free (path);
+ }
+
+ g_strfreev (groups);
+
+ /* Stage 2 */
+
+ valid = gtk_tree_model_get_iter_first (tree_model, &iter);
+
+ while (valid) {
+ const gchar *key = STATE_KEY_EXPANDED;
+ gchar *group_name;
+ gchar *uri;
+
+ gtk_tree_model_get (
+ tree_model, &iter, COL_STRING_URI, &uri, -1);
+
+ if (uri == NULL)
+ goto next;
+
+ group_name = g_strdup_printf ("Store %s", uri);
+
+ if (!g_key_file_has_key (key_file, group_name, key, NULL)) {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (tree_model, &iter);
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+ gtk_tree_path_free (path);
+ }
+
+ g_free (group_name);
+ g_free (uri);
+
+ next:
+ valid = gtk_tree_model_iter_next (tree_model, &iter);
+ }
+}
+
+static void
+mail_shell_sidebar_row_collapsed_cb (EShellSidebar *shell_sidebar,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ GtkTreeView *tree_view)
+{
+ EShellView *shell_view;
+ GtkTreeModel *model;
+ GKeyFile *key_file;
+ const gchar *key;
+ gboolean is_folder;
+ gboolean is_store;
+ gchar *group_name;
+ gchar *uri;
+
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ gtk_tree_model_get (
+ model, iter,
+ COL_STRING_URI, &uri,
+ COL_BOOL_IS_STORE, &is_store,
+ COL_BOOL_IS_FOLDER, &is_folder, -1);
+
+ g_return_if_fail (is_store || is_folder);
+
+ key = STATE_KEY_EXPANDED;
+ if (is_store)
+ group_name = g_strdup_printf ("Store %s", uri);
+ else
+ group_name = g_strdup_printf ("Folder %s", uri);
+
+ g_key_file_set_boolean (key_file, group_name, key, FALSE);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+ g_free (uri);
+}
+
+static void
+mail_shell_sidebar_row_expanded_cb (EShellSidebar *shell_sidebar,
+ GtkTreeIter *unused,
+ GtkTreePath *path,
+ GtkTreeView *tree_view)
+{
+ EShellView *shell_view;
+ GtkTreeModel *model;
+ GKeyFile *key_file;
+ const gchar *key;
+ gboolean is_folder;
+ gboolean is_store;
+ gchar *group_name;
+ gchar *uri;
+
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ path = gtk_tree_path_copy (path);
+ model = gtk_tree_view_get_model (tree_view);
+
+ /* Expand the node and all ancestors. */
+ while (gtk_tree_path_get_depth (path) > 0) {
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ gtk_tree_model_get (
+ model, &iter,
+ COL_STRING_URI, &uri,
+ COL_BOOL_IS_STORE, &is_store,
+ COL_BOOL_IS_FOLDER, &is_folder, -1);
+
+ g_return_if_fail (is_store || is_folder);
+
+ key = STATE_KEY_EXPANDED;
+ if (is_store)
+ group_name = g_strdup_printf ("Store %s", uri);
+ else
+ group_name = g_strdup_printf ("Folder %s", uri);
+
+ g_key_file_set_boolean (key_file, group_name, key, TRUE);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+ g_free (uri);
+
+ gtk_tree_path_up (path);
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+mail_shell_sidebar_model_loaded_row_cb (EMailShellSidebar *mail_shell_sidebar,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GtkTreeModel *model)
+{
+ EShellSidebar *shell_sidebar;
+ EShellView *shell_view;
+ GtkTreeView *tree_view;
+ GKeyFile *key_file;
+ gboolean is_folder;
+ gboolean is_store;
+ const gchar *key;
+ gchar *group_name;
+ gchar *uri;
+ gboolean expanded;
+
+ shell_sidebar = E_SHELL_SIDEBAR (mail_shell_sidebar);
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ tree_view = GTK_TREE_VIEW (mail_shell_sidebar->priv->folder_tree);
+
+ gtk_tree_model_get (
+ model, iter,
+ COL_STRING_URI, &uri,
+ COL_BOOL_IS_STORE, &is_store,
+ COL_BOOL_IS_FOLDER, &is_folder, -1);
+
+ g_return_if_fail (is_store || is_folder);
+
+ key = STATE_KEY_EXPANDED;
+ if (is_store) {
+ group_name = g_strdup_printf ("Store %s", uri);
+ expanded = TRUE;
+ } else {
+ group_name = g_strdup_printf ("Folder %s", uri);
+ expanded = FALSE;
+ }
+
+ if (g_key_file_has_key (key_file, group_name, key, NULL))
+ expanded = g_key_file_get_boolean (
+ key_file, group_name, key, NULL);
+
+ if (expanded)
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+
+ g_free (group_name);
+ g_free (uri);
+}
+
+static void
+mail_shell_sidebar_selection_changed_cb (EShellSidebar *shell_sidebar,
+ GtkTreeSelection *selection)
+{
+ EShellView *shell_view;
+ EShellViewClass *shell_view_class;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GKeyFile *key_file;
+ const gchar *icon_name;
+ gchar *display_name = NULL;
+ gchar *uri = NULL;
+ gboolean is_folder = FALSE;
+ guint flags = 0;
+
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+ shell_view_class = E_SHELL_VIEW_GET_CLASS (shell_view);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ gtk_tree_model_get (
+ model, &iter,
+ COL_STRING_DISPLAY_NAME, &display_name,
+ COL_STRING_URI, &uri,
+ COL_BOOL_IS_FOLDER, &is_folder,
+ COL_UINT_FLAGS, &flags, -1);
+
+ if (uri != NULL)
+ g_key_file_set_string (
+ key_file, "Folder Tree", "Selected", uri);
+ else
+ g_key_file_remove_key (
+ key_file, "Folder Tree", "Selected", NULL);
+
+ e_shell_view_set_state_dirty (shell_view);
+
+ if (is_folder)
+ icon_name = em_folder_utils_get_icon_name (flags);
+ else {
+ icon_name = shell_view_class->icon_name;
+ display_name = g_strdup (shell_view_class->label);
+ }
+
+ e_shell_sidebar_set_icon_name (shell_sidebar, icon_name);
+ e_shell_sidebar_set_primary_text (shell_sidebar, display_name);
+
+ g_free (display_name);
+ g_free (uri);
+}
+
+static void
+mail_shell_sidebar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FOLDER_TREE:
+ g_value_set_object (
+ value, e_mail_shell_sidebar_get_folder_tree (
+ E_MAIL_SHELL_SIDEBAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_shell_sidebar_dispose (GObject *object)
+{
+ EMailShellSidebarPrivate *priv;
+
+ priv = E_MAIL_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ if (priv->folder_tree != NULL) {
+ g_object_unref (priv->folder_tree);
+ priv->folder_tree = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+mail_shell_sidebar_finalize (GObject *object)
+{
+ EMailShellSidebarPrivate *priv;
+
+ priv = E_MAIL_SHELL_SIDEBAR_GET_PRIVATE (object);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+mail_shell_sidebar_constructed (GObject *object)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellSettings *shell_settings;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EShell *shell;
+ GtkTreeSelection *selection;
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ /* 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_window = e_shell_view_get_shell_window (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ mail_shell_sidebar = E_MAIL_SHELL_SIDEBAR (object);
+
+ /* Build sidebar widgets. */
+
+ container = GTK_WIDGET (object);
+
+ widget = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (widget),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (
+ GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = em_folder_tree_new ();
+ em_folder_tree_set_excluded (EM_FOLDER_TREE (widget), 0);
+ em_folder_tree_enable_drag_and_drop (EM_FOLDER_TREE (widget));
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ mail_shell_sidebar->priv->folder_tree = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "mail-side-bar-search",
+ G_OBJECT (widget), "enable-search");
+
+ tree_view = GTK_TREE_VIEW (mail_shell_sidebar->priv->folder_tree);
+ selection = gtk_tree_view_get_selection (tree_view);
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (em_folder_tree_model_get_selection (
+ EM_FOLDER_TREE_MODEL (model)) == NULL)
+ mail_shell_sidebar_restore_state (mail_shell_sidebar);
+
+ em_folder_tree_model_set_selection (
+ EM_FOLDER_TREE_MODEL (model), selection);
+
+ g_signal_connect_swapped (
+ tree_view, "row-collapsed",
+ G_CALLBACK (mail_shell_sidebar_row_collapsed_cb),
+ shell_sidebar);
+
+ g_signal_connect_swapped (
+ tree_view, "row-expanded",
+ G_CALLBACK (mail_shell_sidebar_row_expanded_cb),
+ shell_sidebar);
+
+ g_signal_connect_swapped (
+ model, "loaded-row",
+ G_CALLBACK (mail_shell_sidebar_model_loaded_row_cb),
+ shell_sidebar);
+
+ g_signal_connect_swapped (
+ selection, "changed",
+ G_CALLBACK (mail_shell_sidebar_selection_changed_cb),
+ shell_sidebar);
+}
+
+static guint32
+mail_shell_sidebar_check_state (EShellSidebar *shell_sidebar)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellView *shell_view;
+ EMFolderTree *folder_tree;
+ GtkTreeSelection *selection;
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ CamelFolder *folder;
+ CamelStore *local_store;
+ CamelStore *store;
+ gchar *full_name;
+ gchar *uri;
+ gboolean allows_children = TRUE;
+ gboolean can_delete = TRUE;
+ gboolean is_junk = FALSE;
+ gboolean is_outbox = FALSE;
+ gboolean is_store;
+ gboolean is_trash = FALSE;
+ guint32 folder_flags = 0;
+ guint32 state = 0;
+
+ shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
+
+ local_store = e_mail_local_get_store ();
+
+ mail_shell_sidebar = E_MAIL_SHELL_SIDEBAR (shell_sidebar);
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ tree_view = GTK_TREE_VIEW (folder_tree);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return 0;
+
+ gtk_tree_model_get (
+ model, &iter,
+ COL_POINTER_CAMEL_STORE, &store,
+ COL_STRING_FULL_NAME, &full_name,
+ COL_BOOL_IS_STORE, &is_store,
+ COL_UINT_FLAGS, &folder_flags,
+ COL_STRING_URI, &uri, -1);
+
+ if (!is_store) {
+ is_junk = (strcmp (full_name, CAMEL_VJUNK_NAME) == 0);
+ is_trash = (strcmp (full_name, CAMEL_VTRASH_NAME) == 0);
+ allows_children = !(is_junk || is_trash);
+
+ /* Don't allow deletion of special local folders. */
+ if (store == local_store)
+ can_delete =
+ (strcmp (full_name, "Drafts") != 0) &&
+ (strcmp (full_name, "Inbox") != 0) &&
+ (strcmp (full_name, "Outbox") != 0) &&
+ (strcmp (full_name, "Sent") != 0) &&
+ (strcmp (full_name, "Templates") != 0);
+
+ folder = em_folder_tree_get_selected_folder (folder_tree);
+ is_outbox = em_utils_folder_is_outbox (folder, NULL);
+ can_delete &= !(folder_flags & CAMEL_FOLDER_SYSTEM);
+ }
+
+ if (allows_children)
+ state |= E_MAIL_SHELL_SIDEBAR_FOLDER_ALLOWS_CHILDREN;
+ if (can_delete)
+ state |= E_MAIL_SHELL_SIDEBAR_FOLDER_CAN_DELETE;
+ if (is_junk)
+ state |= E_MAIL_SHELL_SIDEBAR_FOLDER_IS_JUNK;
+ if (is_outbox)
+ state |= E_MAIL_SHELL_SIDEBAR_FOLDER_IS_OUTBOX;
+ if (is_store)
+ state |= E_MAIL_SHELL_SIDEBAR_FOLDER_IS_STORE;
+ if (is_trash)
+ state |= E_MAIL_SHELL_SIDEBAR_FOLDER_IS_TRASH;
+
+ return state;
+}
+
+static void
+mail_shell_sidebar_class_init (EMailShellSidebarClass *class)
+{
+ GObjectClass *object_class;
+ EShellSidebarClass *shell_sidebar_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMailShellSidebarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = mail_shell_sidebar_get_property;
+ object_class->dispose = mail_shell_sidebar_dispose;
+ object_class->finalize = mail_shell_sidebar_finalize;
+ object_class->constructed = mail_shell_sidebar_constructed;
+
+ shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class);
+ shell_sidebar_class->check_state = mail_shell_sidebar_check_state;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FOLDER_TREE,
+ g_param_spec_object (
+ "folder-tree",
+ NULL,
+ NULL,
+ EM_TYPE_FOLDER_TREE,
+ G_PARAM_READABLE));
+}
+
+static void
+mail_shell_sidebar_init (EMailShellSidebar *mail_shell_sidebar)
+{
+ mail_shell_sidebar->priv =
+ E_MAIL_SHELL_SIDEBAR_GET_PRIVATE (mail_shell_sidebar);
+
+ /* Postpone widget construction until we have a shell view. */
+}
+
+GType
+e_mail_shell_sidebar_get_type (void)
+{
+ return mail_shell_sidebar_type;
+}
+
+void
+e_mail_shell_sidebar_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EMailShellSidebarClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_shell_sidebar_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMailShellSidebar),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) mail_shell_sidebar_init,
+ NULL /* value_table */
+ };
+
+ mail_shell_sidebar_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_SIDEBAR,
+ "EMailShellSidebar", &type_info, 0);
+}
+
+GtkWidget *
+e_mail_shell_sidebar_new (EShellView *shell_view)
+{
+ g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
+
+ return g_object_new (
+ E_TYPE_MAIL_SHELL_SIDEBAR,
+ "shell-view", shell_view, NULL);
+}
+
+EMFolderTree *
+e_mail_shell_sidebar_get_folder_tree (EMailShellSidebar *mail_shell_sidebar)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SHELL_SIDEBAR (mail_shell_sidebar), NULL);
+
+ return EM_FOLDER_TREE (mail_shell_sidebar->priv->folder_tree);
+}
diff --git a/modules/mail/e-mail-shell-sidebar.h b/modules/mail/e-mail-shell-sidebar.h
new file mode 100644
index 0000000000..10a2ff6a2a
--- /dev/null
+++ b/modules/mail/e-mail-shell-sidebar.h
@@ -0,0 +1,81 @@
+/*
+ * e-mail-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_MAIL_SHELL_SIDEBAR_H
+#define E_MAIL_SHELL_SIDEBAR_H
+
+#include <shell/e-shell-sidebar.h>
+#include <shell/e-shell-view.h>
+#include <mail/em-folder-tree.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SHELL_SIDEBAR \
+ (e_mail_shell_sidebar_get_type ())
+#define E_MAIL_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_SHELL_SIDEBAR, EMailShellSidebar))
+#define E_MAIL_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_SHELL_SIDEBAR, EMailShellSidebarClass))
+#define E_IS_MAIL_SHELL_SIDEBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_SHELL_SIDEBAR))
+#define E_IS_MAIL_SHELL_SIDEBAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_SHELL_SIDEBAR))
+#define E_MAIL_SHELL_SIDEBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_SHELL_SIDEBAR, EMailShellSidebarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailShellSidebar EMailShellSidebar;
+typedef struct _EMailShellSidebarClass EMailShellSidebarClass;
+typedef struct _EMailShellSidebarPrivate EMailShellSidebarPrivate;
+
+enum {
+ E_MAIL_SHELL_SIDEBAR_FOLDER_ALLOWS_CHILDREN = 1 << 0,
+ E_MAIL_SHELL_SIDEBAR_FOLDER_CAN_DELETE = 1 << 1,
+ E_MAIL_SHELL_SIDEBAR_FOLDER_IS_JUNK = 1 << 2,
+ E_MAIL_SHELL_SIDEBAR_FOLDER_IS_OUTBOX = 1 << 3,
+ E_MAIL_SHELL_SIDEBAR_FOLDER_IS_STORE = 1 << 4,
+ E_MAIL_SHELL_SIDEBAR_FOLDER_IS_TRASH = 1 << 5
+};
+
+struct _EMailShellSidebar {
+ EShellSidebar parent;
+ EMailShellSidebarPrivate *priv;
+};
+
+struct _EMailShellSidebarClass {
+ EShellSidebarClass parent_class;
+};
+
+GType e_mail_shell_sidebar_get_type (void);
+void e_mail_shell_sidebar_register_type
+ (GTypeModule *type_module);
+GtkWidget * e_mail_shell_sidebar_new(EShellView *shell_view);
+EMFolderTree * e_mail_shell_sidebar_get_folder_tree
+ (EMailShellSidebar *mail_shell_sidebar);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_SIDEBAR_H */
diff --git a/modules/mail/e-mail-shell-view-actions.c b/modules/mail/e-mail-shell-view-actions.c
new file mode 100644
index 0000000000..5e6a9ba9cf
--- /dev/null
+++ b/modules/mail/e-mail-shell-view-actions.c
@@ -0,0 +1,1844 @@
+/*
+ * e-mail-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-mail-shell-view-private.h"
+
+#define STATE_KEY_SEARCH_FILTER "SearchFilter"
+#define STATE_KEY_SEARCH_SCOPE "SearchScope"
+#define STATE_KEY_SEARCH_TEXT "SearchText"
+
+static void
+action_gal_save_custom_view_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EShellView *shell_view;
+ GalViewInstance *view_instance;
+
+ /* All shell views repond 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 (mail_shell_view);
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ view_instance = e_mail_shell_content_get_view_instance (mail_shell_content);
+ gal_view_instance_save_as (view_instance);
+}
+
+static void
+action_mail_account_disable_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ EAccountList *account_list;
+ EAccount *account;
+ gchar *folder_uri;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder_uri = em_folder_tree_get_selected_uri (folder_tree);
+ g_return_if_fail (folder_uri != NULL);
+
+ account_list = e_get_account_list ();
+ account = mail_config_get_account_by_source_url (folder_uri);
+ g_return_if_fail (account != NULL);
+
+ if (e_account_list_account_has_proxies (account_list, account))
+ e_account_list_remove_account_proxies (account_list, account);
+
+ account->enabled = !account->enabled;
+ e_account_list_change (account_list, account);
+ e_mail_store_remove_by_uri (folder_uri);
+
+ if (account->parent_uid != NULL)
+ e_account_list_remove (account_list, account);
+
+ e_account_list_save (account_list);
+
+ g_free (folder_uri);
+}
+
+static void
+action_mail_create_search_folder_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ /* FIXME */
+ g_print ("Action: %s\n", gtk_action_get_name (GTK_ACTION (action)));
+}
+
+static void
+action_mail_download_foreach_cb (CamelService *service)
+{
+ if (CAMEL_IS_DISCO_STORE (service) || CAMEL_IS_OFFLINE_STORE (service))
+ mail_store_prepare_offline (CAMEL_STORE (service));
+}
+
+static void
+action_mail_download_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ e_mail_store_foreach ((GHFunc) action_mail_download_foreach_cb, NULL);
+}
+
+static void
+action_mail_empty_trash_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ em_utils_empty_trash (GTK_WIDGET (shell_window));
+}
+
+static void
+action_mail_flush_outbox_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ mail_send ();
+}
+
+static void
+action_mail_folder_copy_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ CamelFolderInfo *folder_info;
+ EMFolderTree *folder_tree;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder_info = em_folder_tree_get_selected_folder_info (folder_tree);
+ g_return_if_fail (folder_info != NULL);
+
+ /* XXX Leaking folder_info? */
+ em_folder_utils_copy_folder (folder_info, FALSE);
+}
+
+static void
+action_mail_folder_delete_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ CamelFolder *folder;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder = em_folder_tree_get_selected_folder (folder_tree);
+ g_return_if_fail (folder != NULL);
+
+ em_folder_utils_delete_folder (folder);
+}
+
+static void
+action_mail_folder_expunge_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ g_return_if_fail (message_list->folder != NULL);
+
+ em_utils_expunge_folder (
+ GTK_WIDGET (shell_window), message_list->folder);
+}
+
+static void
+action_mail_folder_mark_all_as_read_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ CamelFolder *folder;
+ GtkWindow *parent;
+ GPtrArray *uids;
+ const gchar *key;
+ const gchar *prompt;
+ guint ii;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ parent = GTK_WINDOW (shell_window);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder = message_list->folder;
+ g_return_if_fail (folder != NULL);
+
+ key = "/apps/evolution/mail/prompts/mark_all_read";
+ prompt = "mail:ask-mark-all-read";
+
+ if (!em_utils_prompt_user (parent, key, prompt, NULL))
+ return;
+
+ uids = message_list_get_uids (message_list);
+
+ camel_folder_freeze (folder);
+ for (ii = 0; ii < uids->len; ii++)
+ camel_folder_set_message_flags (
+ folder, uids->pdata[ii],
+ CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN);
+ camel_folder_thaw (folder);
+
+ message_list_free_uids (message_list, uids);
+}
+
+static void
+action_mail_folder_move_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ CamelFolderInfo *folder_info;
+ EMFolderTree *folder_tree;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder_info = em_folder_tree_get_selected_folder_info (folder_tree);
+ g_return_if_fail (folder_info != NULL);
+
+ /* XXX Leaking folder_info? */
+ em_folder_utils_copy_folder (folder_info, TRUE);
+}
+
+static void
+action_mail_folder_new_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EMailShellSidebar *mail_shell_sidebar;
+ CamelFolderInfo *folder_info;
+ EMFolderTree *folder_tree;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder_info = em_folder_tree_get_selected_folder_info (folder_tree);
+ g_return_if_fail (folder_info != NULL);
+
+ em_folder_utils_create_folder (
+ folder_info, folder_tree, GTK_WINDOW (shell_window));
+ camel_folder_info_free (folder_info);
+}
+
+static void
+action_mail_folder_properties_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ EShellView *shell_view;
+ GtkTreeSelection *selection;
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *uri;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ tree_view = GTK_TREE_VIEW (folder_tree);
+ selection = gtk_tree_view_get_selection (tree_view);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ gtk_tree_model_get (model, &iter, COL_STRING_URI, &uri, -1);
+ em_folder_properties_show (shell_view, NULL, uri);
+ g_free (uri);
+}
+
+static void
+action_mail_folder_refresh_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ CamelFolder *folder;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder = em_folder_tree_get_selected_folder (folder_tree);
+ g_return_if_fail (folder != NULL);
+
+ mail_refresh_folder (folder, NULL, NULL);
+}
+
+static void
+action_mail_folder_rename_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ CamelFolder *folder;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ folder = em_folder_tree_get_selected_folder (folder_tree);
+ g_return_if_fail (folder != NULL);
+
+ em_folder_utils_rename_folder (folder);
+}
+
+/* Helper for action_mail_folder_select_all_cb() */
+static gboolean
+action_mail_folder_select_all_timeout_cb (MessageList *message_list)
+{
+ message_list_select_all (message_list);
+ gtk_widget_grab_focus (GTK_WIDGET (message_list));
+
+ return FALSE;
+}
+
+static void
+action_mail_folder_select_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ if (message_list->threaded) {
+ gtk_action_activate (ACTION (MAIL_THREADS_EXPAND_ALL));
+
+ /* XXX The timeout below is added so that the execution
+ * thread to expand all conversation threads would
+ * have completed. The timeout 505 is just to ensure
+ * that the value is a small delta more than the
+ * timeout value in mail_regen_list(). */
+ g_timeout_add (
+ 505, (GSourceFunc)
+ action_mail_folder_select_all_timeout_cb,
+ message_list);
+ } else
+ /* If there is no threading, just select all immediately. */
+ action_mail_folder_select_all_timeout_cb (message_list);
+}
+
+static void
+action_mail_folder_select_thread_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_thread (message_list);
+}
+
+static void
+action_mail_folder_select_subthread_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_subthread (message_list);
+}
+
+static void
+action_mail_folder_unsubscribe_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ gchar *folder_uri;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ folder_uri = em_folder_tree_get_selected_uri (folder_tree);
+ em_folder_utils_unsubscribe_folder (folder_uri);
+ g_free (folder_uri);
+}
+
+static void
+action_mail_hide_deleted_cb (GtkToggleAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+ gboolean active;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ active = gtk_toggle_action_get_active (action);
+ message_list_set_hidedeleted (message_list, active);
+}
+
+static void
+action_mail_hide_read_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_hide_add (
+ message_list,
+ "(match-all (system-flag \"seen\"))",
+ ML_HIDE_SAME, ML_HIDE_SAME);
+}
+
+static void
+action_mail_hide_selected_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+ GPtrArray *uids;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ uids = message_list_get_selected (message_list);
+ message_list_hide_uids (message_list, uids);
+ message_list_free_uids (message_list, uids);
+}
+
+static void
+action_mail_label_cb (GtkToggleAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+ CamelFolder *folder;
+ GPtrArray *uids;
+ const gchar *tag;
+ gint ii;
+
+ tag = g_object_get_data (G_OBJECT (action), "tag");
+ g_return_if_fail (tag != NULL);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder = message_list->folder;
+
+ uids = message_list_get_selected (message_list);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ if (gtk_toggle_action_get_active (action))
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], tag, TRUE);
+ else {
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], tag, FALSE);
+ camel_folder_set_message_user_tag (
+ folder, uids->pdata[ii], "label", NULL);
+ }
+ }
+
+ message_list_free_uids (message_list, uids);
+}
+
+static void
+action_mail_label_new_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMailLabelDialog *label_dialog;
+ EMailLabelListStore *store;
+ EMailReader *reader;
+ MessageList *message_list;
+ CamelFolder *folder;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkWidget *dialog;
+ GPtrArray *uids;
+ GdkColor label_color;
+ const gchar *property_name;
+ const gchar *label_name;
+ gchar *label_tag;
+ gint n_children;
+ guint ii;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ dialog = e_mail_label_dialog_new (GTK_WINDOW (shell_window));
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Add Label"));
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
+ goto exit;
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ label_dialog = E_MAIL_LABEL_DIALOG (dialog);
+ label_name = e_mail_label_dialog_get_label_name (label_dialog);
+ e_mail_label_dialog_get_label_color (label_dialog, &label_color);
+
+ property_name = "mail-label-list-store";
+ store = e_shell_settings_get_object (shell_settings, property_name);
+ e_mail_label_list_store_set (store, NULL, label_name, &label_color);
+ g_object_unref (store);
+
+ /* XXX This is awkward. We've added a new label to the list store
+ * but we don't have the new label's tag nor an iterator to use
+ * to fetch it. We know the label was appended to the store,
+ * so we have to dig it out manually. EMailLabelListStore API
+ * probably needs some rethinking. */
+ model = GTK_TREE_MODEL (store);
+ n_children = gtk_tree_model_iter_n_children (model, NULL);
+ gtk_tree_model_iter_nth_child (model, &iter, NULL, n_children - 1);
+ label_tag = e_mail_label_list_store_get_tag (store, &iter);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder = message_list->folder;
+
+ uids = message_list_get_selected (message_list);
+
+ for (ii = 0; ii < uids->len; ii++)
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], label_tag, TRUE);
+
+ message_list_free_uids (message_list, uids);
+
+ g_free (label_tag);
+
+exit:
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_mail_label_none_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellSettings *shell_settings;
+ EShellWindow *shell_window;
+ EMailReader *reader;
+ MessageList *message_list;
+ GtkTreeModel *tree_model;
+ CamelFolder *folder;
+ GtkTreeIter iter;
+ GPtrArray *uids;
+ gboolean valid;
+ guint ii;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ tree_model = e_shell_settings_get_object (
+ shell_settings, "mail-label-list-store");
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ uids = message_list_get_selected (message_list);
+ folder = message_list->folder;
+
+ valid = gtk_tree_model_get_iter_first (tree_model, &iter);
+
+ while (valid) {
+ gchar *tag;
+
+ tag = e_mail_label_list_store_get_tag (
+ E_MAIL_LABEL_LIST_STORE (tree_model), &iter);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], tag, FALSE);
+ camel_folder_set_message_user_tag (
+ folder, uids->pdata[ii], "label", NULL);
+ }
+
+ g_free (tag);
+
+ valid = gtk_tree_model_iter_next (tree_model, &iter);
+ }
+
+ message_list_free_uids (message_list, uids);
+}
+
+static void
+action_mail_search_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMailShellView *mail_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 (mail_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_mail_show_hidden_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_hide_clear (message_list);
+}
+
+static void
+action_mail_smart_backward_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSettings *shell_settings;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ EMFormatHTMLDisplay *html_display;
+ EMailReader *reader;
+ MessageList *message_list;
+ GtkToggleAction *toggle_action;
+ GtkHTML *html;
+ gboolean caret_mode;
+ gboolean magic_spacebar;
+
+ /* This implements the so-called "Magic Backspace". */
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ html_display = e_mail_reader_get_html_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ magic_spacebar = e_shell_settings_get_boolean (
+ shell_settings, "mail-magic-spacebar");
+
+ toggle_action = GTK_TOGGLE_ACTION (ACTION (MAIL_CARET_MODE));
+ caret_mode = gtk_toggle_action_get_active (toggle_action);
+
+ html = EM_FORMAT_HTML (html_display)->html;
+
+ if (gtk_html_command (html, "scroll-backward"))
+ return;
+
+ if (caret_mode || !magic_spacebar)
+ return;
+
+ /* XXX Are two separate calls really necessary? */
+
+ if (message_list_select (
+ message_list, MESSAGE_LIST_SELECT_PREVIOUS,
+ 0, CAMEL_MESSAGE_SEEN))
+ return;
+
+ if (message_list_select (
+ message_list, MESSAGE_LIST_SELECT_PREVIOUS |
+ MESSAGE_LIST_SELECT_WRAP, 0, CAMEL_MESSAGE_SEEN))
+ return;
+
+ em_folder_tree_select_prev_path (folder_tree, TRUE);
+
+ gtk_widget_grab_focus (GTK_WIDGET (message_list));
+}
+
+static void
+action_mail_smart_forward_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSettings *shell_settings;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ EMFormatHTMLDisplay *html_display;
+ EMailReader *reader;
+ MessageList *message_list;
+ GtkToggleAction *toggle_action;
+ GtkHTML *html;
+ gboolean caret_mode;
+ gboolean magic_spacebar;
+
+ /* This implements the so-called "Magic Spacebar". */
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ html_display = e_mail_reader_get_html_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ magic_spacebar = e_shell_settings_get_boolean (
+ shell_settings, "mail-magic-spacebar");
+
+ toggle_action = GTK_TOGGLE_ACTION (ACTION (MAIL_CARET_MODE));
+ caret_mode = gtk_toggle_action_get_active (toggle_action);
+
+ html = EM_FORMAT_HTML (html_display)->html;
+
+ if (gtk_html_command (html, "scroll-forward"))
+ return;
+
+ if (caret_mode || !magic_spacebar)
+ return;
+
+ /* XXX Are two separate calls really necessary? */
+
+ if (message_list_select (
+ message_list, MESSAGE_LIST_SELECT_NEXT,
+ 0, CAMEL_MESSAGE_SEEN))
+ return;
+
+ if (message_list_select (
+ message_list, MESSAGE_LIST_SELECT_NEXT |
+ MESSAGE_LIST_SELECT_WRAP, 0, CAMEL_MESSAGE_SEEN))
+ return;
+
+ em_folder_tree_select_next_path (folder_tree, TRUE);
+
+ gtk_widget_grab_focus (GTK_WIDGET (message_list));
+}
+
+static void
+action_mail_stop_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ mail_cancel_all ();
+}
+
+static void
+action_mail_threads_collapse_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_set_threaded_collapse_all (message_list);
+}
+
+static void
+action_mail_threads_expand_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ MessageList *message_list;
+ EMailReader *reader;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_set_threaded_expand_all (message_list);
+}
+
+static void
+action_mail_threads_group_by_cb (GtkToggleAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ MessageList *message_list;
+ EMailReader *reader;
+ gboolean active;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ active = gtk_toggle_action_get_active (action);
+
+ reader = E_MAIL_READER (mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_set_threaded (message_list, active);
+}
+
+static void
+action_mail_tools_filters_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ em_utils_edit_filters (GTK_WIDGET (shell_window));
+}
+
+static void
+action_mail_tools_search_folders_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ vfolder_edit (E_SHELL_VIEW (mail_shell_view));
+}
+
+static void
+action_mail_tools_subscriptions_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GtkWidget *dialog;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ dialog = em_subscribe_editor_new ();
+ gtk_window_set_transient_for (
+ GTK_WINDOW (dialog), GTK_WINDOW (shell_window));
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ /* XXX Dialog destroys itself. */
+}
+
+static void
+action_mail_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ orientable = GTK_ORIENTABLE (mail_shell_content);
+
+ switch (gtk_radio_action_get_current_value (action)) {
+ case 0:
+ orientation = GTK_ORIENTATION_VERTICAL;
+ break;
+ case 1:
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ gtk_orientable_set_orientation (orientable, orientation);
+}
+
+static void
+action_search_execute_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellContent *shell_content;
+ EMailReader *reader;
+ MessageList *message_list;
+ GKeyFile *key_file;
+ const gchar *folder_uri;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ key_file = e_shell_view_get_state_key_file (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. */
+ if (!e_shell_view_is_active (shell_view))
+ return;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+
+ if (folder_uri != NULL) {
+ const gchar *key;
+ const gchar *string;
+ gchar *group_name;
+
+ key = STATE_KEY_SEARCH_TEXT;
+ string = e_shell_content_get_search_text (shell_content);
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+
+ if (string != NULL && *string != '\0')
+ g_key_file_set_string (
+ key_file, group_name, key, string);
+ else
+ g_key_file_remove_key (
+ key_file, group_name, key, NULL);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+ }
+
+ e_mail_shell_view_execute_search (mail_shell_view);
+}
+
+static void
+action_search_filter_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EMailReader *reader;
+ MessageList *message_list;
+ GKeyFile *key_file;
+ const gchar *folder_uri;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+
+ if (folder_uri != NULL) {
+ const gchar *key;
+ const gchar *string;
+ gchar *group_name;
+
+ key = STATE_KEY_SEARCH_FILTER;
+ string = gtk_action_get_name (GTK_ACTION (current));
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+
+ g_key_file_set_string (key_file, group_name, key, string);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+ }
+
+ gtk_action_activate (ACTION (SEARCH_EXECUTE));
+}
+
+static void
+action_search_scope_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EMailReader *reader;
+ MessageList *message_list;
+ GKeyFile *key_file;
+ const gchar *folder_uri;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ key_file = e_shell_view_get_state_key_file (shell_view);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+
+ if (folder_uri != NULL) {
+ const gchar *key;
+ const gchar *string;
+ gchar *group_name;
+
+ key = STATE_KEY_SEARCH_SCOPE;
+ string = gtk_action_get_name (GTK_ACTION (current));
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+
+ g_key_file_set_string (key_file, group_name, key, string);
+ e_shell_view_set_state_dirty (shell_view);
+
+ g_free (group_name);
+ }
+
+ gtk_action_activate (ACTION (SEARCH_EXECUTE));
+}
+
+static GtkActionEntry mail_entries[] = {
+
+ { "mail-account-disable",
+ NULL,
+ N_("_Disable Account"),
+ NULL,
+ N_("Disable this account"),
+ G_CALLBACK (action_mail_account_disable_cb) },
+
+ { "mail-create-search-folder",
+ NULL,
+ N_("C_reate Search Folder From Search..."),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_create_search_folder_cb) },
+
+ { "mail-download",
+ NULL,
+ N_("_Download Messages for Offline Usage"),
+ NULL,
+ N_("Download messages of accounts and folders marked for offline"),
+ G_CALLBACK (action_mail_download_cb) },
+
+ { "mail-empty-trash",
+ NULL,
+ N_("Empty _Trash"),
+ NULL,
+ N_("Permanently remove all the deleted messages from all folders"),
+ G_CALLBACK (action_mail_empty_trash_cb) },
+
+ { "mail-flush-outbox",
+ "mail-send",
+ N_("Fl_ush Outbox"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_flush_outbox_cb) },
+
+ { "mail-folder-copy",
+ "folder-copy",
+ N_("_Copy Folder To..."),
+ NULL,
+ N_("Copy the selected folder into another folder"),
+ G_CALLBACK (action_mail_folder_copy_cb) },
+
+ { "mail-folder-delete",
+ GTK_STOCK_DELETE,
+ NULL,
+ NULL,
+ N_("Permanently remove this folder"),
+ G_CALLBACK (action_mail_folder_delete_cb) },
+
+ { "mail-folder-expunge",
+ NULL,
+ N_("E_xpunge"),
+ "<Control>e",
+ N_("Permanently remove all deleted messages from this folder"),
+ G_CALLBACK (action_mail_folder_expunge_cb) },
+
+ { "mail-folder-mark-all-as-read",
+ "mail-read",
+ N_("Mar_k All Messages as Read"),
+ NULL,
+ N_("Mark all messages in the folder as read"),
+ G_CALLBACK (action_mail_folder_mark_all_as_read_cb) },
+
+ { "mail-folder-move",
+ "folder-move",
+ N_("_Move Folder To..."),
+ NULL,
+ N_("Move the selected folder into another folder"),
+ G_CALLBACK (action_mail_folder_move_cb) },
+
+ { "mail-folder-new",
+ "folder-new",
+ N_("_New..."),
+ NULL,
+ N_("Create a new folder for storing mail"),
+ G_CALLBACK (action_mail_folder_new_cb) },
+
+ { "mail-folder-properties",
+ GTK_STOCK_PROPERTIES,
+ NULL,
+ NULL,
+ N_("Change the properties of this folder"),
+ G_CALLBACK (action_mail_folder_properties_cb) },
+
+ { "mail-folder-refresh",
+ GTK_STOCK_REFRESH,
+ NULL,
+ "F5",
+ N_("Refresh the folder"),
+ G_CALLBACK (action_mail_folder_refresh_cb) },
+
+ { "mail-folder-rename",
+ NULL,
+ N_("_Rename..."),
+ "F2",
+ N_("Change the name of this folder"),
+ G_CALLBACK (action_mail_folder_rename_cb) },
+
+ { "mail-folder-select-all",
+ NULL,
+ N_("Select _All Messages"),
+ "<Control>a",
+ N_("Select all visible messages"),
+ G_CALLBACK (action_mail_folder_select_all_cb) },
+
+ { "mail-folder-select-thread",
+ NULL,
+ N_("Select Message _Thread"),
+ "<Control>h",
+ N_("Select all messages in the same thread as the selected message"),
+ G_CALLBACK (action_mail_folder_select_thread_cb) },
+
+ { "mail-folder-select-subthread",
+ NULL,
+ N_("Select Message S_ubthread"),
+ "<Shift><Control>h",
+ N_("Select all replies to the currently selected message"),
+ G_CALLBACK (action_mail_folder_select_subthread_cb) },
+
+ { "mail-folder-unsubscribe",
+ NULL,
+ N_("_Unsubscribe"),
+ NULL,
+ N_("Unsubscribe from the selected folder"),
+ G_CALLBACK (action_mail_folder_unsubscribe_cb) },
+
+ { "mail-label-new",
+ NULL,
+ N_("_New Label"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_label_new_cb) },
+
+ { "mail-label-none",
+ NULL,
+ N_("N_one"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_label_none_cb) },
+
+ { "mail-hide-read",
+ NULL,
+ N_("Hide _Read Messages"),
+ NULL,
+ N_("Temporarily hide all messages that have already been read"),
+ G_CALLBACK (action_mail_hide_read_cb) },
+
+ { "mail-hide-selected",
+ NULL,
+ N_("Hide S_elected Messages"),
+ NULL,
+ N_("Temporarily hide the selected messages"),
+ G_CALLBACK (action_mail_hide_selected_cb) },
+
+ { "mail-show-hidden",
+ NULL,
+ N_("Show Hidde_n Messages"),
+ NULL,
+ N_("Show messages that have been temporarily hidden"),
+ G_CALLBACK (action_mail_show_hidden_cb) },
+
+ { "mail-smart-backward",
+ NULL,
+ NULL, /* No menu item; key press only */
+ NULL,
+ NULL,
+ G_CALLBACK (action_mail_smart_backward_cb) },
+
+ { "mail-smart-forward",
+ NULL,
+ NULL, /* No menu item; key press only */
+ NULL,
+ NULL,
+ G_CALLBACK (action_mail_smart_forward_cb) },
+
+ { "mail-stop",
+ GTK_STOCK_STOP,
+ N_("Cancel"),
+ NULL,
+ N_("Cancel the current mail operation"),
+ G_CALLBACK (action_mail_stop_cb) },
+
+ { "mail-threads-collapse-all",
+ NULL,
+ N_("Collapse All _Threads"),
+ "<Shift><Control>b",
+ N_("Collapse all message threads"),
+ G_CALLBACK (action_mail_threads_collapse_all_cb) },
+
+ { "mail-threads-expand-all",
+ NULL,
+ N_("E_xpand All Threads"),
+ NULL,
+ N_("Expand all message threads"),
+ G_CALLBACK (action_mail_threads_expand_all_cb) },
+
+ { "mail-tools-filters",
+ NULL,
+ N_("_Message Filters"),
+ NULL,
+ N_("Create or edit rules for filtering new mail"),
+ G_CALLBACK (action_mail_tools_filters_cb) },
+
+ { "mail-tools-search-folders",
+ NULL,
+ N_("Search F_olders"),
+ NULL,
+ N_("Create or edit search folder definitions"),
+ G_CALLBACK (action_mail_tools_search_folders_cb) },
+
+ { "mail-tools-subscriptions",
+ NULL,
+ N_("_Subscriptions..."),
+ NULL,
+ N_("Subscribe or unsubscribe to folders on remote servers"),
+ G_CALLBACK (action_mail_tools_subscriptions_cb) },
+
+ /*** Menus ***/
+
+ { "mail-folder-menu",
+ NULL,
+ N_("F_older"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-label-menu",
+ NULL,
+ N_("_Label"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-preview-menu",
+ NULL,
+ N_("_Preview"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static EPopupActionEntry mail_popup_entries[] = {
+
+ { "mail-popup-account-disable",
+ NULL,
+ "mail-account-disable" },
+
+ { "mail-popup-empty-trash",
+ NULL,
+ "mail-empty-trash" },
+
+ { "mail-popup-flush-outbox",
+ NULL,
+ "mail-flush-outbox" },
+
+ { "mail-popup-folder-copy",
+ NULL,
+ "mail-folder-copy" },
+
+ { "mail-popup-folder-delete",
+ NULL,
+ "mail-folder-delete" },
+
+ { "mail-popup-folder-move",
+ NULL,
+ "mail-folder-move" },
+
+ { "mail-popup-folder-new",
+ N_("_New Folder..."),
+ "mail-folder-new" },
+
+ { "mail-popup-folder-properties",
+ NULL,
+ "mail-folder-properties" },
+
+ { "mail-popup-folder-refresh",
+ NULL,
+ "mail-folder-refresh" },
+
+ { "mail-popup-folder-rename",
+ NULL,
+ "mail-folder-rename" },
+
+ { "mail-popup-folder-unsubscribe",
+ NULL,
+ "mail-folder-unsubscribe" }
+};
+
+static GtkToggleActionEntry mail_toggle_entries[] = {
+
+ { "mail-hide-deleted",
+ NULL,
+ N_("Hide _Deleted Messages"),
+ NULL,
+ N_("Hide deleted messages rather than displaying "
+ "them with a line through them"),
+ G_CALLBACK (action_mail_hide_deleted_cb),
+ TRUE },
+
+ { "mail-preview",
+ NULL,
+ N_("Show Message _Preview"),
+ "<Control>m",
+ N_("Show message preview pane"),
+ NULL, /* Handled by property bindings */
+ TRUE },
+
+ { "mail-threads-group-by",
+ NULL,
+ N_("_Group By Threads"),
+ "<Control>t",
+ N_("Threaded message list"),
+ G_CALLBACK (action_mail_threads_group_by_cb),
+ FALSE }
+};
+
+static GtkRadioActionEntry mail_view_entries[] = {
+
+ /* This action represents the initial active mail view.
+ * It should not be visible in the UI, nor should it be
+ * possible to switch to it from another shell view. */
+ { "mail-view-initial",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ -1 },
+
+ { "mail-view-classic",
+ NULL,
+ N_("_Classic View"),
+ NULL,
+ N_("Show message preview below the message list"),
+ 0 },
+
+ { "mail-view-vertical",
+ NULL,
+ N_("_Vertical View"),
+ NULL,
+ N_("Show message preview alongside the message list"),
+ 1 }
+};
+
+static GtkRadioActionEntry mail_filter_entries[] = {
+
+ { "mail-filter-all-messages",
+ NULL,
+ N_("All Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_ALL_MESSAGES },
+
+ { "mail-filter-important-messages",
+ "emblem-important",
+ N_("Important Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_IMPORTANT_MESSAGES },
+
+ { "mail-filter-last-5-days-messages",
+ NULL,
+ N_("Last 5 Days' Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_LAST_5_DAYS_MESSAGES },
+
+ { "mail-filter-messages-not-junk",
+ "mail-mark-notjunk",
+ N_("Messages Not Junk"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_MESSAGES_NOT_JUNK },
+
+ { "mail-filter-messages-with-attachments",
+ "mail-attachment",
+ N_("Messages with Attachments"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_MESSAGES_WITH_ATTACHMENTS },
+
+ { "mail-filter-no-label",
+ NULL,
+ N_("No Label"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_NO_LABEL },
+
+ { "mail-filter-read-messages",
+ "mail-read",
+ N_("Read Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_READ_MESSAGES },
+
+ { "mail-filter-recent-messages",
+ NULL,
+ N_("Recent Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_RECENT_MESSAGES },
+
+ { "mail-filter-unread-messages",
+ "mail-unread",
+ N_("Unread Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_UNREAD_MESSAGES }
+};
+
+static GtkRadioActionEntry mail_search_entries[] = {
+
+ { "mail-search-body-contains",
+ NULL,
+ N_("Body contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_BODY_CONTAINS },
+
+ { "mail-search-message-contains",
+ NULL,
+ N_("Message contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_MESSAGE_CONTAINS },
+
+ { "mail-search-recipients-contain",
+ NULL,
+ N_("Recipients contain"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_RECIPIENTS_CONTAIN },
+
+ { "mail-search-sender-contains",
+ NULL,
+ N_("Sender contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SENDER_CONTAINS },
+
+ { "mail-search-subject-contains",
+ NULL,
+ N_("Subject contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SUBJECT_CONTAINS },
+
+ { "mail-search-subject-or-recipients-contains",
+ NULL,
+ N_("Subject or Recipients contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SUBJECT_OR_RECIPIENTS_CONTAINS },
+
+ { "mail-search-subject-or-sender-contains",
+ NULL,
+ N_("Subject or Sender contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SUBJECT_OR_SENDER_CONTAINS }
+};
+
+static GtkRadioActionEntry mail_scope_entries[] = {
+
+ { "mail-scope-all-accounts",
+ NULL,
+ N_("All Accounts"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_ALL_ACCOUNTS },
+
+ { "mail-scope-current-account",
+ NULL,
+ N_("Current Account"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_CURRENT_ACCOUNT },
+
+ { "mail-scope-current-folder",
+ NULL,
+ N_("Current Folder"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_CURRENT_FOLDER }
+};
+
+void
+e_mail_shell_view_actions_init (EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GConfBridge *bridge;
+ GObject *object;
+ GObject *src_object;
+ GObject *dst_object;
+ const gchar *key;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ /* Mail Actions */
+ action_group = ACTION_GROUP (MAIL);
+ gtk_action_group_add_actions (
+ action_group, mail_entries,
+ G_N_ELEMENTS (mail_entries), mail_shell_view);
+ e_action_group_add_popup_actions (
+ action_group, mail_popup_entries,
+ G_N_ELEMENTS (mail_popup_entries));
+ gtk_action_group_add_toggle_actions (
+ action_group, mail_toggle_entries,
+ G_N_ELEMENTS (mail_toggle_entries), mail_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, mail_view_entries,
+ G_N_ELEMENTS (mail_view_entries), -1,
+ G_CALLBACK (action_mail_view_cb), mail_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, mail_search_entries,
+ G_N_ELEMENTS (mail_search_entries),
+ MAIL_SEARCH_SUBJECT_OR_SENDER_CONTAINS,
+ G_CALLBACK (action_mail_search_cb), mail_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, mail_scope_entries,
+ G_N_ELEMENTS (mail_scope_entries),
+ MAIL_SCOPE_CURRENT_FOLDER,
+ G_CALLBACK (action_search_scope_cb), mail_shell_view);
+
+ radio_action = GTK_RADIO_ACTION (ACTION (MAIL_SCOPE_ALL_ACCOUNTS));
+ e_shell_content_set_scope_action (shell_content, radio_action);
+ e_shell_content_set_scope_visible (shell_content, TRUE);
+
+ /* Bind GObject properties for GConf keys. */
+
+ bridge = gconf_bridge_get ();
+
+ object = G_OBJECT (ACTION (MAIL_PREVIEW));
+ key = "/apps/evolution/mail/display/show_preview";
+ gconf_bridge_bind_property (bridge, key, object, "active");
+
+ object = G_OBJECT (ACTION (MAIL_THREADS_GROUP_BY));
+ key = "/apps/evolution/mail/display/thread_list";
+ gconf_bridge_bind_property (bridge, key, object, "active");
+
+ object = G_OBJECT (ACTION (MAIL_VIEW_VERTICAL));
+ key = "/apps/evolution/mail/display/layout";
+ gconf_bridge_bind_property (bridge, key, object, "current-value");
+
+ /* Fine tuning. */
+
+ src_object = G_OBJECT (ACTION (MAIL_THREADS_GROUP_BY));
+
+ dst_object = G_OBJECT (ACTION (MAIL_FOLDER_SELECT_THREAD));
+ e_binding_new (src_object, "active", dst_object, "sensitive");
+
+ dst_object = G_OBJECT (ACTION (MAIL_FOLDER_SELECT_SUBTHREAD));
+ e_binding_new (src_object, "active", dst_object, "sensitive");
+
+ dst_object = G_OBJECT (ACTION (MAIL_THREADS_COLLAPSE_ALL));
+ e_binding_new (src_object, "active", dst_object, "sensitive");
+
+ dst_object = G_OBJECT (ACTION (MAIL_THREADS_EXPAND_ALL));
+ e_binding_new (src_object, "active", dst_object, "sensitive");
+
+ e_mutual_binding_new (
+ G_OBJECT (ACTION (MAIL_PREVIEW)), "active",
+ G_OBJECT (shell_content), "preview-visible");
+
+ /* XXX The boolean sense of the GConf key is the inverse of
+ * the menu item, so we have to maintain two properties. */
+ e_mutual_binding_new_with_negation (
+ G_OBJECT (shell_content), "show-deleted",
+ G_OBJECT (ACTION (MAIL_HIDE_DELETED)), "active");
+
+ g_signal_connect (
+ ACTION (GAL_SAVE_CUSTOM_VIEW), "activate",
+ G_CALLBACK (action_gal_save_custom_view_cb), mail_shell_view);
+
+ g_signal_connect (
+ ACTION (SEARCH_EXECUTE), "activate",
+ G_CALLBACK (action_search_execute_cb), mail_shell_view);
+}
+
+/* Helper for e_mail_shell_view_update_popup_labels() */
+static void
+mail_shell_view_update_label_action (GtkToggleAction *action,
+ MessageList *message_list,
+ GPtrArray *uids,
+ const gchar *label_tag)
+{
+ CamelFolder *folder;
+ gboolean exists = FALSE;
+ gboolean not_exists = FALSE;
+ gboolean sensitive;
+ guint ii;
+
+ folder = message_list->folder;
+
+ /* Figure out the proper label action state for the selected
+ * messages. If all the selected messages have the given label,
+ * make the toggle action active. If all the selected message
+ * DO NOT have the given label, make the toggle action inactive.
+ * If some do and some don't, make the action insensitive. */
+
+ for (ii = 0; ii < uids->len && (!exists || !not_exists); ii++) {
+ const gchar *old_label;
+ gchar *new_label;
+
+ /* Check for new-style labels. */
+ if (camel_folder_get_message_user_flag (
+ folder, uids->pdata[ii], label_tag)) {
+ exists = TRUE;
+ continue;
+ }
+
+ /* Check for old-style labels. */
+ old_label = camel_folder_get_message_user_tag (
+ folder, uids->pdata[ii], "label");
+ if (old_label == NULL) {
+ not_exists = TRUE;
+ continue;
+ }
+
+ /* Convert old-style labels ("<name>") to "$Label<name>". */
+ new_label = g_alloca (strlen (old_label) + 10);
+ g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);
+
+ if (strcmp (new_label, label_tag) == 0)
+ exists = TRUE;
+ else
+ not_exists = TRUE;
+ }
+
+ sensitive = !(exists && not_exists);
+ gtk_toggle_action_set_active (action, exists);
+ gtk_action_set_sensitive (GTK_ACTION (action), sensitive);
+}
+
+void
+e_mail_shell_view_update_popup_labels (EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMailReader *reader;
+ MessageList *message_list;
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ GPtrArray *uids;
+ const gchar *path;
+ gboolean valid;
+ guint merge_id;
+ gint ii = 0;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ ui_manager = e_shell_window_get_ui_manager (shell_window);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ tree_model = e_shell_settings_get_object (
+ shell_settings, "mail-label-list-store");
+
+ action_group = ACTION_GROUP (MAIL_LABEL);
+ merge_id = mail_shell_view->priv->label_merge_id;
+ path = "/mail-message-popup/mail-label-menu/mail-label-actions";
+
+ /* Unmerge the previous menu items. */
+ gtk_ui_manager_remove_ui (ui_manager, merge_id);
+ e_action_group_remove_all_actions (action_group);
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ uids = message_list_get_selected (message_list);
+
+ valid = gtk_tree_model_get_iter_first (tree_model, &iter);
+
+ while (valid) {
+ GtkToggleAction *toggle_action;
+ GtkAction *action;
+ gchar *action_name;
+ gchar *stock_id;
+ gchar *label;
+ gchar *tag;
+
+ label = e_mail_label_list_store_get_name (
+ E_MAIL_LABEL_LIST_STORE (tree_model), &iter);
+ stock_id = e_mail_label_list_store_get_stock_id (
+ E_MAIL_LABEL_LIST_STORE (tree_model), &iter);
+ tag = e_mail_label_list_store_get_tag (
+ E_MAIL_LABEL_LIST_STORE (tree_model), &iter);
+ action_name = g_strdup_printf ("mail-label-%d", ii);
+
+ /* XXX Add a tooltip! */
+ toggle_action = gtk_toggle_action_new (
+ action_name, label, NULL, stock_id);
+
+ g_object_set_data_full (
+ G_OBJECT (toggle_action), "tag",
+ tag, (GDestroyNotify) g_free);
+
+ /* Configure the action before we connect to signals. */
+ mail_shell_view_update_label_action (
+ toggle_action, message_list, uids, tag);
+
+ g_signal_connect (
+ toggle_action, "toggled",
+ G_CALLBACK (action_mail_label_cb), mail_shell_view);
+
+ /* The action group takes ownership of the action. */
+ action = GTK_ACTION (toggle_action);
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (toggle_action);
+
+ gtk_ui_manager_add_ui (
+ ui_manager, merge_id, path, action_name,
+ action_name, GTK_UI_MANAGER_AUTO, FALSE);
+
+ g_free (label);
+ g_free (stock_id);
+ g_free (action_name);
+
+ valid = gtk_tree_model_iter_next (tree_model, &iter);
+ ii++;
+ }
+
+ message_list_free_uids (message_list, uids);
+
+ g_object_unref (tree_model);
+}
+
+void
+e_mail_shell_view_update_search_filter (EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellContent *shell_content;
+ EShellSettings *shell_settings;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ GList *list;
+ GSList *group;
+ gboolean valid;
+ gint ii = 0;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ tree_model = e_shell_settings_get_object (
+ shell_settings, "mail-label-list-store");
+
+ action_group = ACTION_GROUP (MAIL_FILTER);
+ e_action_group_remove_all_actions (action_group);
+
+ /* Add the standard filter actions. */
+ gtk_action_group_add_radio_actions (
+ action_group, mail_filter_entries,
+ G_N_ELEMENTS (mail_filter_entries),
+ MAIL_FILTER_ALL_MESSAGES,
+ G_CALLBACK (action_search_filter_cb),
+ mail_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);
+
+ valid = gtk_tree_model_get_iter_first (tree_model, &iter);
+
+ while (valid) {
+ GtkAction *action;
+ gchar *action_name;
+ gchar *stock_id;
+ gchar *label;
+
+ label = e_mail_label_list_store_get_name (
+ E_MAIL_LABEL_LIST_STORE (tree_model), &iter);
+ stock_id = e_mail_label_list_store_get_stock_id (
+ E_MAIL_LABEL_LIST_STORE (tree_model), &iter);
+
+ action_name = g_strdup_printf ("mail-filter-label-%d", ii);
+ radio_action = gtk_radio_action_new (
+ action_name, label, NULL, stock_id, ii);
+ g_free (action_name);
+
+ 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_free (label);
+ g_free (stock_id);
+
+ valid = gtk_tree_model_iter_next (tree_model, &iter);
+ ii++;
+ }
+
+ /* Use any action in the group; doesn't matter which. */
+ e_shell_content_set_filter_action (shell_content, radio_action);
+
+ ii = MAIL_FILTER_UNREAD_MESSAGES;
+ e_shell_content_add_filter_separator_after (shell_content, ii);
+
+ ii = MAIL_FILTER_READ_MESSAGES;
+ e_shell_content_add_filter_separator_before (shell_content, ii);
+
+ g_object_unref (tree_model);
+}
diff --git a/modules/mail/e-mail-shell-view-actions.h b/modules/mail/e-mail-shell-view-actions.h
new file mode 100644
index 0000000000..3fb7142fff
--- /dev/null
+++ b/modules/mail/e-mail-shell-view-actions.h
@@ -0,0 +1,259 @@
+/*
+ * e-mail-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_MAIL_SHELL_VIEW_ACTIONS_H
+#define E_MAIL_SHELL_VIEW_ACTIONS_H
+
+#include <shell/e-shell-window-actions.h>
+
+/* Mail Actions */
+#define E_SHELL_WINDOW_ACTION_MAIL_ACCOUNT_DISABLE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-account-disable")
+#define E_SHELL_WINDOW_ACTION_MAIL_ADD_SENDER(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-add-sender")
+#define E_SHELL_WINDOW_ACTION_MAIL_CARET_MODE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-caret-mode")
+#define E_SHELL_WINDOW_ACTION_MAIL_CHECK_FOR_JUNK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-check-for-junk")
+#define E_SHELL_WINDOW_ACTION_MAIL_CLIPBOARD_COPY(window) \
+ E_SHELL_WINDOw_ACTION ((window), "mail-clipboard-copy")
+#define E_SHELL_WINDOW_ACTION_MAIL_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-copy")
+#define E_SHELL_WINDOW_ACTION_MAIL_CREATE_SEARCH_FOLDER(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-create-search-folder")
+#define E_SHELL_WINDOW_ACTION_MAIL_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-delete")
+#define E_SHELL_WINDOW_ACTION_MAIL_DOWNLOAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-download")
+#define E_SHELL_WINDOW_ACTION_MAIL_EMPTY_TRASH(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-empty-trash")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_ON_MAILING_LIST(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-on-mailing-list")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_ON_RECIPIENTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-on-recipients")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_ON_SENDER(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-on-sender")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_ON_SUBJECT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-on-subject")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTERS_APPLY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filters-apply")
+#define E_SHELL_WINDOW_ACTION_MAIL_FIND(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-find")
+#define E_SHELL_WINDOW_ACTION_MAIL_FLAG_CLEAR(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-flag-clear")
+#define E_SHELL_WINDOW_ACTION_MAIL_FLAG_COMPLETED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-flag-completed")
+#define E_SHELL_WINDOW_ACTION_MAIL_FLAG_FOR_FOLLOWUP(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-flag-for-followup")
+#define E_SHELL_WINDOW_ACTION_MAIL_FLUSH_OUTBOX(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-flush-outbox")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_COPY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-copy")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_DELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-delete")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_EXPUNGE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-expunge")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_MARK_ALL_READ(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-mark-all-read")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_MOVE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-move")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-new")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_PROPERTIES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-properties")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_REFRESH(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-refresh")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_RENAME(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-rename")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_SELECT_ALL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-select-all")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_SELECT_THREAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-select-thread")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_SELECT_SUBTHREAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-select-subthread")
+#define E_SHELL_WINDOW_ACTION_MAIL_FOLDER_UNSUBSCRIBE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-folder-unsubscribe")
+#define E_SHELL_WINDOW_ACTION_MAIL_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-forward")
+#define E_SHELL_WINDOW_ACTION_MAIL_FORWARD_ATTACHED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-forward-attached")
+#define E_SHELL_WINDOW_ACTION_MAIL_FORWARD_INLINE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-forward-inline")
+#define E_SHELL_WINDOW_ACTION_MAIL_FORWARD_QUOTED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-forward-quoted")
+#define E_SHELL_WINDOW_ACTION_MAIL_HIDE_DELETED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-hide-deleted")
+#define E_SHELL_WINDOW_ACTION_MAIL_HIDE_READ(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-hide-read")
+#define E_SHELL_WINDOW_ACTION_MAIL_HIDE_SELECTED(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-hide-selected")
+#define E_SHELL_WINDOW_ACTION_MAIL_LABEL_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-label-new")
+#define E_SHELL_WINDOW_ACTION_MAIL_LABEL_NONE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-label-none")
+#define E_SHELL_WINDOW_ACTION_MAIL_LOAD_IMAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-load-images")
+#define E_SHELL_WINDOW_ACTION_MAIL_MARK_IMPORTANT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-mark-important")
+#define E_SHELL_WINDOW_ACTION_MAIL_MARK_JUNK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-mark-junk")
+#define E_SHELL_WINDOW_ACTION_MAIL_MARK_NOTJUNK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-mark-notjunk")
+#define E_SHELL_WINDOW_ACTION_MAIL_MARK_READ(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-mark-read")
+#define E_SHELL_WINDOW_ACTION_MAIL_MARK_UNIMPORTANT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-mark-unimportant")
+#define E_SHELL_WINDOW_ACTION_MAIL_MARK_UNREAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-mark-unread")
+#define E_SHELL_WINDOW_ACTION_MAIL_MESSAGE_EDIT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-message-edit")
+#define E_SHELL_WINDOW_ACTION_MAIL_MESSAGE_NEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-message-new")
+#define E_SHELL_WINDOW_ACTION_MAIL_MESSAGE_OPEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-message-open")
+#define E_SHELL_WINDOW_ACTION_MAIL_MOVE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-move")
+#define E_SHELL_WINDOW_ACTION_MAIL_NEXT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-next")
+#define E_SHELL_WINDOW_ACTION_MAIL_NEXT_IMPORTANT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-next-important")
+#define E_SHELL_WINDOW_ACTION_MAIL_NEXT_THREAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-next-thread")
+#define E_SHELL_WINDOW_ACTION_MAIL_NEXT_UNREAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-next-unread")
+#define E_SHELL_WINDOW_ACTION_MAIL_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-preview")
+#define E_SHELL_WINDOW_ACTION_MAIL_PREVIOUS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-previous")
+#define E_SHELL_WINDOW_ACTION_MAIL_PREVIOUS_IMPORTANT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-previous-important")
+#define E_SHELL_WINDOW_ACTION_MAIL_PREVIOUS_UNREAD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-previous-unread")
+#define E_SHELL_WINDOW_ACTION_MAIL_PRINT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-print")
+#define E_SHELL_WINDOW_ACTION_MAIL_PRINT_PREVIEW(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-print-preview")
+#define E_SHELL_WINDOW_ACTION_MAIL_REDIRECT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-redirect")
+#define E_SHELL_WINDOW_ACTION_MAIL_REPLY_ALL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-reply-all")
+#define E_SHELL_WINDOW_ACTION_MAIL_REPLY_LIST(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-reply-list")
+#define E_SHELL_WINDOW_ACTION_MAIL_REPLY_SENDER(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-reply-sender")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_FOLDER_FROM_MAILING_LIST(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-folder-from-mailing-list")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_FOLDER_FROM_RECIPIENTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-folder-from-recipients")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_FOLDER_FROM_SENDER(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-folder-from-sender")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_FOLDER_FROM_SUBJECT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-folder-from-subject")
+#define E_SHELL_WINDOW_ACTION_MAIL_SELECT_ALL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-select-all")
+#define E_SHELL_WINDOW_ACTION_MAIL_SHOW_ALL_HEADERS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-show-all-headers")
+#define E_SHELL_WINDOW_ACTION_MAIL_SHOW_HIDDEN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-show-hidden")
+#define E_SHELL_WINDOW_ACTION_MAIL_SHOW_SOURCE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-show-source")
+#define E_SHELL_WINDOW_ACTION_MAIL_SMART_BACKWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-smart-backward")
+#define E_SHELL_WINDOW_ACTION_MAIL_SMART_FORWARD(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-smart-forward")
+#define E_SHELL_WINDOW_ACTION_MAIL_STOP(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-stop")
+#define E_SHELL_WINDOW_ACTION_MAIL_THREADS_COLLAPSE_ALL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-threads-collapse-all")
+#define E_SHELL_WINDOW_ACTION_MAIL_THREADS_EXPAND_ALL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-threads-expand-all")
+#define E_SHELL_WINDOW_ACTION_MAIL_THREADS_GROUP_BY(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-threads-group-by")
+#define E_SHELL_WINDOW_ACTION_MAIL_TOOLS_FILTERS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-tools-filters")
+#define E_SHELL_WINDOW_ACTION_MAIL_TOOLS_SEARCH_FOLDERS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-tools-search-folders")
+#define E_SHELL_WINDOW_ACTION_MAIL_TOOLS_SUBSCRIPTIONS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-tools-subscriptions")
+#define E_SHELL_WINDOW_ACTION_MAIL_UNDELETE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-undelete")
+#define E_SHELL_WINDOW_ACTION_MAIL_VIEW_CLASSIC(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-view-classic")
+#define E_SHELL_WINDOW_ACTION_MAIL_VIEW_VERTICAL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-view-vertical")
+#define E_SHELL_WINDOW_ACTION_MAIL_ZOOM_100(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-zoom-100")
+#define E_SHELL_WINDOW_ACTION_MAIL_ZOOM_IN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-zoom-in")
+#define E_SHELL_WINDOW_ACTION_MAIL_ZOOM_OUT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-zoom-out")
+
+/* Mail Query Actions */
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_ALL_MESSAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-all-messages")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_IMPORTANT_MESSAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-important-messages")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_LAST_5_DAYS_MESSAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-last-5-days-messages")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_MESSAGES_NOT_JUNK(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-messages-not-junk")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_MESSAGES_WITH_ATTACHMENTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-messages-with-attachments")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_NO_LABEL(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-no-label")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_READ_MESSAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-read-messages")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_RECENT_MESSAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-recent-messages")
+#define E_SHELL_WINDOW_ACTION_MAIL_FILTER_UNREAD_MESSAGES(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-filter-unread-messages")
+#define E_SHELL_WINDOW_ACTION_MAIL_SCOPE_ALL_ACCOUNTS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-scope-all-accounts")
+#define E_SHELL_WINDOW_ACTION_MAIL_SCOPE_CURRENT_ACCOUNT(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-scope-current-account")
+#define E_SHELL_WINDOW_ACTION_MAIL_SCOPE_CURRENT_FOLDER(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-scope-current-folder")
+#define E_SHELL_WINDOW_ACTION_MAIL_SCOPE_CURRENT_MESSAGE(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-scope-current-message")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_BODY_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-body-contains")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_MESSAGE_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-message-contains")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_RECIPIENTS_CONTAIN(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-recipients-contain")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_SENDER_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-sender-contains")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_SUBJECT_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-subject-contains")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_SUBJECT_OR_RECIPIENTS_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-subject-or-recipients-contains")
+#define E_SHELL_WINDOW_ACTION_MAIL_SEARCH_SUBJECT_OR_SENDER_CONTAINS(window) \
+ E_SHELL_WINDOW_ACTION ((window), "mail-search-subject-or-sender-contains")
+
+/* Action Groups */
+#define E_SHELL_WINDOW_ACTION_GROUP_MAIL(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "mail")
+#define E_SHELL_WINDOW_ACTION_GROUP_MAIL_FILTER(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "mail-filter")
+#define E_SHELL_WINDOW_ACTION_GROUP_MAIL_LABEL(window) \
+ E_SHELL_WINDOW_ACTION_GROUP ((window), "mail-label")
+
+#endif /* E_MAIL_SHELL_VIEW_ACTIONS_H */
diff --git a/modules/mail/e-mail-shell-view-private.c b/modules/mail/e-mail-shell-view-private.c
new file mode 100644
index 0000000000..ca2aa8592e
--- /dev/null
+++ b/modules/mail/e-mail-shell-view-private.c
@@ -0,0 +1,1009 @@
+/*
+ * e-mail-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-mail-shell-view-private.h"
+
+#include "widgets/menus/gal-view-factory-etable.h"
+
+static void
+mail_shell_view_folder_tree_selected_cb (EMailShellView *mail_shell_view,
+ const gchar *full_name,
+ const gchar *uri,
+ guint32 flags,
+ EMFolderTree *folder_tree)
+{
+ EShellView *shell_view;
+ EMailReader *reader;
+ gboolean folder_selected;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+
+ folder_selected =
+ !(flags & CAMEL_FOLDER_NOSELECT) &&
+ full_name != NULL;
+
+ if (folder_selected)
+ e_mail_reader_set_folder_uri (reader, uri);
+ else
+ e_mail_reader_set_folder (reader, NULL, NULL);
+
+ e_shell_view_update_actions (shell_view);
+}
+
+static gboolean
+mail_shell_view_folder_tree_key_press_event_cb (EMailShellView *mail_shell_view,
+ GdkEventKey *event)
+{
+ EMailReader *reader;
+ gboolean handled = FALSE;
+
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ goto ctrl;
+
+ /* <keyval> alone */
+ switch (event->keyval) {
+ case GDK_period:
+ case GDK_comma:
+ case GDK_bracketleft:
+ case GDK_bracketright:
+ goto emit;
+
+ default:
+ goto exit;
+ }
+
+ctrl:
+ /* Ctrl + <keyval> */
+ switch (event->keyval) {
+ case GDK_period:
+ case GDK_comma:
+ goto emit;
+
+ default:
+ goto exit;
+ }
+
+ /* All branches jump past this. */
+ g_return_val_if_reached (FALSE);
+
+emit:
+ /* Forward the key press to the EMailReader interface. */
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ g_signal_emit_by_name (reader, "key-press-event", event, &handled);
+
+exit:
+ return handled;
+}
+
+static void
+mail_shell_view_folder_tree_popup_event_cb (EShellView *shell_view,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/mail-folder-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+}
+
+static gboolean
+mail_shell_view_key_press_event_cb (EMailShellView *mail_shell_view,
+ GdkEventKey *event)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ GtkAction *action;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ return FALSE;
+
+ switch (event->keyval) {
+ case GDK_space:
+ action = ACTION (MAIL_SMART_FORWARD);
+ break;
+
+ case GDK_BackSpace:
+ action = ACTION (MAIL_SMART_BACKWARD);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ gtk_action_activate (action);
+
+ return TRUE;
+}
+
+static gint
+mail_shell_view_message_list_key_press_cb (EMailShellView *mail_shell_view,
+ gint row,
+ ETreePath path,
+ gint col,
+ GdkEvent *event)
+{
+ return mail_shell_view_key_press_event_cb (
+ mail_shell_view, &event->key);
+}
+
+static gboolean
+mail_shell_view_message_list_right_click_cb (EShellView *shell_view,
+ gint row,
+ ETreePath path,
+ gint col,
+ GdkEventButton *event)
+{
+ const gchar *widget_path;
+
+ widget_path = "/mail-message-popup";
+ e_shell_view_show_popup_menu (shell_view, widget_path, event);
+
+ return TRUE;
+}
+
+static void
+mail_shell_view_reader_changed_cb (EMailShellView *mail_shell_view,
+ EMailReader *reader)
+{
+ EMailShellContent *mail_shell_content;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ e_mail_shell_content_update_view_instance (mail_shell_content);
+ e_mail_shell_view_update_sidebar (mail_shell_view);
+}
+
+static void
+mail_shell_view_reader_status_message_cb (EMailShellView *mail_shell_view,
+ const gchar *status_message)
+{
+ EShellView *shell_view;
+ EShellTaskbar *shell_taskbar;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_taskbar = e_shell_view_get_shell_taskbar (shell_view);
+
+ e_shell_taskbar_set_message (shell_taskbar, status_message);
+}
+
+static void
+mail_shell_view_scroll_cb (EMailShellView *mail_shell_view,
+ GtkOrientation orientation,
+ GtkScrollType scroll_type,
+ gfloat position,
+ GtkHTML *html)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellSettings *shell_settings;
+ EMailReader *reader;
+ MessageList *message_list;
+ gboolean magic_spacebar;
+
+ if (html->binding_handled)
+ return;
+
+ if (orientation != GTK_ORIENTATION_VERTICAL)
+ return;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ magic_spacebar = e_shell_settings_get_boolean (
+ shell_settings, "mail-magic-spacebar");
+
+ if (!magic_spacebar)
+ return;
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ if (scroll_type == GTK_SCROLL_PAGE_FORWARD) {
+ gtk_widget_grab_focus (GTK_WIDGET (message_list));
+ message_list_select (
+ message_list, MESSAGE_LIST_SELECT_NEXT,
+ 0, CAMEL_MESSAGE_SEEN);
+ } else {
+ gtk_widget_grab_focus (GTK_WIDGET (message_list));
+ message_list_select (
+ message_list, MESSAGE_LIST_SELECT_PREVIOUS,
+ 0, CAMEL_MESSAGE_SEEN);
+ }
+}
+
+static void
+mail_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 mail");
+ 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);
+
+ gal_view_collection_load (collection);
+}
+
+static void
+mail_shell_view_notify_view_id_cb (EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GalViewInstance *view_instance;
+ const gchar *view_id;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ view_instance = NULL; /* FIXME */
+ view_id = e_shell_view_get_view_id (E_SHELL_VIEW (mail_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_mail_shell_view_private_init (EMailShellView *mail_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ if (!gal_view_collection_loaded (shell_view_class->view_collection))
+ mail_shell_view_load_view_collection (shell_view_class);
+
+ g_signal_connect (
+ mail_shell_view, "notify::view-id",
+ G_CALLBACK (mail_shell_view_notify_view_id_cb), NULL);
+}
+
+void
+e_mail_shell_view_private_constructed (EMailShellView *mail_shell_view)
+{
+ EMailShellViewPrivate *priv = mail_shell_view->priv;
+ EMailShellSidebar *mail_shell_sidebar;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ EShellSettings *shell_settings;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ EMFormatHTMLDisplay *html_display;
+ EMFolderTree *folder_tree;
+ RuleContext *context;
+ FilterRule *rule = NULL;
+ GtkTreeModel *tree_model;
+ GtkUIManager *ui_manager;
+ MessageList *message_list;
+ EMailReader *reader;
+ GtkHTML *html;
+ const gchar *source;
+ guint merge_id;
+ gint ii = 0;
+
+ shell_view = E_SHELL_VIEW (mail_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);
+ ui_manager = e_shell_window_get_ui_manager (shell_window);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ tree_model = e_shell_settings_get_object (
+ shell_settings, "mail-label-list-store");
+
+ e_shell_window_add_action_group (shell_window, "mail");
+ e_shell_window_add_action_group (shell_window, "mail-filter");
+ e_shell_window_add_action_group (shell_window, "mail-label");
+
+ merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+ priv->label_merge_id = merge_id;
+
+ /* Cache these to avoid lots of awkward casting. */
+ priv->mail_shell_backend = g_object_ref (shell_backend);
+ priv->mail_shell_content = g_object_ref (shell_content);
+ priv->mail_shell_sidebar = g_object_ref (shell_sidebar);
+
+ reader = E_MAIL_READER (shell_content);
+ html_display = e_mail_reader_get_html_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ mail_shell_sidebar = E_MAIL_SHELL_SIDEBAR (shell_sidebar);
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ html = EM_FORMAT_HTML (html_display)->html;
+
+ g_signal_connect_swapped (
+ folder_tree, "folder-selected",
+ G_CALLBACK (mail_shell_view_folder_tree_selected_cb),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ folder_tree, "key-press-event",
+ G_CALLBACK (mail_shell_view_folder_tree_key_press_event_cb),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ folder_tree, "popup-event",
+ G_CALLBACK (mail_shell_view_folder_tree_popup_event_cb),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ message_list->tree, "key-press",
+ G_CALLBACK (mail_shell_view_message_list_key_press_cb),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ message_list->tree, "right-click",
+ G_CALLBACK (mail_shell_view_message_list_right_click_cb),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ reader, "changed",
+ G_CALLBACK (mail_shell_view_reader_changed_cb),
+ mail_shell_view);
+
+ /* Use the same callback as "changed". */
+ g_signal_connect_swapped (
+ reader, "folder-loaded",
+ G_CALLBACK (mail_shell_view_reader_changed_cb),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ reader, "folder-loaded",
+ G_CALLBACK (e_mail_shell_view_restore_state),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ tree_model, "row-changed",
+ G_CALLBACK (e_mail_shell_view_update_search_filter),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ tree_model, "row-deleted",
+ G_CALLBACK (e_mail_shell_view_update_search_filter),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ tree_model, "row-inserted",
+ G_CALLBACK (e_mail_shell_view_update_search_filter),
+ mail_shell_view);
+
+ g_signal_connect_swapped (
+ html, "key-press-event",
+ G_CALLBACK (mail_shell_view_key_press_event_cb),
+ mail_shell_view);
+
+ g_signal_connect_data (
+ html, "scroll",
+ G_CALLBACK (mail_shell_view_scroll_cb),
+ mail_shell_view, (GClosureNotify) NULL,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+
+ g_signal_connect_swapped (
+ html, "status-message",
+ G_CALLBACK (mail_shell_view_reader_status_message_cb),
+ mail_shell_view);
+
+ e_mail_shell_view_actions_init (mail_shell_view);
+ e_mail_shell_view_update_search_filter (mail_shell_view);
+ e_mail_reader_init (reader);
+
+ /* Populate built-in rules for search entry popup menu.
+ * Keep the assertions, please. If the conditions aren't
+ * met we're going to crash anyway, just more mysteriously. */
+ context = e_shell_content_get_search_context (shell_content);
+ source = FILTER_SOURCE_DEMAND;
+ while ((rule = rule_context_next_rule (context, rule, source))) {
+ g_assert (ii < MAIL_NUM_SEARCH_RULES);
+ priv->search_rules[ii++] = g_object_ref (rule);
+ }
+ g_assert (ii == MAIL_NUM_SEARCH_RULES);
+}
+
+void
+e_mail_shell_view_private_dispose (EMailShellView *mail_shell_view)
+{
+ EMailShellViewPrivate *priv = mail_shell_view->priv;
+ gint ii;
+
+ DISPOSE (priv->mail_shell_backend);
+ DISPOSE (priv->mail_shell_content);
+ DISPOSE (priv->mail_shell_sidebar);
+
+ for (ii = 0; ii < MAIL_NUM_SEARCH_RULES; ii++)
+ DISPOSE (priv->search_rules[ii]);
+}
+
+void
+e_mail_shell_view_private_finalize (EMailShellView *mail_shell_view)
+{
+ /* XXX Nothing to do? */
+}
+
+void
+e_mail_shell_view_restore_state (EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellContent *shell_content;
+ EMailReader *reader;
+ MessageList *message_list;
+ const gchar *folder_uri;
+ gchar *group_name;
+
+ /* XXX Move this to EMailShellContent. */
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ reader = E_MAIL_READER (shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+ g_return_if_fail (folder_uri != NULL);
+
+ group_name = g_strdup_printf ("Folder %s", folder_uri);
+ e_shell_content_restore_state (shell_content, group_name);
+ g_free (group_name);
+}
+
+void
+e_mail_shell_view_execute_search (EMailShellView *mail_shell_view)
+{
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ EShellSettings *shell_settings;
+ EMFormatHTMLDisplay *html_display;
+ EMailShellContent *mail_shell_content;
+ MessageList *message_list;
+ FilterRule *rule;
+ EMailReader *reader;
+ CamelFolder *folder;
+ GtkAction *action;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter tree_iter;
+ GString *string;
+ GList *iter;
+ GSList *search_strings = NULL;
+ const gchar *folder_uri;
+ const gchar *text;
+ gboolean valid;
+ gchar *query;
+ gchar *temp;
+ gchar *tag;
+ gint value;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ shell = e_shell_window_get_shell (shell_window);
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+
+ reader = E_MAIL_READER (shell_content);
+ html_display = e_mail_reader_get_html_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ folder_uri = message_list->folder_uri;
+ folder = message_list->folder;
+
+ /* This returns a new object reference. */
+ model = e_shell_settings_get_object (
+ shell_settings, "mail-label-list-store");
+
+ text = e_shell_content_get_search_text (shell_content);
+ if (text == NULL || *text == '\0') {
+ query = g_strdup ("");
+ goto filter;
+ }
+
+ /* Replace variables in the selected rule with the
+ * current search text and extract a query string. */
+
+ action = ACTION (MAIL_SEARCH_SUBJECT_OR_SENDER_CONTAINS);
+ value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (action));
+ g_return_if_fail (value >= 0 && value < MAIL_NUM_SEARCH_RULES);
+ rule = mail_shell_view->priv->search_rules[value];
+
+ for (iter = rule->parts; iter != NULL; iter = iter->next) {
+ FilterPart *part = iter->data;
+ FilterElement *element = NULL;
+
+ if (strcmp (part->name, "subject") == 0)
+ element = filter_part_find_element (part, "subject");
+ else if (strcmp (part->name, "body") == 0)
+ element = filter_part_find_element (part, "word");
+ else if (strcmp (part->name, "sender") == 0)
+ element = filter_part_find_element (part, "sender");
+ else if (strcmp (part->name, "to") == 0)
+ element = filter_part_find_element (part, "recipient");
+
+ if (strcmp (part->name, "body") == 0) {
+ struct _camel_search_words *words;
+ gint ii;
+
+ words = camel_search_words_split ((guchar *) text);
+ for (ii = 0; ii < words->len; ii++)
+ search_strings = g_slist_prepend (
+ search_strings, g_strdup (
+ words->words[ii]->word));
+ camel_search_words_free (words);
+ }
+
+ if (element != NULL) {
+ FilterInput *input = FILTER_INPUT (element);
+ filter_input_set_value (input, text);
+ }
+ }
+
+ string = g_string_sized_new (1024);
+ filter_rule_build_code (rule, string);
+ query = g_string_free (string, FALSE);
+
+filter:
+
+ /* Apply selected filter. */
+
+ value = e_shell_content_get_filter_value (shell_content);
+ switch (value) {
+ case MAIL_FILTER_ALL_MESSAGES:
+ break;
+
+ case MAIL_FILTER_UNREAD_MESSAGES:
+ temp = g_strdup_printf (
+ "(and %s (match-all (not "
+ "(system-flag \"Seen\"))))", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case MAIL_FILTER_NO_LABEL:
+ string = g_string_sized_new (1024);
+ g_string_append_printf (
+ string, "(and %s (and ", query);
+ valid = gtk_tree_model_get_iter_first (
+ model, &tree_iter);
+ while (valid) {
+ tag = e_mail_label_list_store_get_tag (
+ E_MAIL_LABEL_LIST_STORE (model),
+ &tree_iter);
+ g_string_append_printf (
+ string, " (match-all (not (or "
+ "(= (user-tag \"label\") \"%s\") "
+ "(user-flag \"$Label%s\") "
+ "(user-flag \"%s\"))))",
+ tag, tag, tag);
+ g_free (tag);
+
+ valid = gtk_tree_model_iter_next (
+ model, &tree_iter);
+ }
+ g_string_append_len (string, "))", 2);
+ g_free (query);
+ query = g_string_free (string, FALSE);
+ break;
+
+ case MAIL_FILTER_READ_MESSAGES:
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(system-flag \"Seen\")))", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case MAIL_FILTER_RECENT_MESSAGES:
+ if (em_utils_folder_is_sent (folder, folder_uri))
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(> (get-sent-date) "
+ "(- (get-current-date) 86400))))",
+ query);
+ else
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(> (get-received-date) "
+ "(- (get-current-date) 86400))))",
+ query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case MAIL_FILTER_LAST_5_DAYS_MESSAGES:
+ if (em_utils_folder_is_sent (folder, folder_uri))
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(> (get-sent-date) "
+ "(- (get-current-date) 432000))))",
+ query);
+ else
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(> (get-received-date) "
+ "(- (get-current-date) 432000))))",
+ query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case MAIL_FILTER_MESSAGES_WITH_ATTACHMENTS:
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(system-flag \"Attachments\")))", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case MAIL_FILTER_IMPORTANT_MESSAGES:
+ temp = g_strdup_printf (
+ "(and %s (match-all "
+ "(system-flag \"Flagged\")))", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ case MAIL_FILTER_MESSAGES_NOT_JUNK:
+ temp = g_strdup_printf (
+ "(and %s (match-all (not "
+ "(system-flag \"junk\"))))", query);
+ g_free (query);
+ query = temp;
+ break;
+
+ default:
+ /* The action value also serves as a path for
+ * the label list store. That's why we number
+ * the label actions from zero. */
+ path = gtk_tree_path_new_from_indices (value, -1);
+ gtk_tree_model_get_iter (model, &tree_iter, path);
+ gtk_tree_path_free (path);
+
+ tag = e_mail_label_list_store_get_tag (
+ E_MAIL_LABEL_LIST_STORE (model), &tree_iter);
+ temp = g_strdup_printf (
+ "(and %s (match-all (or "
+ "(= (user-tag \"label\") \"%s\") "
+ "(user-flag \"$Label%s\") "
+ "(user-flag \"%s\"))))",
+ query, tag, tag, tag);
+ g_free (tag);
+
+ g_free (query);
+ query = temp;
+ break;
+ }
+
+ message_list_set_search (message_list, query);
+
+ e_mail_shell_content_set_search_strings (
+ mail_shell_content, search_strings);
+
+ g_slist_foreach (search_strings, (GFunc) g_free, NULL);
+ g_slist_free (search_strings);
+
+ g_object_unref (model);
+ g_free (query);
+}
+
+/* Helper for e_mail_shell_view_create_filter_from_selected() */
+static void
+mail_shell_view_create_filter_cb (CamelFolder *folder,
+ const gchar *uid,
+ CamelMimeMessage *message,
+ gpointer user_data)
+{
+ struct {
+ const gchar *source;
+ gint type;
+ } *filter_data = user_data;
+
+ if (message != NULL)
+ filter_gui_add_from_message (
+ message, filter_data->source, filter_data->type);
+
+ g_free (filter_data);
+}
+
+void
+e_mail_shell_view_create_filter_from_selected (EMailShellView *mail_shell_view,
+ gint filter_type)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+ CamelFolder *folder;
+ const gchar *filter_source;
+ const gchar *folder_uri;
+ GPtrArray *uids;
+
+ struct {
+ const gchar *source;
+ gint type;
+ } *filter_data;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+ folder = message_list->folder;
+
+ if (em_utils_folder_is_sent (folder, folder_uri))
+ filter_source = FILTER_SOURCE_OUTGOING;
+ else if (em_utils_folder_is_outbox (folder, folder_uri))
+ filter_source = FILTER_SOURCE_OUTGOING;
+ else
+ filter_source = FILTER_SOURCE_INCOMING;
+
+ uids = message_list_get_selected (message_list);
+
+ if (uids->len == 1) {
+ filter_data = g_malloc (sizeof (*filter_data));
+ filter_data->source = filter_source;
+ filter_data->type = filter_type;
+
+ mail_get_message (
+ folder, uids->pdata[0],
+ mail_shell_view_create_filter_cb,
+ filter_data, mail_msg_unordered_push);
+ }
+
+ em_utils_uids_free (uids);
+}
+
+/* Helper for e_mail_shell_view_create_vfolder_from_selected() */
+static void
+mail_shell_view_create_vfolder_cb (CamelFolder *folder,
+ const gchar *uid,
+ CamelMimeMessage *message,
+ gpointer user_data)
+{
+ struct {
+ gchar *uri;
+ gint type;
+ } *vfolder_data = user_data;
+
+ if (message != NULL)
+ vfolder_gui_add_from_message (
+ message, vfolder_data->type, vfolder_data->uri);
+
+ g_free (vfolder_data->uri);
+ g_free (vfolder_data);
+}
+
+void
+e_mail_shell_view_create_vfolder_from_selected (EMailShellView *mail_shell_view,
+ gint vfolder_type)
+{
+ EMailReader *reader;
+ MessageList *message_list;
+ CamelFolder *folder;
+ const gchar *folder_uri;
+ GPtrArray *uids;
+
+ struct {
+ gchar *uri;
+ gint type;
+ } *vfolder_data;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ reader = E_MAIL_READER (mail_shell_view->priv->mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+ folder = message_list->folder;
+
+ uids = message_list_get_selected (message_list);
+
+ if (uids->len == 1) {
+ vfolder_data = g_malloc (sizeof (*vfolder_data));
+ vfolder_data->uri = g_strdup (folder_uri);
+ vfolder_data->type = vfolder_type;
+
+ mail_get_message (
+ folder, uids->pdata[0],
+ mail_shell_view_create_vfolder_cb,
+ vfolder_data, mail_msg_unordered_push);
+ }
+
+ em_utils_uids_free (uids);
+}
+
+void
+e_mail_shell_view_update_sidebar (EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellView *shell_view;
+ EMailReader *reader;
+ MessageList *message_list;
+ CamelStore *local_store;
+ CamelFolder *folder;
+ GPtrArray *selected;
+ GString *buffer;
+ const gchar *display_name;
+ const gchar *folder_uri;
+ gchar *folder_name;
+ gchar *title;
+ guint32 num_deleted;
+ guint32 num_junked;
+ guint32 num_junked_not_deleted;
+ guint32 num_unread;
+ guint32 num_visible;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+
+ reader = E_MAIL_READER (mail_shell_content);
+ message_list = e_mail_reader_get_message_list (reader);
+ folder_uri = message_list->folder_uri;
+ folder = message_list->folder;
+
+ local_store = e_mail_local_get_store ();
+
+ /* If no folder is selected, reset the sidebar banners
+ * to their default values and stop. */
+ if (folder == NULL) {
+ GtkAction *action;
+ gchar *label;
+
+ action = e_shell_view_get_action (shell_view);
+
+ g_object_get (action, "label", &label, NULL);
+ e_shell_sidebar_set_secondary_text (shell_sidebar, NULL);
+ e_shell_view_set_title (shell_view, label);
+ g_free (label);
+
+ return;
+ }
+
+ camel_object_get (
+ folder, NULL,
+ CAMEL_FOLDER_NAME, &folder_name,
+ CAMEL_FOLDER_DELETED, &num_deleted,
+ CAMEL_FOLDER_JUNKED, &num_junked,
+ CAMEL_FOLDER_JUNKED_NOT_DELETED, &num_junked_not_deleted,
+ CAMEL_FOLDER_UNREAD, &num_unread,
+ CAMEL_FOLDER_VISIBLE, &num_visible,
+ NULL);
+
+ buffer = g_string_sized_new (256);
+ selected = message_list_get_selected (message_list);
+
+ if (selected->len > 1)
+ g_string_append_printf (
+ buffer, ngettext ("%d selected, ", "%d selected, ",
+ selected->len), selected->len);
+
+ if (CAMEL_IS_VTRASH_FOLDER (folder)) {
+ CamelVTrashFolder *trash_folder;
+
+ trash_folder = (CamelVTrashFolder *) folder;
+
+ /* "Trash" folder */
+ if (trash_folder->type == CAMEL_VTRASH_FOLDER_TRASH)
+ g_string_append_printf (
+ buffer, ngettext ("%d deleted",
+ "%d deleted", num_deleted), num_deleted);
+
+ /* "Junk" folder (hide deleted messages) */
+ else if (e_mail_reader_get_hide_deleted (reader))
+ g_string_append_printf (
+ buffer, ngettext ("%d junk",
+ "%d junk", num_junked_not_deleted),
+ num_junked_not_deleted);
+
+ /* "Junk" folder (show deleted messages) */
+ else
+ g_string_append_printf (
+ buffer, ngettext ("%d junk", "%d junk",
+ num_junked), num_junked);
+
+ /* "Drafts" folder */
+ } else if (em_utils_folder_is_drafts (folder, folder_uri)) {
+ g_string_append_printf (
+ buffer, ngettext ("%d draft", "%d drafts",
+ num_visible), num_visible);
+
+ /* "Outbox" folder */
+ } else if (em_utils_folder_is_outbox (folder, folder_uri)) {
+ g_string_append_printf (
+ buffer, ngettext ("%d unsent", "%d unsent",
+ num_visible), num_visible);
+
+ /* "Sent" folder */
+ } else if (em_utils_folder_is_sent (folder, folder_uri)) {
+ g_string_append_printf (
+ buffer, ngettext ("%d sent", "%d sent",
+ num_visible), num_visible);
+
+ /* Normal folder */
+ } else {
+ if (!e_mail_reader_get_hide_deleted (reader))
+ num_visible +=
+ num_deleted - num_junked +
+ num_junked_not_deleted;
+
+ if (num_unread > 0 && selected->len <= 1)
+ g_string_append_printf (
+ buffer, ngettext ("%d unread, ",
+ "%d unread, ", num_unread), num_unread);
+ g_string_append_printf (
+ buffer, ngettext ("%d total", "%d total",
+ num_visible), num_visible);
+ }
+
+ message_list_free_uids (message_list, selected);
+
+ /* Choose a suitable folder name for displaying. */
+ if (folder->parent_store == local_store && (
+ strcmp (folder_name, "Drafts") == 0 ||
+ strcmp (folder_name, "Inbox") == 0 ||
+ strcmp (folder_name, "Outbox") == 0 ||
+ strcmp (folder_name, "Sent") == 0 ||
+ strcmp (folder_name, "Templates") == 0))
+ display_name = _(folder_name);
+ else if (strcmp (folder_name, "INBOX") == 0)
+ display_name = _("Inbox");
+ else
+ display_name = folder_name;
+
+ title = g_strdup_printf ("%s (%s)", display_name, buffer->str);
+ e_shell_sidebar_set_secondary_text (shell_sidebar, buffer->str);
+ e_shell_view_set_title (shell_view, title);
+ g_free (title);
+
+ camel_object_free (folder, CAMEL_FOLDER_NAME, folder_name);
+ g_string_free (buffer, TRUE);
+}
diff --git a/modules/mail/e-mail-shell-view-private.h b/modules/mail/e-mail-shell-view-private.h
new file mode 100644
index 0000000000..988d494219
--- /dev/null
+++ b/modules/mail/e-mail-shell-view-private.h
@@ -0,0 +1,173 @@
+/*
+ * e-mail-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_MAIL_SHELL_VIEW_PRIVATE_H
+#define E_MAIL_SHELL_VIEW_PRIVATE_H
+
+#include "e-mail-shell-view.h"
+
+#include <glib/gi18n.h>
+#include <gtkhtml/gtkhtml.h>
+#include <camel/camel-disco-store.h>
+#include <camel/camel-offline-store.h>
+#include <camel/camel-vtrash-folder.h>
+#include <camel/camel-search-private.h> /* for camel_search_word */
+
+#include "e-util/e-util.h"
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+#include "e-util/e-account-utils.h"
+#include "filter/filter-part.h"
+#include "widgets/misc/e-popup-action.h"
+#include "widgets/menus/gal-view-instance.h"
+
+#include "e-mail-label-dialog.h"
+#include "e-mail-label-list-store.h"
+#include "e-mail-local.h"
+#include "e-mail-reader.h"
+#include "e-mail-store.h"
+#include "em-composer-utils.h"
+#include "em-folder-properties.h"
+#include "em-folder-selector.h"
+#include "em-folder-utils.h"
+#include "em-subscribe-editor.h"
+#include "em-utils.h"
+#include "mail-autofilter.h"
+#include "mail-config.h"
+#include "mail-ops.h"
+#include "mail-send-recv.h"
+#include "mail-vfolder.h"
+
+#include "e-mail-shell-backend.h"
+#include "e-mail-shell-content.h"
+#include "e-mail-shell-sidebar.h"
+#include "e-mail-shell-view-actions.h"
+
+#define E_MAIL_SHELL_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SHELL_VIEW, EMailShellViewPrivate))
+
+/* 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 "message-list.etspec"
+
+G_BEGIN_DECLS
+
+/* Filter items are displayed in ascending order.
+ * Labels are numbered from zero, so subsequent items must have
+ * sufficiently large values. Unfortunately this introduces an
+ * arbitrary upper bound on labels. */
+enum {
+ MAIL_FILTER_ALL_MESSAGES = -3,
+ MAIL_FILTER_UNREAD_MESSAGES = -2,
+ MAIL_FILTER_NO_LABEL = -1,
+ /* Labels go here */
+ MAIL_FILTER_READ_MESSAGES = 5000,
+ MAIL_FILTER_RECENT_MESSAGES = 5001,
+ MAIL_FILTER_LAST_5_DAYS_MESSAGES = 5002,
+ MAIL_FILTER_MESSAGES_WITH_ATTACHMENTS = 5003,
+ MAIL_FILTER_IMPORTANT_MESSAGES = 5004,
+ MAIL_FILTER_MESSAGES_NOT_JUNK = 5005
+};
+
+/* Search items are displayed in ascending order. */
+enum {
+ MAIL_SEARCH_SUBJECT_OR_SENDER_CONTAINS,
+ MAIL_SEARCH_SUBJECT_OR_RECIPIENTS_CONTAINS,
+ MAIL_SEARCH_RECIPIENTS_CONTAIN,
+ MAIL_SEARCH_MESSAGE_CONTAINS,
+ MAIL_SEARCH_SUBJECT_CONTAINS,
+ MAIL_SEARCH_SENDER_CONTAINS,
+ MAIL_SEARCH_BODY_CONTAINS,
+ MAIL_NUM_SEARCH_RULES
+};
+
+/* Scope items are displayed in ascending order. */
+enum {
+ MAIL_SCOPE_CURRENT_FOLDER,
+ MAIL_SCOPE_CURRENT_ACCOUNT,
+ MAIL_SCOPE_ALL_ACCOUNTS
+};
+
+struct _EMailShellViewPrivate {
+
+ /*** Other Stuff ***/
+
+ /* These are just for convenience. */
+ EMailShellBackend *mail_shell_backend;
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+
+ /* For UI merging and unmerging. */
+ guint merge_id;
+ guint label_merge_id;
+
+ /* Filter rules correspond to the search entry menu. */
+ FilterRule *search_rules[MAIL_NUM_SEARCH_RULES];
+
+ guint show_deleted : 1;
+};
+
+void e_mail_shell_view_private_init
+ (EMailShellView *mail_shell_view,
+ EShellViewClass *shell_view_class);
+void e_mail_shell_view_private_constructed
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_private_dispose
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_private_finalize
+ (EMailShellView *mail_shell_view);
+
+/* Private Utilities */
+
+void e_mail_shell_view_actions_init
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_restore_state
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_execute_search
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_create_filter_from_selected
+ (EMailShellView *mail_shell_view,
+ gint filter_type);
+void e_mail_shell_view_create_vfolder_from_selected
+ (EMailShellView *mail_shell_view,
+ gint vfolder_type);
+void e_mail_shell_view_update_popup_labels
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_update_search_filter
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_update_sidebar
+ (EMailShellView *mail_shell_view);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_VIEW_PRIVATE_H */
diff --git a/modules/mail/e-mail-shell-view.c b/modules/mail/e-mail-shell-view.c
new file mode 100644
index 0000000000..fb045905e6
--- /dev/null
+++ b/modules/mail/e-mail-shell-view.c
@@ -0,0 +1,262 @@
+/*
+ * e-mail-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-mail-shell-view-private.h"
+
+static gpointer parent_class;
+static GType mail_shell_view_type;
+
+static void
+mail_shell_view_dispose (GObject *object)
+{
+ e_mail_shell_view_private_dispose (E_MAIL_SHELL_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+mail_shell_view_finalize (GObject *object)
+{
+ e_mail_shell_view_private_finalize (E_MAIL_SHELL_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+mail_shell_view_constructed (GObject *object)
+{
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ e_mail_shell_view_private_constructed (E_MAIL_SHELL_VIEW (object));
+}
+
+static void
+mail_shell_view_toggled (EShellView *shell_view)
+{
+ EMailShellViewPrivate *priv;
+ EShellWindow *shell_window;
+ GtkUIManager *ui_manager;
+ const gchar *basename;
+ gboolean view_is_active;
+
+ priv = E_MAIL_SHELL_VIEW_GET_PRIVATE (shell_view);
+
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ ui_manager = e_shell_window_get_ui_manager (shell_window);
+ view_is_active = e_shell_view_is_active (shell_view);
+ basename = E_MAIL_READER_UI_DEFINITION;
+
+ if (view_is_active && priv->merge_id == 0) {
+ priv->merge_id = e_load_ui_definition (ui_manager, basename);
+ e_mail_reader_create_charset_menu (
+ E_MAIL_READER (priv->mail_shell_content),
+ ui_manager, priv->merge_id);
+ } else if (!view_is_active && priv->merge_id != 0) {
+ gtk_ui_manager_remove_ui (ui_manager, priv->merge_id);
+ priv->merge_id = 0;
+ }
+
+ /* Chain up to parent's toggled() method. */
+ E_SHELL_VIEW_CLASS (parent_class)->toggled (shell_view);
+}
+
+static void
+mail_shell_view_update_actions (EShellView *shell_view)
+{
+ EMailShellView *mail_shell_view;
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellContent *shell_content;
+ EShellSidebar *shell_sidebar;
+ EShellWindow *shell_window;
+ EMFolderTree *folder_tree;
+ EAccount *account = NULL;
+ GtkAction *action;
+ const gchar *label;
+ gchar *uri;
+ gboolean sensitive;
+ guint32 state;
+
+ /* Be descriptive. */
+ gboolean account_is_groupwise;
+ gboolean folder_allows_children;
+ gboolean folder_can_be_deleted;
+ gboolean folder_is_junk;
+ gboolean folder_is_outbox;
+ gboolean folder_is_store;
+ gboolean folder_is_trash;
+
+ mail_shell_view = E_MAIL_SHELL_VIEW (shell_view);
+
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ e_mail_reader_update_actions (E_MAIL_READER (shell_content));
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ state = e_shell_sidebar_check_state (shell_sidebar);
+
+ folder_allows_children =
+ (state & E_MAIL_SHELL_SIDEBAR_FOLDER_ALLOWS_CHILDREN);
+ folder_can_be_deleted =
+ (state & E_MAIL_SHELL_SIDEBAR_FOLDER_CAN_DELETE);
+ folder_is_junk =
+ (state & E_MAIL_SHELL_SIDEBAR_FOLDER_IS_JUNK);
+ folder_is_outbox =
+ (state & E_MAIL_SHELL_SIDEBAR_FOLDER_IS_OUTBOX);
+ folder_is_store =
+ (state & E_MAIL_SHELL_SIDEBAR_FOLDER_IS_STORE);
+ folder_is_trash =
+ (state & E_MAIL_SHELL_SIDEBAR_FOLDER_IS_TRASH);
+
+ uri = em_folder_tree_get_selected_uri (folder_tree);
+ if (uri != NULL) {
+ account = mail_config_get_account_by_source_url (uri);
+
+ /* FIXME This belongs in a GroupWise plugin. */
+ account_is_groupwise =
+ (g_strrstr (uri, "groupwise://") != NULL) &&
+ account != NULL && account->parent_uid != NULL;
+
+ g_free (uri);
+ }
+
+ action = ACTION (MAIL_ACCOUNT_DISABLE);
+ sensitive = (account != NULL) && folder_is_store;
+ if (account_is_groupwise)
+ label = _("Proxy _Logout");
+ else
+ label = _("_Disable Account");
+ gtk_action_set_sensitive (action, sensitive);
+ g_object_set (action, "label", label, NULL);
+
+ action = ACTION (MAIL_EMPTY_TRASH);
+ sensitive = folder_is_trash;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FLUSH_OUTBOX);
+ sensitive = folder_is_outbox;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_COPY);
+ sensitive = !folder_is_store;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_DELETE);
+ sensitive = !folder_is_store && folder_can_be_deleted;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_MOVE);
+ sensitive = !folder_is_store && folder_can_be_deleted;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_NEW);
+ sensitive = folder_allows_children;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_PROPERTIES);
+ sensitive = !folder_is_store;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_REFRESH);
+ sensitive = !folder_is_store;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_RENAME);
+ sensitive = !folder_is_store && folder_can_be_deleted;
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = ACTION (MAIL_FOLDER_UNSUBSCRIBE);
+ sensitive = !folder_is_store && folder_can_be_deleted;
+ gtk_action_set_sensitive (action, sensitive);
+
+ e_mail_shell_view_update_popup_labels (mail_shell_view);
+}
+
+static void
+mail_shell_view_class_init (EMailShellViewClass *class,
+ GTypeModule *type_module)
+{
+ GObjectClass *object_class;
+ EShellViewClass *shell_view_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMailShellViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = mail_shell_view_dispose;
+ object_class->finalize = mail_shell_view_finalize;
+ object_class->constructed = mail_shell_view_constructed;
+
+ shell_view_class = E_SHELL_VIEW_CLASS (class);
+ shell_view_class->label = _("Mail");
+ shell_view_class->icon_name = "evolution-mail";
+ shell_view_class->ui_definition = "evolution-mail.ui";
+ shell_view_class->ui_manager_id = "org.gnome.evolution.mail";
+ shell_view_class->search_options = "/mail-search-options";
+ shell_view_class->search_rules = "searchtypes.xml";
+ shell_view_class->new_shell_content = e_mail_shell_content_new;
+ shell_view_class->new_shell_sidebar = e_mail_shell_sidebar_new;
+ shell_view_class->toggled = mail_shell_view_toggled;
+ shell_view_class->update_actions = mail_shell_view_update_actions;
+}
+
+static void
+mail_shell_view_init (EMailShellView *mail_shell_view,
+ EShellViewClass *shell_view_class)
+{
+ mail_shell_view->priv =
+ E_MAIL_SHELL_VIEW_GET_PRIVATE (mail_shell_view);
+
+ e_mail_shell_view_private_init (mail_shell_view, shell_view_class);
+}
+
+GType
+e_mail_shell_view_get_type (void)
+{
+ return mail_shell_view_type;
+}
+
+void
+e_mail_shell_view_register_type (GTypeModule *type_module)
+{
+ const GTypeInfo type_info = {
+ sizeof (EMailShellViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) mail_shell_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMailShellView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) mail_shell_view_init,
+ NULL /* value_table */
+ };
+
+ mail_shell_view_type = g_type_module_register_type (
+ type_module, E_TYPE_SHELL_VIEW,
+ "EMailShellView", &type_info, 0);
+}
diff --git a/modules/mail/e-mail-shell-view.h b/modules/mail/e-mail-shell-view.h
new file mode 100644
index 0000000000..d20bde74a6
--- /dev/null
+++ b/modules/mail/e-mail-shell-view.h
@@ -0,0 +1,72 @@
+/*
+ * e-mail-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_MAIL_SHELL_VIEW_H
+#define E_MAIL_SHELL_VIEW_H
+
+#include <shell/e-shell-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SHELL_VIEW \
+ (e_mail_shell_view_get_type ())
+#define E_MAIL_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_SHELL_VIEW, EMailShellView))
+#define E_MAIL_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_SHELL_VIEW, EMailShellViewClass))
+#define E_IS_MAIL_SHELL_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_SHELL_VIEW))
+#define E_IS_MAIL_SHELL_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_SHELL_VIEW))
+#define E_MAIL_SHELL_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_SHELL_VIEW, EMailShellViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailShellView EMailShellView;
+typedef struct _EMailShellViewClass EMailShellViewClass;
+typedef struct _EMailShellViewPrivate EMailShellViewPrivate;
+
+struct _EMailShellView {
+ EShellView parent;
+ EMailShellViewPrivate *priv;
+};
+
+struct _EMailShellViewClass {
+ EShellViewClass parent_class;
+};
+
+GType e_mail_shell_view_get_type (void);
+void e_mail_shell_view_register_type
+ (GTypeModule *type_module);
+gboolean e_mail_shell_view_get_show_deleted
+ (EMailShellView *mail_shell_view);
+void e_mail_shell_view_set_show_deleted
+ (EMailShellView *mail_shell_view,
+ gboolean show_deleted);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SHELL_VIEW_H */
diff --git a/modules/mail/em-account-prefs.c b/modules/mail/em-account-prefs.c
new file mode 100644
index 0000000000..0f86cd8341
--- /dev/null
+++ b/modules/mail/em-account-prefs.c
@@ -0,0 +1,323 @@
+/*
+ * em-account-prefs.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)
+ *
+ */
+
+/* XXX EAccountManager handles all the user interface stuff. This subclass
+ * applies policies using mailer resources that EAccountManager does not
+ * have access to. The desire is to someday move account management
+ * completely out of the mailer, perhaps to evolution-data-server. */
+
+#include "em-account-prefs.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <camel/camel-url.h>
+
+#include <glib/gi18n.h>
+
+#include "e-util/e-error.h"
+
+#include "e-mail-store.h"
+#include "em-config.h"
+#include "em-account-editor.h"
+
+#define EM_ACCOUNT_PREFS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), EM_TYPE_ACCOUNT_PREFS, EMAccountPrefsPrivate))
+
+struct _EMAccountPrefsPrivate {
+ gpointer assistant; /* weak pointer */
+ gpointer editor; /* weak pointer */
+};
+
+static gpointer parent_class;
+
+static void
+account_prefs_enable_account_cb (EAccountTreeView *tree_view)
+{
+ EAccount *account;
+
+ account = e_account_tree_view_get_selected (tree_view);
+ g_return_if_fail (account != NULL);
+
+ e_mail_store_add_by_uri (account->source->url, account->name);
+}
+
+static void
+account_prefs_disable_account_cb (EAccountTreeView *tree_view)
+{
+ EAccountList *account_list;
+ EAccount *account;
+ gpointer parent;
+ gint response;
+
+ account = e_account_tree_view_get_selected (tree_view);
+ g_return_if_fail (account != NULL);
+
+ account_list = e_account_tree_view_get_account_list (tree_view);
+ g_return_if_fail (account_list != NULL);
+
+ if (!e_account_list_account_has_proxies (account_list, account))
+ return;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ response = e_error_run (
+ parent, "mail:ask-delete-proxy-accounts", NULL);
+
+ if (response != GTK_RESPONSE_YES) {
+ g_signal_stop_emission_by_name (tree_view, "disable-account");
+ return;
+ }
+
+ e_account_list_remove_account_proxies (account_list, account);
+
+ e_mail_store_remove_by_uri (account->source->url);
+}
+
+static void
+account_prefs_add_account (EAccountManager *manager)
+{
+ EMAccountPrefsPrivate *priv;
+ EMAccountEditor *emae;
+ gpointer parent;
+
+ priv = EM_ACCOUNT_PREFS_GET_PRIVATE (manager);
+
+ if (priv->assistant != NULL) {
+ gtk_window_present (GTK_WINDOW (priv->assistant));
+ return;
+ }
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ /** @HookPoint-EMConfig: New Mail Account Assistant
+ * @Id: org.gnome.evolution.mail.config.accountAssistant
+ * @Type: E_CONFIG_ASSISTANT
+ * @Class: org.gnome.evolution.mail.config:1.0
+ * @Target: EMConfigTargetAccount
+ *
+ * The new mail account assistant.
+ */
+ emae = em_account_editor_new (
+ NULL, EMAE_ASSISTANT,
+ "org.gnome.evolution.mail.config.accountAssistant");
+ priv->assistant = emae->editor;
+
+ g_object_add_weak_pointer (G_OBJECT (priv->assistant), &priv->assistant);
+ gtk_window_set_transient_for (GTK_WINDOW (priv->assistant), parent);
+ gtk_widget_show (priv->assistant);
+}
+
+static void
+account_prefs_edit_account (EAccountManager *manager)
+{
+ EMAccountPrefsPrivate *priv;
+ EMAccountEditor *emae;
+ EAccountTreeView *tree_view;
+ EAccountList *account_list;
+ EAccount *account;
+ gpointer parent;
+
+ priv = EM_ACCOUNT_PREFS_GET_PRIVATE (manager);
+
+ if (priv->editor != NULL) {
+ gtk_window_present (GTK_WINDOW (priv->editor));
+ return;
+ }
+
+ account_list = e_account_manager_get_account_list (manager);
+ tree_view = e_account_manager_get_tree_view (manager);
+ account = e_account_tree_view_get_selected (tree_view);
+ g_return_if_fail (account != NULL);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ /** @HookPoint-EMConfig: Mail Account Editor
+ * @Id: org.gnome.evolution.mail.config.accountEditor
+ * @Type: E_CONFIG_BOOK
+ * @Class: org.gnome.evolution.mail.config:1.0
+ * @Target: EMConfigTargetAccount
+ *
+ * The account editor window.
+ */
+ emae = em_account_editor_new (
+ account, EMAE_NOTEBOOK,
+ "org.gnome.evolution.mail.config.accountEditor");
+ priv->editor = emae->editor;
+
+ g_object_add_weak_pointer (G_OBJECT (priv->editor), &priv->editor);
+ gtk_window_set_transient_for (GTK_WINDOW (priv->editor), parent);
+ gtk_widget_show (priv->editor);
+}
+
+static void
+account_prefs_delete_account (EAccountManager *manager)
+{
+ EMAccountPrefsPrivate *priv;
+ EAccountTreeView *tree_view;
+ EAccountList *account_list;
+ EAccount *account;
+ gboolean has_proxies;
+ gpointer parent;
+ gint response;
+
+ priv = EM_ACCOUNT_PREFS_GET_PRIVATE (manager);
+
+ account_list = e_account_manager_get_account_list (manager);
+ tree_view = e_account_manager_get_tree_view (manager);
+ account = e_account_tree_view_get_selected (tree_view);
+ g_return_if_fail (account != NULL);
+
+ /* Make sure we aren't editing anything... */
+ if (priv->editor != NULL)
+ return;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ has_proxies =
+ e_account_list_account_has_proxies (account_list, account);
+
+ response = e_error_run (
+ parent, has_proxies ?
+ "mail:ask-delete-account-with-proxies" :
+ "mail:ask-delete-account", NULL);
+
+ if (response != GTK_RESPONSE_YES) {
+ g_signal_stop_emission_by_name (manager, "delete-account");
+ return;
+ }
+
+ /* Remove the account from the folder tree. */
+ if (account->enabled && account->source && account->source->url)
+ e_mail_store_remove_by_uri (account->source->url);
+
+ /* Remove all the proxies the account has created. */
+ if (has_proxies)
+ e_account_list_remove_account_proxies (account_list, account);
+
+ /* Remove it from the config file. */
+ e_account_list_remove (account_list, account);
+
+ e_account_list_save (account_list);
+}
+
+static void
+account_prefs_dispose (GObject *object)
+{
+ EMAccountPrefsPrivate *priv;
+
+ priv = EM_ACCOUNT_PREFS_GET_PRIVATE (object);
+
+ if (priv->assistant != NULL) {
+ g_object_remove_weak_pointer (
+ G_OBJECT (priv->assistant), &priv->assistant);
+ priv->assistant = NULL;
+ }
+
+ if (priv->editor != NULL) {
+ g_object_remove_weak_pointer (
+ G_OBJECT (priv->editor), &priv->editor);
+ priv->editor = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+account_prefs_class_init (EMAccountPrefsClass *class)
+{
+ GObjectClass *object_class;
+ EAccountManagerClass *account_manager_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMAccountPrefsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = account_prefs_dispose;
+
+ account_manager_class = E_ACCOUNT_MANAGER_CLASS (class);
+ account_manager_class->add_account = account_prefs_add_account;
+ account_manager_class->edit_account = account_prefs_edit_account;
+ account_manager_class->delete_account = account_prefs_delete_account;
+}
+
+static void
+account_prefs_init (EMAccountPrefs *prefs)
+{
+ EAccountManager *manager;
+ EAccountTreeView *tree_view;
+
+ prefs->priv = EM_ACCOUNT_PREFS_GET_PRIVATE (prefs);
+
+ manager = E_ACCOUNT_MANAGER (prefs);
+ tree_view = e_account_manager_get_tree_view (manager);
+
+ g_signal_connect (
+ tree_view, "enable-account",
+ G_CALLBACK (account_prefs_enable_account_cb), NULL);
+
+ g_signal_connect (
+ tree_view, "disable-account",
+ G_CALLBACK (account_prefs_disable_account_cb), NULL);
+}
+
+GType
+em_account_prefs_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EMAccountPrefsClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) account_prefs_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMAccountPrefs),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) account_prefs_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ E_TYPE_ACCOUNT_MANAGER, "EMAccountPrefs",
+ &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkWidget *
+em_account_prefs_new (EAccountList *account_list)
+{
+ g_return_val_if_fail (E_IS_ACCOUNT_LIST (account_list), NULL);
+
+ return g_object_new (
+ EM_TYPE_ACCOUNT_PREFS, "account-list", account_list, NULL);
+}
diff --git a/modules/mail/em-account-prefs.h b/modules/mail/em-account-prefs.h
new file mode 100644
index 0000000000..82df8fa941
--- /dev/null
+++ b/modules/mail/em-account-prefs.h
@@ -0,0 +1,69 @@
+/*
+ * em-account-prefs.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 EM_ACCOUNT_PREFS_H
+#define EM_ACCOUNT_PREFS_H
+
+#include <gtk/gtk.h>
+#include <table/e-table.h>
+#include <libedataserver/e-account-list.h>
+#include <misc/e-account-manager.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_ACCOUNT_PREFS \
+ (em_account_prefs_get_type ())
+#define EM_ACCOUNT_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), EM_TYPE_ACCOUNT_PREFS, EMAccountPrefs))
+#define EM_ACCOUNT_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), EM_TYPE_ACCOUNT_PREFS, EMAccountPrefsClass))
+#define EM_IS_ACCOUNT_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), EM_TYPE_ACCOUNT_PREFS))
+#define EM_IS_ACCOUNT_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), EM_TYPE_ACCOUNT_PREFS))
+#define EM_ACCOUNT_PREFS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), EM_TYPE_ACCOUNT_PREFS, EMAccountPrefsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMAccountPrefs EMAccountPrefs;
+typedef struct _EMAccountPrefsClass EMAccountPrefsClass;
+typedef struct _EMAccountPrefsPrivate EMAccountPrefsPrivate;
+
+struct _EMAccountPrefs {
+ EAccountManager parent;
+ EMAccountPrefsPrivate *priv;
+};
+
+struct _EMAccountPrefsClass {
+ EAccountManagerClass parent_class;
+};
+
+GType em_account_prefs_get_type (void);
+GtkWidget * em_account_prefs_new (EAccountList *account_list);
+
+G_END_DECLS
+
+#endif /* EM_ACCOUNT_PREFS_H */
diff --git a/modules/mail/em-composer-prefs.c b/modules/mail/em-composer-prefs.c
new file mode 100644
index 0000000000..4c29125c0b
--- /dev/null
+++ b/modules/mail/em-composer-prefs.c
@@ -0,0 +1,567 @@
+/*
+ * 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:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/e-signature-utils.h"
+#include "e-util/gconf-bridge.h"
+
+#include "em-composer-prefs.h"
+#include "composer/e-msg-composer.h"
+
+#include <camel/camel-iconv.h>
+
+#include <misc/e-gui-utils.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <gtkhtml/gtkhtml.h>
+#include <editor/gtkhtml-spell-language.h>
+
+#include "e-util/e-error.h"
+#include "e-util/e-util-private.h"
+#include "widgets/misc/e-charset-combo-box.h"
+#include "widgets/misc/e-signature-editor.h"
+#include "widgets/misc/e-signature-manager.h"
+#include "widgets/misc/e-signature-preview.h"
+
+#include "mail-config.h"
+#include "em-config.h"
+
+static gpointer parent_class;
+
+static gboolean
+transform_color_to_string (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ const GdkColor *color;
+ gchar *string;
+
+ color = g_value_get_boxed (src_value);
+ string = gdk_color_to_string (color);
+ g_value_set_string (dst_value, string);
+ g_free (string);
+
+ return TRUE;
+}
+
+static gboolean
+transform_string_to_color (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ GdkColor color;
+ const gchar *string;
+ gboolean success = FALSE;
+
+ string = g_value_get_string (src_value);
+ if (gdk_color_parse (string, &color)) {
+ g_value_set_boxed (dst_value, &color);
+ success = TRUE;
+ }
+
+ return success;
+}
+
+static gboolean
+transform_old_to_new_reply_style (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gboolean success = TRUE;
+
+ /* XXX This is the kind of legacy crap we wind up
+ * with when we don't migrate things properly. */
+
+ switch (g_value_get_int (src_value)) {
+ case 0: /* Quoted: 0 -> 2 */
+ g_value_set_int (dst_value, 2);
+ break;
+
+ case 1: /* Do Not Quote: 1 -> 3 */
+ g_value_set_int (dst_value, 3);
+ break;
+
+ case 2: /* Attach: 2 -> 0 */
+ g_value_set_int (dst_value, 0);
+ break;
+
+ case 3: /* Outlook: 3 -> 1 */
+ g_value_set_int (dst_value, 1);
+ break;
+
+ default:
+ success = FALSE;
+ break;
+ }
+
+ return success;
+}
+
+static gboolean
+transform_new_to_old_reply_style (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gboolean success = TRUE;
+
+ /* XXX This is the kind of legacy crap we wind up
+ * with when we don't migrate things properly. */
+
+ switch (g_value_get_int (src_value)) {
+ case 0: /* Attach: 0 -> 2 */
+ g_value_set_int (dst_value, 2);
+ break;
+
+ case 1: /* Outlook: 1 -> 3 */
+ g_value_set_int (dst_value, 3);
+ break;
+
+ case 2: /* Quoted: 2 -> 0 */
+ g_value_set_int (dst_value, 0);
+ break;
+
+ case 3: /* Do Not Quote: 3 -> 1 */
+ g_value_set_int (dst_value, 1);
+ break;
+
+ default:
+ success = FALSE;
+ break;
+ }
+
+ return success;
+}
+
+static void
+composer_prefs_finalize (GObject *object)
+{
+ EMComposerPrefs *prefs = (EMComposerPrefs *) object;
+
+ g_object_unref (prefs->gui);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+composer_prefs_class_init (EMComposerPrefsClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = composer_prefs_finalize;
+}
+
+static void
+composer_prefs_init (EMComposerPrefs *prefs)
+{
+}
+
+GType
+em_composer_prefs_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EMComposerPrefsClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) composer_prefs_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMComposerPrefs),
+ 0, /* n_allocs */
+ (GInstanceInitFunc) composer_prefs_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_VBOX, "EMComposerPrefs", &type_info, 0);
+ }
+
+ return type;
+}
+
+void
+em_composer_prefs_new_signature (GtkWindow *parent,
+ gboolean html_mode)
+{
+ GtkWidget *editor;
+
+ editor = e_signature_editor_new ();
+ gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (editor), html_mode);
+ gtk_window_set_transient_for (GTK_WINDOW (editor), parent);
+ gtk_widget_show (editor);
+}
+
+static void
+spell_language_toggled_cb (GtkCellRendererToggle *renderer,
+ const gchar *path_string,
+ EMComposerPrefs *prefs)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean active;
+ gboolean valid;
+
+ model = prefs->language_model;
+
+ /* Convert the path string to a tree iterator. */
+ path = gtk_tree_path_new_from_string (path_string);
+ valid = gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+ g_return_if_fail (valid);
+
+ /* Toggle the active state. */
+ gtk_tree_model_get (model, &iter, 0, &active, -1);
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, !active, -1);
+}
+
+static void
+spell_language_save (EMComposerPrefs *prefs)
+{
+ GList *spell_languages = NULL;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ model = prefs->language_model;
+
+ /* Build a list of active spell languages. */
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+ while (valid) {
+ const GtkhtmlSpellLanguage *language;
+ gboolean active;
+
+ gtk_tree_model_get (
+ model, &iter, 0, &active, 2, &language, -1);
+
+ if (active)
+ spell_languages = g_list_prepend (
+ spell_languages, (gpointer) language);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+ spell_languages = g_list_reverse (spell_languages);
+
+ /* Update the GConf value. */
+ e_save_spell_languages (spell_languages);
+
+ g_list_free (spell_languages);
+}
+
+static void
+spell_setup (EMComposerPrefs *prefs)
+{
+ const GList *available_languages;
+ GList *active_languages;
+ GtkListStore *store;
+
+ store = GTK_LIST_STORE (prefs->language_model);
+ available_languages = gtkhtml_spell_language_get_available ();
+
+ active_languages = e_load_spell_languages ();
+
+ /* Populate the GtkListStore. */
+ while (available_languages != NULL) {
+ const GtkhtmlSpellLanguage *language;
+ GtkTreeIter tree_iter;
+ const gchar *name;
+ gboolean active;
+
+ language = available_languages->data;
+ name = gtkhtml_spell_language_get_name (language);
+ active = (g_list_find (active_languages, language) != NULL);
+
+ gtk_list_store_append (store, &tree_iter);
+
+ gtk_list_store_set (
+ store, &tree_iter,
+ 0, active, 1, name, 2, language, -1);
+
+ available_languages = available_languages->next;
+ }
+
+ g_list_free (active_languages);
+}
+
+static GtkWidget *
+emcp_widget_glade (EConfig *ec,
+ EConfigItem *item,
+ GtkWidget *parent,
+ GtkWidget *old,
+ gpointer data)
+{
+ EMComposerPrefs *prefs = data;
+
+ return glade_xml_get_widget (prefs->gui, item->label);
+}
+
+/* plugin meta-data */
+static EMConfigItem emcp_items[] = {
+ { E_CONFIG_BOOK, (gchar *) "", (gchar *) "composer_toplevel", emcp_widget_glade },
+ { E_CONFIG_PAGE, (gchar *) "00.general", (gchar *) "vboxGeneral", emcp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/00.behavior", (gchar *) "vboxBehavior", emcp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/10.alerts", (gchar *) "vboxAlerts", emcp_widget_glade },
+ { E_CONFIG_PAGE, (gchar *) "10.signatures", (gchar *) "vboxSignatures", emcp_widget_glade },
+ /* signature/signatures and signature/preview parts not usable */
+
+ { E_CONFIG_PAGE, (gchar *) "20.spellcheck", (gchar *) "vboxSpellChecking", emcp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "20.spellcheck/00.languages", (gchar *) "vbox178", emcp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "20.spellcheck/00.options", (gchar *) "vboxOptions", emcp_widget_glade },
+};
+
+static void
+emcp_free (EConfig *ec, GSList *items, gpointer data)
+{
+ /* the prefs data is freed automagically */
+ g_slist_free (items);
+}
+
+static void
+em_composer_prefs_construct (EMComposerPrefs *prefs,
+ EShell *shell)
+{
+ GtkWidget *toplevel, *widget, *info_pixmap;
+ GtkWidget *container;
+ EShellSettings *shell_settings;
+ ESignatureList *signature_list;
+ ESignatureTreeView *signature_tree_view;
+ GladeXML *gui;
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkCellRenderer *renderer;
+ GConfBridge *bridge;
+ GConfClient *client;
+ EMConfig *ec;
+ EMConfigTargetPrefs *target;
+ GSList *l;
+ gint i;
+ gchar *gladefile;
+
+ bridge = gconf_bridge_get ();
+ client = mail_config_get_gconf_client ();
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ gladefile = g_build_filename (EVOLUTION_GLADEDIR,
+ "mail-config.glade",
+ NULL);
+ gui = glade_xml_new (gladefile, "composer_toplevel", NULL);
+ prefs->gui = gui;
+ g_free (gladefile);
+
+ /** @HookPoint-EMConfig: Mail Composer Preferences
+ * @Id: org.gnome.evolution.mail.composerPrefs
+ * @Type: E_CONFIG_BOOK
+ * @Class: org.gnome.evolution.mail.config:1.0
+ * @Target: EMConfigTargetPrefs
+ *
+ * The mail composer preferences settings page.
+ */
+ ec = em_config_new(E_CONFIG_BOOK, "org.gnome.evolution.mail.composerPrefs");
+ l = NULL;
+ for (i = 0; i < G_N_ELEMENTS (emcp_items); i++)
+ l = g_slist_prepend(l, &emcp_items[i]);
+ e_config_add_items((EConfig *)ec, l, NULL, NULL, emcp_free, prefs);
+
+ /* General tab */
+
+ /* Default Behavior */
+ widget = glade_xml_get_widget (gui, "chkSendHTML");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-format-html",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkPromptEmptySubject");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-prompt-empty-subject",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkPromptBccOnly");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-prompt-only-bcc",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkAutoSmileys");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-magic-smileys",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkRequestReceipt");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-request-receipt",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkReplyStartBottom");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-reply-start-bottom",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkOutlookFilenames");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-outlook-filenames",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkTopSignature");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-top-signature",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkEnableSpellChecking");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-inline-spelling",
+ G_OBJECT (widget), "active");
+
+ widget = e_charset_combo_box_new ();
+ container = glade_xml_get_widget (gui, "hboxComposerCharset");
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "composer-charset",
+ G_OBJECT (widget), "charset");
+
+ /* Spell Checking */
+ widget = glade_xml_get_widget (gui, "listSpellCheckLanguage");
+ view = GTK_TREE_VIEW (widget);
+ store = gtk_list_store_new (
+ 3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER);
+ g_signal_connect_swapped (
+ store, "row-changed",
+ G_CALLBACK (spell_language_save), prefs);
+ prefs->language_model = GTK_TREE_MODEL (store);
+ gtk_tree_view_set_model (view, prefs->language_model);
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (
+ renderer, "toggled",
+ G_CALLBACK (spell_language_toggled_cb), prefs);
+ gtk_tree_view_insert_column_with_attributes (
+ view, -1, _("Enabled"),
+ renderer, "active", 0, NULL);
+
+ gtk_tree_view_insert_column_with_attributes (
+ view, -1, _("Language(s)"),
+ gtk_cell_renderer_text_new (),
+ "text", 1, NULL);
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
+ info_pixmap = glade_xml_get_widget (gui, "pixmapSpellInfo");
+ gtk_image_set_from_stock (
+ GTK_IMAGE (info_pixmap),
+ GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_BUTTON);
+
+ widget = glade_xml_get_widget (gui, "colorButtonSpellCheckColor");
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "composer-spell-color",
+ G_OBJECT (widget), "color",
+ transform_string_to_color,
+ transform_color_to_string,
+ NULL, NULL);
+
+ spell_setup (prefs);
+
+ /* Forwards and Replies */
+ widget = glade_xml_get_widget (gui, "comboboxForwardStyle");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-forward-style",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "comboboxReplyStyle");
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "mail-reply-style",
+ G_OBJECT (widget), "active",
+ transform_old_to_new_reply_style,
+ transform_new_to_old_reply_style,
+ NULL, NULL);
+
+ /* Signatures */
+ signature_list = e_get_signature_list ();
+ container = glade_xml_get_widget (gui, "alignSignatures");
+ widget = e_signature_manager_new (signature_list);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ /* The mail shell backend responds to the "window-created" signal
+ * that this triggers and configures it with composer preferences. */
+ g_signal_connect_swapped (
+ widget, "editor-created",
+ G_CALLBACK (e_shell_watch_window), shell);
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "composer-format-html",
+ G_OBJECT (widget), "prefer-html");
+
+ e_binding_new_with_negation (
+ G_OBJECT (shell_settings), "disable-command-line",
+ G_OBJECT (widget), "allow-scripts");
+
+ signature_tree_view = e_signature_manager_get_tree_view (
+ E_SIGNATURE_MANAGER (widget));
+
+ container = glade_xml_get_widget (gui, "scrolled-sig");
+ widget = e_signature_preview_new ();
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ e_binding_new_with_negation (
+ G_OBJECT (shell_settings), "disable-command-line",
+ G_OBJECT (widget), "allow-scripts");
+
+ e_binding_new (
+ G_OBJECT (signature_tree_view), "selected",
+ G_OBJECT (widget), "signature");
+
+ /* get our toplevel widget */
+ target = em_config_target_new_prefs (ec, client);
+ e_config_set_target ((EConfig *)ec, (EConfigTarget *)target);
+ toplevel = e_config_create_widget ((EConfig *)ec);
+ gtk_container_add (GTK_CONTAINER (prefs), toplevel);
+}
+
+GtkWidget *
+em_composer_prefs_new (EShell *shell)
+{
+ EMComposerPrefs *prefs;
+
+ g_return_val_if_fail (E_IS_SHELL (shell), NULL);
+
+ prefs = g_object_new (EM_TYPE_COMPOSER_PREFS, NULL);
+ em_composer_prefs_construct (prefs, shell);
+
+ return GTK_WIDGET (prefs);
+}
diff --git a/modules/mail/em-composer-prefs.h b/modules/mail/em-composer-prefs.h
new file mode 100644
index 0000000000..6faa18b7d9
--- /dev/null
+++ b/modules/mail/em-composer-prefs.h
@@ -0,0 +1,86 @@
+/*
+ * 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:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_COMPOSER_PREFS_H
+#define EM_COMPOSER_PREFS_H
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <gtkhtml/gtkhtml.h>
+
+#include <shell/e-shell.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_COMPOSER_PREFS \
+ (em_composer_prefs_get_type ())
+#define EM_COMPOSER_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), EM_TYPE_COMPOSER_PREFS, EMComposerPrefs))
+#define EM_COMPOSER_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), EM_TYPE_COMPOSER_PREFS, EMComposerPrefsClass))
+#define EM_IS_COMPOSER_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), EM_TYPE_COMPOSER_PREFS))
+#define EM_IS_COMPOSER_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), EM_TYPE_COMPOSER_PREFS))
+#define EM_COMPOSER_PREFS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), EM_TYPE_COMPOSER_PREFS, EMComposerPrefsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMComposerPrefs EMComposerPrefs;
+typedef struct _EMComposerPrefsClass EMComposerPrefsClass;
+
+struct _ESignature;
+
+struct _EMComposerPrefs {
+ GtkVBox parent;
+
+ GladeXML *gui;
+
+ /* General tab */
+
+ GtkTreeModel *language_model;
+
+ /* Forwards and Replies */
+ GtkComboBox *forward_style;
+ GtkComboBox *reply_style;
+
+ /* Signatures */
+ GtkHTML *sig_preview;
+};
+
+struct _EMComposerPrefsClass {
+ GtkVBoxClass parent_class;
+};
+
+GType em_composer_prefs_get_type (void);
+GtkWidget * em_composer_prefs_new (EShell *shell);
+void em_composer_prefs_new_signature (GtkWindow *parent,
+ gboolean html_mode);
+
+G_END_DECLS
+
+#endif /* EM_COMPOSER_PREFS_H */
diff --git a/modules/mail/em-mailer-prefs.c b/modules/mail/em-mailer-prefs.c
new file mode 100644
index 0000000000..621dae4ce5
--- /dev/null
+++ b/modules/mail/em-mailer-prefs.c
@@ -0,0 +1,1320 @@
+/*
+ * 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:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "em-mailer-prefs.h"
+#include "em-format/em-format.h"
+
+#include <camel/camel-iconv.h>
+#include <gtkhtml/gtkhtml-properties.h>
+#include <libxml/tree.h>
+
+#include <glade/glade.h>
+
+#include <gconf/gconf-client.h>
+
+#include "libedataserverui/e-cell-renderer-color.h"
+
+#include "e-util/e-binding.h"
+#include "e-util/e-datetime-format.h"
+#include "e-util/e-util-private.h"
+#include "widgets/misc/e-charset-combo-box.h"
+
+#include "e-mail-label-manager.h"
+#include "mail-config.h"
+#include "em-junk.h"
+#include "em-config.h"
+#include "mail-session.h"
+
+static void em_mailer_prefs_class_init (EMMailerPrefsClass *class);
+static void em_mailer_prefs_init (EMMailerPrefs *dialog);
+static void em_mailer_prefs_finalize (GObject *object);
+
+static GtkVBoxClass *parent_class = NULL;
+
+enum {
+ HEADER_LIST_NAME_COLUMN, /* displayable name of the header (may be a translation) */
+ HEADER_LIST_ENABLED_COLUMN, /* is the header enabled? */
+ HEADER_LIST_IS_DEFAULT_COLUMN, /* is this header a default header, eg From: */
+ HEADER_LIST_HEADER_COLUMN, /* the real name of this header */
+ HEADER_LIST_N_COLUMNS
+};
+
+static GType col_types[] = {
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN,
+ G_TYPE_STRING
+};
+
+/* temporarily copied from em-format.c */
+static const gchar *default_headers[] = {
+ N_("From"),
+ N_("Reply-To"),
+ N_("To"),
+ N_("Cc"),
+ N_("Bcc"),
+ N_("Subject"),
+ N_("Date"),
+ N_("Newsgroups"),
+ N_("Face"),
+ "x-evolution-mailer", /* DO NOT translate */
+};
+
+#define EM_FORMAT_HEADER_XMAILER "x-evolution-mailer"
+
+/* for empty trash on exit frequency */
+static const struct {
+ const gchar *label;
+ gint days;
+} empty_trash_frequency[] = {
+ { N_("Every time"), 0 },
+ { N_("Once per day"), 1 },
+ { N_("Once per week"), 7 },
+ { N_("Once per month"), 30 },
+};
+
+GType
+em_mailer_prefs_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ GTypeInfo type_info = {
+ sizeof (EMMailerPrefsClass),
+ NULL, NULL,
+ (GClassInitFunc) em_mailer_prefs_class_init,
+ NULL, NULL,
+ sizeof (EMMailerPrefs),
+ 0,
+ (GInstanceInitFunc) em_mailer_prefs_init,
+ };
+
+ type = g_type_register_static (gtk_vbox_get_type (), "EMMailerPrefs", &type_info, 0);
+ }
+
+ return type;
+}
+
+static void
+em_mailer_prefs_class_init (EMMailerPrefsClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = (GObjectClass *) klass;
+ parent_class = g_type_class_ref (gtk_vbox_get_type ());
+
+ object_class->finalize = em_mailer_prefs_finalize;
+}
+
+static void
+em_mailer_prefs_init (EMMailerPrefs *preferences)
+{
+ preferences->gconf = mail_config_get_gconf_client ();
+}
+
+static void
+em_mailer_prefs_finalize (GObject *obj)
+{
+ EMMailerPrefs *prefs = (EMMailerPrefs *) obj;
+
+ g_object_unref (prefs->gui);
+
+ if (prefs->labels_change_notify_id) {
+ gconf_client_notify_remove (prefs->gconf, prefs->labels_change_notify_id);
+
+ prefs->labels_change_notify_id = 0;
+ }
+
+ ((GObjectClass *)(parent_class))->finalize (obj);
+}
+
+static gboolean
+mark_seen_timeout_transform (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gdouble v_double;
+
+ /* Shell Settings (gint) -> Spin Button (double) */
+ v_double = (gdouble) g_value_get_int (src_value);
+ g_value_set_double (dst_value, v_double / 1000.0);
+
+ return TRUE;
+}
+
+static gboolean
+mark_seen_timeout_reverse_transform (const GValue *src_value,
+ GValue *dst_value,
+ gpointer user_data)
+{
+ gdouble v_double;
+
+ /* Spin Button (double) -> Shell Settings (gint) */
+ v_double = g_value_get_double (src_value);
+ g_value_set_int (dst_value, v_double * 1000);
+
+ return TRUE;
+}
+
+enum {
+ JH_LIST_COLUMN_NAME,
+ JH_LIST_COLUMN_VALUE
+};
+
+static void
+jh_tree_refill (EMMailerPrefs *prefs)
+{
+ GtkListStore *store = prefs->junk_header_list_store;
+ GSList *l, *cjh = gconf_client_get_list (prefs->gconf, "/apps/evolution/mail/junk/custom_header", GCONF_VALUE_STRING, NULL);
+
+ gtk_list_store_clear (store);
+
+ for (l = cjh; l; l = l->next) {
+ GtkTreeIter iter;
+ gchar **tokens = g_strsplit (l->data, "=", 2);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (
+ store, &iter,
+ JH_LIST_COLUMN_NAME , tokens[0] ? tokens[0] : "",
+ JH_LIST_COLUMN_VALUE, tokens[1] ? tokens[1] : "" ,
+ -1);
+ g_strfreev (tokens);
+ }
+
+ g_slist_foreach (cjh, (GFunc) g_free, NULL);
+ g_slist_free (cjh);
+}
+
+static void
+jh_add_cb (GtkWidget *widget, gpointer user_data)
+{
+ EMMailerPrefs *prefs = (EMMailerPrefs *) user_data;
+ GtkWidget *dialog, *l1, *l2, *entry1, *entry2, *vbox, *hbox;
+ gint response;
+ dialog = gtk_dialog_new_with_buttons (_("Add Custom Junk Header"), (GtkWindow *)gtk_widget_get_toplevel (widget), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ hbox = gtk_hbox_new (FALSE, 0);
+ l1 = gtk_label_new_with_mnemonic (_("Header Name:"));
+ l2 = gtk_label_new_with_mnemonic (_("Header Value Contains:"));
+ entry1 = gtk_entry_new ();
+ entry2 = gtk_entry_new ();
+ gtk_box_pack_start ((GtkBox *) hbox, l1, FALSE, FALSE, 6);
+ gtk_box_pack_start ((GtkBox *)hbox, entry1, FALSE, FALSE, 6);
+ gtk_box_pack_start ((GtkBox *)vbox, hbox, FALSE, FALSE, 6);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start ((GtkBox *)hbox, l2, FALSE, FALSE, 6);
+ gtk_box_pack_start ((GtkBox *)hbox, entry2, FALSE, FALSE, 6);
+ gtk_box_pack_start ((GtkBox *)vbox, hbox, FALSE, FALSE, 6);
+
+ gtk_widget_show_all (vbox);
+ gtk_container_add ((GtkContainer *)((GtkDialog *)dialog)->vbox, vbox);
+ response = gtk_dialog_run ((GtkDialog *)dialog);
+ if (response == GTK_RESPONSE_ACCEPT) {
+ const gchar *name = gtk_entry_get_text ((GtkEntry *)entry1);
+ const gchar *value = gtk_entry_get_text ((GtkEntry *)entry2);
+ gchar *tok;
+ GSList *list = gconf_client_get_list (prefs->gconf, "/apps/evolution/mail/junk/custom_header", GCONF_VALUE_STRING, NULL);
+
+ /* FIXME: Validate the values */
+
+ tok = g_strdup_printf ("%s=%s", name, value);
+ list = g_slist_append (list, tok);
+ gconf_client_set_list (prefs->gconf, "/apps/evolution/mail/junk/custom_header", GCONF_VALUE_STRING, list, NULL);
+ g_slist_foreach (list, (GFunc)g_free, NULL);
+
+ g_slist_free (list);
+ }
+ gtk_widget_destroy (dialog);
+ jh_tree_refill (prefs);
+}
+
+static void
+jh_remove_cb (GtkWidget *widget, gpointer user_data)
+{
+ EMMailerPrefs *prefs = user_data;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ g_return_if_fail (prefs != NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (prefs->junk_header_tree));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gchar *name=NULL, *value=NULL;
+ GSList *prev = NULL, *node, *list = gconf_client_get_list (prefs->gconf, "/apps/evolution/mail/junk/custom_header", GCONF_VALUE_STRING, NULL);
+ gtk_tree_model_get (model, &iter, JH_LIST_COLUMN_NAME, &name, JH_LIST_COLUMN_VALUE, &value, -1);
+ node = list;
+ while (node) {
+ gchar *test;
+ gint len = strlen (name);
+ test = strncmp (node->data, name, len) == 0 ? (gchar *) node->data+len:NULL;
+
+ if (test) {
+ test++;
+ if (strcmp (test, value) == 0)
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (prev && !node) {
+ /* Not found. So what? */
+ } else if (prev && node) {
+ prev->next = node->next;
+ g_free (node->data);
+ } else if (!prev && node) {
+ list = list->next;
+ g_free (node->data);
+ }
+
+ gconf_client_set_list (prefs->gconf, "/apps/evolution/mail/junk/custom_header", GCONF_VALUE_STRING, list, NULL);
+
+ g_slist_foreach (list, (GFunc)g_free, NULL);
+ g_slist_free (list);
+ g_free (name);
+ g_free (value);
+
+ jh_tree_refill (prefs);
+ }
+}
+
+static GtkListStore *
+init_junk_tree (GtkWidget *label_tree, EMMailerPrefs *prefs)
+{
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ gint col;
+
+ g_return_val_if_fail (label_tree != NULL, NULL);
+ g_return_val_if_fail (prefs != NULL, NULL);
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (label_tree), GTK_TREE_MODEL (store));
+
+ renderer = gtk_cell_renderer_text_new ();
+ col = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (label_tree), -1, _("Header"), renderer, "text", JH_LIST_COLUMN_NAME, NULL);
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (label_tree), -1, _("Contains Value"), renderer, "text", JH_LIST_COLUMN_VALUE, NULL);
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+
+ return store;
+}
+
+static void
+emmp_header_remove_sensitivity (EMMailerPrefs *prefs)
+{
+ GtkTreeIter iter;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (prefs->header_list);
+ gboolean is_default;
+
+ /* remove button should be sensitive if the currenlty selected entry in the list view
+ is not a default header. if there are no entries, or none is selected, it should be
+ disabled
+ */
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
+ gtk_tree_model_get (GTK_TREE_MODEL (prefs->header_list_store), &iter,
+ HEADER_LIST_IS_DEFAULT_COLUMN, &is_default,
+ -1);
+ if (is_default)
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->remove_header), FALSE);
+ else
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->remove_header), TRUE);
+ } else {
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->remove_header), FALSE);
+ }
+}
+
+static gboolean
+emmp_header_is_valid (const gchar *header)
+{
+ gint len = g_utf8_strlen (header, -1);
+
+ if (header[0] == 0
+ || g_utf8_strchr (header, len, ':') != NULL
+ || g_utf8_strchr (header, len, ' ') != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+emmp_header_add_sensitivity (EMMailerPrefs *prefs)
+{
+ const gchar *entry_contents;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ /* the add header button should be sensitive if the text box contains
+ a valid header string, that is not a duplicate with something already
+ in the list view
+ */
+ entry_contents = gtk_entry_get_text (GTK_ENTRY (prefs->entry_header));
+ if (!emmp_header_is_valid (entry_contents)) {
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->add_header), FALSE);
+ return;
+ }
+
+ /* check if this is a duplicate */
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (prefs->header_list_store), &iter);
+ while (valid) {
+ gchar *header_name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (prefs->header_list_store), &iter,
+ HEADER_LIST_HEADER_COLUMN, &header_name,
+ -1);
+ if (g_ascii_strcasecmp (header_name, entry_contents) == 0) {
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->add_header), FALSE);
+ return;
+ }
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (prefs->header_list_store), &iter);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->add_header), TRUE);
+}
+
+static void
+emmp_save_headers (EMMailerPrefs *prefs)
+{
+ GSList *header_list;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ /* Headers */
+ header_list = NULL;
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (prefs->header_list_store), &iter);
+ while (valid) {
+ struct _EMMailerPrefsHeader h;
+ gboolean enabled;
+ gchar *xml;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (prefs->header_list_store), &iter,
+ HEADER_LIST_HEADER_COLUMN, &h.name,
+ HEADER_LIST_ENABLED_COLUMN, &enabled,
+ -1);
+ h.enabled = enabled;
+
+ if ((xml = em_mailer_prefs_header_to_xml (&h)))
+ header_list = g_slist_append (header_list, xml);
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (prefs->header_list_store), &iter);
+ }
+
+ gconf_client_set_list (prefs->gconf, "/apps/evolution/mail/display/headers", GCONF_VALUE_STRING, header_list, NULL);
+ g_slist_foreach (header_list, (GFunc) g_free, NULL);
+ g_slist_free (header_list);
+}
+
+static void
+emmp_header_list_enabled_toggled (GtkCellRendererToggle *cell, const gchar *path_string, EMMailerPrefs *prefs)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (prefs->header_list_store);
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+ GtkTreeIter iter;
+ gint enabled;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, HEADER_LIST_ENABLED_COLUMN, &enabled, -1);
+ enabled = !enabled;
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, HEADER_LIST_ENABLED_COLUMN,
+ enabled, -1);
+ gtk_tree_path_free (path);
+
+ emmp_save_headers (prefs);
+}
+
+static void
+emmp_header_add_header (GtkWidget *widget, EMMailerPrefs *prefs)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (prefs->header_list_store);
+ GtkTreeIter iter;
+ const gchar *text = gtk_entry_get_text (prefs->entry_header);
+
+ g_strstrip ((gchar *)text);
+
+ if (text && (strlen (text)>0)) {
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ HEADER_LIST_NAME_COLUMN, text,
+ HEADER_LIST_ENABLED_COLUMN, TRUE,
+ HEADER_LIST_HEADER_COLUMN, text,
+ HEADER_LIST_IS_DEFAULT_COLUMN, FALSE,
+ -1);
+ gtk_entry_set_text (prefs->entry_header, "");
+ emmp_header_remove_sensitivity (prefs);
+ emmp_header_add_sensitivity (prefs);
+
+ emmp_save_headers (prefs);
+ }
+}
+
+static void
+emmp_header_remove_header (GtkWidget *button, gpointer user_data)
+{
+ EMMailerPrefs *prefs = (EMMailerPrefs *) user_data;
+ GtkTreeModel *model = GTK_TREE_MODEL (prefs->header_list_store);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (prefs->header_list);
+ GtkTreeIter iter;
+
+ if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
+ return;
+
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ emmp_header_remove_sensitivity (prefs);
+
+ emmp_save_headers (prefs);
+}
+
+static void
+emmp_header_list_row_selected (GtkTreeSelection *selection, gpointer user_data)
+{
+ EMMailerPrefs *prefs = (EMMailerPrefs *) user_data;
+
+ emmp_header_remove_sensitivity (prefs);
+}
+
+static void
+emmp_header_entry_changed (GtkWidget *entry, gpointer user_data)
+{
+ EMMailerPrefs *prefs = (EMMailerPrefs *) user_data;
+
+ emmp_header_add_sensitivity (prefs);
+}
+
+static void
+toggle_button_toggled (GtkToggleButton *toggle, EMMailerPrefs *prefs)
+{
+ const gchar *key;
+
+ key = g_object_get_data ((GObject *) toggle, "key");
+ gconf_client_set_bool (prefs->gconf, key, gtk_toggle_button_get_active (toggle), NULL);
+}
+
+static void
+junk_book_lookup_button_toggled (GtkToggleButton *toggle, EMMailerPrefs *prefs)
+{
+ toggle_button_toggled (toggle, prefs);
+ gtk_widget_set_sensitive (GTK_WIDGET (prefs->junk_lookup_local_only), gtk_toggle_button_get_active (toggle));
+}
+
+static void
+custom_junk_button_toggled (GtkToggleButton *toggle, EMMailerPrefs *prefs)
+{
+ toggle_button_toggled (toggle, prefs);
+ if (gtk_toggle_button_get_active (toggle)) {
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->junk_header_remove, TRUE);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->junk_header_add, TRUE);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->junk_header_tree, TRUE);
+ } else {
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->junk_header_tree, FALSE);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->junk_header_add, FALSE);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->junk_header_remove, FALSE);
+ }
+
+}
+
+static void
+toggle_button_init (EMMailerPrefs *prefs, GtkToggleButton *toggle, gint not, const gchar *key, GCallback toggled)
+{
+ gboolean bool;
+
+ bool = gconf_client_get_bool (prefs->gconf, key, NULL);
+ gtk_toggle_button_set_active (toggle, not ? !bool : bool);
+
+ if (toggled) {
+ g_object_set_data ((GObject *) toggle, "key", (gpointer) key);
+ g_signal_connect (toggle, "toggled", toggled, prefs);
+ }
+
+ if (!gconf_client_key_is_writable (prefs->gconf, key, NULL))
+ gtk_widget_set_sensitive ((GtkWidget *) toggle, FALSE);
+}
+
+static void
+trash_days_changed (GtkComboBox *combo_box,
+ EMMailerPrefs *prefs)
+{
+ gint index;
+
+ index = gtk_combo_box_get_active (combo_box);
+ g_return_if_fail (index >= 0);
+ g_return_if_fail (index < G_N_ELEMENTS (empty_trash_frequency));
+
+ gconf_client_set_int (
+ prefs->gconf,
+ "/apps/evolution/mail/trash/empty_on_exit_days",
+ empty_trash_frequency[index].days, NULL);
+}
+
+static void
+emmp_empty_trash_init (EMMailerPrefs *prefs,
+ GtkComboBox *combo_box)
+{
+ gint days, hist = 0, ii;
+ GtkTreeModel *model;
+
+ days = gconf_client_get_int (
+ prefs->gconf,
+ "/apps/evolution/mail/trash/empty_on_exit_days", NULL);
+
+ model = gtk_combo_box_get_model (combo_box);
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ for (ii = 0; ii < G_N_ELEMENTS (empty_trash_frequency); ii++) {
+ if (days >= empty_trash_frequency[ii].days)
+ hist = ii;
+ gtk_combo_box_append_text (
+ combo_box, gettext (empty_trash_frequency[ii].label));
+ }
+
+ g_signal_connect (
+ combo_box, "changed",
+ G_CALLBACK (trash_days_changed), prefs);
+
+ gtk_combo_box_set_active (combo_box, hist);
+}
+
+static void
+junk_days_changed (GtkComboBox *combo_box,
+ EMMailerPrefs *prefs)
+{
+ gint index;
+
+ index = gtk_combo_box_get_active (combo_box);
+ g_return_if_fail (index >= 0);
+ g_return_if_fail (index < G_N_ELEMENTS (empty_trash_frequency));
+
+ gconf_client_set_int (
+ prefs->gconf,
+ "/apps/evolution/mail/junk/empty_on_exit_days",
+ empty_trash_frequency[index].days, NULL);
+}
+
+static void
+emmp_empty_junk_init (EMMailerPrefs *prefs,
+ GtkComboBox *combo_box)
+{
+ gint days, hist = 0, ii;
+ GtkTreeModel *model;
+
+ days = gconf_client_get_int (
+ prefs->gconf,
+ "/apps/evolution/mail/junk/empty_on_exit_days", NULL);
+
+ model = gtk_combo_box_get_model (combo_box);
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ for (ii = 0; ii < G_N_ELEMENTS (empty_trash_frequency); ii++) {
+ if (days >= empty_trash_frequency[ii].days)
+ hist = ii;
+ gtk_combo_box_append_text (
+ combo_box, gettext (empty_trash_frequency[ii].label));
+ }
+
+ g_signal_connect (
+ combo_box, "changed",
+ G_CALLBACK (junk_days_changed), prefs);
+
+ gtk_combo_box_set_active (combo_box, hist);
+}
+
+static void
+http_images_changed (GtkWidget *widget, EMMailerPrefs *prefs)
+{
+ gint when;
+
+ if (gtk_toggle_button_get_active (prefs->images_always))
+ when = MAIL_CONFIG_HTTP_ALWAYS;
+ else if (gtk_toggle_button_get_active (prefs->images_sometimes))
+ when = MAIL_CONFIG_HTTP_SOMETIMES;
+ else
+ when = MAIL_CONFIG_HTTP_NEVER;
+
+ gconf_client_set_int (prefs->gconf, "/apps/evolution/mail/display/load_http_images", when, NULL);
+}
+
+static GtkWidget *
+emmp_widget_glade(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data)
+{
+ EMMailerPrefs *prefs = data;
+
+ return glade_xml_get_widget(prefs->gui, item->label);
+}
+
+/* plugin meta-data */
+static EMConfigItem emmp_items[] = {
+ { E_CONFIG_BOOK, (gchar *) "", (gchar *) "preferences_toplevel", emmp_widget_glade },
+ { E_CONFIG_PAGE, (gchar *) "00.general", (gchar *) "vboxGeneral", emmp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/00.fonts", (gchar *) "vboxMessageFonts", emmp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/10.display", (gchar *) "vboxMessageDisplay", emmp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/20.delete", (gchar *) "vboxDeletingMail", emmp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/30.newmail", (gchar *) "vboxNewMailNotify", emmp_widget_glade },
+ { E_CONFIG_PAGE, (gchar *) "10.html", (gchar *) "vboxHtmlMail", emmp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "10.html/00.general", (gchar *) "vbox173", emmp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "10.html/10.images", (gchar *) "vbox190", emmp_widget_glade },
+ { E_CONFIG_PAGE, (gchar *) "20.labels", (gchar *) "frameColours", emmp_widget_glade },
+ /* this is a table, so we can't use it { E_CONFIG_SECTION, "20.labels/00.labels", "tableColours", emmp_widget_glade }, */
+ { E_CONFIG_PAGE, (gchar *) "30.headers", (gchar *) "vboxHeaderTab", emmp_widget_glade },
+ /* no subvbox for section { E_CONFIG_PAGE, "30.headers/00.headers", "vbox199", emmp_widget_glade }, */
+ { E_CONFIG_PAGE, (gchar *) "40.junk", (gchar *) "vbox161", emmp_widget_glade },
+ /* no subvbox for section { E_CONFIG_SECTION, "40.junk/00.general", xxx, emmp_widget_glade } */
+ { E_CONFIG_SECTION, (gchar *) "40.junk/10.options", (gchar *) "vbox204", emmp_widget_glade },
+};
+
+static void
+emmp_free(EConfig *ec, GSList *items, gpointer data)
+{
+ /* the prefs data is freed automagically */
+
+ g_slist_free(items);
+}
+
+static void
+junk_plugin_changed (GtkWidget *combo, EMMailerPrefs *prefs)
+{
+ gchar *def_plugin = gtk_combo_box_get_active_text(GTK_COMBO_BOX (combo));
+ const GList *plugins = mail_session_get_junk_plugins();
+
+ gconf_client_set_string (prefs->gconf, "/apps/evolution/mail/junk/default_plugin", def_plugin, NULL);
+ while (plugins) {
+ EMJunkInterface *iface = plugins->data;
+
+ if (iface->plugin_name && def_plugin && !strcmp (iface->plugin_name, def_plugin)) {
+ gboolean status;
+
+ session->junk_plugin = CAMEL_JUNK_PLUGIN (&iface->camel);
+ status = e_plugin_invoke (iface->hook->plugin, iface->validate_binary, NULL) != NULL;
+ if ((gboolean)status == TRUE) {
+ gchar *text, *html;
+ gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-info", GTK_ICON_SIZE_MENU);
+ text = g_strdup_printf (_("%s plugin is available and the binary is installed."), iface->plugin_name);
+ html = g_strdup_printf ("<i>%s</i>", text);
+ gtk_label_set_markup (prefs->plugin_status, html);
+ g_free (html);
+ g_free (text);
+ } else {
+ gchar *text, *html;
+ gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-warning", GTK_ICON_SIZE_MENU);
+ text = g_strdup_printf (_("%s plugin is not available. Please check whether the package is installed."), iface->plugin_name);
+ html = g_strdup_printf ("<i>%s</i>", text);
+ gtk_label_set_markup (prefs->plugin_status, html);
+ g_free (html);
+ g_free (text);
+ }
+ break;
+ }
+ plugins = plugins->next;
+ }
+}
+
+static void
+junk_plugin_setup (GtkWidget *combo, EMMailerPrefs *prefs)
+{
+ gint index = 0;
+ gboolean def_set = FALSE;
+ const GList *plugins = mail_session_get_junk_plugins();
+ gchar *pdefault = gconf_client_get_string (prefs->gconf, "/apps/evolution/mail/junk/default_plugin", NULL);
+
+ if (!plugins || !g_list_length ((GList *)plugins)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo), _("No Junk plugin available"));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+ gtk_widget_hide (GTK_WIDGET (prefs->plugin_image));
+ gtk_widget_hide (GTK_WIDGET (prefs->plugin_status));
+ gtk_image_set_from_stock (prefs->plugin_image, NULL, 0);
+ g_free (pdefault);
+
+ return;
+ }
+
+ while (plugins) {
+ EMJunkInterface *iface = plugins->data;
+
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo), iface->plugin_name);
+ if (!def_set && pdefault && iface->plugin_name && !strcmp(pdefault, iface->plugin_name)) {
+ gboolean status;
+
+ def_set = TRUE;
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), index);
+ status = e_plugin_invoke (iface->hook->plugin, iface->validate_binary, NULL) != NULL;
+ if (status) {
+ gchar *text, *html;
+ gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-info", GTK_ICON_SIZE_MENU);
+ /* May be a better text */
+ text = g_strdup_printf (_("%s plugin is available and the binary is installed."), iface->plugin_name);
+ html = g_strdup_printf ("<i>%s</i>", text);
+ gtk_label_set_markup (prefs->plugin_status, html);
+ g_free (html);
+ g_free (text);
+ } else {
+ gchar *text, *html;
+ gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-warning", GTK_ICON_SIZE_MENU);
+ /* May be a better text */
+ text = g_strdup_printf (_("%s plugin is not available. Please check whether the package is installed."), iface->plugin_name);
+ html = g_strdup_printf ("<i>%s</i>", text);
+ gtk_label_set_markup (prefs->plugin_status, html);
+ g_free (html);
+ g_free (text);
+ }
+ }
+ plugins = plugins->next;
+ index++;
+ }
+
+ g_signal_connect (combo, "changed", G_CALLBACK(junk_plugin_changed), prefs);
+ g_free (pdefault);
+}
+
+GtkWidget *
+create_combo_text_widget (void) {
+ return gtk_combo_box_new_text ();
+}
+
+static void
+em_mailer_prefs_construct (EMMailerPrefs *prefs,
+ EShell *shell)
+{
+ GSList *header_config_list, *header_add_list, *p;
+ EShellSettings *shell_settings;
+ GHashTable *default_header_hash;
+ GtkWidget *toplevel;
+ GtkWidget *container;
+ GtkWidget *table;
+ GtkWidget *widget;
+ GtkTreeSelection *selection;
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+ GladeXML *gui;
+ gboolean locked;
+ gint val, i;
+ EMConfig *ec;
+ EMConfigTargetPrefs *target;
+ GSList *l;
+ gchar *gladefile;
+
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ gladefile = g_build_filename (EVOLUTION_GLADEDIR,
+ "mail-config.glade",
+ NULL);
+ gui = glade_xml_new (gladefile, "preferences_toplevel", NULL);
+ g_free (gladefile);
+
+ prefs->gui = gui;
+
+ /** @HookPoint-EMConfig: Mail Preferences Page
+ * @Id: org.gnome.evolution.mail.prefs
+ * @Type: E_CONFIG_BOOK
+ * @Class: org.gnome.evolution.mail.config:1.0
+ * @Target: EMConfigTargetPrefs
+ *
+ * The main mail preferences page.
+ */
+ ec = em_config_new(E_CONFIG_BOOK, "org.gnome.evolution.mail.prefs");
+ l = NULL;
+ for (i=0;i<sizeof(emmp_items)/sizeof(emmp_items[0]);i++)
+ l = g_slist_prepend(l, &emmp_items[i]);
+ e_config_add_items((EConfig *)ec, l, NULL, NULL, emmp_free, prefs);
+
+ /* General tab */
+
+ /* Message Display */
+ widget = glade_xml_get_widget (gui, "chkMarkTimeout");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-mark-seen",
+ G_OBJECT (widget), "active");
+
+ /* The "mark seen" timeout requires special transform functions
+ * because we display the timeout value to the user in seconds
+ * but store the settings value in milliseconds. */
+ widget = glade_xml_get_widget (gui, "spinMarkTimeout");
+ prefs->timeout = GTK_SPIN_BUTTON (widget);
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-mark-seen",
+ G_OBJECT (widget), "sensitive");
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "mail-mark-seen-timeout",
+ G_OBJECT (widget), "value",
+ mark_seen_timeout_transform,
+ mark_seen_timeout_reverse_transform,
+ NULL, NULL);
+
+ widget = glade_xml_get_widget (gui, "mlimit_checkbutton");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-force-message-limit",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "mlimit_spin");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-force-message-limit",
+ G_OBJECT (widget), "sensitive");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-message-text-part-limit",
+ G_OBJECT (widget), "value");
+
+ widget = glade_xml_get_widget (gui, "address_checkbox");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-address-compress",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "address_spin");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-address-compress",
+ G_OBJECT (widget), "sensitive");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-address-count",
+ G_OBJECT (widget), "value");
+
+ widget = glade_xml_get_widget (gui, "magic_spacebar_checkbox");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-magic-spacebar",
+ G_OBJECT (widget), "active");
+
+ widget = e_charset_combo_box_new ();
+ container = glade_xml_get_widget (gui, "hboxDefaultCharset");
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-charset",
+ G_OBJECT (widget), "charset");
+
+ widget = glade_xml_get_widget (gui, "chkHighlightCitations");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-mark-citations",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "colorButtonHighlightCitations");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-mark-citations",
+ G_OBJECT (widget), "sensitive");
+ e_mutual_binding_new_full (
+ G_OBJECT (shell_settings), "mail-citation-color",
+ G_OBJECT (widget), "color",
+ e_binding_transform_string_to_color,
+ e_binding_transform_color_to_string,
+ NULL, NULL);
+
+ widget = glade_xml_get_widget (gui, "chkEnableSearchFolders");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-enable-search-folders",
+ G_OBJECT (widget), "active");
+
+ /* Deleting Mail */
+ widget = glade_xml_get_widget (gui, "chkEmptyTrashOnExit");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-empty-trash-on-exit",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "comboboxEmptyTrashDays");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-empty-trash-on-exit",
+ G_OBJECT (widget), "sensitive");
+ emmp_empty_trash_init (prefs, GTK_COMBO_BOX (widget));
+
+ widget = glade_xml_get_widget (gui, "chkConfirmExpunge");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-confirm-expunge",
+ G_OBJECT (widget), "active");
+
+ /* Mail Fonts */
+ widget = glade_xml_get_widget (gui, "radFontUseSame");
+ e_mutual_binding_new_with_negation (
+ G_OBJECT (shell_settings), "mail-use-custom-fonts",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "FontFixed");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-font-monospace",
+ G_OBJECT (widget), "font-name");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-use-custom-fonts",
+ G_OBJECT (widget), "sensitive");
+
+ widget = glade_xml_get_widget (gui, "FontVariable");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-font-variable",
+ G_OBJECT (widget), "font-name");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-use-custom-fonts",
+ G_OBJECT (widget), "sensitive");
+
+ /* HTML Mail tab */
+
+ /* Loading Images */
+ locked = !gconf_client_key_is_writable (prefs->gconf, "/apps/evolution/mail/display/load_http_images", NULL);
+
+ val = gconf_client_get_int (prefs->gconf, "/apps/evolution/mail/display/load_http_images", NULL);
+ prefs->images_never = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "radImagesNever"));
+ gtk_toggle_button_set_active (prefs->images_never, val == MAIL_CONFIG_HTTP_NEVER);
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->images_never, FALSE);
+
+ prefs->images_sometimes = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "radImagesSometimes"));
+ gtk_toggle_button_set_active (prefs->images_sometimes, val == MAIL_CONFIG_HTTP_SOMETIMES);
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->images_sometimes, FALSE);
+
+ prefs->images_always = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "radImagesAlways"));
+ gtk_toggle_button_set_active (prefs->images_always, val == MAIL_CONFIG_HTTP_ALWAYS);
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->images_always, FALSE);
+
+ g_signal_connect (prefs->images_never, "toggled", G_CALLBACK (http_images_changed), prefs);
+ g_signal_connect (prefs->images_sometimes, "toggled", G_CALLBACK (http_images_changed), prefs);
+ g_signal_connect (prefs->images_always, "toggled", G_CALLBACK (http_images_changed), prefs);
+
+ widget = glade_xml_get_widget (gui, "chkShowAnimatedImages");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-show-animated-images",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "chkPromptWantHTML");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-confirm-unwanted-html",
+ G_OBJECT (widget), "active");
+
+ container = glade_xml_get_widget (gui, "labels-alignment");
+ widget = e_mail_label_manager_new ();
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ G_OBJECT (shell_settings), "mail-label-list-store",
+ G_OBJECT (widget), "list-store");
+
+ /* headers */
+ locked = !gconf_client_key_is_writable (prefs->gconf, "/apps/evolution/mail/display/headers", NULL);
+
+ widget = glade_xml_get_widget (gui, "photo_show");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-show-sender-photo",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "photo_local");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-show-sender-photo",
+ G_OBJECT (widget), "sensitive");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-only-local-photos",
+ G_OBJECT (widget), "active");
+
+ /* always de-sensitised until the user types something in the entry */
+ prefs->add_header = GTK_BUTTON (glade_xml_get_widget (gui, "cmdHeadersAdd"));
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->add_header, FALSE);
+
+ /* always de-sensitised until the user selects a header in the list */
+ prefs->remove_header = GTK_BUTTON (glade_xml_get_widget (gui, "cmdHeadersRemove"));
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->remove_header, FALSE);
+
+ prefs->entry_header = GTK_ENTRY (glade_xml_get_widget (gui, "txtHeaders"));
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->entry_header, !locked);
+
+ prefs->header_list = GTK_TREE_VIEW (glade_xml_get_widget (gui, "treeHeaders"));
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->header_list, !locked);
+
+ selection = gtk_tree_view_get_selection (prefs->header_list);
+ g_signal_connect (selection, "changed", G_CALLBACK (emmp_header_list_row_selected), prefs);
+ g_signal_connect (prefs->entry_header, "changed", G_CALLBACK (emmp_header_entry_changed), prefs);
+ g_signal_connect (prefs->entry_header, "activate", G_CALLBACK (emmp_header_add_header), prefs);
+ /* initialise the tree with appropriate headings */
+ prefs->header_list_store = gtk_list_store_newv (HEADER_LIST_N_COLUMNS, col_types);
+ g_signal_connect (prefs->add_header, "clicked", G_CALLBACK (emmp_header_add_header), prefs);
+ g_signal_connect (prefs->remove_header, "clicked", G_CALLBACK (emmp_header_remove_header), prefs);
+ gtk_tree_view_set_model (prefs->header_list, GTK_TREE_MODEL (prefs->header_list_store));
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_object_set (renderer, "activatable", TRUE, NULL);
+ g_signal_connect (renderer, "toggled", G_CALLBACK (emmp_header_list_enabled_toggled), prefs);
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (prefs->header_list), -1,
+ "Enabled", renderer,
+ "active", HEADER_LIST_ENABLED_COLUMN,
+ NULL);
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (prefs->header_list), -1,
+ "Name", renderer,
+ "text", HEADER_LIST_NAME_COLUMN,
+ NULL);
+
+ /* populated the listview with entries; firstly we add all the default headers, and then
+ we add read header configuration out of gconf. If a header in gconf is a default header,
+ we update the enabled flag accordingly
+ */
+ header_add_list = NULL;
+ default_header_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < G_N_ELEMENTS (default_headers); i++) {
+ struct _EMMailerPrefsHeader *h;
+
+ h = g_malloc (sizeof (struct _EMMailerPrefsHeader));
+ h->is_default = TRUE;
+ h->name = g_strdup (default_headers[i]);
+ h->enabled = strcmp ((gchar *)default_headers[i], "x-evolution-mailer") != 0;
+ g_hash_table_insert (default_header_hash, (gpointer) default_headers[i], h);
+ header_add_list = g_slist_append (header_add_list, h);
+ }
+
+ /* read stored headers from gconf */
+ header_config_list = gconf_client_get_list (prefs->gconf, "/apps/evolution/mail/display/headers", GCONF_VALUE_STRING, NULL);
+ p = header_config_list;
+ while (p) {
+ struct _EMMailerPrefsHeader *h, *def;
+ gchar *xml = (gchar *) p->data;
+
+ h = em_mailer_prefs_header_from_xml (xml);
+ if (h) {
+ def = g_hash_table_lookup (default_header_hash, h->name);
+ if (def) {
+ def->enabled = h->enabled;
+ em_mailer_prefs_header_free (h);
+ } else {
+ h->is_default = FALSE;
+ header_add_list = g_slist_append (header_add_list, h);
+ }
+ }
+
+ p = p->next;
+ }
+
+ g_hash_table_destroy (default_header_hash);
+ g_slist_foreach (header_config_list, (GFunc) g_free, NULL);
+ g_slist_free (header_config_list);
+
+ p = header_add_list;
+ while (p) {
+ struct _EMMailerPrefsHeader *h = (struct _EMMailerPrefsHeader *) p->data;
+ const gchar *name;
+
+ if (g_ascii_strcasecmp (h->name, EM_FORMAT_HEADER_XMAILER) == 0)
+ name = _("Mailer");
+ else
+ name = _(h->name);
+
+ gtk_list_store_append (prefs->header_list_store, &iter);
+ gtk_list_store_set (prefs->header_list_store, &iter,
+ HEADER_LIST_NAME_COLUMN, name,
+ HEADER_LIST_ENABLED_COLUMN, h->enabled,
+ HEADER_LIST_IS_DEFAULT_COLUMN, h->is_default,
+ HEADER_LIST_HEADER_COLUMN, h->name,
+ -1);
+
+ em_mailer_prefs_header_free (h);
+ p = p->next;
+ }
+
+ g_slist_free (header_add_list);
+
+ /* date/time format */
+ table = glade_xml_get_widget (gui, "datetime_format_table");
+ e_datetime_format_add_setup_widget (table, 0, "mail", "header", DTFormatKindDateTime, _("Date header:"));
+ e_datetime_format_add_setup_widget (table, 1, "mail", "table", DTFormatKindDateTime, _("Table column:"));
+
+ /* Junk prefs */
+ widget = glade_xml_get_widget (gui, "chkCheckIncomingMail");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-check-for-junk",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "junk_empty_check");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-empty-junk-on-exit",
+ G_OBJECT (widget), "active");
+
+ widget = glade_xml_get_widget (gui, "junk_empty_combobox");
+ e_mutual_binding_new (
+ G_OBJECT (shell_settings), "mail-empty-junk-on-exit",
+ G_OBJECT (widget), "sensitive");
+ emmp_empty_junk_init (prefs, GTK_COMBO_BOX (widget));
+
+ prefs->default_junk_plugin = GTK_COMBO_BOX (glade_xml_get_widget (gui, "default_junk_plugin"));
+ prefs->plugin_status = GTK_LABEL (glade_xml_get_widget (gui, "plugin_status"));
+ prefs->plugin_image = GTK_IMAGE (glade_xml_get_widget (gui, "plugin_image"));
+ junk_plugin_setup (GTK_WIDGET (prefs->default_junk_plugin), prefs);
+
+ prefs->junk_header_check = (GtkToggleButton *)glade_xml_get_widget (gui, "junk_header_check");
+ prefs->junk_header_tree = (GtkTreeView *)glade_xml_get_widget (gui, "junk_header_tree");
+ prefs->junk_header_add = (GtkButton *)glade_xml_get_widget (gui, "junk_header_add");
+ prefs->junk_header_remove = (GtkButton *)glade_xml_get_widget (gui, "junk_header_remove");
+ prefs->junk_book_lookup = (GtkToggleButton *)glade_xml_get_widget (gui, "lookup_book");
+ prefs->junk_lookup_local_only = (GtkToggleButton *)glade_xml_get_widget (gui, "junk_lookup_local_only");
+ toggle_button_init (prefs, prefs->junk_book_lookup, FALSE,
+ "/apps/evolution/mail/junk/lookup_addressbook",
+ G_CALLBACK (junk_book_lookup_button_toggled));
+
+ toggle_button_init (prefs, prefs->junk_lookup_local_only, FALSE,
+ "/apps/evolution/mail/junk/lookup_addressbook_local_only",
+ G_CALLBACK (toggle_button_toggled));
+
+ junk_book_lookup_button_toggled (prefs->junk_book_lookup, prefs);
+
+ prefs->junk_header_list_store = init_junk_tree ((GtkWidget *)prefs->junk_header_tree, prefs);
+ toggle_button_init (prefs, prefs->junk_header_check, FALSE,
+ "/apps/evolution/mail/junk/check_custom_header",
+ G_CALLBACK (custom_junk_button_toggled));
+
+ custom_junk_button_toggled (prefs->junk_header_check, prefs);
+ jh_tree_refill (prefs);
+ g_signal_connect (G_OBJECT (prefs->junk_header_add), "clicked", G_CALLBACK (jh_add_cb), prefs);
+ g_signal_connect (G_OBJECT (prefs->junk_header_remove), "clicked", G_CALLBACK (jh_remove_cb), prefs);
+
+ /* get our toplevel widget */
+ target = em_config_target_new_prefs(ec, prefs->gconf);
+ e_config_set_target((EConfig *)ec, (EConfigTarget *)target);
+ toplevel = e_config_create_widget((EConfig *)ec);
+ gtk_container_add (GTK_CONTAINER (prefs), toplevel);
+}
+
+GtkWidget *
+em_mailer_prefs_new (EShell *shell)
+{
+ EMMailerPrefs *new;
+
+ g_return_val_if_fail (E_IS_SHELL (shell), NULL);
+
+ new = g_object_new (EM_TYPE_MAILER_PREFS, NULL);
+
+ /* FIXME Kill this function. */
+ em_mailer_prefs_construct (new, shell);
+
+ return GTK_WIDGET (new);
+}
+
+static struct _EMMailerPrefsHeader *
+emmp_header_from_xmldoc (xmlDocPtr doc)
+{
+ struct _EMMailerPrefsHeader *h;
+ xmlNodePtr root;
+ xmlChar *name;
+
+ if (doc == NULL)
+ return NULL;
+
+ root = doc->children;
+ if (strcmp ((gchar *)root->name, "header") != 0)
+ return NULL;
+
+ name = xmlGetProp (root, (const guchar *)"name");
+ if (name == NULL)
+ return NULL;
+
+ h = g_malloc0 (sizeof (struct _EMMailerPrefsHeader));
+ h->name = g_strdup ((gchar *)name);
+ xmlFree (name);
+
+ if (xmlHasProp (root, (const guchar *)"enabled"))
+ h->enabled = 1;
+ else
+ h->enabled = 0;
+
+ return h;
+}
+
+/**
+ * em_mailer_prefs_header_from_xml
+ * @xml: XML configuration data
+ *
+ * Parses passed XML data, which should be of
+ * the format <header name="foo" enabled />, and
+ * returns a EMMailerPrefs structure, or NULL if there
+ * is an error.
+ **/
+struct _EMMailerPrefsHeader *
+em_mailer_prefs_header_from_xml (const gchar *xml)
+{
+ struct _EMMailerPrefsHeader *header;
+ xmlDocPtr doc;
+
+ if (!(doc = xmlParseDoc ((guchar *) xml)))
+ return NULL;
+
+ header = emmp_header_from_xmldoc (doc);
+ xmlFreeDoc (doc);
+
+ return header;
+}
+
+/**
+ * em_mailer_prefs_header_free
+ * @header: header to free
+ *
+ * Frees the memory associated with the passed header
+ * structure.
+ */
+void
+em_mailer_prefs_header_free (struct _EMMailerPrefsHeader *header)
+{
+ if (header == NULL)
+ return;
+
+ g_free (header->name);
+ g_free (header);
+}
+
+/**
+ * em_mailer_prefs_header_to_xml
+ * @header: header from which to generate XML
+ *
+ * Returns the passed header as a XML structure,
+ * or NULL on error
+ */
+gchar *
+em_mailer_prefs_header_to_xml (struct _EMMailerPrefsHeader *header)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlChar *xml;
+ gchar *out;
+ gint size;
+
+ g_return_val_if_fail (header != NULL, NULL);
+ g_return_val_if_fail (header->name != NULL, NULL);
+
+ doc = xmlNewDoc ((const guchar *)"1.0");
+
+ root = xmlNewDocNode (doc, NULL, (const guchar *)"header", NULL);
+ xmlSetProp (root, (const guchar *)"name", (guchar *)header->name);
+ if (header->enabled)
+ xmlSetProp (root, (const guchar *)"enabled", NULL);
+
+ xmlDocSetRootElement (doc, root);
+ xmlDocDumpMemory (doc, &xml, &size);
+ xmlFreeDoc (doc);
+
+ out = g_malloc (size + 1);
+ memcpy (out, xml, size);
+ out[size] = '\0';
+ xmlFree (xml);
+
+ return out;
+}
diff --git a/modules/mail/em-mailer-prefs.h b/modules/mail/em-mailer-prefs.h
new file mode 100644
index 0000000000..01f823b11e
--- /dev/null
+++ b/modules/mail/em-mailer-prefs.h
@@ -0,0 +1,131 @@
+/*
+ * 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:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_MAILER_PREFS_H
+#define EM_MAILER_PREFS_H
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <gconf/gconf-client.h>
+#include <shell/e-shell.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_MAILER_PREFS \
+ (em_mailer_prefs_get_type ())
+#define EM_MAILER_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), EM_TYPE_MAILER_PREFS, EMMailerPrefs))
+#define EM_MAILER_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), EM_TYPE_MAILER_PREFS, EMMailerPrefsClass))
+#define EM_IS_MAILER_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), EM_TYPE_MAILER_PREFS))
+#define EM_IS_MAILER_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), EM_TYPE_MAILER_PREFS))
+#define EM_MAILER_PREFS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), EM_TYPE_MAILER_PREFS))
+
+G_BEGIN_DECLS
+
+typedef struct _EMMailerPrefs EMMailerPrefs;
+typedef struct _EMMailerPrefsClass EMMailerPrefsClass;
+typedef struct _EMMailerPrefsHeader EMMailerPrefsHeader;
+
+struct _EMMailerPrefsHeader {
+ gchar *name;
+ guint enabled:1;
+ guint is_default:1;
+};
+
+struct _EMMailerPrefs {
+ GtkVBox parent_object;
+
+ GladeXML *gui;
+ GConfClient *gconf;
+
+ /* General tab */
+
+ /* Message Display */
+ GtkSpinButton *timeout;
+
+ /* HTML Mail tab */
+ GtkFontButton *font_variable;
+ GtkFontButton *font_fixed;
+ GtkToggleButton *font_share;
+
+ /* Loading Images */
+ GtkToggleButton *images_always;
+ GtkToggleButton *images_sometimes;
+ GtkToggleButton *images_never;
+
+ GtkToggleButton *autodetect_links;
+
+ /* Labels and Colours tab */
+ GtkWidget *label_add;
+ GtkWidget *label_edit;
+ GtkWidget *label_remove;
+ GtkWidget *label_tree;
+ GtkListStore *label_list_store;
+ guint labels_change_notify_id; /* mail_config's notify id */
+
+ /* Headers tab */
+ GtkButton *add_header;
+ GtkButton *remove_header;
+ GtkEntry *entry_header;
+ GtkTreeView *header_list;
+ GtkListStore *header_list_store;
+
+ GtkToggleButton *sa_local_tests_only;
+ GtkToggleButton *sa_use_daemon;
+ GtkComboBox *default_junk_plugin;
+ GtkLabel *plugin_status;
+ GtkImage *plugin_image;
+
+ GtkToggleButton *junk_header_check;
+ GtkTreeView *junk_header_tree;
+ GtkListStore *junk_header_list_store;
+ GtkButton *junk_header_add;
+ GtkButton *junk_header_remove;
+ GtkToggleButton *junk_book_lookup;
+ GtkToggleButton *junk_lookup_local_only;
+};
+
+struct _EMMailerPrefsClass {
+ GtkVBoxClass parent_class;
+};
+
+GType em_mailer_prefs_get_type (void);
+GtkWidget * create_combo_text_widget (void);
+
+GtkWidget * em_mailer_prefs_new (EShell *shell);
+
+EMMailerPrefsHeader *
+ em_mailer_prefs_header_from_xml (const gchar *xml);
+gchar * em_mailer_prefs_header_to_xml (EMMailerPrefsHeader *header);
+void em_mailer_prefs_header_free (EMMailerPrefsHeader *header);
+
+G_END_DECLS
+
+#endif /* EM_MAILER_PREFS_H */
diff --git a/modules/mail/em-network-prefs.c b/modules/mail/em-network-prefs.c
new file mode 100644
index 0000000000..4c05fca77d
--- /dev/null
+++ b/modules/mail/em-network-prefs.c
@@ -0,0 +1,497 @@
+/*
+ * 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:
+ * Veerapuram Varadhan <vvaradhan@novell.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "em-network-prefs.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <gconf/gconf-client.h>
+#include <glade/glade.h>
+
+#include <glib/gstdio.h>
+
+#include "e-util/e-error.h"
+#include "e-util/e-util-private.h"
+
+#include "mail-config.h"
+#include "em-config.h"
+
+#define d(x)
+
+#define GCONF_E_SHELL_NETWORK_CONFIG_PATH "/apps/evolution/shell/network_config/"
+#define GCONF_E_HTTP_HOST_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "http_host"
+#define GCONF_E_HTTP_PORT_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "http_port"
+#define GCONF_E_HTTPS_HOST_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "secure_host"
+#define GCONF_E_HTTPS_PORT_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "secure_port"
+#define GCONF_E_SOCKS_HOST_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "socks_host"
+#define GCONF_E_SOCKS_PORT_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "socks_port"
+#define GCONF_E_IGNORE_HOSTS_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "ignore_hosts"
+#define GCONF_E_USE_AUTH_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "use_authentication"
+#define GCONF_E_PROXY_TYPE_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "proxy_type"
+#define GCONF_E_AUTH_USER_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "authentication_user"
+#define GCONF_E_AUTH_PWD_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "authentication_password"
+#define GCONF_E_USE_PROXY_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "use_http_proxy"
+#define GCONF_E_AUTOCONFIG_URL_KEY GCONF_E_SHELL_NETWORK_CONFIG_PATH "autoconfig_url"
+
+static void em_network_prefs_class_init (EMNetworkPrefsClass *class);
+static void em_network_prefs_init (EMNetworkPrefs *dialog);
+static void em_network_prefs_destroy (GtkObject *obj);
+static void em_network_prefs_finalise (GObject *obj);
+
+static GtkVBoxClass *parent_class = NULL;
+
+GType
+em_network_prefs_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof (EMNetworkPrefsClass),
+ NULL, NULL,
+ (GClassInitFunc) em_network_prefs_class_init,
+ NULL, NULL,
+ sizeof (EMNetworkPrefs),
+ 0,
+ (GInstanceInitFunc) em_network_prefs_init,
+ };
+
+ type = g_type_register_static (gtk_vbox_get_type (), "EMNetworkPrefs", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+em_network_prefs_class_init (EMNetworkPrefsClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_ref (gtk_vbox_get_type ());
+
+ object_class->destroy = em_network_prefs_destroy;
+ gobject_class->finalize = em_network_prefs_finalise;
+}
+
+static void
+em_network_prefs_init (EMNetworkPrefs *prefs)
+{
+ /* do something here */
+}
+
+static void
+em_network_prefs_finalise (GObject *obj)
+{
+ d(g_print ("Network preferences finalize is called\n"));
+
+ /* do something here */
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+em_network_prefs_destroy (GtkObject *obj)
+{
+ d(g_print ("Network preferences destroy is called\n"));
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (obj);
+}
+
+static void
+toggle_button_toggled (GtkToggleButton *toggle, EMNetworkPrefs *prefs)
+{
+ const gchar *key;
+
+ key = g_object_get_data ((GObject *) toggle, "key");
+ gconf_client_set_bool (prefs->gconf, key, gtk_toggle_button_get_active (toggle), NULL);
+ if (toggle == prefs->use_auth) {
+ gboolean sensitivity = gtk_toggle_button_get_active (prefs->use_auth);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_auth_user, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_auth_pwd, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->auth_user, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->auth_pwd, sensitivity);
+ }
+}
+
+static void
+toggle_button_init (EMNetworkPrefs *prefs, GtkToggleButton *toggle, const gchar *key)
+{
+ gboolean bool;
+
+ bool = gconf_client_get_bool (prefs->gconf, key, NULL);
+ gtk_toggle_button_set_active (toggle, bool);
+
+ g_object_set_data ((GObject *) toggle, "key", (gpointer) key);
+ g_signal_connect (toggle, "toggled", G_CALLBACK (toggle_button_toggled), prefs);
+
+ if (!gconf_client_key_is_writable (prefs->gconf, key, NULL))
+ gtk_widget_set_sensitive ((GtkWidget *) toggle, FALSE);
+}
+
+static GtkWidget *
+emnp_widget_glade(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data)
+{
+ EMNetworkPrefs *prefs = data;
+
+ return glade_xml_get_widget(prefs->gui, item->label);
+}
+
+static void
+emnp_set_sensitiveness (EMNetworkPrefs *prefs, NetworkConfigProxyType type, gboolean sensitivity)
+{
+#if 0
+ if (type == NETWORK_PROXY_AUTOCONFIG) {
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->auto_proxy_url, sensitivity);
+ d(g_print ("Setting sensitivity of autoconfig to: %d\n", sensitivity));
+ } else
+#endif
+ if (type == NETWORK_PROXY_MANUAL) {
+ gboolean state;
+
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->http_host, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->https_host, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->ignore_hosts, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->use_auth, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->http_port, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->https_port, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_ignore_hosts, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_http_host, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_http_port, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_https_host, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_https_port, sensitivity);
+#if 0
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->socks_host, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->socks_port, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_socks_host, sensitivity);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_socks_port, sensitivity);
+#endif
+ state = sensitivity && gtk_toggle_button_get_active (prefs->use_auth);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_auth_user, state);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->lbl_auth_pwd, state);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->auth_user, state);
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->auth_pwd, state);
+
+ d(g_print ("Setting sensitivity of manual proxy to: %d\n", sensitivity));
+ }
+}
+
+static void
+notify_proxy_type_changed (GtkWidget *widget, EMNetworkPrefs *prefs)
+{
+ gint type;
+
+ if (gtk_toggle_button_get_active (prefs->sys_proxy))
+ type = NETWORK_PROXY_SYS_SETTINGS;
+ else if (gtk_toggle_button_get_active (prefs->no_proxy))
+ type = NETWORK_PROXY_DIRECT_CONNECTION;
+ else if (gtk_toggle_button_get_active (prefs->manual_proxy))
+ type = NETWORK_PROXY_MANUAL;
+ else
+#if 0
+ type = NETWORK_PROXY_AUTOCONFIG;
+#else
+ type = NETWORK_PROXY_SYS_SETTINGS;
+#endif
+
+ gconf_client_set_int (prefs->gconf, "/apps/evolution/shell/network_config/proxy_type", type, NULL);
+
+ if (type == NETWORK_PROXY_DIRECT_CONNECTION ||
+ type == NETWORK_PROXY_SYS_SETTINGS) {
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_MANUAL, FALSE);
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_AUTOCONFIG, FALSE);
+ } else if (type == NETWORK_PROXY_AUTOCONFIG) {
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_MANUAL, FALSE);
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_AUTOCONFIG, TRUE);
+ } else if (type == NETWORK_PROXY_MANUAL) {
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_AUTOCONFIG, FALSE);
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_MANUAL, TRUE);
+ }
+
+ if (type != NETWORK_PROXY_DIRECT_CONNECTION)
+ gconf_client_set_bool (prefs->gconf, GCONF_E_USE_PROXY_KEY, TRUE, NULL);
+ else if (type != NETWORK_PROXY_SYS_SETTINGS)
+ gconf_client_set_bool (prefs->gconf, GCONF_E_USE_PROXY_KEY, FALSE, NULL);
+
+}
+
+static void
+widget_entry_changed_cb (GtkWidget *widget, gpointer data)
+{
+ const gchar *value;
+ gint port = -1;
+ GConfClient *gconf = mail_config_get_gconf_client ();
+
+ /*
+ Do not change the order of comparison -
+ GtkSpinButton is an extended form of GtkEntry
+ */
+ if (GTK_IS_SPIN_BUTTON (widget)) {
+ port = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
+ gconf_client_set_int (gconf, (const gchar *)data, port, NULL);
+ d(g_print ("%s:%s: %s is SpinButton: value = [%d]\n", G_STRLOC, G_STRFUNC, (const gchar *)data, port));
+ } else if (GTK_IS_ENTRY (widget)) {
+ value = gtk_entry_get_text (GTK_ENTRY (widget));
+ gconf_client_set_string (gconf, (const gchar *)data, value, NULL);
+ d(g_print ("%s:%s: %s is Entry: value = [%s]\n", G_STRLOC, G_STRFUNC, (const gchar *)data, value));
+ }
+
+}
+
+/* plugin meta-data */
+static EMConfigItem emnp_items[] = {
+ { E_CONFIG_BOOK, (gchar *) "", (gchar *) "network_preferences_toplevel", emnp_widget_glade },
+ { E_CONFIG_PAGE, (gchar *) "00.general", (gchar *) "vboxGeneral", emnp_widget_glade },
+ { E_CONFIG_SECTION, (gchar *) "00.general/00.proxy", (gchar *) "frameProxy", emnp_widget_glade },
+};
+
+static void
+emnp_free(EConfig *ec, GSList *items, gpointer data)
+{
+ /* the prefs data is freed automagically */
+
+ g_slist_free(items);
+}
+
+static void
+emnp_set_markups (EMNetworkPrefs *prefs)
+{
+ gtk_label_set_use_markup (GTK_LABEL (GTK_BIN(prefs->sys_proxy)->child), TRUE);
+ gtk_label_set_use_markup (GTK_LABEL (GTK_BIN(prefs->no_proxy)->child), TRUE);
+ gtk_label_set_use_markup (GTK_LABEL (GTK_BIN(prefs->manual_proxy)->child), TRUE);
+#if 0
+ gtk_label_set_use_markup (GTK_LABEL (GTK_BIN(prefs->auto_proxy)->child), TRUE);
+#endif
+}
+
+static void
+em_network_prefs_construct (EMNetworkPrefs *prefs)
+{
+ GtkWidget *toplevel;
+ GladeXML *gui;
+ GSList* l;
+ gchar *buf;
+ EMConfig *ec;
+ EMConfigTargetPrefs *target;
+ gboolean locked;
+ gint i, val, port;
+ gchar *gladefile;
+
+ prefs->gconf = mail_config_get_gconf_client ();
+
+ gladefile = g_build_filename (EVOLUTION_GLADEDIR,
+ "mail-config.glade",
+ NULL);
+ gui = glade_xml_new (gladefile, "network_preferences_toplevel", NULL);
+ prefs->gui = gui;
+ g_free (gladefile);
+
+ /** @HookPoint-EMConfig: Network Preferences
+ * @Id: org.gnome.evolution.mail.networkPrefs
+ * @Type: E_CONFIG_BOOK
+ * @Class: org.gnome.evolution.mail.config:1.0
+ * @Target: EMConfigTargetPrefs
+ *
+ * The network preferences settings page.
+ */
+ ec = em_config_new(E_CONFIG_BOOK, "org.gnome.evolution.mail.networkPrefs");
+ l = NULL;
+ for (i=0;i<sizeof(emnp_items)/sizeof(emnp_items[0]);i++)
+ l = g_slist_prepend(l, &emnp_items[i]);
+ e_config_add_items((EConfig *)ec, l, NULL, NULL, emnp_free, prefs);
+
+ /* Proxy tab */
+
+ /* Default Behavior */
+ locked = !gconf_client_key_is_writable (prefs->gconf, GCONF_E_PROXY_TYPE_KEY, NULL);
+
+ val = gconf_client_get_int (prefs->gconf, GCONF_E_PROXY_TYPE_KEY, NULL);
+
+ /* no auto-proxy at the moment */
+ if (val == NETWORK_PROXY_AUTOCONFIG)
+ val = NETWORK_PROXY_SYS_SETTINGS;
+
+ prefs->sys_proxy = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "rdoSysSettings"));
+ gtk_toggle_button_set_active (prefs->sys_proxy, val == NETWORK_PROXY_SYS_SETTINGS);
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->sys_proxy, FALSE);
+
+ d(g_print ("Sys settings ----!!! \n"));
+
+ prefs->no_proxy = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "rdoNoProxy"));
+ gtk_toggle_button_set_active (prefs->no_proxy, val == NETWORK_PROXY_DIRECT_CONNECTION);
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->no_proxy, FALSE);
+
+ d(g_print ("No proxy settings ----!!! \n"));
+
+ /* no auto-proxy at the moment */
+#if 0
+ prefs->auto_proxy = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "rdoAutoConfig"));
+ prefs->auto_proxy_url = GTK_ENTRY (glade_xml_get_widget (gui, "txtAutoConfigUrl"));
+
+ gtk_toggle_button_set_active (prefs->auto_proxy, val == NETWORK_PROXY_AUTOCONFIG);
+
+ g_signal_connect(prefs->auto_proxy_url, "changed", G_CALLBACK(widget_entry_changed_cb), GCONF_E_AUTOCONFIG_URL_KEY);
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->auto_proxy, FALSE);
+#endif
+
+ d(g_print ("Auto config settings ----!!! \n"));
+
+ prefs->manual_proxy = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "rdoManualProxy"));
+ prefs->http_host = GTK_ENTRY (glade_xml_get_widget (gui, "txtHttpHost"));
+ prefs->https_host = GTK_ENTRY (glade_xml_get_widget (gui, "txtHttpsHost"));
+ prefs->ignore_hosts = GTK_ENTRY (glade_xml_get_widget (gui, "txtIgnoreHosts"));
+ prefs->http_port = GTK_SPIN_BUTTON (glade_xml_get_widget (gui, "spnHttpPort"));
+ prefs->https_port = GTK_SPIN_BUTTON (glade_xml_get_widget (gui, "spnHttpsPort"));
+ prefs->lbl_http_host = GTK_LABEL (glade_xml_get_widget (gui, "lblHttpHost"));
+ prefs->lbl_http_port = GTK_LABEL (glade_xml_get_widget (gui, "lblHttpPort"));
+ prefs->lbl_https_host = GTK_LABEL (glade_xml_get_widget (gui, "lblHttpsHost"));
+ prefs->lbl_https_port = GTK_LABEL (glade_xml_get_widget (gui, "lblHttpsPort"));
+ prefs->lbl_ignore_hosts = GTK_LABEL (glade_xml_get_widget (gui, "lblIgnoreHosts"));
+ prefs->use_auth = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "chkUseAuth"));
+ toggle_button_init (prefs, prefs->use_auth, GCONF_E_USE_AUTH_KEY);
+ prefs->lbl_auth_user = GTK_LABEL (glade_xml_get_widget (gui, "lblAuthUser"));
+ prefs->lbl_auth_pwd = GTK_LABEL (glade_xml_get_widget (gui, "lblAuthPwd"));
+ prefs->auth_user = GTK_ENTRY (glade_xml_get_widget (gui, "txtAuthUser"));
+ prefs->auth_pwd = GTK_ENTRY (glade_xml_get_widget (gui, "txtAuthPwd"));
+
+#if 0
+ prefs->socks_host = GTK_ENTRY (glade_xml_get_widget (gui, "txtSocksHost"));
+ prefs->socks_port = GTK_SPIN_BUTTON (glade_xml_get_widget (gui, "spnSocksPort"));
+ prefs->lbl_socks_host = GTK_LABEL (glade_xml_get_widget (gui, "lblSocksHost"));
+ prefs->lbl_socks_port = GTK_LABEL (glade_xml_get_widget (gui, "lblSocksPort"));
+ g_signal_connect (prefs->socks_host, "changed",
+ G_CALLBACK(widget_entry_changed_cb), GCONF_E_SOCKS_HOST_KEY);
+ g_signal_connect (prefs->socks_port, "value_changed",
+ G_CALLBACK(widget_entry_changed_cb), GCONF_E_SOCKS_PORT_KEY);
+#endif
+
+ /* Manual proxy options */
+ g_signal_connect (prefs->http_host, "changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_HTTP_HOST_KEY);
+ g_signal_connect (prefs->https_host, "changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_HTTPS_HOST_KEY);
+ g_signal_connect (prefs->ignore_hosts, "changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_IGNORE_HOSTS_KEY);
+ g_signal_connect (prefs->http_port, "value_changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_HTTP_PORT_KEY);
+ g_signal_connect (prefs->https_port, "value_changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_HTTPS_PORT_KEY);
+ g_signal_connect (prefs->auth_user, "changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_AUTH_USER_KEY);
+ g_signal_connect (prefs->auth_pwd, "changed",
+ G_CALLBACK(widget_entry_changed_cb),
+ (gpointer) GCONF_E_AUTH_PWD_KEY);
+
+ gtk_toggle_button_set_active (prefs->manual_proxy, val == NETWORK_PROXY_MANUAL);
+ g_signal_connect (prefs->sys_proxy, "toggled", G_CALLBACK (notify_proxy_type_changed), prefs);
+ g_signal_connect (prefs->no_proxy, "toggled", G_CALLBACK (notify_proxy_type_changed), prefs);
+#if 0
+ g_signal_connect (prefs->auto_proxy, "toggled", G_CALLBACK (notify_proxy_type_changed), prefs);
+#endif
+ g_signal_connect (prefs->manual_proxy, "toggled", G_CALLBACK (notify_proxy_type_changed), prefs);
+
+ if (locked)
+ gtk_widget_set_sensitive ((GtkWidget *) prefs->manual_proxy, FALSE);
+ d(g_print ("Manual settings ----!!! \n"));
+
+ buf = gconf_client_get_string (prefs->gconf, GCONF_E_HTTP_HOST_KEY, NULL);
+ gtk_entry_set_text (prefs->http_host, buf ? buf : "");
+ g_free (buf);
+
+ buf = gconf_client_get_string (prefs->gconf, GCONF_E_HTTPS_HOST_KEY, NULL);
+ gtk_entry_set_text (prefs->https_host, buf ? buf : "");
+ g_free (buf);
+
+ buf = gconf_client_get_string (prefs->gconf, GCONF_E_IGNORE_HOSTS_KEY, NULL);
+ gtk_entry_set_text (prefs->ignore_hosts, buf ? buf : "");
+ g_free (buf);
+
+ buf = gconf_client_get_string (prefs->gconf, GCONF_E_AUTH_USER_KEY, NULL);
+ gtk_entry_set_text (prefs->auth_user, buf ? buf : "");
+ g_free (buf);
+
+ buf = gconf_client_get_string (prefs->gconf, GCONF_E_AUTH_PWD_KEY, NULL);
+ gtk_entry_set_text (prefs->auth_pwd, buf ? buf : "");
+ g_free (buf);
+
+ port = gconf_client_get_int (prefs->gconf, GCONF_E_HTTP_PORT_KEY, NULL);
+ gtk_spin_button_set_value (prefs->http_port, (gdouble)port);
+
+ port = gconf_client_get_int (prefs->gconf, GCONF_E_HTTPS_PORT_KEY, NULL);
+ gtk_spin_button_set_value (prefs->https_port, (gdouble)port);
+
+#if 0
+ buf = gconf_client_get_string (prefs->gconf, GCONF_E_SOCKS_HOST_KEY, NULL);
+ gtk_entry_set_text (prefs->socks_host, buf ? buf : "");
+ g_free (buf);
+
+ port = gconf_client_get_int (prefs->gconf, GCONF_E_SOCKS_PORT_KEY, NULL);
+ gtk_spin_button_set_value (prefs->socks_port, (gdouble)port);
+#endif
+ emnp_set_markups (prefs);
+
+ if (val == NETWORK_PROXY_DIRECT_CONNECTION ||
+ val == NETWORK_PROXY_SYS_SETTINGS) {
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_MANUAL, FALSE);
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_AUTOCONFIG, FALSE);
+ } else if (val == NETWORK_PROXY_AUTOCONFIG) {
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_MANUAL, FALSE);
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_AUTOCONFIG, TRUE);
+ } else if (val == NETWORK_PROXY_MANUAL) {
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_AUTOCONFIG, FALSE);
+ emnp_set_sensitiveness (prefs, NETWORK_PROXY_MANUAL, TRUE);
+ }
+
+ /* get our toplevel widget */
+ target = em_config_target_new_prefs(ec, prefs->gconf);
+ e_config_set_target((EConfig *)ec, (EConfigTarget *)target);
+ toplevel = e_config_create_widget((EConfig *)ec);
+ gtk_container_add (GTK_CONTAINER (prefs), toplevel);
+}
+
+GtkWidget *
+em_network_prefs_new (void)
+{
+ EMNetworkPrefs *new;
+
+ new = (EMNetworkPrefs *) g_object_new (em_network_prefs_get_type (), NULL);
+ em_network_prefs_construct (new);
+
+ return (GtkWidget *) new;
+}
diff --git a/modules/mail/em-network-prefs.h b/modules/mail/em-network-prefs.h
new file mode 100644
index 0000000000..057247b750
--- /dev/null
+++ b/modules/mail/em-network-prefs.h
@@ -0,0 +1,102 @@
+/*
+ * 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:
+ * Veerapuram Varadhan <vvaradhan@novell.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_NETWORK_PREFS_H
+#define EM_NETWORK_PREFS_H
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <gconf/gconf-client.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_NETWORK_PREFS \
+ (em_network_prefs_get_type ())
+#define EM_NETWORK_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), EM_TYPE_NETWORK_PREFS, EMNetworkPrefs))
+#define EM_NETWORK_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), EM_TYPE_NETWORK_PREFS, EMNetworkPrefsClass))
+#define EM_IS_NETWORK_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), EM_TYPE_NETWORK_PREFS))
+#define EM_IS_NETWORK_PREFS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), EM_TYPE_NETWORK_PREFS))
+#define EM_NETWORK_PREFS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), EM_TYPE_NETWORK_PREFS, EMNetworkPrefsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMNetworkPrefs EMNetworkPrefs;
+typedef struct _EMNetworkPrefsClass EMNetworkPrefsClass;
+
+typedef enum {
+ NETWORK_PROXY_SYS_SETTINGS,
+ NETWORK_PROXY_DIRECT_CONNECTION,
+ NETWORK_PROXY_MANUAL,
+ NETWORK_PROXY_AUTOCONFIG
+} NetworkConfigProxyType;
+
+struct _EMNetworkPrefs {
+ GtkVBox parent_object;
+
+ GConfClient *gconf;
+
+ GladeXML *gui;
+
+ /* Default Behavior */
+ GtkToggleButton *sys_proxy;
+ GtkToggleButton *no_proxy;
+ GtkToggleButton *manual_proxy;
+ GtkToggleButton *use_auth;
+
+ GtkEntry *http_host;
+ GtkEntry *https_host;
+ GtkEntry *socks_host;
+ GtkEntry *ignore_hosts;
+ GtkEntry *auth_user;
+ GtkEntry *auth_pwd;
+
+ GtkLabel *lbl_http_host;
+ GtkLabel *lbl_http_port;
+ GtkLabel *lbl_https_host;
+ GtkLabel *lbl_https_port;
+ GtkLabel *lbl_ignore_hosts;
+ GtkLabel *lbl_auth_user;
+ GtkLabel *lbl_auth_pwd;
+
+ GtkSpinButton *http_port;
+ GtkSpinButton *https_port;
+};
+
+struct _EMNetworkPrefsClass {
+ GtkVBoxClass parent_class;
+};
+
+GType em_network_prefs_get_type (void);
+GtkWidget * em_network_prefs_new (void);
+
+G_END_DECLS
+
+#endif /* EM_NETWORK_PREFS_H */
diff --git a/modules/mail/evolution-module-mail.c b/modules/mail/evolution-module-mail.c
new file mode 100644
index 0000000000..1bb58c7742
--- /dev/null
+++ b/modules/mail/evolution-module-mail.c
@@ -0,0 +1,71 @@
+/*
+ * evolution-module-mail.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-mail-attachment-handler.h"
+
+#include "e-mail-config-hook.h"
+#include "e-mail-event-hook.h"
+#include "e-mail-junk-hook.h"
+
+#include "e-mail-shell-backend.h"
+#include "e-mail-shell-content.h"
+#include "e-mail-shell-sidebar.h"
+#include "e-mail-shell-view.h"
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+const gchar * g_module_check_init (GModule *module);
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ /* Register dynamically loaded types. */
+
+ e_mail_attachment_handler_register_type (type_module);
+
+ e_mail_config_hook_register_type (type_module);
+ e_mail_event_hook_register_type (type_module);
+ e_mail_junk_hook_register_type (type_module);
+
+ e_mail_shell_backend_register_type (type_module);
+ e_mail_shell_content_register_type (type_module);
+ e_mail_shell_sidebar_register_type (type_module);
+ e_mail_shell_view_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
+
+G_MODULE_EXPORT const gchar *
+g_module_check_init (GModule *module)
+{
+ /* FIXME Until mail is split into a module library and a
+ * reusable shared library, prevent the module from
+ * being unloaded. Unloading the module resets all
+ * static variables, which screws up foo_get_type()
+ * functions among other things. */
+ g_module_make_resident (module);
+
+ return NULL;
+}
diff --git a/modules/plugin-lib/Makefile.am b/modules/plugin-lib/Makefile.am
new file mode 100644
index 0000000000..cce60902fa
--- /dev/null
+++ b/modules/plugin-lib/Makefile.am
@@ -0,0 +1,20 @@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"evolution-plugin-lib\" \
+ -I$(top_srcdir) \
+ $(E_UTIL_CFLAGS)
+
+module_LTLIBRARIES = libevolution-module-plugin-lib.la
+
+libevolution_module_plugin_lib_la_SOURCES = \
+ evolution-module-plugin-lib.c \
+ e-plugin-lib.c \
+ e-plugin-lib.h
+
+libevolution_module_plugin_lib_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(E_UTIL_LIBS)
+
+libevolution_module_plugin_lib_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/plugin-lib/e-plugin-lib.c b/modules/plugin-lib/e-plugin-lib.c
new file mode 100644
index 0000000000..c7a0233bb3
--- /dev/null
+++ b/modules/plugin-lib/e-plugin-lib.c
@@ -0,0 +1,249 @@
+/*
+ * e-plugin-lib.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-plugin-lib.h"
+
+#include <string.h>
+
+static gpointer parent_class;
+static GType plugin_lib_type;
+
+/* TODO:
+ We need some way to manage lifecycle.
+ We need some way to manage state.
+
+ Maybe just the g module init method will do, or we could add
+ another which returns context.
+
+ There is also the question of per-instance context, e.g. for config
+ pages.
+*/
+
+static gint
+plugin_lib_loadmodule (EPlugin *plugin)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (plugin);
+ EPluginLibEnableFunc enable;
+
+ if (plugin_lib->module != NULL)
+ return 0;
+
+ if (plugin_lib->location == NULL) {
+ g_warning ("Location not set in plugin '%s'", plugin->name);
+ return -1;
+ }
+
+ if ((plugin_lib->module = g_module_open (plugin_lib->location, 0)) == NULL) {
+ g_warning ("can't load plugin '%s': %s", plugin_lib->location, g_module_error ());
+ return -1;
+ }
+
+ if (g_module_symbol (plugin_lib->module, "e_plugin_lib_enable", (gpointer)&enable)) {
+ if (enable (plugin_lib, TRUE) != 0) {
+ plugin->enabled = FALSE;
+ g_module_close (plugin_lib->module);
+ plugin_lib->module = NULL;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static gpointer
+plugin_lib_invoke (EPlugin *plugin, const gchar *name, gpointer data)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (plugin);
+ EPluginLibFunc cb;
+
+ if (!plugin->enabled) {
+ g_warning ("trying to invoke '%s' on disabled plugin '%s'", name, plugin->id);
+ return NULL;
+ }
+
+ if (plugin_lib_loadmodule (plugin) != 0)
+ return NULL;
+
+ if (!g_module_symbol (plugin_lib->module, name, (gpointer)&cb)) {
+ g_warning ("Cannot resolve symbol '%s' in plugin '%s' (not exported?)", name, plugin_lib->location);
+ return NULL;
+ }
+
+ return cb (plugin_lib, data);
+}
+
+static gpointer
+plugin_lib_get_symbol (EPlugin *plugin, const gchar *name)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (plugin);
+ gpointer symbol;
+
+ if (plugin_lib_loadmodule (plugin) != 0)
+ return NULL;
+
+ if (!g_module_symbol (plugin_lib->module, name, &symbol))
+ return NULL;
+
+ return symbol;
+}
+
+static gint
+plugin_lib_construct (EPlugin *plugin, xmlNodePtr root)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (plugin);
+
+ /* Set the location before chaining up, as some EPluginHooks
+ * will cause the module to load during hook construction. */
+
+ plugin_lib->location = e_plugin_xml_prop (root, "location");
+
+ if (plugin_lib->location == NULL) {
+ g_warning ("Library plugin '%s' has no location", plugin->id);
+ return -1;
+ }
+#ifdef G_OS_WIN32
+ {
+ gchar *mapped_location =
+ e_util_rplugin_libace_prefix (EVOLUTION_PREFIX,
+ e_util_get_prefix (),
+ plugin_lib->location);
+ g_free (plugin_lib->location);
+ plugin_lib->location = mapped_location;
+ }
+#endif
+
+ /* Chain up to parent's construct() method. */
+ if (E_PLUGIN_CLASS (parent_class)->construct (plugin, root) == -1)
+ return -1;
+
+ /* If we're enabled, check for the load-on-startup property */
+ if (plugin->enabled) {
+ xmlChar *tmp;
+
+ tmp = xmlGetProp (root, (const guchar *)"load-on-startup");
+ if (tmp) {
+ if (plugin_lib_loadmodule (plugin) != 0) {
+ xmlFree (tmp);
+ return -1;
+ }
+ xmlFree (tmp);
+ }
+ }
+
+ return 0;
+}
+
+static GtkWidget *
+plugin_lib_get_configure_widget (EPlugin *plugin)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (plugin);
+ EPluginLibGetConfigureWidgetFunc get_configure_widget;
+
+ if (plugin_lib_loadmodule (plugin) != 0) {
+ return NULL;
+ }
+
+ if (g_module_symbol (plugin_lib->module, "e_plugin_lib_get_configure_widget", (gpointer)&get_configure_widget)) {
+ return (GtkWidget*) get_configure_widget (plugin_lib);
+ }
+ return NULL;
+}
+
+static void
+plugin_lib_enable (EPlugin *plugin, gint state)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (plugin);
+ EPluginLibEnableFunc enable;
+
+ E_PLUGIN_CLASS (parent_class)->enable (plugin, state);
+
+ /* if we're disabling and it isn't loaded, nothing to do */
+ if (!state && plugin_lib->module == NULL)
+ return;
+
+ /* this will noop if we're disabling since we tested it above */
+ if (plugin_lib_loadmodule (plugin) != 0)
+ return;
+
+ if (g_module_symbol (plugin_lib->module, "e_plugin_lib_enable", (gpointer) &enable)) {
+ if (enable (plugin_lib, state) != 0)
+ return;
+ }
+}
+
+static void
+plugin_lib_finalize (GObject *object)
+{
+ EPluginLib *plugin_lib = E_PLUGIN_LIB (object);
+
+ g_free (plugin_lib->location);
+
+ if (plugin_lib->module)
+ g_module_close (plugin_lib->module);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+plugin_lib_class_init (EPluginClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = plugin_lib_finalize;
+
+ class->construct = plugin_lib_construct;
+ class->invoke = plugin_lib_invoke;
+ class->get_symbol = plugin_lib_get_symbol;
+ class->enable = plugin_lib_enable;
+ class->get_configure_widget = plugin_lib_get_configure_widget;
+ class->type = "shlib";
+}
+
+GType
+e_plugin_lib_get_type (void)
+{
+ return plugin_lib_type;
+}
+
+void
+e_plugin_lib_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EPluginLibClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) plugin_lib_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EPluginLib),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ plugin_lib_type = g_type_module_register_type (
+ type_module, E_TYPE_PLUGIN,
+ "EPluginLib", &type_info, 0);
+}
diff --git a/modules/plugin-lib/e-plugin-lib.h b/modules/plugin-lib/e-plugin-lib.h
new file mode 100644
index 0000000000..b485d9625e
--- /dev/null
+++ b/modules/plugin-lib/e-plugin-lib.h
@@ -0,0 +1,92 @@
+/*
+ * e-plugin-lib.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_PLUGIN_LIB_H
+#define E_PLUGIN_LIB_H
+
+#include <gmodule.h>
+#include <e-util/e-plugin.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PLUGIN_LIB \
+ (e_plugin_lib_get_type ())
+#define E_PLUGIN_LIB(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_PLUGIN_LIB, EPluginLib))
+#define E_PLUGIN_LIB_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_PLUGIN_LIB, EPluginLibClass))
+#define E_IS_PLUGIN_LIB(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_PLUGIN_LIB))
+#define E_IS_PLUGIN_LIB_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_PLUGIN_LIB))
+#define E_PLUGIN_LIB_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_PLUGIN_LIB, EPluginLibClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPluginLib EPluginLib;
+typedef struct _EPluginLibClass EPluginLibClass;
+
+/* The callback signature used for epluginlib methods */
+typedef gpointer (*EPluginLibFunc) (EPluginLib *ep, gpointer data);
+
+/* The setup method, this will be called when the plugin is
+ * initialized. In the future it may also be called when the plugin
+ * is disabled. */
+typedef gint (*EPluginLibEnableFunc) (EPluginLib *ep, gint enable);
+
+typedef gpointer (*EPluginLibGetConfigureWidgetFunc) (EPluginLib *ep);
+
+/**
+ * struct _EPluginLib -
+ *
+ * @plugin: Superclass.
+ * @location: The filename of the shared object.
+ * @module: The GModule once it is loaded.
+ *
+ * This is a concrete EPlugin class. It loads and invokes dynamically
+ * loaded libraries using GModule. The shared object isn't loaded
+ * until the first callback is invoked.
+ *
+ * When the plugin is loaded, and if it exists, "e_plugin_lib_enable"
+ * will be invoked to initialize the plugin.
+ **/
+struct _EPluginLib {
+ EPlugin parent;
+
+ gchar *location;
+ GModule *module;
+};
+
+struct _EPluginLibClass {
+ EPluginClass parent_class;
+};
+
+GType e_plugin_lib_get_type (void);
+void e_plugin_lib_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_PLUGIN_LIB_H */
diff --git a/modules/plugin-lib/evolution-module-plugin-lib.c b/modules/plugin-lib/evolution-module-plugin-lib.c
new file mode 100644
index 0000000000..833ca5906d
--- /dev/null
+++ b/modules/plugin-lib/evolution-module-plugin-lib.c
@@ -0,0 +1,41 @@
+/*
+ * evolution-module-plugin-lib.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 <config.h>
+
+#include "e-plugin-lib.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_plugin_lib_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/plugin-mono/Camel.cs b/modules/plugin-mono/Camel.cs
new file mode 100644
index 0000000000..4ad80e7012
--- /dev/null
+++ b/modules/plugin-mono/Camel.cs
@@ -0,0 +1,1278 @@
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace Camel {
+ [StructLayout (LayoutKind.Sequential)]
+ public struct CamelException {
+ public int id;
+ public string desc;
+ }
+
+ public class Arg {
+ public enum Tag : uint {
+ END = 0,
+ IGNORE = 1,
+ FIRST = 1024,
+
+ TYPE = 0xf0000000, /* type field for tags */
+ TAG = 0x0fffffff, /* tag field for args */
+
+ OBJ = 0x00000000, /* object */
+ INT = 0x10000000, /* int */
+ DBL = 0x20000000, /* double */
+ STR = 0x30000000, /* c string */
+ PTR = 0x40000000, /* ptr */
+ BOO = 0x50000000 /* bool */
+ }
+ }
+
+ public class Exception : System.ApplicationException {
+ public enum Type {
+ NONE = 0,
+ SYSTEM = 1
+ }
+
+ public Type id;
+ public string desc;
+
+ public Exception(CamelException ex) {
+ id = (Type)ex.id;
+ desc = ex.desc;
+ }
+
+ public Exception(Type _id, string _desc) {
+ id = _id;
+ desc = _desc;
+ }
+ }
+
+ public class Util {
+ [DllImport("camel-1.2")] static extern int camel_init(string certdir, bool nss);
+
+ public static void Init(string certdir, bool nss) {
+ if (camel_init(certdir, nss) != 0)
+ throw new Exception(Exception.Type.SYSTEM, "Init failure");
+ }
+
+ public static string [] getUIDArray(IntPtr o) {
+ GPtrArray pa = (GPtrArray)Marshal.PtrToStructure(o, typeof(GPtrArray));
+ string [] uids = new string[pa.len];
+
+ for (int i=0;i<pa.len;i++) {
+ IntPtr x = Marshal.ReadIntPtr(pa.pdata, i * Marshal.SizeOf(typeof(IntPtr)));
+ uids[i] = Marshal.PtrToStringAuto(x);
+ }
+
+ return uids;
+ }
+/*
+ public static IntPtr setUIDs(string [] uids) {
+
+ }
+*/
+ public struct UIDArray {
+ public string [] uids;
+ public int len;
+
+ public UIDArray(string [] _uids) {
+ uids = _uids;
+ len = _uids.Length;
+ }
+
+ public UIDArray(IntPtr raw) {
+ uids = new string[0];
+ len = 0;
+ Marshal.PtrToStructure(raw, this);
+ }
+ }
+ }
+
+ public class Object {
+ // should be library scope
+ public IntPtr cobject;
+ private int finaliseID = -1;
+
+ protected EventHandlerList events = new EventHandlerList();
+
+ // reffing & wrapping stuff.
+ struct CamelObject {
+ public IntPtr klass;
+ }
+
+ struct CamelObjectClass {
+ public IntPtr parent;
+ int magic;
+ IntPtr next;
+ IntPtr prev;
+ public string name;
+ };
+
+ private static Hashtable types = new Hashtable();
+ private static Hashtable objects = new Hashtable();
+
+ [DllImport("camel-1.2")] static extern void camel_object_ref(IntPtr raw);
+ [DllImport("camel-1.2")] static extern void camel_object_unref(IntPtr raw);
+
+ public Object(IntPtr raw) {
+ // ok this is a hack around c# crap to do with unargumented constructors.
+ // we can bypass to a null raw so we can properly instantiate new types
+ if (raw != (IntPtr)0) {
+ cobject = raw;
+ toCamel(this);
+ }
+ }
+
+ public Object() {
+ // this is invalid?
+ }
+
+ ~Object() {
+ System.Console.WriteLine("object disposed " + cobject + " type " + this);
+
+ // well we can never get a finalised event anyway ...
+ if (finalise_id != -1)
+ camel_object_remove_event(cobject, finalise_id);
+ if (meta_changed_id != -1)
+ camel_object_remove_event(cobject, meta_changed_id);
+
+ objects.Remove(cobject);
+ camel_object_remove_event(cobject, finaliseID);
+ finaliseID = -1;
+ camel_object_unref(cobject);
+ cobject = (IntPtr)0;
+
+ // FIXME: remove any event hooks too
+ }
+
+ static Object() {
+ types.Add("CamelObject", typeof(Camel.Object));
+ types.Add("CamelSession", typeof(Camel.Session));
+ types.Add("CamelFolder", typeof(Camel.Folder));
+ types.Add("CamelDataWrapper", typeof(Camel.DataWrapper));
+ types.Add("CamelMedium", typeof(Camel.Medium));
+ types.Add("CamelMimeMessage", typeof(Camel.MimeMessage));
+ types.Add("CamelMimePart", typeof(Camel.MimePart));
+ types.Add("CamelMultipart", typeof(Camel.Multipart));
+
+ types.Add("CamelStore", typeof(Camel.Store));
+ types.Add("CamelTransport", typeof(Camel.Transport));
+ types.Add("CamelAddress", typeof(Camel.Address));
+ types.Add("CamelInternetAddress", typeof(Camel.InternetAddress));
+ types.Add("CamelStream", typeof(Camel.Stream));
+ types.Add("CamelStreamMem", typeof(Camel.StreamMem));
+ types.Add("CamelStreamFs", typeof(Camel.StreamFS));
+ }
+
+ public static void objectFinalised(IntPtr o, IntPtr info, IntPtr data) {
+ System.Console.WriteLine("object finalised " + o);
+ objects.Remove(o);
+ }
+
+ public static Object fromCamel(IntPtr raw) {
+ CamelObject o;
+ CamelObjectClass klass;
+ WeakReference weak = (WeakReference)objects[raw];
+
+ System.Console.WriteLine("object from camel " + raw);
+
+ if (weak != null)
+ return (Object)weak.Target;
+
+ o = (CamelObject)Marshal.PtrToStructure(raw, typeof(CamelObject));
+ if ((object)o == null)
+ return null;
+
+ klass = (CamelObjectClass)Marshal.PtrToStructure(o.klass, typeof(CamelObjectClass));
+ while ((object)klass != null) {
+ Console.WriteLine(" checking is " + klass.name);
+ if (types.ContainsKey(klass.name)) {
+ Console.WriteLine(" yep!");
+ camel_object_ref(raw);
+ return (Camel.Object)Activator.CreateInstance((Type)types[klass.name], new object [] { raw });
+ }
+
+ klass = (CamelObjectClass)Marshal.PtrToStructure(klass.parent, typeof(CamelObjectClass));
+ }
+
+ Console.WriteLine(" unknown type?");
+ camel_object_unref(raw);
+ return null;
+ }
+
+ /* this just registers an object created on the cil side */
+ public static void toCamel(Object res) {
+ System.Console.WriteLine("object to camel " + res.cobject);
+
+ objects.Add(res.cobject, new WeakReference(res));
+ res.finaliseID = camel_object_hook_event(res.cobject, "finalize", (CamelEventFunc)objectFinalised, (IntPtr)0);
+ }
+
+ // Camel event Wrapper and helpers
+ public delegate void CamelEventFunc(IntPtr o, IntPtr info, IntPtr data);
+
+ [DllImport("camel-1.2")] public static extern int camel_object_hook_event(IntPtr raw, string name, CamelEventFunc func, IntPtr data);
+ [DllImport("camel-1.2")] public static extern void camel_object_remove_event(IntPtr raw, int id);
+
+ protected void addEvent(String name, ref int hookid, CamelEventFunc hook, Delegate value) {
+ if (hookid == -1)
+ hookid = camel_object_hook_event(cobject, name, hook, (IntPtr)0);
+ events.AddHandler(name, value);
+ }
+
+ protected void removeEvent(String name, ref int hookid, Delegate value) {
+ events.RemoveHandler(name, value);
+ if (events[name] == null) {
+ camel_object_remove_event(cobject, hookid);
+ hookid = -1;
+ }
+ }
+
+ // object events
+ public delegate void FinaliseEvent(Camel.Object o);
+ public delegate void MetaChangedEvent(Camel.Object o, String name);
+
+ // how to remove these, at dispose time?
+ private int finalise_id = -1;
+ private int meta_changed_id = -1;
+
+ private static void finaliseHook(IntPtr co, IntPtr info, IntPtr data) {
+ Object o = fromCamel(co);
+ FinaliseEvent f;
+
+ if (o != null
+ && (f = (FinaliseEvent)o.events["finalize"]) != null)
+ f(o);
+ }
+
+ private static void metaChangedHook(IntPtr co, IntPtr info, IntPtr data) {
+ Object o = fromCamel(co);
+ MetaChangedEvent f;
+
+ if (o != null
+ && (f = (MetaChangedEvent)o.events["finalize"]) != null)
+ f(o, Marshal.PtrToStringAnsi(info));
+ }
+
+ public event FinaliseEvent Finalise {
+ add { addEvent("finalize", ref finalise_id, (CamelEventFunc)finaliseHook, value); }
+ remove { removeEvent("finalize", ref finalise_id, value); }
+ }
+
+ public event MetaChangedEvent MetaChanged {
+ add { addEvent("meta_changed", ref meta_changed_id, (CamelEventFunc)metaChangedHook, value); }
+ remove { removeEvent("meta_changed", ref meta_changed_id, value); }
+ }
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_object_get_ptr(IntPtr raw, ref CamelException ex, int tag);
+ [DllImport("camel-1.2")] static extern void camel_object_free(IntPtr raw, int tag, IntPtr val);
+ [DllImport("camel-1.2")] static extern int camel_object_get_int(IntPtr raw, ref CamelException ex, int tag);
+
+ // maybe we want an indexer class to get properties?
+ // e.g. name = folder.properties[Folder.Tag.NAME]
+ public String getString(int type) {
+ String s;
+ IntPtr o;
+ CamelException ex = new CamelException();
+
+ o = camel_object_get_ptr(cobject, ref ex, type);
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ s = Marshal.PtrToStringAuto(o);
+ camel_object_free(cobject, type, o);
+
+ return s;
+ }
+
+ public Camel.Object getObject(int type) {
+ IntPtr o;
+ Camel.Object co;
+ CamelException ex = new CamelException();
+
+ o = camel_object_get_ptr(cobject, ref ex, type);
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ co = fromCamel(o);
+ camel_object_free(cobject, type, o);
+
+ return co;
+ }
+
+ public int getInt(int type) {
+ int r;
+ CamelException ex = new CamelException();
+
+ r = camel_object_get_int(cobject, ref ex, type);
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ return r;
+ }
+
+ // meta-data
+ [DllImport("camel-1.2")] static extern String camel_object_meta_get(IntPtr raw, string name);
+ [DllImport("camel-1.2")] static extern bool camel_object_meta_set(IntPtr raw, string name, string value);
+
+ public String metaGet(String name) {
+ return camel_object_meta_get(cobject, name);
+ }
+
+ public bool metaSet(String name, String value) {
+ return camel_object_meta_set(cobject, name, value);
+ }
+ }
+
+ public class Provider {
+ public enum Type {
+ STORE = 0,
+ TRANSPORT = 1
+ }
+ }
+
+ public class Session : Object {
+ public Session(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_session_get_service(IntPtr o, string uri, int type, ref CamelException ex);
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_session_get_service_connected(IntPtr o, string uri, int type, ref CamelException ex);
+
+ public Service getService(string uri, Provider.Type type) {
+ IntPtr s;
+ CamelException ex = new CamelException();
+
+ s = camel_session_get_service(cobject, uri, (int)type, ref ex);
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ return (Service)fromCamel(s);
+ }
+ }
+
+ public class Service : Object {
+ public Service(IntPtr raw) : base(raw) { }
+ // wrap service shit
+ }
+
+ public class Store : Service {
+ public Store(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-provider-1.2")]
+ static extern IntPtr camel_store_get_folder(IntPtr o, string name, int flags, ref CamelException ex);
+
+ Folder getFolder(string name, int flags) {
+ IntPtr s;
+ CamelException ex = new CamelException();
+
+ s = camel_store_get_folder(cobject, name, flags, ref ex);
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ return (Folder)fromCamel(s);
+ }
+
+ void createFolder(string name) {
+ }
+ }
+
+ public class Transport : Service {
+ public Transport(IntPtr raw) : base(raw) { }
+
+ // send to (message, from, reciepients);
+ }
+
+ public class Folder : Camel.Object {
+ public Folder(IntPtr raw) : base(raw) { }
+
+ ~Folder() {
+ if (changed_id != -1)
+ camel_object_remove_event(cobject, changed_id);
+ }
+
+ public enum Tag {
+ NAME = (int) (0x1400 + Arg.Tag.STR),
+ FULL_NAME = (int) (0x1401 + Arg.Tag.STR),
+ STORE = (int) (0x1402 + Arg.Tag.OBJ),
+ PERMANENTFLAGS = (int) (0x1403 + Arg.Tag.INT),
+ TOTAL = (int) (0x1404 + Arg.Tag.INT),
+ UNREAD = (int) (0x1405 + Arg.Tag.INT),
+ DELETED = (int) (0x1406 + Arg.Tag.INT),
+ JUNKED = (int) (0x1407 + Arg.Tag.INT),
+ VISIBLE = (int) (0x1408 + Arg.Tag.INT),
+ UID_ARRAY = (int) (0x1409 + Arg.Tag.PTR),
+ INFO_ARRAY = (int) (0x140a + Arg.Tag.PTR), // GPtrArray
+ PROPERTIES = (int) (0x140b + Arg.Tag.PTR), // GSList of properties
+ }
+
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_folder_get_message(IntPtr o, string uid, ref CamelException ex);
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_folder_get_uids(IntPtr o);
+ [DllImport("camel-provider-1.2")] static extern void camel_folder_free_uids(IntPtr o, IntPtr uids);
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_folder_search_by_expression(IntPtr o, string expr, ref CamelException ex);
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_folder_search_by_uids(IntPtr o, string expr, ref Util.UIDArray uids, ref CamelException ex);
+ [DllImport("camel-provider-1.2")] static extern void camel_folder_search_free(IntPtr o, IntPtr uids);
+
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_folder_get_message_info(IntPtr raw, String uid);
+
+ public MimeMessage getMessage(string uid) {
+ CamelException ex = new CamelException();
+ IntPtr o = camel_folder_get_message(cobject, uid, ref ex);
+
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ return (MimeMessage)fromCamel(o);
+ }
+
+ public MessageInfo getMessageInfo(string uid) {
+ IntPtr o = camel_folder_get_message_info(cobject, uid);
+
+ if (o == (IntPtr)0)
+ return null;
+ else
+ return new MessageInfo(o);
+ }
+
+ public string [] getUIDs() {
+ IntPtr o = camel_folder_get_uids(cobject);
+ Util.UIDArray uids = new Util.UIDArray(o);
+
+ camel_folder_free_uids(cobject, o);
+
+ return uids.uids;
+ }
+
+ public string [] search(string expr) {
+ CamelException ex = new CamelException();
+ IntPtr o = camel_folder_search_by_expression(cobject, expr, ref ex);
+ Util.UIDArray uids;
+
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ uids = new Util.UIDArray(o);
+ camel_folder_search_free(cobject, o);
+
+ return uids.uids;
+ }
+
+ public string [] searchUIDs(string expr, string [] sub) {
+ CamelException ex = new CamelException();
+ Util.UIDArray uids = new Util.UIDArray(sub);
+ IntPtr o = camel_folder_search_by_uids(cobject, expr, ref uids, ref ex);
+
+ if (ex.id != 0)
+ throw new Camel.Exception(ex);
+
+ uids = new Util.UIDArray(o);
+ camel_folder_search_free(cobject, o);
+
+ return uids.uids;
+ }
+
+ public String name {
+ get { return getString((int)Folder.Tag.NAME); }
+ }
+
+ public String fullName {
+ get { return getString((int)Folder.Tag.FULL_NAME); }
+ }
+
+ public Camel.Store store {
+ get { return (Camel.Store)getObject((int)Folder.Tag.STORE); }
+ }
+
+ // Folder events
+ public delegate void ChangedEvent(Camel.Folder f);
+
+ private int changed_id = -1;
+
+ private static void changedHook(IntPtr co, IntPtr info, IntPtr data) {
+ Camel.Folder o = (Camel.Folder)fromCamel(co);
+ ChangedEvent f;
+
+ Console.WriteLine("changed hook called for: " + o.cobject);
+
+ if (o != null
+ && (f = (ChangedEvent)o.events["folder_changed"]) != null)
+ f(o);
+ }
+
+ public event ChangedEvent Changed {
+ add { addEvent("folder_changed", ref changed_id, (CamelEventFunc)changedHook, value); }
+ remove { removeEvent("folder_changed", ref changed_id, value); }
+ }
+ }
+
+ public class DataWrapper : Camel.Object {
+ public DataWrapper(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-1.2")] static extern int camel_data_wrapper_write_to_stream(IntPtr o, IntPtr s);
+ [DllImport("camel-1.2")] static extern int camel_data_wrapper_decode_to_stream(IntPtr o, IntPtr s);
+ [DllImport("camel-1.2")] static extern int camel_data_wrapper_construct_from_stream(IntPtr o, IntPtr s);
+ [DllImport("camel-1.2")] static extern IntPtr camel_data_wrapper_get_mime_type_field(IntPtr o);
+
+ public void writeToStream(Camel.Stream stream) {
+ int res;
+
+ res = camel_data_wrapper_write_to_stream(cobject, stream.cobject);
+ if (res == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO Error");
+ }
+
+ public void decodeToStream(Camel.Stream stream) {
+ int res;
+
+ res = camel_data_wrapper_decode_to_stream(cobject, stream.cobject);
+ if (res == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO Error");
+ }
+
+ public void constructFromStream(Camel.Stream stream) {
+ int res;
+
+ res = camel_data_wrapper_construct_from_stream(cobject, stream.cobject);
+ if (res == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO Error");
+ }
+
+ public ContentType mimeType { get { return new ContentType(camel_data_wrapper_get_mime_type_field(cobject)); } }
+ }
+
+ public class Medium : Camel.DataWrapper {
+ public Medium(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_medium_get_content_object(IntPtr o);
+ [DllImport("camel-1.2")] static extern void camel_medium_set_content_object(IntPtr o, IntPtr s);
+
+ public DataWrapper content {
+ get {
+ IntPtr o = camel_medium_get_content_object(cobject);
+
+ if (o != (IntPtr)0)
+ return (DataWrapper)Object.fromCamel(o);
+ else
+ return null;
+ }
+ set {
+ camel_medium_set_content_object(cobject, value.cobject);
+ }
+ }
+ }
+
+ public class Multipart : Camel.DataWrapper {
+ [DllImport("camel-1.2")] static extern IntPtr camel_multipart_new();
+ [DllImport("camel-1.2")] static extern void camel_multipart_add_part(IntPtr o, IntPtr p);
+ [DllImport("camel-1.2")] static extern void camel_multipart_remove_part(IntPtr o, IntPtr p);
+ [DllImport("camel-1.2")] static extern IntPtr camel_multipart_get_part(IntPtr o, int index);
+ [DllImport("camel-1.2")] static extern int camel_multipart_get_number(IntPtr o);
+
+ public Multipart(IntPtr raw) : base(raw) { }
+
+ public void addPart(MimePart part) {
+ camel_multipart_add_part(cobject, part.cobject);
+ }
+
+ public void removePart(MimePart part) {
+ camel_multipart_add_part(cobject, part.cobject);
+ }
+
+ public MimePart getPart(int index) {
+ IntPtr o;
+
+ o = camel_multipart_get_part(cobject, index);
+ if (o != (IntPtr)0)
+ return (MimePart)Object.fromCamel(o);
+ else
+ return null;
+ }
+
+ public int getNumber() {
+ return camel_multipart_get_number(cobject);
+ }
+
+ // FIXME: finish
+ }
+
+ public class MimePart : Camel.Medium {
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_part_new();
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_part_get_description(IntPtr o);
+ [DllImport("camel-1.2")] static extern void camel_mime_part_set_description(IntPtr o, string s);
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_part_get_disposition(IntPtr o);
+ [DllImport("camel-1.2")] static extern void camel_mime_part_set_disposition(IntPtr o, string s);
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_part_get_filename(IntPtr o);
+ [DllImport("camel-1.2")] static extern void camel_mime_part_set_filename(IntPtr o, string s);
+
+ public MimePart(IntPtr raw) : base(raw) { }
+
+ public string description {
+ get { return Marshal.PtrToStringAuto(camel_mime_part_get_description(cobject)); }
+ set { camel_mime_part_set_description(cobject, value); }
+ }
+
+ public string disposition {
+ get { return Marshal.PtrToStringAuto(camel_mime_part_get_disposition(cobject)); }
+ set { camel_mime_part_set_disposition(cobject, value); }
+ }
+
+ public string filename {
+ get { return Marshal.PtrToStringAuto(camel_mime_part_get_filename(cobject)); }
+ set { camel_mime_part_set_filename(cobject, value); }
+ }
+
+ // FIXME: finish
+ }
+
+ public class MimeMessage : Camel.MimePart {
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_message_new();
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_message_get_subject(IntPtr o);
+ [DllImport("camel-1.2")] static extern void camel_mime_message_set_subject(IntPtr o, string s);
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_message_get_from(IntPtr o);
+ [DllImport("camel-1.2")] static extern void camel_mime_message_set_from(IntPtr o, IntPtr s);
+ [DllImport("camel-1.2")] static extern IntPtr camel_mime_message_get_recipients(IntPtr o, string type);
+ [DllImport("camel-1.2")] static extern void camel_mime_message_set_recipients(IntPtr o, string type, IntPtr s);
+
+ public MimeMessage(IntPtr raw) : base(raw) { }
+
+ /* We need to use factories to create new objects otherwise the parent will instantiate an instance
+ of itself instead during the constructor setup */
+ public MimeMessage() : base((IntPtr)0) {
+ cobject = camel_mime_message_new();
+ toCamel(this);
+ }
+
+ public string subject {
+ get { return Marshal.PtrToStringAuto(camel_mime_message_get_subject(cobject)); }
+ set { camel_mime_message_set_subject(cobject, value); }
+ }
+
+ public InternetAddress from {
+ get { return new InternetAddress(camel_mime_message_get_from(cobject)); }
+ set { camel_mime_message_set_from(cobject, value.cobject); }
+ }
+
+ public InternetAddress to {
+ get { return new InternetAddress(camel_mime_message_get_recipients(cobject, "to")); }
+ set { camel_mime_message_set_recipients(cobject, "to", value.cobject); }
+ }
+
+ public InternetAddress cc {
+ get { return new InternetAddress(camel_mime_message_get_recipients(cobject, "cc")); }
+ set { camel_mime_message_set_recipients(cobject, "cc", value.cobject); }
+ }
+
+ public InternetAddress bcc {
+ get { return new InternetAddress(camel_mime_message_get_recipients(cobject, "bcc")); }
+ set { camel_mime_message_set_recipients(cobject, "bcc", value.cobject); }
+ }
+
+ public InternetAddress resentTO {
+ get { return new InternetAddress(camel_mime_message_get_recipients(cobject, "resent-to")); }
+ set { camel_mime_message_set_recipients(cobject, "resent-to", value.cobject); }
+ }
+
+ public InternetAddress resentCC {
+ get { return new InternetAddress(camel_mime_message_get_recipients(cobject, "resent-cc")); }
+ set { camel_mime_message_set_recipients(cobject, "resent-cc", value.cobject); }
+ }
+
+ public InternetAddress resentBCC {
+ get { return new InternetAddress(camel_mime_message_get_recipients(cobject, "resent-bcc")); }
+ set { camel_mime_message_set_recipients(cobject, "resent-bcc", value.cobject); }
+ }
+ }
+
+ // subclass real streams? or real stream interfaces?
+ public class Stream : Camel.Object {
+ public Stream(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-1.2")] static extern int camel_stream_write(IntPtr o, byte [] data, int len);
+ [DllImport("camel-1.2")] static extern int camel_stream_read(IntPtr o, byte [] data, int len);
+ [DllImport("camel-1.2")] static extern int camel_stream_eos(IntPtr o);
+ [DllImport("camel-1.2")] static extern int camel_stream_close(IntPtr o);
+ [DllImport("camel-1.2")] static extern int camel_stream_flush(IntPtr o);
+ [DllImport("camel-1.2")] static extern int camel_stream_reset(IntPtr o);
+
+ public int write(byte [] data, int len) {
+ int ret;
+
+ ret = camel_stream_write(cobject, data, len);
+ if (ret == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO write Error");
+
+ return ret;
+ }
+
+ public int write(string value) {
+ int ret;
+ byte [] data;
+ System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
+
+ data = enc.GetBytes(value);
+ ret = camel_stream_write(cobject, data, data.Length);
+ if (ret == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO write Error");
+
+ return ret;
+ }
+
+
+ public int read(byte [] data, int len) {
+ int ret;
+
+ ret = camel_stream_read(cobject, data, len);
+ if (ret == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO read Error");
+
+ return ret;
+ }
+
+ public void close() {
+ if (camel_stream_close(cobject) == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO close Error");
+ }
+
+ public void reset() {
+ if (camel_stream_reset(cobject) == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO reset Error");
+ }
+
+ public void flush() {
+ if (camel_stream_flush(cobject) == -1)
+ throw new Exception(Exception.Type.SYSTEM, "IO close Error");
+ }
+
+ public bool eos() {
+ return (camel_stream_eos(cobject) != 0);
+ }
+ }
+
+ public class SeekableStream : Camel.Stream {
+ public SeekableStream(IntPtr raw) : base(raw) { }
+ }
+
+ public class StreamFS : Camel.SeekableStream {
+ public enum Flags {
+ O_RDONLY = 00,
+ O_WRONLY = 01,
+ O_RDWR = 02,
+ O_CREAT = 0100,
+ O_EXCL = 0200,
+ O_TRUNC = 01000,
+ O_APPEND = 02000
+ }
+
+ public static int STDIN_FILENO = 0;
+ public static int STDOUT_FILENO = 1;
+ public static int STDERR_FILENO = 2;
+
+ public StreamFS(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_stream_fs_new_with_name(string name, int flags, int mode);
+ [DllImport("camel-1.2")] static extern IntPtr camel_stream_fs_new_with_fd(int fd);
+
+ public StreamFS(string name, Flags flags, int mode) : base((IntPtr)0) {
+ cobject = camel_stream_fs_new_with_name(name, (int)flags, mode);
+ toCamel(this);
+ }
+
+ public StreamFS(int fd) : base((IntPtr)0) {
+ cobject = camel_stream_fs_new_with_fd(fd);
+ toCamel(this);
+ }
+ }
+
+ // this should obviously be extracted at build time
+ [StructLayout (LayoutKind.Explicit)]
+ struct CamelStreamMem {
+ [FieldOffset(44)] public IntPtr buffer;
+ }
+
+ struct GByteArray {
+ public IntPtr data;
+ public int len;
+ }
+
+ struct GPtrArray {
+ public IntPtr pdata;
+ public int len;
+ }
+
+ public class StreamMem : Camel.SeekableStream {
+ public StreamMem(IntPtr raw) : base(raw) { }
+
+ [DllImport("camel-1.2")]
+ static extern IntPtr camel_stream_mem_new();
+
+ /* stupid c# */
+ public StreamMem() : base((IntPtr)0) {
+ cobject = camel_stream_mem_new();
+ toCamel(this);
+ }
+
+ // should probably have some sort of interface for incremental/range gets too
+ public Byte[] getBuffer() {
+ CamelStreamMem mem = (CamelStreamMem)Marshal.PtrToStructure(cobject, typeof(CamelStreamMem));
+ GByteArray ba = (GByteArray)Marshal.PtrToStructure(mem.buffer, typeof(GByteArray));
+ Byte[] res = new Byte[ba.len];
+
+ Marshal.Copy(ba.data, res, 0, ba.len);
+
+ return res;
+ }
+ }
+
+ // should do iterators etc?
+ public class Address : Camel.Object {
+ public Address(IntPtr raw) : base (raw) { }
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_address_new();
+ [DllImport("camel-1.2")] static extern int camel_address_length(IntPtr raw);
+ [DllImport("camel-1.2")] static extern int camel_address_decode(IntPtr raw, string addr);
+ [DllImport("camel-1.2")] static extern string camel_address_encode(IntPtr raw);
+ [DllImport("camel-1.2")] static extern int camel_address_unformat(IntPtr raw, string addr);
+ [DllImport("camel-1.2")] static extern string camel_address_format(IntPtr raw);
+ [DllImport("camel-1.2")] static extern int camel_address_cat(IntPtr raw, IntPtr src);
+ [DllImport("camel-1.2")] static extern int camel_address_copy(IntPtr raw, IntPtr src);
+ [DllImport("camel-1.2")] static extern void camel_address_remove(IntPtr raw, int index);
+
+ public Address() : base((IntPtr)0) {
+ cobject = camel_address_new();
+ toCamel(this);
+ }
+
+ public int length() {
+ return camel_address_length(cobject);
+ }
+
+ public void decode(string addr) {
+ if (camel_address_decode(cobject, addr) == -1)
+ throw new Exception(Exception.Type.SYSTEM, "Invalid address: " + addr);
+ }
+
+ public string encode() {
+ return camel_address_encode(cobject);
+ }
+
+ public void unformat(string addr) {
+ if (camel_address_unformat(cobject, addr) == -1)
+ throw new Exception(Exception.Type.SYSTEM, "Invalid address: " + addr);
+ }
+
+ public string format() {
+ return camel_address_format(cobject);
+ }
+
+ public void cat(Address from) {
+ camel_address_cat(cobject, from.cobject);
+ }
+
+ public void copy(Address from) {
+ camel_address_copy(cobject, from.cobject);
+ }
+ }
+
+ public class InternetAddress : Camel.Address {
+ public InternetAddress(IntPtr raw) : base (raw) { }
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_internet_address_new();
+ [DllImport("camel-1.2")] static extern int camel_internet_address_add(IntPtr raw, string name, string addr);
+ [DllImport("camel-1.2")] static extern bool camel_internet_address_get(IntPtr raw, out string name, out string addr);
+ [DllImport("camel-1.2")] static extern int camel_internet_address_find_name(IntPtr raw, string name, out string addr);
+ [DllImport("camel-1.2")] static extern int camel_internet_address_find_address(IntPtr raw, string addr, out string name);
+ [DllImport("camel-1.2")] static extern string camel_internet_address_encode_address(out int len, string name, string addr);
+ [DllImport("camel-1.2")] static extern string camel_internet_address_format_address(string name, string addr);
+
+ public InternetAddress() : base((IntPtr)0) {
+ cobject = camel_internet_address_new();
+ toCamel(this);
+ }
+
+ public void add(string name, string addr) {
+ camel_internet_address_add(cobject, name, addr);
+ }
+
+ public bool get(out string name, out string addr) {
+ name = null;
+ addr = null;
+ return camel_internet_address_get(cobject, out name, out addr);
+ }
+
+ // this is a weird arsed interface ...
+ public int findName(string name, out string addr) {
+ addr = null;
+ // FIXME: addr is const, need to marshal to local
+ return camel_internet_address_find_name(cobject, name, out addr);
+ }
+
+ public int findAddress(string addr, out string name) {
+ name = null;
+ return camel_internet_address_find_name(cobject, addr, out name);
+ }
+
+ public static string encode(string name, string addr) {
+ int len = 0;
+ // another weird-arsed interface
+ return camel_internet_address_encode_address(out len, name, addr);
+ }
+
+ public static string format(string name, string addr) {
+ return camel_internet_address_format_address(name, addr);
+ }
+ }
+
+ public class ContentType {
+ public IntPtr cobject;
+
+ public ContentType(IntPtr raw) {
+ cobject = raw;
+ }
+
+ [DllImport("camel-1.2")] static extern bool camel_content_type_is(IntPtr raw, string type, string subtype);
+
+ ~ContentType() {
+ }
+
+ public bool isType(string type, string subtype) {
+ return camel_content_type_is(cobject, type, subtype);
+ }
+ }
+
+ public class MessageInfo {
+ public IntPtr cobject;
+ private Tags user_tags;
+ private Flags user_flags;
+
+ private enum Type {
+ SUBJECT,
+ FROM,
+ TO,
+ CC,
+ MLIST,
+
+ FLAGS,
+ SIZE,
+
+ DATE_SENT,
+ DATE_RECEIVED,
+
+ MESSAGE_ID,
+ REFERENCES,
+
+ USER_FLAGS,
+ USER_TAGS,
+
+ LAST,
+ }
+
+ public class Tags {
+ private MessageInfo mi;
+
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_message_info_user_tag(IntPtr mi, String name);
+ [DllImport("camel-provider-1.2")] static extern bool camel_message_info_set_user_tag(IntPtr mi, String name, String value);
+
+ public Tags(MessageInfo raw) {
+ mi = raw;
+ }
+
+ public String this [String tag] {
+ get {
+ return Marshal.PtrToStringAnsi(camel_message_info_user_tag(mi.cobject, tag));
+ }
+ set {
+ camel_message_info_set_user_tag(mi.cobject, tag, value);
+ }
+ }
+ }
+
+ public class Flags {
+ private MessageInfo mi;
+
+ [DllImport("camel-provider-1.2")] static extern bool camel_message_info_user_flag(IntPtr miptr, String name);
+ [DllImport("camel-provider-1.2")] static extern bool camel_message_info_set_user_flag(IntPtr miptr, String name, bool value);
+
+ // note raw is a pointer to a pointer of tags
+ public Flags(MessageInfo raw) {
+ mi = raw;
+ }
+
+ public bool this [String tag] {
+ get {
+ return camel_message_info_user_flag(mi.cobject, tag);
+ }
+ set {
+ camel_message_info_set_user_flag(mi.cobject, tag, value);
+ }
+ }
+ }
+
+ // only used to calculate offsets
+ private struct CamelMessageInfo {
+ IntPtr summary;
+ uint refcount;
+ string uid;
+ };
+
+ public MessageInfo(IntPtr raw) {
+ cobject = raw;
+ }
+
+ [DllImport("camel-provider-1.2")] static extern void camel_folder_free_message_info(IntPtr raw, IntPtr info);
+ [DllImport("camel-provider-1.2")] static extern void camel_message_info_free(IntPtr info);
+
+ ~MessageInfo() {
+ camel_message_info_free(cobject);
+ }
+
+ [DllImport("camel-provider-1.2")] static extern IntPtr camel_message_info_ptr(IntPtr raw, int type);
+ [DllImport("camel-provider-1.2")] static extern uint camel_message_info_uint32(IntPtr raw, int type);
+ [DllImport("camel-provider-1.2")] static extern uint camel_message_info_time(IntPtr raw, int type);
+
+ public String uid { get { return Marshal.PtrToStringAuto(Marshal.ReadIntPtr(cobject, (int)Marshal.OffsetOf(typeof(CamelMessageInfo), "uid"))); } }
+
+ public String subject { get { return Marshal.PtrToStringAnsi(camel_message_info_ptr(cobject, (int)Type.SUBJECT)); } }
+ public String from { get { return Marshal.PtrToStringAnsi(camel_message_info_ptr(cobject, (int)Type.FROM)); } }
+ public String to { get { return Marshal.PtrToStringAnsi(camel_message_info_ptr(cobject, (int)Type.TO)); } }
+ public String cc { get { return Marshal.PtrToStringAnsi(camel_message_info_ptr(cobject, (int)Type.CC)); } }
+ public String mlist { get { return Marshal.PtrToStringAnsi(camel_message_info_ptr(cobject, (int)Type.MLIST)); } }
+
+ public uint flags { get { return camel_message_info_uint32(cobject, (int)Type.FLAGS); } }
+ public uint size { get { return camel_message_info_uint32(cobject, (int)Type.SIZE); } }
+
+ public Tags userTags {
+ get {
+ if (user_tags == null)
+ user_tags = new Tags(this);
+ return user_tags;
+ }
+ }
+
+ public Flags userFlags {
+ get {
+ if (user_flags == null)
+ user_flags = new Flags(this);
+ return user_flags;
+ }
+ }
+ }
+
+ public class URL {
+ public IntPtr cobject;
+ internal Params param_list;
+
+ // we never instantiate this, we just use it to describe the layout
+ internal struct CamelURL {
+ internal IntPtr protocol;
+ internal IntPtr user;
+ internal IntPtr authmech;
+ internal IntPtr passwd;
+ internal IntPtr host;
+ internal int port;
+ internal IntPtr path;
+ internal IntPtr pparams;
+ internal IntPtr query;
+ internal IntPtr fragment;
+ };
+
+ public class Params {
+ private URL parent;
+
+ internal Params(URL _parent) {
+ parent = _parent;
+ }
+
+ public string this[string name] {
+ set { camel_url_set_param(parent.cobject, name, value); }
+ get { return Marshal.PtrToStringAnsi(camel_url_get_param(parent.cobject, name)); }
+ }
+ }
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_url_new_with_base(IntPtr bbase, string url);
+ [DllImport("camel-1.2")] static extern IntPtr camel_url_new(string url, ref CamelException ex);
+ [DllImport("camel-1.2")] static extern string camel_url_to_string(IntPtr url, int flags);
+ [DllImport("camel-1.2")] static extern void camel_url_free(IntPtr url);
+
+ // this is a shit to wrap, needs accessors or other pain
+ [DllImport("camel-1.2")] static extern void camel_url_set_protocol(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_user(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_authmech(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_passwd(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_host(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_port(IntPtr url, int p);
+ [DllImport("camel-1.2")] static extern void camel_url_set_path(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_param(IntPtr url, string s, string v);
+ [DllImport("camel-1.2")] static extern void camel_url_set_query(IntPtr url, string s);
+ [DllImport("camel-1.2")] static extern void camel_url_set_fragment(IntPtr url, string s);
+
+ [DllImport("camel-1.2")] static extern IntPtr camel_url_get_param(IntPtr url, string s);
+
+ [DllImport("camel-1.2")] static extern string camel_url_encode(string url, string escape);
+ // ugh we can't do this, it writes to its result??
+ // -> use StringBuilder
+ [DllImport("camel-1.2")] static extern IntPtr camel_url_decode(ref string url);
+
+ public URL(string uri) {
+ CamelException ex = new CamelException();
+
+ cobject = camel_url_new(uri, ref ex);
+ if (ex.id != 0)
+ throw new Exception(ex);
+ }
+
+ public URL(URL bbase, string uri) {
+ cobject = camel_url_new_with_base(bbase.cobject, uri);
+ }
+
+ ~URL() {
+ camel_url_free(cobject);
+ }
+
+ /* its ugly but it works */
+ private string field(string name) {
+ return Marshal.PtrToStringAuto(Marshal.ReadIntPtr(cobject, (int)Marshal.OffsetOf(typeof(CamelURL), name)));
+ }
+
+ public string protocol {
+ set { camel_url_set_protocol(cobject, value); }
+ get { return field("protocol"); }
+ }
+
+ public string user {
+ set { camel_url_set_user(cobject, value); }
+ get { return field("user"); }
+ }
+
+ public string authmech {
+ set { camel_url_set_authmech(cobject, value); }
+ get { return field("authmech"); }
+ }
+
+ public string passwd {
+ set { camel_url_set_passwd(cobject, value); }
+ get { return field("passwd"); }
+ }
+
+ public string host {
+ set { camel_url_set_host(cobject, value); }
+ get { return field("host"); }
+ }
+
+ public int port {
+ set { camel_url_set_port(cobject, value); }
+ get { return (int)Marshal.ReadIntPtr(cobject, (int)Marshal.OffsetOf(typeof(CamelURL), "port")); }
+ }
+
+ public string path {
+ set { camel_url_set_path(cobject, value); }
+ get { return field("path"); }
+ }
+
+ public string query {
+ set { camel_url_set_query(cobject, value); }
+ get { return field("query"); }
+ }
+
+ public string fragment {
+ set { camel_url_set_fragment(cobject, value); }
+ get { return field("fragment"); }
+ }
+
+ public Params paramlist {
+ get {
+ if (param_list == null)
+ param_list = new Params(this);
+ return param_list;
+ }
+ }
+
+ public override string ToString() {
+ return camel_url_to_string(cobject, 0);
+ }
+
+ public static string encode(string val) {
+ return camel_url_encode(val, null);
+ }
+
+ public static string encode(string val, string escape) {
+ return camel_url_encode(val, escape);
+ }
+ }
+}
+
+namespace Camel.Hash {
+ public class Stream : System.IO.Stream {
+ protected Camel.Stream substream;
+
+ public Stream(Camel.Stream sub) {
+ substream = sub;
+ }
+
+ public override bool CanSeek { get { return false; } }
+ public override bool CanRead { get { return true; } }
+ public override bool CanWrite { get { return true; } }
+ public override long Length {
+ get {
+ throw new System.IO.IOException("Cannot get stream length");
+ }
+ }
+ public override long Position {
+ get {
+ throw new System.IO.IOException("Cannot get stream position");
+ }
+ set {
+ if (value == 0) {
+ substream.reset();
+ } else {
+ throw new System.IO.IOException("Cannot set stream position");
+ }
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count) {
+ // FIXME: how to add the offset to the buffer?
+ return substream.read(buffer, count);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count) {
+ // FIXME: how to add the offset to the buffer?
+ substream.write(buffer, count);
+ }
+
+ public override void Flush() {
+ substream.flush();
+ }
+
+ public override long Seek(long offset, System.IO.SeekOrigin seek) {
+ throw new System.IO.IOException("Seeking not supported");
+ }
+
+ public override void SetLength(long len) {
+ throw new System.IO.IOException("Cannot set stream length");
+ }
+ }
+}
+
+/*
+namespace Evolution.Mail {
+ class Component : GLib.Object {
+ public Component(IntPtr raw) : base(raw) {}
+ public Component() : base() {}
+
+ ~Component() {
+ Dispose();
+ }
+
+ [DllImport("libevolution-mail.so")] static extern IntPtr mail_component_peek();
+ [DllImport("libevolution-mail.so")] static extern IntPtr mail_component_peek_base_directory(IntPtr component);
+ [DllImport("libevolution-mail.so")] static extern IntPtr mail_component_peek();
+
+ public static Component peek() {
+ return new Component(mail_component_peek());
+ }
+
+ public String baseDirectory {
+ get {}
+ }
+}
+*/
diff --git a/modules/plugin-mono/Evolution.cs b/modules/plugin-mono/Evolution.cs
new file mode 100644
index 0000000000..0db54405b3
--- /dev/null
+++ b/modules/plugin-mono/Evolution.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Reflection;
+
+using Camel;
+
+namespace Evolution {
+ [StructLayout (LayoutKind.Sequential)]
+ public class PopupTarget {
+ public IntPtr popup;
+ public IntPtr widget;
+ public int type;
+ public int mask;
+ };
+
+ [StructLayout (LayoutKind.Sequential)]
+ public class MenuTarget {
+ public IntPtr menu;
+ public IntPtr widget;
+ public int type;
+ public int mask;
+ };
+
+ [StructLayout (LayoutKind.Sequential)]
+ public class EventTarget {
+ public IntPtr aevent;
+ public int type;
+ public int mask;
+ };
+};
+
+namespace Evolution {
+ public class Error {
+ // can we marshal varags from c#?
+ [DllImport("eutil")] static extern int e_error_run(IntPtr parent, string tag, IntPtr end);
+ [DllImport("eutil")] static extern int e_error_run(IntPtr parent, string tag, string arg0, IntPtr end);
+ [DllImport("eutil")] static extern int e_error_run(IntPtr parent, string tag, string arg0, string arg1, IntPtr end);
+ [DllImport("eutil")] static extern int e_error_run(IntPtr parent, string tag, string arg0, string arg1, string arg2, IntPtr end);
+
+ public static int run(IntPtr parent, string tag) {
+ return e_error_run(parent, tag, (IntPtr)0);
+ }
+ public static int run(IntPtr parent, string tag, string arg0) {
+ return e_error_run(parent, tag, arg0, (IntPtr)0);
+ }
+ public static int run(IntPtr parent, string tag, string arg0, string arg1) {
+ return e_error_run(parent, tag, arg0, arg1, (IntPtr)0);
+ }
+ public static int run(IntPtr parent, string tag, string arg0, string arg1, string arg2) {
+ return e_error_run(parent, tag, arg0, arg1, arg2, (IntPtr)0);
+ }
+ }
+}
+
+namespace Evolution.Mail {
+ /* ********************************************************************** */
+ [StructLayout (LayoutKind.Sequential)]
+ public class PopupTargetSelect : PopupTarget {
+ public IntPtr _folder;
+ public string uri;
+ public IntPtr _uids;
+
+ public static PopupTargetSelect get(IntPtr o) {
+ return (PopupTargetSelect)Marshal.PtrToStructure(o, typeof(PopupTargetSelect));
+ }
+
+ public Camel.Folder folder {
+ get { return (Camel.Folder)Camel.Object.fromCamel(_folder); }
+ }
+
+ public string [] uids {
+ get { return Camel.Util.getUIDArray(_uids); }
+ }
+ }
+
+ [StructLayout (LayoutKind.Sequential)]
+ public class PopupTargetURI : Evolution.PopupTarget {
+ public string uri;
+
+ public static PopupTargetURI get(IntPtr o) {
+ return (PopupTargetURI)Marshal.PtrToStructure(o, typeof(PopupTargetURI));
+ }
+ }
+
+ [StructLayout (LayoutKind.Sequential)]
+ public class PopupTargetPart : PopupTarget {
+ public string mimeType;
+ public IntPtr _part;
+
+ public static PopupTargetPart get(IntPtr o) {
+ return (PopupTargetPart)Marshal.PtrToStructure(o, typeof(PopupTargetPart));
+ }
+
+ public Camel.Object part {
+ get { return (Camel.Object)Camel.Object.fromCamel(_part); }
+ }
+ }
+
+ [StructLayout (LayoutKind.Sequential)]
+ public struct PopupTargetFolder {
+ public Evolution.PopupTarget target;
+ public string uri;
+
+ public static PopupTargetFolder get(IntPtr o) {
+ return (PopupTargetFolder)Marshal.PtrToStructure(o, typeof(PopupTargetFolder));
+ }
+ }
+
+ /* ********************************************************************** */
+ [StructLayout (LayoutKind.Sequential)]
+ public class MenuTargetSelect : MenuTarget {
+ public IntPtr _folder;
+ public string uri;
+ public IntPtr _uids;
+
+ public static MenuTargetSelect get(IntPtr o) {
+ return (MenuTargetSelect)Marshal.PtrToStructure(o, typeof(MenuTargetSelect));
+ }
+
+ public Camel.Folder folder {
+ get { return (Camel.Folder)Camel.Object.fromCamel(_folder); }
+ }
+
+ public string [] uids {
+ get { return Camel.Util.getUIDArray(_uids); }
+ }
+ }
+
+ /* ********************************************************************** */
+ [StructLayout (LayoutKind.Sequential)]
+ public class EventTargetFolder : EventTarget {
+ public string uri;
+
+ public static EventTargetFolder get(IntPtr o) {
+ return (EventTargetFolder)Marshal.PtrToStructure(o, typeof(EventTargetFolder));
+ }
+ }
+
+ [StructLayout (LayoutKind.Sequential)]
+ public class EventTargetMessage : EventTarget {
+ public IntPtr _folder;
+ public string uid;
+ public IntPtr _message;
+
+ public static EventTargetMessage get(IntPtr o) {
+ return (EventTargetMessage)Marshal.PtrToStructure(o, typeof(EventTargetMessage));
+ }
+
+ public Camel.Folder folder {
+ get { return (Camel.Folder)Camel.Object.fromCamel(_folder); }
+ }
+
+ public Camel.MimeMessage message {
+ get { return (Camel.MimeMessage)Camel.Object.fromCamel(_message); }
+ }
+
+ }
+};
diff --git a/modules/plugin-mono/Makefile.am b/modules/plugin-mono/Makefile.am
new file mode 100644
index 0000000000..f4f436ae66
--- /dev/null
+++ b/modules/plugin-mono/Makefile.am
@@ -0,0 +1,22 @@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"evolution-plugin-mono\" \
+ -I$(top_srcdir) \
+ $(E_UTIL_CFLAGS) \
+ $(MONO_CFLAGS)
+
+module_LTLIBRARIES = libevolution-module-plugin-mono.la
+
+libevolution_module_plugin_mono_la_SOURCES = \
+ evolution-module-plugin-mono.c \
+ e-plugin-mono.c \
+ e-plugin-mono.h
+
+libevolution_module_plugin_mono_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(E_UTIL_LIBS) \
+ $(MONO_LIBS)
+
+libevolution_module_plugin_mono_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/plugin-mono/e-plugin-mono.c b/modules/plugin-mono/e-plugin-mono.c
new file mode 100644
index 0000000000..1c43fb9d56
--- /dev/null
+++ b/modules/plugin-mono/e-plugin-mono.c
@@ -0,0 +1,261 @@
+/*
+ * e-plugin-mono.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-plugin-mono.h"
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "e-plugin-mono.h"
+
+#include <mono/metadata/debug-helpers.h>
+#include <mono/metadata/object.h>
+#include <mono/metadata/appdomain.h>
+#include <mono/metadata/assembly.h>
+#include <mono/metadata/threads.h>
+#include <mono/metadata/mono-config.h>
+#include <mono/jit/jit.h>
+
+#define E_PLUGIN_MONO_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PLUGIN_MONO, EPluginMonoPrivate))
+
+struct _EPluginMonoPrivate {
+ MonoAssembly *assembly;
+ MonoClass *class;
+ MonoObject *plugin;
+ GHashTable *methods;
+};
+
+static MonoDomain *domain;
+static gpointer parent_class;
+static GType plugin_mono_type;
+
+static gchar *
+get_xml_prop (xmlNodePtr node, const gchar *id)
+{
+ xmlChar *prop;
+ gchar *out = NULL;
+
+ prop = xmlGetProp (node, (xmlChar *) id);
+
+ if (prop != NULL) {
+ out = g_strdup ((gchar *) prop);
+ xmlFree (prop);
+ }
+
+ return out;
+}
+
+static void
+plugin_mono_finalize (GObject *object)
+{
+ EPluginMono *plugin_mono;
+
+ plugin_mono = E_PLUGIN_MONO (object);
+
+ g_free (plugin_mono->location);
+ g_free (plugin_mono->handler);
+
+ g_hash_table_destroy (plugin_mono->priv->methods);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint
+plugin_mono_construct (EPlugin *plugin, xmlNodePtr root)
+{
+ EPluginMono *plugin_mono;
+
+ /* Chain up to parent's construct() method. */
+ if (E_PLUGIN_CLASS (parent_class)->construct (plugin, root) == -1)
+ return -1;
+
+ plugin_mono = E_PLUGIN_MONO (plugin);
+ plugin_mono->location = get_xml_prop (root, "location");
+ plugin_mono->handler = get_xml_prop (root, "handler");
+
+ return (plugin_mono->location != NULL) ? 0 : -1;
+}
+
+/*
+ Two approaches:
+ You can have a Evolution.Plugin implementation which has every
+ callback as methods on it. Or you can just use static methods
+ for everything.
+
+ All methods take a single (structured) argument.
+*/
+
+static gpointer
+plugin_mono_invoke (EPlugin *plugin,
+ const gchar *name,
+ gpointer data)
+{
+ EPluginMono *plugin_mono;
+ EPluginMonoPrivate *priv;
+ MonoMethodDesc *d;
+ MonoMethod *m;
+ MonoObject *x = NULL, *res;
+ gpointer *params;
+
+ plugin_mono = E_PLUGIN_MONO (plugin);
+ priv = plugin_mono->priv;
+
+ /* we need to do this every time since we may be called from any thread for some uses */
+ mono_thread_attach (domain);
+
+ if (priv->assembly == NULL) {
+ priv->assembly = mono_domain_assembly_open (
+ domain, plugin_mono->location);
+ if (priv->assembly == NULL) {
+ g_warning (
+ "Can't load assembly '%s'",
+ plugin_mono->location);
+ return NULL;
+ }
+
+ if (plugin_mono->handler == NULL
+ || (priv->class = mono_class_from_name (mono_assembly_get_image (priv->assembly), "", plugin_mono->handler)) == NULL) {
+ } else {
+ priv->plugin = mono_object_new (domain, priv->class);
+ /* could conceivably init with some context too */
+ mono_runtime_object_init (priv->plugin);
+ }
+ }
+
+ m = g_hash_table_lookup (priv->methods, name);
+ if (m == NULL) {
+ if (priv->class) {
+ /* class method */
+ MonoMethod* mono_method;
+ gpointer iter = NULL;
+
+ d = mono_method_desc_new (name, FALSE);
+ /*if (d == NULL) {
+ g_warning ("Can't create method descriptor for '%s'", name);
+ return NULL;
+ }*/
+
+ while ((mono_method = mono_class_get_methods (priv->class, &iter))) {
+ g_print ("\n\a Method name is : <%s>\n\a", mono_method_get_name (mono_method));
+ }
+//mono_class_get_method_from_name
+ m = mono_class_get_method_from_name (priv->class, name, -1);
+ if (m == NULL) {
+ g_warning ("Can't find method callback '%s'", name);
+ return NULL;
+ }
+ } else {
+ /* static method */
+ d = mono_method_desc_new (name, FALSE);
+ if (d == NULL) {
+ g_warning ("Can't create method descriptor for '%s'", name);
+ return NULL;
+ }
+
+ m = mono_method_desc_search_in_image (d, mono_assembly_get_image (priv->assembly));
+ if (m == NULL) {
+ g_warning ("Can't find method callback '%s'", name);
+ return NULL;
+ }
+ }
+
+ g_hash_table_insert (priv->methods, g_strdup (name), m);
+ }
+
+ params = g_malloc0(sizeof (*params)*1);
+ params[0] = &data;
+ res = mono_runtime_invoke (m, priv->plugin, params, &x);
+ /* do i need to free params?? */
+
+ if (x)
+ mono_print_unhandled_exception (x);
+
+ if (res) {
+ gpointer *p = mono_object_unbox (res);
+ return *p;
+ } else
+ return NULL;
+}
+
+static void
+plugin_mono_class_init (EPluginMonoClass *class)
+{
+ GObjectClass *object_class;
+ EPluginClass *plugin_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EPluginMonoPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = plugin_mono_finalize;
+
+ plugin_class = E_PLUGIN_CLASS (class);
+ plugin_class->construct = plugin_mono_construct;
+ plugin_class->invoke = plugin_mono_invoke;
+ plugin_class->type = "mono";
+}
+
+static void
+plugin_mono_init (EPluginMono *plugin_mono)
+{
+ GHashTable *methods;
+
+ methods = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) NULL);
+
+ plugin_mono->priv = E_PLUGIN_MONO_GET_PRIVATE (plugin_mono);
+ plugin_mono->priv->methods = methods;
+}
+
+GType
+e_plugin_mono_get_type (void)
+{
+ return plugin_mono_type;
+}
+
+void
+e_plugin_mono_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EPluginMonoClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) plugin_mono_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EPluginMono),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) plugin_mono_init,
+ NULL /* value_table */
+ };
+
+ plugin_mono_type = g_type_module_register_type (
+ type_module, E_TYPE_PLUGIN,
+ "EPluginMono", &type_info, 0);
+
+ domain = mono_jit_init ("Evolution");
+ mono_thread_attach (domain);
+}
diff --git a/modules/plugin-mono/e-plugin-mono.h b/modules/plugin-mono/e-plugin-mono.h
new file mode 100644
index 0000000000..a4ed2d42ec
--- /dev/null
+++ b/modules/plugin-mono/e-plugin-mono.h
@@ -0,0 +1,69 @@
+/*
+ * e-plugin-mono.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_PLUGIN_MONO_H
+#define E_PLUGIN_MONO_H
+
+#include <e-util/e-plugin.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PLUGIN_MONO \
+ (e_plugin_mono_get_type ())
+#define E_PLUGIN_MONO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_PLUGIN_MONO, EPluginMono))
+#define E_PLUGIN_MONO_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_PLUGIN_MONO, EPluginMonoClass))
+#define E_IS_PLUGIN_MONO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_PLUGIN_MONO))
+#define E_IS_PLUGIN_MONO_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_PLUGIN_MONO))
+#define E_PLUGIN_MONO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_PLUGIN_MONO, EPluginMonoClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPluginMono EPluginMono;
+typedef struct _EPluginMonoClass EPluginMonoClass;
+typedef struct _EPluginMonoPrivate EPluginMonoPrivate;
+
+struct _EPluginMono {
+ EPlugin parent;
+ EPluginMonoPrivate *priv;
+
+ gchar *location;
+ gchar *handler;
+};
+
+struct _EPluginMonoClass {
+ EPluginClass parent_class;
+};
+
+GType e_plugin_mono_get_type (void);
+void e_plugin_mono_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_PLUGIN_MONO_H */
diff --git a/modules/plugin-mono/evolution-module-plugin-mono.c b/modules/plugin-mono/evolution-module-plugin-mono.c
new file mode 100644
index 0000000000..75ad534a47
--- /dev/null
+++ b/modules/plugin-mono/evolution-module-plugin-mono.c
@@ -0,0 +1,41 @@
+/*
+ * evolution-module-plugin-mono.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 <config.h>
+
+#include "e-plugin-mono.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_plugin_mono_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/plugin-python/Makefile.am b/modules/plugin-python/Makefile.am
new file mode 100644
index 0000000000..e6a32c3c83
--- /dev/null
+++ b/modules/plugin-python/Makefile.am
@@ -0,0 +1,31 @@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"evolution-plugin-python\" \
+ -I$(top_srcdir) \
+ $(E_UTIL_CFLAGS) \
+ $(PY_INCLUDES)
+
+module_LTLIBRARIES = libevolution-module-plugin-python.la
+
+libevolution_module_plugin_python_la_SOURCES = \
+ evolution-module-plugin-python.c \
+ e-plugin-python.c \
+ e-plugin-python.h
+
+libevolution_module_plugin_python_la_LIBADD = \
+ -lpthread -ldl -lutil -lm \
+ $(top_builddir)/e-util/libeutil.la \
+ $(E_UTIL_LIBS) \
+ $(PY_LIBS)
+
+libevolution_module_plugin_python_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+example_sources = \
+ example/hello_python.py \
+ example/org-gnome-hello-python-ui.xml \
+ example/org-gnome-hello-python.eplug.xml \
+ example/Makefile.am
+
+EXTRA_DIST = $(example_sources)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/plugin-python/e-plugin-python.c b/modules/plugin-python/e-plugin-python.c
new file mode 100644
index 0000000000..747ba57bac
--- /dev/null
+++ b/modules/plugin-python/e-plugin-python.c
@@ -0,0 +1,230 @@
+/*
+ * e-plugin-python.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 <Python.h> first to avoid:
+ * warning: "_POSIX_C_SOURCE" redefined */
+#include <Python.h>
+
+#include "e-plugin-python.h"
+
+#include <sys/types.h>
+#include <string.h>
+
+#define E_PLUGIN_PYTHON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PLUGIN_PYTHON, EPluginPythonPrivate))
+
+struct _EPluginPythonPrivate {
+ PyObject *pModule;
+ PyObject *pClass;
+ PyObject *pFunc;
+ PyObject *pDict;
+ GHashTable *methods;
+};
+
+static gpointer parent_class;
+static GType plugin_python_type;
+
+static gchar *
+get_xml_prop (xmlNodePtr node, const gchar *id)
+{
+ xmlChar *prop;
+ gchar *out = NULL;
+
+ prop = xmlGetProp (node, (xmlChar *) id);
+
+ if (prop != NULL) {
+ out = g_strdup ((gchar *) prop);
+ xmlFree (prop);
+ }
+
+ return out;
+}
+
+static void
+plugin_python_finalize (GObject *object)
+{
+ EPluginPython *plugin_python;
+
+ plugin_python = E_PLUGIN_PYTHON (object);
+
+ g_free (plugin_python->location);
+ g_free (plugin_python->module_name);
+ g_free (plugin_python->pClass);
+
+ g_hash_table_destroy (plugin_python->priv->methods);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint
+plugin_python_construct (EPlugin *plugin, xmlNodePtr root)
+{
+ EPluginPython *plugin_python;
+
+ /* Chain up to parent's construct() method. */
+ if (E_PLUGIN_CLASS (parent_class)->construct (plugin, root) == -1)
+ return -1;
+
+ plugin_python = E_PLUGIN_PYTHON (plugin);
+ plugin_python->location = get_xml_prop (root, "location");
+ plugin_python->module_name = get_xml_prop (root, "module_name");
+ plugin_python->pClass = get_xml_prop (root, "pClass");
+
+ return (plugin_python->location != NULL) ? 0 : -1;
+}
+
+static gpointer
+plugin_python_invoke (EPlugin *plugin,
+ const gchar *name,
+ gpointer data)
+{
+ EPluginPython *plugin_python;
+ EPluginPythonPrivate *priv;
+ PyObject *pModuleName, *pFunc;
+ PyObject *pInstance, *pValue = NULL;
+
+ plugin_python = E_PLUGIN_PYTHON (plugin);
+ priv = plugin_python->priv;
+
+ /* We need to do this every time since we may be called
+ * from any thread for some uses. */
+ Py_Initialize ();
+
+ if (priv->pModule == NULL) {
+ gchar *string;
+
+ pModuleName = PyString_FromString (plugin_python->module_name);
+
+ string = g_strdup_printf (
+ "import sys; "
+ "sys.path.insert(0, '%s')",
+ plugin_python->location);
+ PyRun_SimpleString (string);
+ g_free (string);
+
+ priv->pModule = PyImport_Import (pModuleName);
+
+ Py_DECREF (pModuleName); //Free
+
+ if (priv->pModule == NULL) {
+ PyErr_Print ();
+ g_warning (
+ "Can't load python module '%s'",
+ plugin_python->location);
+ return NULL;
+ }
+
+ priv->pDict = PyModule_GetDict (priv->pModule);
+
+ if (plugin_python->pClass != NULL) {
+ priv->pClass = PyDict_GetItemString (
+ priv->pDict, plugin_python->pClass);
+ }
+ }
+
+ if (priv->pClass) {
+
+ if (PyCallable_Check (priv->pClass))
+ pInstance = PyObject_CallObject (priv->pClass, NULL);
+
+ pValue = PyObject_CallMethod (pInstance, (gchar *) name, NULL);
+
+ } else {
+
+ pFunc = PyDict_GetItemString (priv->pDict, name);
+
+ if (pFunc && PyCallable_Check (pFunc))
+ pValue = PyObject_CallObject (pFunc, NULL);
+ else
+ PyErr_Print ();
+ }
+
+ if (pValue) {
+ Py_DECREF(pValue);
+ /* Fixme */
+ return NULL;
+ } else
+ return NULL;
+}
+
+static void
+plugin_python_class_init (EPluginPythonClass *class)
+{
+ GObjectClass *object_class;
+ EPluginClass *plugin_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EPluginPythonPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = plugin_python_finalize;
+
+ plugin_class = E_PLUGIN_CLASS (class);
+ plugin_class->construct = plugin_python_construct;
+ plugin_class->invoke = plugin_python_invoke;
+ plugin_class->type = "python";
+}
+
+static void
+plugin_python_init (EPluginPython *plugin_python)
+{
+ GHashTable *methods;
+
+ methods = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) NULL);
+
+ plugin_python->priv = E_PLUGIN_PYTHON_GET_PRIVATE (plugin_python);
+ plugin_python->priv->methods = methods;
+}
+
+GType
+e_plugin_python_get_type (void)
+{
+ return plugin_python_type;
+}
+
+void
+e_plugin_python_register_type (GTypeModule *type_module)
+{
+ static const GTypeInfo type_info = {
+ sizeof (EPluginPythonClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) plugin_python_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EPluginPython),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) plugin_python_init,
+ NULL /* value_table */
+ };
+
+ plugin_python_type = g_type_module_register_type (
+ type_module, E_TYPE_PLUGIN,
+ "EPluginPython", &type_info, 0);
+
+ /* TODO Does this mean I can cache the instance of pyobjects? */
+ Py_Initialize ();
+}
diff --git a/modules/plugin-python/e-plugin-python.h b/modules/plugin-python/e-plugin-python.h
new file mode 100644
index 0000000000..9ee780c76c
--- /dev/null
+++ b/modules/plugin-python/e-plugin-python.h
@@ -0,0 +1,70 @@
+/*
+ * e-plugin-python.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_PLUGIN_PYTHON_H
+#define E_PLUGIN_PYTHON_H
+
+#include <e-util/e-plugin.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PLUGIN_PYTHON \
+ (e_plugin_python_get_type ())
+#define E_PLUGIN_PYTHON(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_PLUGIN_PYTHON, EPluginPython))
+#define E_PLUGIN_PYTHON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_PLUGIN_PYTHON, EPluginPythonClass))
+#define E_IS_PLUGIN_PYTHON(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_PLUGIN_PYTHON))
+#define E_IS_PLUGIN_PYTHON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_PLUGIN_PYTHON))
+#define E_PLUGIN_PYTHON_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_PLUGIN_PYTHON, EPluginPythonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPluginPython EPluginPython;
+typedef struct _EPluginPythonClass EPluginPythonClass;
+typedef struct _EPluginPythonPrivate EPluginPythonPrivate;
+
+struct _EPluginPython {
+ EPlugin parent;
+ EPluginPythonPrivate *priv;
+
+ gchar *location;
+ gchar *pClass;
+ gchar *module_name;
+};
+
+struct _EPluginPythonClass {
+ EPluginClass parent_class;
+};
+
+GType e_plugin_python_get_type (void);
+void e_plugin_python_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_PLUGIN_PYTHON_H */
diff --git a/modules/plugin-python/evolution-module-plugin-python.c b/modules/plugin-python/evolution-module-plugin-python.c
new file mode 100644
index 0000000000..84ab3b3e8c
--- /dev/null
+++ b/modules/plugin-python/evolution-module-plugin-python.c
@@ -0,0 +1,41 @@
+/*
+ * evolution-module-plugin-python.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 <config.h>
+
+#include "e-plugin-python.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_plugin_python_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/plugin-python/example/Makefile.am b/modules/plugin-python/example/Makefile.am
new file mode 100644
index 0000000000..cc14dc94d9
--- /dev/null
+++ b/modules/plugin-python/example/Makefile.am
@@ -0,0 +1,29 @@
+AM_CPPFLAGS = \
+ -DEVOLUTION_GLADEDIR=\""$(gladedir)"\" \
+ -DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"
+
+@EVO_PLUGIN_RULE@
+
+plugin_DATA = \
+ hello_python.py \
+ org-gnome-hello-python-ui.xml \
+ org-gnome-hello-python.eplug
+
+liborg_gnome_py_plug_test_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
+
+errordir = $(privdatadir)/errors
+
+BUILDME = org-gnome-hello-python.eplug \
+$(error_i18n)
+
+BUILT_SOURCES = \
+ $(BUILDME)
+
+EXTRA_DIST = \
+ hello_python.py \
+ org-gnome-hello-python-ui.xml \
+ org-gnome-hello-python.eplug.xml
+
+CLEANFILES = $(BUILT_SOURCES)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/plugin-python/example/hello_python.py b/modules/plugin-python/example/hello_python.py
new file mode 100644
index 0000000000..16dc2a12f8
--- /dev/null
+++ b/modules/plugin-python/example/hello_python.py
@@ -0,0 +1,5 @@
+'''hello_python.py - Python source designed to '''
+'''demonstrate the use of python Eplugins'''
+
+def say_hello():
+ print 'Hello ! From python'
diff --git a/modules/plugin-python/example/org-gnome-hello-python-ui.xml b/modules/plugin-python/example/org-gnome-hello-python-ui.xml
new file mode 100644
index 0000000000..074960e84d
--- /dev/null
+++ b/modules/plugin-python/example/org-gnome-hello-python-ui.xml
@@ -0,0 +1,16 @@
+<Root>
+ <commands>
+ <cmd name="HelloPy" _label="Hello Python"
+ _tip="Python Plugin Loader tests"
+ />
+ </commands>
+
+ <menu>
+ <placeholder name="MessagePlaceholder">
+ <submenu name="Message">
+ <separator f="" name="sep"/>
+ <menuitem name="HelloPy" verb=""/>
+ </submenu>
+ </placeholder>
+ </menu>
+</Root>
diff --git a/modules/plugin-python/example/org-gnome-hello-python.eplug.xml b/modules/plugin-python/example/org-gnome-hello-python.eplug.xml
new file mode 100644
index 0000000000..8f77d5ba01
--- /dev/null
+++ b/modules/plugin-python/example/org-gnome-hello-python.eplug.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<e-plugin-list>
+ <e-plugin id="org.gnome.evolution.hello_python" type="python" _name="Python Test Plugin" location="@PLUGINDIR@" module_name="hello_python">
+
+ <author name="Johnny Jacob" email="jjohnny@novell.com"/>
+
+ <_description>
+ Test Plugin for Python EPlugin loader.
+ </_description>
+
+ <hook class="org.gnome.evolution.mail.bonobomenu:1.0">
+ <menu id="org.gnome.evolution.mail.browser" target="select">
+ <!-- the path to the bonobo menu description. Any UI items on Evolution should come here -->
+ <ui file="@PLUGINDIR@/org-gnome-hello-python-ui.xml"/>
+ <item type="item" verb="HelloPy" path="/commands/HelloPy" enable="one" activate="say_hello"/>
+ </menu>
+ </hook>
+
+ </e-plugin>
+</e-plugin-list>