diff options
Diffstat (limited to 'modules')
48 files changed, 12438 insertions, 1 deletions
diff --git a/modules/Makefile.am b/modules/Makefile.am index b26d161e36..16810fd55c 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -18,6 +18,14 @@ if ENABLE_ONLINE_ACCOUNTS ONLINE_ACCOUNTS_DIR = online-accounts endif +if ENABLE_TNEF +TNEF_ATTACHMENT_DIR = tnef-attachment +endif + +if ENABLE_AUDIO_INLINE +AUDIO_INLINE_DIR = audio-inline +endif + SUBDIRS = \ addressbook \ bogofilter \ @@ -36,17 +44,23 @@ SUBDIRS = \ cal-config-webcal \ composer-autosave \ imap-features \ + itip-formatter \ mail-config \ mailto-handler \ mdn \ offline-alert \ plugin-lib \ plugin-manager \ + prefer-plain \ spamassassin \ startup-wizard \ + text-highlight \ + vcard-inline \ web-inspector \ $(MONO_DIR) \ $(PYTHON_DIR) \ - $(ONLINE_ACCOUNTS_DIR) + $(ONLINE_ACCOUNTS_DIR) \ + $(TNEF_ATTACHMENT_DIR) \ + $(AUDIO_INLINE_DIR) -include $(top_srcdir)/git.mk diff --git a/modules/audio-inline/Makefile.am b/modules/audio-inline/Makefile.am new file mode 100644 index 0000000000..152086ba08 --- /dev/null +++ b/modules/audio-inline/Makefile.am @@ -0,0 +1,31 @@ +module_LTLIBRARIES = module-audio-inline.la + +module_audio_inline_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + -DG_LOG_DOMAIN=\"evolution-module-audio-inline\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) \ + $(GSTREAMER_CFLAGS) + +module_audio_inline_la_SOURCES = \ + e-mail-formatter-audio-inline.c \ + e-mail-formatter-audio-inline.h \ + e-mail-parser-audio-inline.c \ + e-mail-parser-audio-inline.h \ + evolution-module-audio-inline.c + +module_audio_inline_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/em-format/libemformat.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + $(GSTREAMER_LIBS) + +module_audio_inline_la_LDFLAGS = \ + -avoid-version -module $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/audio-inline/e-mail-formatter-audio-inline.c b/modules/audio-inline/e-mail-formatter-audio-inline.c new file mode 100644 index 0000000000..4116748923 --- /dev/null +++ b/modules/audio-inline/e-mail-formatter-audio-inline.c @@ -0,0 +1,383 @@ +/* + * e-mail-formatter-audio-inline.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/> + * + */ + +#include "e-mail-formatter-audio-inline.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n-lib.h> + +#include <libebackend/libebackend.h> + +#include <em-format/e-mail-formatter-extension.h> +#include <em-format/e-mail-formatter.h> + +#include "e-util/e-mktemp.h" + +#include <camel/camel.h> +#include <gst/gst.h> + +#include "e-mail-part-audio-inline.h" + +#define d(x) + +typedef struct _EMailFormatterAudioInline { + EExtension parent; +} EMailFormatterAudioInline; + +typedef struct _EMailFormatterAudioInlineClass { + EExtensionClass parent_class; +} EMailFormatterAudioInlineClass; + +GType e_mail_formatter_audio_inline_get_type (void); +static void e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface); +static void e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailFormatterAudioInline, + e_mail_formatter_audio_inline, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_formatter_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_FORMATTER_EXTENSION, + e_mail_formatter_formatter_extension_interface_init)); + +static const gchar* formatter_mime_types[] = { "application/vnd.evolution.widget.audio-inline", + "audio/ac3", "audio/x-ac3", + "audio/basic", "audio/mpeg", + "audio/x-mpeg", "audio/mpeg3", + "audio/x-mpeg3", "audio/mp3", + "audio/x-mp3", "audio/mp4", + "audio/flac", "audio/x-flac", + "audio/mod", "audio/x-mod", + "audio/x-wav", "audio/microsoft-wav", + "audio/x-wma", "audio/x-ms-wma", + "application/ogg", "application/x-ogg", + NULL }; + +static void +pause_clicked (GtkWidget *button, + EMailPartAudioInline *part) +{ + if (part->playbin) { + /* pause playing */ + gst_element_set_state (part->playbin, GST_STATE_PAUSED); + } +} + +static void +stop_clicked (GtkWidget *button, + EMailPartAudioInline *part) +{ + if (part->playbin) { + /* ready to play */ + gst_element_set_state (part->playbin, GST_STATE_READY); + part->target_state = GST_STATE_READY; + } +} + +static void +set_audiosink (GstElement *playbin) +{ + GstElement *audiosink; + + /* now it's time to get the audio sink */ + audiosink = gst_element_factory_make ("gconfaudiosink", "play_audio"); + if (audiosink == NULL) { + audiosink = gst_element_factory_make ("autoaudiosink", "play_audio"); + } + + if (audiosink) { + g_object_set (playbin, "audio-sink", audiosink, NULL); + } +} + +static gboolean +gst_callback (GstBus *bus, + GstMessage *message, + gpointer data) +{ + EMailPartAudioInline *part = data; + GstMessageType msg_type; + + g_return_val_if_fail (part != NULL, TRUE); + g_return_val_if_fail (part->playbin != NULL, TRUE); + + msg_type = GST_MESSAGE_TYPE (message); + + switch (msg_type) { + case GST_MESSAGE_ERROR: + gst_element_set_state (part->playbin, GST_STATE_NULL); + break; + case GST_MESSAGE_EOS: + gst_element_set_state (part->playbin, GST_STATE_READY); + break; + case GST_MESSAGE_STATE_CHANGED: + { + GstState old_state, new_state; + + if (GST_MESSAGE_SRC (message) != GST_OBJECT (part->playbin)) + break; + + gst_message_parse_state_changed (message, &old_state, &new_state, NULL); + + if (old_state == new_state) + break; + + if (part->play_button) + gtk_widget_set_sensitive ( + part->play_button, + new_state <= GST_STATE_PAUSED); + if (part->pause_button) + gtk_widget_set_sensitive ( + part->pause_button, + new_state > GST_STATE_PAUSED); + if (part->stop_button) + gtk_widget_set_sensitive ( + part->stop_button, + new_state >= GST_STATE_PAUSED); + } + + break; + default: + break; + } + + return TRUE; +} + +static void +play_clicked (GtkWidget *button, + EMailPartAudioInline *part) +{ + GstState cur_state; + + d(printf ("audio inline formatter: play\n")); + + if (!part->filename) { + CamelStream *stream; + CamelDataWrapper *data; + GError *error = NULL; + gint argc = 1; + const gchar *argv [] = { "org_gnome_audio_inline", NULL }; + + /* FIXME this is ugly, we should stream this directly to gstreamer */ + part->filename = e_mktemp ("org-gnome-audio-inline-file-XXXXXX"); + + d(printf ("audio inline formatter: write to temp file %s\n", po->filename)); + + stream = camel_stream_fs_new_with_name ( + part->filename, O_RDWR | O_CREAT | O_TRUNC, 0600, NULL); + data = camel_medium_get_content (CAMEL_MEDIUM (part->parent.part)); + camel_data_wrapper_decode_to_stream_sync (data, stream, NULL, NULL); + camel_stream_flush (stream, NULL, NULL); + g_object_unref (stream); + + d(printf ("audio inline formatter: init gst playbin\n")); + + if (gst_init_check (&argc, (gchar ***) &argv, &error)) { + gchar *uri; + GstBus *bus; + + /* create a disk reader */ + part->playbin = gst_element_factory_make ("playbin", "playbin"); + if (part->playbin == NULL) { + g_printerr ("Failed to create gst_element_factory playbin; check your installation\n"); + return; + + } + + uri = g_filename_to_uri (part->filename, NULL, NULL); + g_object_set (part->playbin, "uri", uri, NULL); + g_free (uri); + set_audiosink (part->playbin); + + bus = gst_element_get_bus (part->playbin); + part->bus_id = gst_bus_add_watch (bus, gst_callback, part); + gst_object_unref (bus); + + } else { + g_printerr ("GStreamer failed to initialize: %s",error ? error->message : ""); + g_error_free (error); + } + } + + gst_element_get_state (part->playbin, &cur_state, NULL, 0); + + if (cur_state >= GST_STATE_PAUSED) { + gst_element_set_state (part->playbin, GST_STATE_READY); + } + + if (part->playbin) { + /* start playing */ + gst_element_set_state (part->playbin, GST_STATE_PLAYING); + } +} + +static GtkWidget * +add_button (GtkWidget *box, + const gchar *stock_icon, + GCallback cb, + gpointer data, + gboolean sensitive) +{ + GtkWidget *button; + + button = gtk_button_new_from_stock (stock_icon); + gtk_widget_set_sensitive (button, sensitive); + g_signal_connect (button, "clicked", cb, data); + + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (box), button, TRUE, TRUE, 0); + + return button; +} + +static gboolean +emfe_audio_inline_format (EMailFormatterExtension *extension, + EMailFormatter *formatter, + EMailFormatterContext *context, + EMailPart *part, + CamelStream *stream, + GCancellable *cancellable) +{ + gchar *str; + + str = g_strdup_printf ( + "<object type=\"application/vnd.evolution.widget.audio-inline\" " + "width=\"100%%\" height=\"auto\" data=\"%s\" id=\"%s\"></object>", + part->id, part->id); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + + return TRUE; +} + +static GtkWidget * +emfe_audio_inline_get_widget (EMailFormatterExtension *extension, + EMailPartList *context, + EMailPart *part, + GHashTable *params) +{ + GtkWidget *box; + EMailPartAudioInline *ai_part; + + g_return_val_if_fail (E_MAIL_PART_IS (part, EMailPartAudioInline), NULL); + ai_part = (EMailPartAudioInline *) part; + + /* it is OK to call UI functions here, since we are called from UI thread */ + box = gtk_hbutton_box_new (); + ai_part->play_button = g_object_ref ( + add_button (box, GTK_STOCK_MEDIA_PLAY, + G_CALLBACK (play_clicked), part, TRUE)); + ai_part->pause_button = g_object_ref ( + add_button (box, GTK_STOCK_MEDIA_PAUSE, + G_CALLBACK (pause_clicked), part, FALSE)); + ai_part->stop_button = g_object_ref ( + add_button (box, GTK_STOCK_MEDIA_STOP, + G_CALLBACK (stop_clicked), part, FALSE)); + + gtk_widget_show (box); + + return box; +} + +static const gchar * +emfe_audio_inline_get_display_name (EMailFormatterExtension *extension) +{ + return _("Audio Player"); +} + +static const gchar * +emfe_audio_inline_get_description (EMailFormatterExtension *extension) +{ + return _("Play the attachment in embedded audio player"); +} + +static const gchar ** +emfe_audio_inline_mime_types (EMailExtension *extension) +{ + return formatter_mime_types; +} + +static void +e_mail_formatter_audio_inline_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_formatter_audio_inline_class_init (EMailFormatterAudioInlineClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_formatter_audio_inline_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_formatter_audio_inline_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_FORMATTER_EXTENSION_REGISTRY; +} + +static void +e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface) +{ + iface->format = emfe_audio_inline_format; + iface->get_widget = emfe_audio_inline_get_widget; + iface->get_display_name = emfe_audio_inline_get_display_name; + iface->get_description = emfe_audio_inline_get_description; +} + +static void +e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = emfe_audio_inline_mime_types; +} + +static void +e_mail_formatter_audio_inline_init (EMailFormatterAudioInline *formatter) +{ + +} + +void +e_mail_formatter_audio_inline_type_register (GTypeModule *type_module) +{ + e_mail_formatter_audio_inline_register_type (type_module); +} + +static void +e_mail_formatter_audio_inline_class_finalize (EMailFormatterAudioInlineClass *klass) +{ + +} diff --git a/modules/audio-inline/e-mail-formatter-audio-inline.h b/modules/audio-inline/e-mail-formatter-audio-inline.h new file mode 100644 index 0000000000..1960f382da --- /dev/null +++ b/modules/audio-inline/e-mail-formatter-audio-inline.h @@ -0,0 +1,30 @@ +/* + * e-mail-formatter-audio-inline.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/> + * + */ + +#ifndef E_MAIL_FORMATTER_AUDIO_INLINE_H +#define E_MAIL_FORMATTER_AUDIO_INLINE_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_formatter_audio_inline_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_FORMATTER_AUDIO_INLINE_H */ diff --git a/modules/audio-inline/e-mail-parser-audio-inline.c b/modules/audio-inline/e-mail-parser-audio-inline.c new file mode 100644 index 0000000000..0e872971f4 --- /dev/null +++ b/modules/audio-inline/e-mail-parser-audio-inline.c @@ -0,0 +1,199 @@ +/* + * e-mail-parser-audio-inline.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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include "e-mail-parser-audio-inline.h" +#include "e-mail-part-audio-inline.h" + +#include <camel/camel.h> + +#include <em-format/e-mail-extension-registry.h> +#include <em-format/e-mail-parser-extension.h> +#include <em-format/e-mail-part.h> + +#include <libebackend/libebackend.h> + +#define d(x) + +typedef struct _EMailParserInlineAudio { + EExtension parent; +} EMailParserAudioInline; + +typedef struct _EMailParserAudioInlineClass { + EExtensionClass parent_class; +} EMailParserAudioInlineClass; + +GType e_mail_parser_audio_inline_get_type (void); +static void e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface); +static void e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailParserAudioInline, + e_mail_parser_audio_inline, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_parser_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_PARSER_EXTENSION, + e_mail_parser_parser_extension_interface_init)); + +static const gchar* parser_mime_types[] = { "audio/ac3", "audio/x-ac3", + "audio/basic", "audio/mpeg", + "audio/x-mpeg", "audio/mpeg3", + "audio/x-mpeg3", "audio/mp3", + "audio/x-mp3", "audio/mp4", + "audio/flac", "audio/x-flac", + "audio/mod", "audio/x-mod", + "audio/x-wav", "audio/microsoft-wav", + "audio/x-wma", "audio/x-ms-wma", + "application/ogg", "application/x-ogg", + NULL }; + +static void +mail_part_audio_inline_free (EMailPart *mail_part) +{ + EMailPartAudioInline *ai_part = (EMailPartAudioInline *) mail_part; + + g_clear_object (&ai_part->play_button); + g_clear_object (&ai_part->pause_button); + g_clear_object (&ai_part->stop_button); + + if (ai_part->filename) { + g_unlink (ai_part->filename); + g_free (ai_part->filename); + ai_part->filename = NULL; + } + + if (ai_part->bus_id) { + g_source_remove (ai_part->bus_id); + ai_part->bus_id = 0; + } + + if (ai_part->playbin) { + gst_element_set_state (ai_part->playbin, GST_STATE_NULL); + gst_object_unref (ai_part->playbin); + ai_part->playbin = NULL; + } +} + +static GSList * +empe_audio_inline_parse (EMailParserExtension *extension, + EMailParser *parser, + CamelMimePart *part, + GString *part_id, + GCancellable *cancellable) +{ + EMailPartAudioInline *mail_part; + gint len; + + len = part_id->len; + g_string_append (part_id, ".org-gnome-audio-inline-button-panel"); + + d(printf ("audio inline formatter: format classid %s\n", part_id->str)); + + mail_part = (EMailPartAudioInline *) e_mail_part_subclass_new ( + part, part_id->str, sizeof (EMailPartAudioInline), + (GFreeFunc) mail_part_audio_inline_free); + mail_part->parent.mime_type = camel_content_type_simple ( + camel_mime_part_get_content_type (part)); + mail_part->parent.is_attachment = TRUE; + g_string_truncate (part_id, len); + + return e_mail_parser_wrap_as_attachment ( + parser, part, g_slist_append (NULL, mail_part), + part_id, cancellable); +} + +static guint32 +empe_audio_inline_get_flags (EMailParserExtension *extension) +{ + return E_MAIL_PARSER_EXTENSION_INLINE_DISPOSITION; +} + +static const gchar ** +empe_mime_types (EMailExtension *extension) +{ + return parser_mime_types; +} + +void +e_mail_parser_audio_inline_type_register (GTypeModule *type_module) +{ + e_mail_parser_audio_inline_register_type (type_module); +} + +static void +e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = empe_mime_types; +} + +static void +e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface) +{ + iface->parse = empe_audio_inline_parse; + iface->get_flags = empe_audio_inline_get_flags; +} + +static void +e_mail_parser_audio_inline_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_parser_audio_inline_class_init (EMailParserAudioInlineClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_parser_audio_inline_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_parser_audio_inline_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY; +} + +static void +e_mail_parser_audio_inline_class_finalize (EMailParserAudioInlineClass *klass) +{ + +} + +static void +e_mail_parser_audio_inline_init (EMailParserAudioInline *self) +{ +} diff --git a/modules/audio-inline/e-mail-parser-audio-inline.h b/modules/audio-inline/e-mail-parser-audio-inline.h new file mode 100644 index 0000000000..8c80becb20 --- /dev/null +++ b/modules/audio-inline/e-mail-parser-audio-inline.h @@ -0,0 +1,30 @@ +/* + * e-mail-parser-audio-inline.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/> + * + */ + +#ifndef E_MAIL_PARSER_AUDIO_INLINE_H +#define E_MAIL_PARSER_AUDIO_INLINE_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_parser_audio_inline_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_PARSER_AUDIO_INLINE_H */ diff --git a/modules/audio-inline/e-mail-part-audio-inline.h b/modules/audio-inline/e-mail-part-audio-inline.h new file mode 100644 index 0000000000..e087ecdff6 --- /dev/null +++ b/modules/audio-inline/e-mail-part-audio-inline.h @@ -0,0 +1,46 @@ +/* + * e-mail-part-audio-inline.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/> + * + */ + +#ifndef E_MAIL_PART_AUDIO_INLINE_H +#define E_MAIL_PART_AUDIO_INLINE_H + +#include <glib-object.h> + +#include <em-format/e-mail-part.h> +#include <gst/gst.h> + +G_BEGIN_DECLS + +typedef struct _EMailPartAudioInline EMailPartAudioInline; + +struct _EMailPartAudioInline { + EMailPart parent; + + gchar *filename; + GstElement *playbin; + gulong bus_id; + GstState target_state; + GtkWidget *play_button; + GtkWidget *pause_button; + GtkWidget *stop_button; +}; + +G_END_DECLS + +#endif /* E_MAIL_PART_AUDIO_INLINE_H */ + diff --git a/modules/audio-inline/evolution-module-audio-inline.c b/modules/audio-inline/evolution-module-audio-inline.c new file mode 100644 index 0000000000..b5dffb183e --- /dev/null +++ b/modules/audio-inline/evolution-module-audio-inline.c @@ -0,0 +1,54 @@ +/* + * evolution-module-audio-inline.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/> + * + */ + +#include "e-mail-formatter-audio-inline.h" +#include "e-mail-parser-audio-inline.h" + +#include <gmodule.h> + +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_parser_audio_inline_type_register (type_module); + e_mail_formatter_audio_inline_type_register (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/bogofilter/evolution-bogofilter.c b/modules/bogofilter/evolution-bogofilter.c index 0467c0f654..7d00888565 100644 --- a/modules/bogofilter/evolution-bogofilter.c +++ b/modules/bogofilter/evolution-bogofilter.c @@ -16,6 +16,10 @@ * */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <config.h> #include <sys/types.h> #include <sys/wait.h> diff --git a/modules/itip-formatter/Makefile.am b/modules/itip-formatter/Makefile.am new file mode 100644 index 0000000000..84fd8830a3 --- /dev/null +++ b/modules/itip-formatter/Makefile.am @@ -0,0 +1,51 @@ +module_LTLIBRARIES = module-itip-formatter.la + +module_itip_formatter_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + -DG_LOG_DOMAIN=\"evolution-module-itip-formatter\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +module_itip_formatter_la_SOURCES = \ + e-conflict-search-selector.c \ + e-conflict-search-selector.h \ + e-mail-formatter-itip.c \ + e-mail-formatter-itip.h \ + e-mail-parser-itip.c \ + e-mail-parser-itip.h \ + e-source-conflict-search.c \ + e-sourec-conflict-search.h \ + itip-view.c \ + itip-view.h \ + evolution-module-itip-formatter.c + +module_itip_formatter_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/calendar/gui/libevolution-calendar.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/shell/libeshell.la \ + $(top_builddir)/em-format/libemformat.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ + $(top_builddir)/libemail-utils/libemail-utils.la \ + $(top_builddir)/libemail-engine/libemail-engine.la \ + $(top_builddir)/libevolution-utils/libevolution-utils.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +module_itip_formatter_la_LDFLAGS = \ + -avoid-version -module $(NO_UNDEFINED) + +error_DATA = org-gnome-itip-formatter.error.xml +errordir = $(privdatadir)/errors + +BUILT_SOURCES = $(error_DATA) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = \ + org-gnome-itip-formatter.error.xml + +-include $(top_srcdir)/git.mk diff --git a/modules/itip-formatter/e-conflict-search-selector.c b/modules/itip-formatter/e-conflict-search-selector.c new file mode 100644 index 0000000000..8f497f5d4f --- /dev/null +++ b/modules/itip-formatter/e-conflict-search-selector.c @@ -0,0 +1,103 @@ +/* + * e-conflict-search-selector.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/> + * + */ + +#include "e-conflict-search-selector.h" + +#include "e-source-conflict-search.h" + +/* This forces the GType to be registered in a way that + * avoids a "statement with no effect" compiler warning. */ +#define REGISTER_TYPE(type) \ + (g_type_class_unref (g_type_class_ref (type))) + +G_DEFINE_TYPE ( + EConflictSearchSelector, + e_conflict_search_selector, + E_TYPE_SOURCE_SELECTOR) + +static gboolean +conflict_search_selector_get_source_selected (ESourceSelector *selector, + ESource *source) +{ + ESourceConflictSearch *extension; + const gchar *extension_name; + + /* Make sure this source is a calendar. */ + extension_name = e_source_selector_get_extension_name (selector); + if (!e_source_has_extension (source, extension_name)) + return FALSE; + + extension_name = E_SOURCE_EXTENSION_CONFLICT_SEARCH; + extension = e_source_get_extension (source, extension_name); + g_return_val_if_fail (E_IS_SOURCE_CONFLICT_SEARCH (extension), FALSE); + + return e_source_conflict_search_get_include_me (extension); +} + +static void +conflict_search_selector_set_source_selected (ESourceSelector *selector, + ESource *source, + gboolean selected) +{ + ESourceConflictSearch *extension; + const gchar *extension_name; + + /* Make sure this source is a calendar. */ + extension_name = e_source_selector_get_extension_name (selector); + if (!e_source_has_extension (source, extension_name)) + return; + + extension_name = E_SOURCE_EXTENSION_CONFLICT_SEARCH; + extension = e_source_get_extension (source, extension_name); + g_return_if_fail (E_IS_SOURCE_CONFLICT_SEARCH (extension)); + + if (selected != e_source_conflict_search_get_include_me (extension)) { + e_source_conflict_search_set_include_me (extension, selected); + e_source_selector_queue_write (selector, source); + } +} + +static void +e_conflict_search_selector_class_init (EConflictSearchSelectorClass *class) +{ + ESourceSelectorClass *source_selector_class; + + source_selector_class = E_SOURCE_SELECTOR_CLASS (class); + source_selector_class->get_source_selected = + conflict_search_selector_get_source_selected; + source_selector_class->set_source_selected = + conflict_search_selector_set_source_selected; + + REGISTER_TYPE (E_TYPE_SOURCE_CONFLICT_SEARCH); +} + +static void +e_conflict_search_selector_init (EConflictSearchSelector *selector) +{ +} + +GtkWidget * +e_conflict_search_selector_new (ESourceRegistry *registry) +{ + g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); + + return g_object_new ( + E_TYPE_CONFLICT_SEARCH_SELECTOR, + "extension-name", E_SOURCE_EXTENSION_CALENDAR, + "registry", registry, NULL); +} diff --git a/modules/itip-formatter/e-conflict-search-selector.h b/modules/itip-formatter/e-conflict-search-selector.h new file mode 100644 index 0000000000..091e1c9328 --- /dev/null +++ b/modules/itip-formatter/e-conflict-search-selector.h @@ -0,0 +1,64 @@ +/* + * e-conflict-search-selector.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/> + * + */ + +#ifndef E_CONFLICT_SEARCH_SELECTOR_H +#define E_CONFLICT_SEARCH_SELECTOR_H + +#include <libedataserverui/libedataserverui.h> + +/* Standard GObject macros */ +#define E_TYPE_CONFLICT_SEARCH_SELECTOR \ + (e_conflict_search_selector_get_type ()) +#define E_CONFLICT_SEARCH_SELECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CONFLICT_SEARCH_SELECTOR, EConflictSearchSelector)) +#define E_CONFLICT_SEARCH_SELECTOR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CONFLICT_SEARCH_SELECTOR, EConflictSearchSelectorClass)) +#define E_IS_CONFLICT_SEARCH_SELECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CONFLICT_SEARCH_SELECTOR)) +#define E_IS_CONFLICT_SEARCH_SELECTOR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CONFLICT_SEARCH_SELECTOR)) +#define E_CONFLICT_SEARCH_SELECTOR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CONFLICT_SEARCH_SELECTOR, EConflictSearchSelectorClass)) + +G_BEGIN_DECLS + +typedef struct _EConflictSearchSelector EConflictSearchSelector; +typedef struct _EConflictSearchSelectorClass EConflictSearchSelectorClass; +typedef struct _EConflictSearchSelectorPrivate EConflictSearchSelectorPrivate; + +struct _EConflictSearchSelector { + ESourceSelector parent; + EConflictSearchSelectorPrivate *priv; +}; + +struct _EConflictSearchSelectorClass { + ESourceSelectorClass parent_class; +}; + +GType e_conflict_search_selector_get_type + (void) G_GNUC_CONST; +GtkWidget * e_conflict_search_selector_new (ESourceRegistry *registry); + +G_END_DECLS + +#endif /* E_CONFLICT_SEARCH_SELECTOR_H */ diff --git a/modules/itip-formatter/e-mail-formatter-itip.c b/modules/itip-formatter/e-mail-formatter-itip.c new file mode 100644 index 0000000000..ebec6bc56c --- /dev/null +++ b/modules/itip-formatter/e-mail-formatter-itip.c @@ -0,0 +1,219 @@ +/* + * e-mail-formatter-itip.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/> + * + */ + +#include "e-mail-formatter-itip.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n-lib.h> + +#include <em-format/e-mail-formatter-extension.h> +#include <em-format/e-mail-formatter.h> +#include <em-format/e-mail-part-utils.h> +#include <libebackend/libebackend.h> + +#include "itip-view.h" +#include "e-mail-part-itip.h" + +#define d(x) + +typedef struct _EMailFormatterItip { + EExtension parent; +} EMailFormatterItip; + +typedef struct _EMailFormatterItipClass { + EExtensionClass parent_class; +} EMailFormatterItipClass; + +GType e_mail_formatter_itip_get_type (void); +static void e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface); +static void e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailFormatterItip, + e_mail_formatter_itip, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_formatter_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_FORMATTER_EXTENSION, + e_mail_formatter_formatter_extension_interface_init)); + +static const gchar* formatter_mime_types[] = { "text/calendar" , NULL }; + +static gboolean +emfe_itip_format (EMailFormatterExtension *extension, + EMailFormatter *formatter, + EMailFormatterContext *context, + EMailPart *part, + CamelStream *stream, + GCancellable *cancellable) +{ + GString *buffer; + EMailPartItip *itip_part; + + g_return_val_if_fail (E_MAIL_PART_IS (part, EMailPartItip), FALSE); + itip_part = (EMailPartItip *) part; + + if (context->mode == E_MAIL_FORMATTER_MODE_PRINTING) { + buffer = g_string_sized_new (1024); + + itip_part->view = itip_view_new (itip_part, itip_part->registry); + + itip_view_init_view (itip_part->view); + itip_view_write_for_printing (itip_part->view, buffer); + + } else if (context->mode == E_MAIL_FORMATTER_MODE_RAW) { + buffer = g_string_sized_new (2048); + + itip_view_write (buffer); + + } else { + gchar *uri; + + /* mark message as containing calendar, thus it will show the + * icon in message list now on */ + if (context->message_uid && context->folder && + !camel_folder_get_message_user_flag ( + context->folder, context->message_uid, "$has_cal")) { + + camel_folder_set_message_user_flag ( + context->folder, context->message_uid, + "$has_cal", TRUE); + } + + itip_part->folder = g_object_ref (context->folder); + itip_part->uid = g_strdup (context->message_uid); + itip_part->msg = g_object_ref (context->message); + + uri = e_mail_part_build_uri (context->folder, context->message_uid, + "part_id", G_TYPE_STRING, part->id, + "mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_RAW, + NULL); + + buffer = g_string_sized_new (256); + g_string_append_printf (buffer, + "<div class=\"part-container\" " + "style=\"border: none; background: none;\">" + "<iframe width=\"100%%\" height=\"auto\"" + " frameborder=\"0\" src=\"%s\" name=\"%s\" id=\"%s\"></iframe>" + "</div>", + uri, part->id, part->id); + + g_free (uri); + } + + camel_stream_write_string (stream, buffer->str, cancellable, NULL); + + g_string_free (buffer, TRUE); + + return TRUE; +} + +static const gchar * +emfe_itip_get_display_name (EMailFormatterExtension *extension) +{ + return _("ITIP"); +} + +static const gchar * +emfe_itip_get_description (EMailFormatterExtension *extension) +{ + return _("Display part as an invitation"); +} + +static const gchar ** +emfe_itip_mime_types (EMailExtension *extension) +{ + return formatter_mime_types; +} + +static void +e_mail_formatter_itip_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_formatter_itip_finalize (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_remove_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_formatter_itip_class_init (EMailFormatterItipClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_formatter_itip_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_formatter_itip_constructed; + object_class->finalize = e_mail_formatter_itip_finalize; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_FORMATTER_EXTENSION_REGISTRY; +} + +static void +e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface) +{ + iface->format = emfe_itip_format; + iface->get_display_name = emfe_itip_get_display_name; + iface->get_description = emfe_itip_get_description; +} + +static void +e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = emfe_itip_mime_types; +} + +static void +e_mail_formatter_itip_init (EMailFormatterItip *formatter) +{ +} + +void +e_mail_formatter_itip_type_register (GTypeModule *type_module) +{ + e_mail_formatter_itip_register_type (type_module); +} + +static void +e_mail_formatter_itip_class_finalize (EMailFormatterItipClass *klass) +{ + +} diff --git a/modules/itip-formatter/e-mail-formatter-itip.h b/modules/itip-formatter/e-mail-formatter-itip.h new file mode 100644 index 0000000000..737926d578 --- /dev/null +++ b/modules/itip-formatter/e-mail-formatter-itip.h @@ -0,0 +1,30 @@ +/* + * e-mail-formatter-itip.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/> + * + */ + +#ifndef E_MAIL_FORMATTER_ITIP_H +#define E_MAIL_FORMATTER_ITIP_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_formatter_itip_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_FORMATTER_ITIP_H */ diff --git a/modules/itip-formatter/e-mail-parser-itip.c b/modules/itip-formatter/e-mail-parser-itip.c new file mode 100644 index 0000000000..f8000aa46f --- /dev/null +++ b/modules/itip-formatter/e-mail-parser-itip.c @@ -0,0 +1,314 @@ +/* + * 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: + * JP Rosevear <jpr@novell.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "e-mail-parser-itip.h" + +#include <em-format/e-mail-extension-registry.h> +#include <em-format/e-mail-parser-extension.h> +#include <em-format/e-mail-part.h> + +#include <misc/e-attachment.h> + +#include "e-mail-part-itip.h" +#include "itip-view.h" +#include <shell/e-shell.h> + +#define CONF_KEY_DELETE "delete-processed" + +#define d(x) + +typedef struct _EMailParserItip { + EExtension parent; +} EMailParserItip; + +typedef struct _EMailParserItipClass { + EExtensionClass parent_class; +} EMailParserItipClass; + +GType e_mail_parser_itip_get_type (void); +static void e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface); +static void e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailParserItip, + e_mail_parser_itip, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_parser_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_PARSER_EXTENSION, + e_mail_parser_parser_extension_interface_init)); + +static const gchar* parser_mime_types[] = { "text/calendar", NULL }; + +static void +mail_part_itip_free (EMailPart *mail_part) +{ + EMailPartItip *pitip = (EMailPartItip *) mail_part; + gint i; + + g_cancellable_cancel (pitip->cancellable); + g_clear_object (&pitip->cancellable); + g_clear_object (&pitip->registry); + + for (i = 0; i < E_CAL_CLIENT_SOURCE_TYPE_LAST; i++) { + g_hash_table_destroy (pitip->clients[i]); + pitip->clients[i] = NULL; + } + + g_free (pitip->vcalendar); + pitip->vcalendar = NULL; + + if (pitip->comp) { + g_object_unref (pitip->comp); + pitip->comp = NULL; + } + + if (pitip->top_level) { + icalcomponent_free (pitip->top_level); + pitip->top_level = NULL; + } + + if (pitip->main_comp) { + icalcomponent_free (pitip->main_comp); + pitip->main_comp = NULL; + } + pitip->ical_comp = NULL; + + g_free (pitip->calendar_uid); + pitip->calendar_uid = NULL; + + g_free (pitip->from_address); + pitip->from_address = NULL; + g_free (pitip->from_name); + pitip->from_name = NULL; + g_free (pitip->to_address); + pitip->to_address = NULL; + g_free (pitip->to_name); + pitip->to_name = NULL; + g_free (pitip->delegator_address); + pitip->delegator_address = NULL; + g_free (pitip->delegator_name); + pitip->delegator_name = NULL; + g_free (pitip->my_address); + pitip->my_address = NULL; + g_free (pitip->uid); + g_hash_table_destroy (pitip->real_comps); + + g_clear_object (&pitip->view); +} + +/******************************************************************************/ + +static void +bind_itip_view (EMailPart *part, + WebKitDOMElement *element) +{ + GString *buffer; + WebKitDOMDocument *document; + ItipView *view; + EMailPartItip *pitip; + + if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element)) { + + WebKitDOMNodeList *nodes; + guint length, i; + + nodes = webkit_dom_element_get_elements_by_tag_name ( + element, "iframe"); + length = webkit_dom_node_list_get_length (nodes); + for (i = 0; i < length; i++) { + + element = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_list_item (nodes, i)); + break; + } + + } + + g_return_if_fail (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element)); + + buffer = g_string_new (""); + document = webkit_dom_html_iframe_element_get_content_document ( + WEBKIT_DOM_HTML_IFRAME_ELEMENT (element)); + pitip = E_MAIL_PART_ITIP (part); + + view = itip_view_new (pitip, pitip->registry); + g_object_set_data_full ( + G_OBJECT (element), "view", view, + (GDestroyNotify) g_object_unref); + + itip_view_create_dom_bindings (view, + webkit_dom_document_get_document_element (document)); + + itip_view_init_view (view); + g_string_free (buffer, TRUE); +} + +/*******************************************************************************/ + +static GSList * +empe_itip_parse (EMailParserExtension *extension, + EMailParser *parser, + CamelMimePart *part, + GString *part_id, + GCancellable *cancellable) +{ + EShell *shell; + GSettings *settings; + EMailPartItip *itip_part; + CamelDataWrapper *content; + CamelStream *stream; + GByteArray *byte_array; + gint len; + + len = part_id->len; + g_string_append_printf (part_id, ".itip"); + + settings = g_settings_new ("org.gnome.evolution.plugin.itip"); + shell = e_shell_get_default (); + + itip_part = (EMailPartItip *) e_mail_part_subclass_new ( + part, part_id->str, sizeof (EMailPartItip), + (GFreeFunc) mail_part_itip_free); + itip_part->parent.mime_type = g_strdup ("text/calendar"); + itip_part->parent.bind_func = bind_itip_view; + itip_part->delete_message = g_settings_get_boolean (settings, CONF_KEY_DELETE); + itip_part->has_organizer = FALSE; + itip_part->no_reply_wanted = FALSE; + itip_part->part = part; + itip_part->cancellable = g_cancellable_new (); + itip_part->real_comps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + itip_part->registry = g_object_ref (e_shell_get_registry (shell)); + + g_object_unref (settings); + + /* This is non-gui thread. Download the part for using in the main thread */ + content = camel_medium_get_content ((CamelMedium *) part); + + byte_array = g_byte_array_new (); + stream = camel_stream_mem_new_with_byte_array (byte_array); + camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL); + + if (byte_array->len == 0) + itip_part->vcalendar = NULL; + else + itip_part->vcalendar = g_strndup ( + (gchar *) byte_array->data, byte_array->len); + + g_object_unref (stream); + g_string_truncate (part_id, len); + + return g_slist_append (NULL, itip_part); +} + +static guint32 +empe_itip_get_flags (EMailParserExtension *extension) +{ + return E_MAIL_PARSER_EXTENSION_INLINE_DISPOSITION; +} + +static const gchar ** +empe_mime_types (EMailExtension *extension) +{ + return parser_mime_types; +} + +void +e_mail_parser_itip_type_register (GTypeModule *type_module) +{ + e_mail_parser_itip_register_type (type_module); +} + +static void +e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = empe_mime_types; +} + +static void +e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface) +{ + iface->parse = empe_itip_parse; + iface->get_flags = empe_itip_get_flags; +} + +static void +e_mail_parser_itip_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_parser_itip_finalize (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_remove_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_parser_itip_class_init (EMailParserItipClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_parser_itip_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_parser_itip_constructed; + object_class->finalize = e_mail_parser_itip_finalize; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY; +} + +static void +e_mail_parser_itip_class_finalize (EMailParserItipClass *klass) +{ + +} + +static void +e_mail_parser_itip_init (EMailParserItip *self) +{ +} diff --git a/modules/itip-formatter/e-mail-parser-itip.h b/modules/itip-formatter/e-mail-parser-itip.h new file mode 100644 index 0000000000..956af460d0 --- /dev/null +++ b/modules/itip-formatter/e-mail-parser-itip.h @@ -0,0 +1,30 @@ +/* + * e-mail-parser-itip.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/> + * + */ + +#ifndef E_MAIL_PARSER_ITIP_H +#define E_MAIL_PARSER_ITIP_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_parser_itip_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_PARSER_ITIP_H */ diff --git a/modules/itip-formatter/e-mail-part-itip.h b/modules/itip-formatter/e-mail-part-itip.h new file mode 100644 index 0000000000..d1d577a675 --- /dev/null +++ b/modules/itip-formatter/e-mail-part-itip.h @@ -0,0 +1,123 @@ +/* + * 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/> + * + */ + +#ifndef E_MAIL_PART_ITIP_H +#define E_MAIL_PART_ITIP_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <libecal/libecal.h> +#include <libedataserverui/libedataserverui.h> +#include <libebackend/libebackend.h> + +#include <em-format/e-mail-part.h> + +#include "itip-view.h" + +#define E_MAIL_PART_ITIP(p) ((EMailPartItip *) p) + +G_BEGIN_DECLS + +typedef struct _EMailPartItip EMailPartItip; + +struct _EMailPartItip { + EMailPart parent; + + CamelFolder *folder; + CamelMimeMessage *msg; + CamelMimePart *part; + + gchar *uid; + + ESourceRegistry *registry; + GHashTable *clients[E_CAL_CLIENT_SOURCE_TYPE_LAST]; + + ECalClient *current_client; + ECalClientSourceType type; + + /* cancelled when freeing the puri */ + GCancellable *cancellable; + + gchar *vcalendar; + ECalComponent *comp; + icalcomponent *main_comp; + icalcomponent *ical_comp; + icalcomponent *top_level; + icalcompiter iter; + icalproperty_method method; + time_t start_time; + time_t end_time; + + gint current; + gint total; + + gchar *calendar_uid; + + gchar *from_address; + gchar *from_name; + gchar *to_address; + gchar *to_name; + gchar *delegator_address; + gchar *delegator_name; + gchar *my_address; + gint view_only; + + guint progress_info_id; + + gboolean delete_message; + /* a reply can only be sent if and only if there is an organizer */ + gboolean has_organizer; + /* + * Usually replies are sent unless the user unchecks that option. + * There are some cases when the default is not to sent a reply + * (but the user can still chose to do so by checking the option): + * - the organizer explicitly set RSVP=FALSE for the current user + * - the event has no ATTENDEEs: that's the case for most non-meeting + * events + * + * The last case is meant for forwarded non-meeting + * events. Traditionally Evolution hasn't offered to send a + * reply, therefore the updated implementation mimics that + * behavior. + * + * Unfortunately some software apparently strips all ATTENDEEs + * when forwarding a meeting; in that case sending a reply is + * also unchecked by default. So the check for ATTENDEEs is a + * tradeoff between sending unwanted replies in cases where + * that wasn't done in the past and not sending a possibly + * wanted reply where that wasn't possible in the past + * (because replies to forwarded events were not + * supported). Overall that should be an improvement, and the + * user can always override the default. + */ + gboolean no_reply_wanted; + + guint update_item_progress_info_id; + guint update_item_error_info_id; + ItipViewResponse update_item_response; + gboolean can_delete_invitation_from_cache; + GHashTable *real_comps; /* ESource's UID -> ECalComponent stored on the server */ + + ItipView *view; +}; + +typedef struct _EMailPartItip EMailPartItip; + +G_END_DECLS + +#endif /* E_MAIL_PART_ITIP_H */ diff --git a/modules/itip-formatter/e-source-conflict-search.c b/modules/itip-formatter/e-source-conflict-search.c new file mode 100644 index 0000000000..c2f5eb6e99 --- /dev/null +++ b/modules/itip-formatter/e-source-conflict-search.c @@ -0,0 +1,151 @@ +/* + * e-source-conflict-search.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/> + * + */ + +#include "e-source-conflict-search.h" + +#define E_SOURCE_CONFLICT_SEARCH_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_SOURCE_CONFLICT_SEARCH, ESourceConflictSearchPrivate)) + +struct _ESourceConflictSearchPrivate { + gboolean include_me; +}; + +enum { + PROP_0, + PROP_INCLUDE_ME +}; + +G_DEFINE_TYPE ( + ESourceConflictSearch, + e_source_conflict_search, + E_TYPE_SOURCE_EXTENSION) + +static void +source_conflict_search_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_INCLUDE_ME: + e_source_conflict_search_set_include_me ( + E_SOURCE_CONFLICT_SEARCH (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +source_conflict_search_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_INCLUDE_ME: + g_value_set_boolean ( + value, + e_source_conflict_search_get_include_me ( + E_SOURCE_CONFLICT_SEARCH (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_source_conflict_search_class_init (ESourceConflictSearchClass *class) +{ + GObjectClass *object_class; + ESourceExtensionClass *extension_class; + + g_type_class_add_private (class, sizeof (ESourceConflictSearchPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = source_conflict_search_set_property; + object_class->get_property = source_conflict_search_get_property; + + extension_class = E_SOURCE_EXTENSION_CLASS (class); + extension_class->name = E_SOURCE_EXTENSION_CONFLICT_SEARCH; + + g_object_class_install_property ( + object_class, + PROP_INCLUDE_ME, + g_param_spec_boolean ( + "include-me", + "IncludeMe", + "Include this calendar in when " + "searching for scheduling conflicts", + TRUE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS | + E_SOURCE_PARAM_SETTING)); +} + +static void +e_source_conflict_search_init (ESourceConflictSearch *extension) +{ + extension->priv = E_SOURCE_CONFLICT_SEARCH_GET_PRIVATE (extension); +} + +/** + * e_source_conflict_search_get_include_me: + * @extension: an #ESourceConflictSearch + * + * Returns whether the calendar described by the #ESource to which + * @extension belongs should be queried for scheduling conflicts when + * negotiating a meeting invitation. + * + * Returns: whether to search for scheduling conflicts + * + * Since: 3.6 + **/ +gboolean +e_source_conflict_search_get_include_me (ESourceConflictSearch *extension) +{ + g_return_val_if_fail (E_IS_SOURCE_CONFLICT_SEARCH (extension), FALSE); + + return extension->priv->include_me; +} + +/** + * e_source_conflict_search_set_include_me: + * @extension: an #ESourceConflictSearch + * @include_me: whether to search for scheduling conflicts + * + * Sets whether the calendar described by the #ESource to which @extension + * belongs should be queried for scheduling conflicts when negotiating a + * meeting invitation. + * + * Since: 3.6 + **/ +void +e_source_conflict_search_set_include_me (ESourceConflictSearch *extension, + gboolean include_me) +{ + g_return_if_fail (E_IS_SOURCE_CONFLICT_SEARCH (extension)); + + extension->priv->include_me = include_me; + + g_object_notify (G_OBJECT (extension), "include-me"); +} + diff --git a/modules/itip-formatter/e-source-conflict-search.h b/modules/itip-formatter/e-source-conflict-search.h new file mode 100644 index 0000000000..f91fcc4d43 --- /dev/null +++ b/modules/itip-formatter/e-source-conflict-search.h @@ -0,0 +1,86 @@ +/* + * e-source-conflict-search.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/> + * + */ + +#ifndef E_SOURCE_CONFLICT_SEARCH_H +#define E_SOURCE_CONFLICT_SEARCH_H + +#include <libedataserver/libedataserver.h> + +/* Standard GObject macros */ +#define E_TYPE_SOURCE_CONFLICT_SEARCH \ + (e_source_conflict_search_get_type ()) +#define E_SOURCE_CONFLICT_SEARCH(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_SOURCE_CONFLICT_SEARCH, ESourceConflictSearch)) +#define E_SOURCE_CONFLICT_SEARCH_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_SOURCE_CONFLICT_SEARCH, ESourceConflictSearchClass)) +#define E_IS_SOURCE_CONFLICT_SEARCH(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_SOURCE_CONFLICT_SEARCH)) +#define E_IS_SOURCE_CONFLICT_SEARCH_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_SOURCE_CONFLICT_SEARCH)) +#define E_SOURCE_CONFLICT_SEARCH_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_SOURCE_CONFLICT_SEARCH, ESourceConflictSearchClass)) + +/** + * E_SOURCE_EXTENSION_CONFLICT_SEARCH: + * + * Pass this extension name to e_source_get_extension() to access + * #ESourceConflictSearch. This is also used as a group name in key files. + * + * Since: 3.6 + **/ +#define E_SOURCE_EXTENSION_CONFLICT_SEARCH "Conflict Search" + +G_BEGIN_DECLS + +typedef struct _ESourceConflictSearch ESourceConflictSearch; +typedef struct _ESourceConflictSearchClass ESourceConflictSearchClass; +typedef struct _ESourceConflictSearchPrivate ESourceConflictSearchPrivate; + +/** + * ESourceConflictSearch: + * + * Contains only private data that should be read and manipulated using the + * functions below. + * + * Since: 3.6 + **/ +struct _ESourceConflictSearch { + ESourceExtension parent; + ESourceConflictSearchPrivate *priv; +}; + +struct _ESourceConflictSearchClass { + ESourceExtensionClass parent_class; +}; + +GType e_source_conflict_search_get_type + (void) G_GNUC_CONST; +gboolean e_source_conflict_search_get_include_me + (ESourceConflictSearch *extension); +void e_source_conflict_search_set_include_me + (ESourceConflictSearch *extension, + gboolean include_me); + +G_END_DECLS + +#endif /* E_SOURCE_CONFLICT_SEARCH_H */ diff --git a/modules/itip-formatter/evolution-module-itip-formatter.c b/modules/itip-formatter/evolution-module-itip-formatter.c new file mode 100644 index 0000000000..13a73a30d7 --- /dev/null +++ b/modules/itip-formatter/evolution-module-itip-formatter.c @@ -0,0 +1,75 @@ +/* + * evolution-module-itip-formatter.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/> + * + */ + +#include "e-mail-formatter-itip.h" +#include "e-mail-parser-itip.h" + +#include <gmodule.h> +#include <gio/gio.h> + +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) +{ + GSettings *settings; + gchar **disabled_plugins; + gint i = 0; + + settings = g_settings_new ("org.gnome.evolution"); + disabled_plugins = g_settings_get_strv (settings, "disabled-eplugins"); + + for (i = 0; disabled_plugins && disabled_plugins[i] != NULL; i++) { + + if (g_strcmp0 ( + disabled_plugins[i], + "org.gnome.evolution.itip_formatter") == 0) { + + g_strfreev (disabled_plugins); + g_object_unref (settings); + return; + } + + } + + e_mail_parser_itip_type_register (type_module); + e_mail_formatter_itip_type_register (type_module); + + g_strfreev (disabled_plugins); + g_object_unref (settings); +} + +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/itip-formatter/itip-view.c b/modules/itip-formatter/itip-view.c new file mode 100644 index 0000000000..5e655eb74d --- /dev/null +++ b/modules/itip-formatter/itip-view.c @@ -0,0 +1,6137 @@ +/* + * 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: + * JP Rosevear <jpr@novell.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <glib/gi18n.h> +#include <libedataserverui/libedataserverui.h> +#include <libedataserver/libedataserver.h> + +#include <e-util/e-util.h> +#include <e-util/e-unicode.h> +#include <calendar/gui/itip-utils.h> +#include <webkit/webkitdom.h> + +#include <libevolution-utils/e-alert-dialog.h> +#include <e-util/e-mktemp.h> + +#include <shell/e-shell.h> +#include <shell/e-shell-utils.h> + +#include <libemail-utils/mail-mt.h> + +#include <libemail-engine/mail-folder-cache.h> +#include <libemail-engine/mail-tools.h> + +#include <mail/em-config.h> +#include <mail/em-utils.h> + +#include <calendar/gui/itip-utils.h> + +#include "e-conflict-search-selector.h" +#include "e-source-conflict-search.h" +#include "itip-view.h" +#include "e-mail-part-itip.h" + +#define d(x) + +#define MEETING_ICON "stock_new-meeting" + +#define ITIP_VIEW_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), ITIP_TYPE_VIEW, ItipViewPrivate)) + +G_DEFINE_TYPE (ItipView, itip_view, G_TYPE_OBJECT) + +typedef struct { + ItipViewInfoItemType type; + gchar *message; + + guint id; +} ItipViewInfoItem; + +struct _ItipViewPrivate { + ESourceRegistry *registry; + gulong source_added_id; + gulong source_removed_id; + gchar *extension_name; + + ItipViewMode mode; + ECalClientSourceType type; + + gchar *sender; + gchar *organizer; + gchar *organizer_sentby; + gchar *delegator; + gchar *attendee; + gchar *attendee_sentby; + gchar *proxy; + + gchar *summary; + + gchar *location; + gchar *status; + gchar *comment; + + struct tm *start_tm; + gint start_tm_is_date : 1; + gchar *start_label; + const gchar *start_header; + + struct tm *end_tm; + gint end_tm_is_date : 1; + gchar *end_label; + const gchar *end_header; + + GSList *upper_info_items; + GSList *lower_info_items; + + guint next_info_item_id; + + gchar *description; + + gint buttons_sensitive : 1; + + gboolean is_recur_set; + + gint needs_decline : 1; + + WebKitDOMDocument *dom_document; + EMailPartItip *itip_part; + + gchar *error; +}; + +#define TEXT_ROW_SENDER "text_row_sender" +#define TABLE_ROW_SUMMARY "table_row_summary" +#define TABLE_ROW_LOCATION "table_row_location" +#define TABLE_ROW_START_DATE "table_row_start_time" +#define TABLE_ROW_END_DATE "table_row_end_time" +#define TABLE_ROW_STATUS "table_row_status" +#define TABLE_ROW_COMMENT "table_row_comment" +#define TABLE_ROW_DESCRIPTION "table_row_description" +#define TABLE_ROW_RSVP_COMMENT "table_row_rsvp_comment" +#define TABLE_ROW_ESCB "table_row_escb" +#define TABLE_ROW_BUTTONS "table_row_buttons" +#define TABLE_ROW_ESCB_LABEL "table_row_escb_label" + +#define TABLE_BUTTONS "table_buttons" + +#define SELECT_ESOURCE "select_esource" +#define TEXTAREA_RSVP_COMMENT "textarea_rsvp_comment" + +#define CHECKBOX_RSVP "checkbox_rsvp" +#define CHECKBOX_RECUR "checkbox_recur" +#define CHECKBOX_UPDATE "checkbox_update" +#define CHECKBOX_FREE_TIME "checkbox_free_time" +#define CHECKBOX_KEEP_ALARM "checkbox_keep_alarm" +#define CHECKBOX_INHERIT_ALARM "checkbox_inherit_alarm" + +#define BUTTON_OPEN_CALENDAR "button_open_calendar" +#define BUTTON_DECLINE "button_decline" +#define BUTTON_DECLINE_ALL "button_decline_all" +#define BUTTON_ACCEPT "button_accept" +#define BUTTON_ACCEPT_ALL "button_accept_all" +#define BUTTON_TENTATIVE "button_tentative" +#define BUTTON_TENTATIVE_ALL "button_tentative_all" +#define BUTTON_SEND_INFORMATION "button_send_information" +#define BUTTON_UPDATE "button_update" +#define BUTTON_UPDATE_ATTENDEE_STATUS "button_update_attendee_status" +#define BUTTON_SAVE "button_save" + +#define TABLE_UPPER_ITIP_INFO "table_upper_itip_info" +#define TABLE_LOWER_ITIP_INFO "table_lower_itip_info" + +#define DIV_ITIP_CONTENT "div_itip_content" +#define DIV_ITIP_ERROR "div_itip_error" + +enum { + PROP_0, + PROP_EXTENSION_NAME, + PROP_REGISTRY +}; + +enum { + SOURCE_SELECTED, + RESPONSE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +format_date_and_time_x (struct tm *date_tm, + struct tm *current_tm, + gboolean use_24_hour_format, + gboolean show_midnight, + gboolean show_zero_seconds, + gboolean is_date, + gchar *buffer, + gint buffer_size) +{ + gchar *format; + struct tm tomorrow_tm, week_tm; + + /* Calculate a normalized "tomorrow" */ + tomorrow_tm = *current_tm; + /* Don't need this if date is in the past. Also, year assumption won't fail. */ + if (date_tm->tm_year >= current_tm->tm_year && tomorrow_tm.tm_mday == time_days_in_month (current_tm->tm_year + 1900, current_tm->tm_mon)) { + tomorrow_tm.tm_mday = 1; + if (tomorrow_tm.tm_mon == 11) { + tomorrow_tm.tm_mon = 1; + tomorrow_tm.tm_year++; + } else { + tomorrow_tm.tm_mon++; + } + } else { + tomorrow_tm.tm_mday++; + } + + /* Calculate a normalized "next seven days" */ + week_tm = *current_tm; + /* Don't need this if date is in the past. Also, year assumption won't fail. */ + if (date_tm->tm_year >= current_tm->tm_year && week_tm.tm_mday + 6 > time_days_in_month (date_tm->tm_year + 1900, date_tm->tm_mon)) { + week_tm.tm_mday = (week_tm.tm_mday + 6) % time_days_in_month (date_tm->tm_year + 1900, date_tm->tm_mon); + if (week_tm.tm_mon == 11) { + week_tm.tm_mon = 1; + week_tm.tm_year++; + } else { + week_tm.tm_mon++; + } + } else { + week_tm.tm_mday += 6; + } + + /* Today */ + if (date_tm->tm_mday == current_tm->tm_mday && + date_tm->tm_mon == current_tm->tm_mon && + date_tm->tm_year == current_tm->tm_year) { + if (is_date || (!show_midnight && date_tm->tm_hour == 0 + && date_tm->tm_min == 0 && date_tm->tm_sec == 0)) { + /* strftime format of a weekday and a date. */ + format = _("Today"); + } else if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a time, + * in 24-hour format, without seconds. */ + format = _("Today %H:%M"); + else + /* strftime format of a time, + * in 24-hour format. */ + format = _("Today %H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a time, + * in 12-hour format, without seconds. */ + format = _("Today %l:%M %p"); + else + /* strftime format of a time, + * in 12-hour format. */ + format = _("Today %l:%M:%S %p"); + } + + /* Tomorrow */ + } else if (date_tm->tm_mday == tomorrow_tm.tm_mday && + date_tm->tm_mon == tomorrow_tm.tm_mon && + date_tm->tm_year == tomorrow_tm.tm_year) { + if (is_date || (!show_midnight && date_tm->tm_hour == 0 + && date_tm->tm_min == 0 && date_tm->tm_sec == 0)) { + /* strftime format of a weekday and a date. */ + format = _("Tomorrow"); + } else if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a time, + * in 24-hour format, without seconds. */ + format = _("Tomorrow %H:%M"); + else + /* strftime format of a time, + * in 24-hour format. */ + format = _("Tomorrow %H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a time, + * in 12-hour format, without seconds. */ + format = _("Tomorrow %l:%M %p"); + else + /* strftime format of a time, + * in 12-hour format. */ + format = _("Tomorrow %l:%M:%S %p"); + } + + /* Within 6 days */ + } else if ((date_tm->tm_year >= current_tm->tm_year && + date_tm->tm_mon >= current_tm->tm_mon && + date_tm->tm_mday >= current_tm->tm_mday) && + + (date_tm->tm_year < week_tm.tm_year || + + (date_tm->tm_year == week_tm.tm_year && + date_tm->tm_mon < week_tm.tm_mon) || + + (date_tm->tm_year == week_tm.tm_year && + date_tm->tm_mon == week_tm.tm_mon && + date_tm->tm_mday < week_tm.tm_mday))) { + if (is_date || (!show_midnight && date_tm->tm_hour == 0 + && date_tm->tm_min == 0 && date_tm->tm_sec == 0)) { + /* strftime format of a weekday. */ + format = _("%A"); + } else if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday and a + * time, in 24-hour format, without seconds. */ + format = _("%A %H:%M"); + else + /* strftime format of a weekday and a + * time, in 24-hour format. */ + format = _("%A %H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday and a + * time, in 12-hour format, without seconds. */ + format = _("%A %l:%M %p"); + else + /* strftime format of a weekday and a + * time, in 12-hour format. */ + format = _("%A %l:%M:%S %p"); + } + + /* This Year */ + } else if (date_tm->tm_year == current_tm->tm_year) { + if (is_date || (!show_midnight && date_tm->tm_hour == 0 + && date_tm->tm_min == 0 && date_tm->tm_sec == 0)) { + /* strftime format of a weekday and a date + * without a year. */ + format = _("%A, %B %e"); + } else if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday, a date + * without a year and a time, + * in 24-hour format, without seconds. */ + format = _("%A, %B %e %H:%M"); + else + /* strftime format of a weekday, a date without a year + * and a time, in 24-hour format. */ + format = _("%A, %B %e %H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday, a date without a year + * and a time, in 12-hour format, without seconds. */ + format = _("%A, %B %e %l:%M %p"); + else + /* strftime format of a weekday, a date without a year + * and a time, in 12-hour format. */ + format = _("%A, %B %e %l:%M:%S %p"); + } + } else { + if (is_date || (!show_midnight && date_tm->tm_hour == 0 + && date_tm->tm_min == 0 && date_tm->tm_sec == 0)) { + /* strftime format of a weekday and a date. */ + format = _("%A, %B %e, %Y"); + } else if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday, a date and a + * time, in 24-hour format, without seconds. */ + format = _("%A, %B %e, %Y %H:%M"); + else + /* strftime format of a weekday, a date and a + * time, in 24-hour format. */ + format = _("%A, %B %e, %Y %H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday, a date and a + * time, in 12-hour format, without seconds. */ + format = _("%A, %B %e, %Y %l:%M %p"); + else + /* strftime format of a weekday, a date and a + * time, in 12-hour format. */ + format = _("%A, %B %e, %Y %l:%M:%S %p"); + } + } + + /* strftime returns 0 if the string doesn't fit, and leaves the buffer + * undefined, so we set it to the empty string in that case. */ + if (e_utf8_strftime_fix_am_pm (buffer, buffer_size, format, date_tm) == 0) + buffer[0] = '\0'; +} + +static gchar * +dupe_first_bold (const gchar *format, + const gchar *first, + const gchar *second) +{ + gchar *f, *s, *res; + + f = g_markup_printf_escaped ("<b>%s</b>", first ? first : ""); + s = g_markup_escape_text (second ? second : "", -1); + + res = g_strdup_printf (format, f, s); + + g_free (f); + g_free (s); + + return res; +} + +static gchar * +set_calendar_sender_text (ItipView *view) +{ + ItipViewPrivate *priv; + const gchar *organizer, *attendee; + gchar *sender = NULL; + gchar *on_behalf_of = NULL; + + priv = view->priv; + + organizer = priv->organizer ? priv->organizer : _("An unknown person"); + attendee = priv->attendee ? priv->attendee : _("An unknown person"); + + /* The current account ID (i.e. the delegatee) is receiving a copy of the request/response. Here we ask the delegatee to respond/accept on behalf of the delegator. */ + if (priv->organizer && priv->proxy) + on_behalf_of = dupe_first_bold (_("Please respond on behalf of %s"), priv->proxy, NULL); + else if (priv->attendee && priv->proxy) + on_behalf_of = dupe_first_bold (_("Received on behalf of %s"), priv->proxy, NULL); + + switch (priv->mode) { + case ITIP_VIEW_MODE_PUBLISH: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has published the following meeting information:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has published the following meeting information:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_REQUEST: + /* FIXME is the delegator stuff handled correctly here? */ + if (priv->delegator) { + sender = dupe_first_bold (_("%s has delegated the following meeting to you:"), priv->delegator, NULL); + } else { + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s requests your presence at the following meeting:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s requests your presence at the following meeting:"), organizer, NULL); + } + break; + case ITIP_VIEW_MODE_ADD: + /* FIXME What text for this? */ + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s wishes to add to an existing meeting:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s wishes to add to an existing meeting:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_REFRESH: + if (priv->attendee_sentby) + sender = dupe_first_bold (_("%s through %s wishes to receive the latest information for the following meeting:"), attendee, priv->attendee_sentby); + else + sender = dupe_first_bold (_("%s wishes to receive the latest information for the following meeting:"), attendee, NULL); + break; + case ITIP_VIEW_MODE_REPLY: + if (priv->attendee_sentby) + sender = dupe_first_bold (_("%s through %s has sent back the following meeting response:"), attendee, priv->attendee_sentby); + else + sender = dupe_first_bold (_("%s has sent back the following meeting response:"), attendee, NULL); + break; + case ITIP_VIEW_MODE_CANCEL: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has canceled the following meeting:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has canceled the following meeting:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_COUNTER: + if (priv->attendee_sentby) + sender = dupe_first_bold (_("%s through %s has proposed the following meeting changes."), attendee, priv->attendee_sentby); + else + sender = dupe_first_bold (_("%s has proposed the following meeting changes:"), attendee, NULL); + break; + case ITIP_VIEW_MODE_DECLINECOUNTER: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has declined the following meeting changes:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has declined the following meeting changes:"), organizer, NULL); + break; + default: + break; + } + + if (sender && on_behalf_of) { + gchar *tmp; + tmp = g_strjoin (NULL, sender, "\n", on_behalf_of, NULL); + g_free (sender); + sender = tmp; + } + + g_free (on_behalf_of); + + return sender; +} + +static gchar * +set_tasklist_sender_text (ItipView *view) +{ + ItipViewPrivate *priv; + const gchar *organizer, *attendee; + gchar *sender = NULL; + gchar *on_behalf_of = NULL; + + priv = view->priv; + + organizer = priv->organizer ? priv->organizer : _("An unknown person"); + attendee = priv->attendee ? priv->attendee : _("An unknown person"); + + /* The current account ID (i.e. the delegatee) is receiving a copy of the request/response. Here we ask the delegatee to respond/accept on behalf of the delegator. */ + if (priv->organizer && priv->proxy) + on_behalf_of = dupe_first_bold (_("Please respond on behalf of %s"), priv->proxy, NULL); + else if (priv->attendee && priv->proxy) + on_behalf_of = dupe_first_bold (_("Received on behalf of %s"), priv->proxy, NULL); + + switch (priv->mode) { + case ITIP_VIEW_MODE_PUBLISH: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has published the following task:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has published the following task:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_REQUEST: + /* FIXME is the delegator stuff handled correctly here? */ + if (priv->delegator) { + sender = dupe_first_bold (_("%s requests the assignment of %s to the following task:"), organizer, priv->delegator); + } else { + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has assigned you a task:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has assigned you a task:"), organizer, NULL); + } + break; + case ITIP_VIEW_MODE_ADD: + /* FIXME What text for this? */ + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s wishes to add to an existing task:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s wishes to add to an existing task:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_REFRESH: + if (priv->attendee_sentby) + sender = dupe_first_bold (_("%s through %s wishes to receive the latest information for the following assigned task:"), attendee, priv->attendee_sentby); + else + sender = dupe_first_bold (_("%s wishes to receive the latest information for the following assigned task:"), attendee, NULL); + break; + case ITIP_VIEW_MODE_REPLY: + if (priv->attendee_sentby) + sender = dupe_first_bold (_("%s through %s has sent back the following assigned task response:"), attendee, priv->attendee_sentby); + else + sender = dupe_first_bold (_("%s has sent back the following assigned task response:"), attendee, NULL); + break; + case ITIP_VIEW_MODE_CANCEL: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has canceled the following assigned task:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has canceled the following assigned task:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_COUNTER: + if (priv->attendee_sentby) + sender = dupe_first_bold (_("%s through %s has proposed the following task assignment changes:"), attendee, priv->attendee_sentby); + else + sender = dupe_first_bold (_("%s has proposed the following task assignment changes:"), attendee, NULL); + break; + case ITIP_VIEW_MODE_DECLINECOUNTER: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has declined the following assigned task:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has declined the following assigned task:"), organizer, NULL); + break; + default: + break; + } + + if (sender && on_behalf_of) { + gchar *tmp; + tmp = g_strjoin (NULL, sender, "\n", on_behalf_of, NULL); + g_free (sender); + sender = tmp; + } + + g_free (on_behalf_of); + + return sender; +} + +static gchar * +set_journal_sender_text (ItipView *view) +{ + ItipViewPrivate *priv; + const gchar *organizer; + gchar *sender = NULL; + gchar *on_behalf_of = NULL; + + priv = view->priv; + + organizer = priv->organizer ? priv->organizer : _("An unknown person"); + + /* The current account ID (i.e. the delegatee) is receiving a copy of the request/response. Here we ask the delegatee to respond/accept on behalf of the delegator. */ + if (priv->organizer && priv->proxy) + on_behalf_of = dupe_first_bold (_("Please respond on behalf of %s"), priv->proxy, NULL); + else if (priv->attendee && priv->proxy) + on_behalf_of = dupe_first_bold (_("Received on behalf of %s"), priv->proxy, NULL); + + switch (priv->mode) { + case ITIP_VIEW_MODE_PUBLISH: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has published the following memo:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has published the following memo:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_ADD: + /* FIXME What text for this? */ + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s wishes to add to an existing memo:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s wishes to add to an existing memo:"), organizer, NULL); + break; + case ITIP_VIEW_MODE_CANCEL: + if (priv->organizer_sentby) + sender = dupe_first_bold (_("%s through %s has canceled the following shared memo:"), organizer, priv->organizer_sentby); + else + sender = dupe_first_bold (_("%s has canceled the following shared memo:"), organizer, NULL); + break; + default: + break; + } + + if (sender && on_behalf_of) + sender = g_strjoin (NULL, sender, "\n", on_behalf_of, NULL); + + g_free (on_behalf_of); + + return sender; +} + +static void +set_sender_text (ItipView *view) +{ + ItipViewPrivate *priv; + priv = view->priv; + + if (priv->sender) + g_free (priv->sender); + + switch (priv->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + priv->sender = set_calendar_sender_text (view); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + priv->sender = set_tasklist_sender_text (view); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + priv->sender = set_journal_sender_text (view); + break; + default: + priv->sender = NULL; + break; + } + + if (priv->sender && priv->dom_document) { + WebKitDOMElement *div; + + div = webkit_dom_document_get_element_by_id ( + priv->dom_document, TEXT_ROW_SENDER); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (div), priv->sender, NULL); + } +} + +static void +update_start_end_times (ItipView *view) +{ + ItipViewPrivate *priv; + WebKitDOMElement *row, *col; + gchar buffer[256]; + time_t now; + struct tm *now_tm; + + priv = view->priv; + + now = time (NULL); + now_tm = localtime (&now); + + if (priv->start_label) + g_free (priv->start_label); + if (priv->end_label) + g_free (priv->end_label); + + #define is_same(_member) (priv->start_tm->_member == priv->end_tm->_member) + if (priv->start_tm && priv->end_tm && priv->start_tm_is_date && priv->end_tm_is_date + && is_same (tm_mday) && is_same (tm_mon) && is_same (tm_year)) { + /* it's an all day event in one particular day */ + format_date_and_time_x (priv->start_tm, now_tm, FALSE, TRUE, FALSE, priv->start_tm_is_date, buffer, 256); + priv->start_label = g_strdup (buffer); + priv->start_header = _("All day:"); + priv->end_header = NULL; + priv->end_label = NULL; + } else { + if (priv->start_tm) { + format_date_and_time_x (priv->start_tm, now_tm, FALSE, TRUE, FALSE, priv->start_tm_is_date, buffer, 256); + priv->start_header = priv->start_tm_is_date ? _("Start day:") : _("Start time:"); + priv->start_label = g_strdup (buffer); + } else { + priv->start_header = NULL; + priv->start_label = NULL; + } + + if (priv->end_tm) { + format_date_and_time_x (priv->end_tm, now_tm, FALSE, TRUE, FALSE, priv->end_tm_is_date, buffer, 256); + priv->end_header = priv->end_tm_is_date ? _("End day:") : _("End time:"); + priv->end_label = g_strdup (buffer); + } else { + priv->end_header = NULL; + priv->end_label = NULL; + } + } + #undef is_same + + if (priv->dom_document) { + row = webkit_dom_document_get_element_by_id ( + priv->dom_document, TABLE_ROW_START_DATE); + if (priv->start_header && priv->start_label) { + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), FALSE); + + col = webkit_dom_element_get_first_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), priv->start_header, NULL); + + col = webkit_dom_element_get_last_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), priv->start_label, NULL); + } else { + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), TRUE); + } + + row = webkit_dom_document_get_element_by_id ( + priv->dom_document, TABLE_ROW_END_DATE); + if (priv->end_header && priv->end_label) { + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), FALSE); + + col = webkit_dom_element_get_first_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), priv->end_header, NULL); + + col = webkit_dom_element_get_last_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), priv->end_label, NULL); + } else { + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), TRUE); + } + } +} + +static void +button_clicked_cb (WebKitDOMElement *element, + WebKitDOMEvent *event, + gpointer data) +{ + ItipViewResponse response; + gchar *responseStr; + + responseStr = webkit_dom_html_button_element_get_value ( + WEBKIT_DOM_HTML_BUTTON_ELEMENT (element)); + + response = atoi (responseStr); + + //d(printf("Clicked btton %d\n", response)); + g_signal_emit (G_OBJECT (data), signals[RESPONSE], 0, response); +} + +static void +rsvp_toggled_cb (WebKitDOMHTMLInputElement *input, + WebKitDOMEvent *event, + gpointer data) +{ + WebKitDOMElement *el; + + ItipView *view = data; + gboolean rsvp; + + rsvp = webkit_dom_html_input_element_get_checked (input); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TEXTAREA_RSVP_COMMENT); + webkit_dom_html_text_area_element_set_disabled ( + WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT (el), !rsvp); +} + +static void +recur_toggled_cb (WebKitDOMHTMLInputElement *input, + WebKitDOMEvent *event, + gpointer data) +{ + ItipView *view = data; + + itip_view_set_mode (view, view->priv->mode); +} + +/* + alarm_check_toggled_cb + check1 was changed, so make the second available based on state of the first check. +*/ +static void +alarm_check_toggled_cb (WebKitDOMHTMLInputElement *check1, + WebKitDOMEvent *event, + ItipView *view) +{ + WebKitDOMElement *check2; + gchar *id = webkit_dom_html_element_get_id (WEBKIT_DOM_HTML_ELEMENT (check1)); + + if (g_strcmp0 (id, CHECKBOX_INHERIT_ALARM)) { + check2 = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_KEEP_ALARM); + } else { + check2 = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_INHERIT_ALARM); + } + + g_free (id); + + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (check2), + (webkit_dom_html_element_get_hidden ( + WEBKIT_DOM_HTML_ELEMENT (check1)) && + webkit_dom_html_input_element_get_checked (check1))); +} + +static void +source_changed_cb (WebKitDOMElement *select, + WebKitDOMEvent *event, + ItipView *view) +{ + ESource *source; + + source = itip_view_ref_source (view); + + d(printf("Source changed to '%s'\n", e_source_get_display_name (source))); + g_signal_emit (view, signals[SOURCE_SELECTED], 0, source); + + g_object_unref (source); +} + +static gchar * +parse_html_mnemonics (const gchar *label, + gchar **access_key) +{ + const gchar *pos = NULL; + gchar ak = 0; + GString *html_label = NULL; + + pos = strstr (label, "_"); + if (pos != NULL) { + ak = pos[1]; + + /* Convert to uppercase */ + if (ak >= 'a') + ak = ak - 32; + + html_label = g_string_new (""); + g_string_append_len (html_label, label, pos - label); + g_string_append_printf (html_label, "<u>%c</u>", pos[1]); + g_string_append (html_label, &pos[2]); + + if (access_key) { + if (ak) { + *access_key = g_strdup_printf ("%c", ak); + } else { + *access_key = NULL; + } + } + + } else { + html_label = g_string_new (label); + + if (access_key) { + *access_key = NULL; + } + } + + return g_string_free (html_label, FALSE); +} + +static void +append_checkbox_table_row (GString *buffer, + const gchar *name, + const gchar *label) +{ + gchar *access_key, *html_label; + + html_label = parse_html_mnemonics (label, &access_key); + + g_string_append_printf ( + buffer, + "<tr id=\"table_row_%s\" hidden=\"\"><td colspan=\"2\">" + "<input type=\"checkbox\" name=\"%s\" id=\"%s\" value=\"%s\" >" + "<label for=\"%s\" accesskey=\"%s\">%s</label>" + "</td></tr>\n", + name, name, name, name, name, + access_key ? access_key : "", html_label); + + g_free (html_label); + + if (access_key) + g_free (access_key); +} + +static void +append_text_table_row (GString *buffer, + const gchar *id, + const gchar *label, + const gchar *value) +{ + if (label && *label) { + + g_string_append_printf (buffer, + "<tr id=\"%s\" %s><th>%s</th><td>%s</td></tr>\n", + id, (value && *value) ? "" : "hidden=\"\"", label, value); + + } else { + + g_string_append_printf ( + buffer, + "<tr id=\"%s\" hidden=\"\"><td colspan=\"2\"></td></tr>\n", + id); + + } +} + +static void +append_info_item_row (ItipView *view, + const gchar *table_id, + ItipViewInfoItem *item) +{ + WebKitDOMElement *table; + WebKitDOMHTMLElement *row, *cell; + const gchar *icon_name; + gchar *id; + + table = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, table_id); + row = webkit_dom_html_table_element_insert_row ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table), -1, NULL); + + id = g_strdup_printf ("%s_row_%d", table_id, item->id); + webkit_dom_html_element_set_id (row, id); + g_free (id); + + switch (item->type) { + case ITIP_VIEW_INFO_ITEM_TYPE_INFO: + icon_name = GTK_STOCK_DIALOG_INFO; + break; + case ITIP_VIEW_INFO_ITEM_TYPE_WARNING: + icon_name = GTK_STOCK_DIALOG_WARNING; + break; + case ITIP_VIEW_INFO_ITEM_TYPE_ERROR: + icon_name = GTK_STOCK_DIALOG_ERROR; + break; + case ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS: + icon_name = GTK_STOCK_FIND; + break; + case ITIP_VIEW_INFO_ITEM_TYPE_NONE: + default: + icon_name = NULL; + } + + cell = webkit_dom_html_table_row_element_insert_cell ( + (WebKitDOMHTMLTableRowElement *) row, -1, NULL); + + if (icon_name) { + WebKitDOMElement *image; + gchar *icon_uri; + + image = webkit_dom_document_create_element ( + view->priv->dom_document, "IMG", NULL); + + icon_uri = g_strdup_printf ("gtk-stock://%s", icon_name); + webkit_dom_html_image_element_set_src ( + WEBKIT_DOM_HTML_IMAGE_ELEMENT (image), icon_uri); + g_free (icon_uri); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (cell), + WEBKIT_DOM_NODE (image), + NULL); + } + + cell = webkit_dom_html_table_row_element_insert_cell ( + (WebKitDOMHTMLTableRowElement *) row, -1, NULL); + + webkit_dom_html_element_set_inner_html (cell, item->message, NULL); + + d(printf("Added row %s_row_%d ('%s')\n", table_id, item->id, item->message)); +} + +static void +remove_info_item_row (ItipView *view, + const gchar *table_id, + guint id) +{ + WebKitDOMElement *row; + gchar *row_id; + + row_id = g_strdup_printf ("%s_row_%d", table_id, id); + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, row_id); + g_free (row_id); + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)), + WEBKIT_DOM_NODE (row), + NULL); + + d(printf("Removed row %s_row_%d\n", table_id, id)); +} + +static void +buttons_table_write_button (GString *buffer, + const gchar *name, + const gchar *label, + const gchar *icon, + ItipViewResponse response) +{ + gchar *access_key, *html_label; + + html_label = parse_html_mnemonics (label, &access_key); + + g_string_append_printf ( + buffer, + "<td><button type=\"button\" name=\"%s\" value=\"%d\" id=\"%s\" accesskey=\"%s\" hidden>" + "<div><img src=\"gtk-stock://%s?size=%d\"> <span>%s</span></div>" + "</button></td>\n", + name, response, name, access_key ? access_key : "" , icon, + GTK_ICON_SIZE_BUTTON, html_label); + + g_free (html_label); + + if (access_key) + g_free (access_key); +} + +static void +append_buttons_table (GString *buffer) +{ + g_string_append (buffer, + "<table class=\"itip buttons\" border=\"0\" " + "id=\"" TABLE_BUTTONS "\" cellspacing=\"6\" " + "cellpadding=\"0\" >" + "<tr id=\"" TABLE_ROW_BUTTONS "\">"); + + /* Everything gets the open button */ + buttons_table_write_button ( + buffer, BUTTON_OPEN_CALENDAR, _("_Open Calendar"), + GTK_STOCK_JUMP_TO, ITIP_VIEW_RESPONSE_OPEN); + buttons_table_write_button ( + buffer, BUTTON_DECLINE_ALL, _("_Decline all"), + GTK_STOCK_CANCEL, ITIP_VIEW_RESPONSE_DECLINE); + buttons_table_write_button ( + buffer, BUTTON_DECLINE, _("_Decline"), + GTK_STOCK_CANCEL, ITIP_VIEW_RESPONSE_DECLINE); + buttons_table_write_button ( + buffer, BUTTON_TENTATIVE_ALL, _("_Tentative all"), + GTK_STOCK_DIALOG_QUESTION, ITIP_VIEW_RESPONSE_TENTATIVE); + buttons_table_write_button ( + buffer, BUTTON_TENTATIVE, _("_Tentative"), + GTK_STOCK_DIALOG_QUESTION, ITIP_VIEW_RESPONSE_TENTATIVE); + buttons_table_write_button ( + buffer, BUTTON_ACCEPT_ALL, _("A_ccept all"), + GTK_STOCK_APPLY, ITIP_VIEW_RESPONSE_ACCEPT); + buttons_table_write_button ( + buffer, BUTTON_ACCEPT, _("A_ccept"), + GTK_STOCK_APPLY, ITIP_VIEW_RESPONSE_ACCEPT); + buttons_table_write_button ( + buffer, BUTTON_SEND_INFORMATION, _("_Send Information"), + GTK_STOCK_REFRESH, ITIP_VIEW_RESPONSE_REFRESH); + buttons_table_write_button ( + buffer, BUTTON_UPDATE_ATTENDEE_STATUS, _("_Update Attendee Status"), + GTK_STOCK_REFRESH, ITIP_VIEW_RESPONSE_UPDATE); + buttons_table_write_button ( + buffer, BUTTON_UPDATE, _("_Update"), + GTK_STOCK_REFRESH, ITIP_VIEW_RESPONSE_CANCEL); + + g_string_append (buffer, "</tr></table>"); +} + +static void +itip_view_rebuild_source_list (ItipView *view) +{ + ESourceRegistry *registry; + WebKitDOMElement *select; + GList *list, *link; + const gchar *extension_name; + + d(printf("Assigning a new source list!\n")); + + if (!view->priv->dom_document) + return; + + registry = itip_view_get_registry (view); + extension_name = itip_view_get_extension_name (view); + + select = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, SELECT_ESOURCE); + + while (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (select))) { + webkit_dom_node_remove_child ( + WEBKIT_DOM_NODE (select), + webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (select)), + NULL); + } + + if (extension_name == NULL) + return; + + list = e_source_registry_list_sources (registry, extension_name); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + WebKitDOMElement *option; + + option = webkit_dom_document_create_element ( + view->priv->dom_document, "OPTION", NULL); + webkit_dom_html_option_element_set_value ( + WEBKIT_DOM_HTML_OPTION_ELEMENT (option), + e_source_get_uid (source)); + webkit_dom_html_option_element_set_label ( + WEBKIT_DOM_HTML_OPTION_ELEMENT (option), + e_source_get_display_name (source)); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (option), + e_source_get_display_name (source), NULL); + webkit_dom_html_element_set_class_name ( + WEBKIT_DOM_HTML_ELEMENT (option), "calendar"); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (select), + WEBKIT_DOM_NODE (option), + NULL); + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + source_changed_cb (select, NULL, view); +} + +static void +itip_view_source_added_cb (ESourceRegistry *registry, + ESource *source, + ItipView *view) +{ + const gchar *extension_name; + + extension_name = itip_view_get_extension_name (view); + + /* If we don't have an extension name set + * yet then disregard the signal emission. */ + if (extension_name == NULL) + return; + + if (e_source_has_extension (source, extension_name)) + itip_view_rebuild_source_list (view); +} + +static void +itip_view_source_removed_cb (ESourceRegistry *registry, + ESource *source, + ItipView *view) +{ + const gchar *extension_name; + + extension_name = itip_view_get_extension_name (view); + + /* If we don't have an extension name set + * yet then disregard the signal emission. */ + if (extension_name == NULL) + return; + + if (e_source_has_extension (source, extension_name)) + itip_view_rebuild_source_list (view); +} + +static void +itip_view_set_registry (ItipView *view, + ESourceRegistry *registry) +{ + g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); + g_return_if_fail (view->priv->registry == NULL); + + view->priv->registry = g_object_ref (registry); +} + +static void +itip_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_EXTENSION_NAME: + itip_view_set_extension_name ( + ITIP_VIEW (object), + g_value_get_string (value)); + return; + + case PROP_REGISTRY: + itip_view_set_registry ( + ITIP_VIEW (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +itip_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_EXTENSION_NAME: + g_value_set_string ( + value, itip_view_get_extension_name ( + ITIP_VIEW (object))); + return; + + case PROP_REGISTRY: + g_value_set_object ( + value, itip_view_get_registry ( + ITIP_VIEW (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +itip_view_dispose (GObject *object) +{ + ItipViewPrivate *priv; + + priv = ITIP_VIEW_GET_PRIVATE (object); + + if (priv->registry != NULL) { + g_signal_handler_disconnect ( + priv->registry, priv->source_added_id); + g_signal_handler_disconnect ( + priv->registry, priv->source_removed_id); + g_object_unref (priv->registry); + priv->registry = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (itip_view_parent_class)->dispose (object); +} + +static void +itip_view_finalize (GObject *object) +{ + ItipViewPrivate *priv; + GSList *iter; + + priv = ITIP_VIEW_GET_PRIVATE (object); + + d(printf("Itip view finalized!\n")); + + g_free (priv->extension_name); + g_free (priv->sender); + g_free (priv->organizer); + g_free (priv->organizer_sentby); + g_free (priv->delegator); + g_free (priv->attendee); + g_free (priv->attendee_sentby); + g_free (priv->proxy); + g_free (priv->summary); + g_free (priv->location); + g_free (priv->status); + g_free (priv->comment); + g_free (priv->start_tm); + g_free (priv->start_label); + g_free (priv->end_tm); + g_free (priv->end_label); + g_free (priv->description); + g_free (priv->error); + + for (iter = priv->lower_info_items; iter; iter = iter->next) { + ItipViewInfoItem *item = iter->data; + g_free (item->message); + g_free (item); + } + + g_slist_free (priv->lower_info_items); + + for (iter = priv->upper_info_items; iter; iter = iter->next) { + ItipViewInfoItem *item = iter->data; + g_free (item->message); + g_free (item); + } + + g_slist_free (priv->upper_info_items); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (itip_view_parent_class)->finalize (object); +} + +static void +itip_view_constructed (GObject *object) +{ + ItipView *view; + ESourceRegistry *registry; + + view = ITIP_VIEW (object); + registry = itip_view_get_registry (view); + + view->priv->source_added_id = g_signal_connect ( + registry, "source-added", + G_CALLBACK (itip_view_source_added_cb), view); + + view->priv->source_removed_id = g_signal_connect ( + registry, "source-removed", + G_CALLBACK (itip_view_source_removed_cb), view); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (itip_view_parent_class)->constructed (object); +} + +static void +itip_view_class_init (ItipViewClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (ItipViewPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = itip_view_set_property; + object_class->get_property = itip_view_get_property; + object_class->dispose = itip_view_dispose; + object_class->finalize = itip_view_finalize; + object_class->constructed = itip_view_constructed; + + g_object_class_install_property ( + object_class, + PROP_REGISTRY, + g_param_spec_string ( + "extension-name", + "Extension Name", + "Show only data sources with this extension", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_REGISTRY, + g_param_spec_object ( + "registry", + "Registry", + "Data source registry", + E_TYPE_SOURCE_REGISTRY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + signals[SOURCE_SELECTED] = g_signal_new ( + "source_selected", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ItipViewClass, source_selected), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_SOURCE); + + signals[RESPONSE] = g_signal_new ( + "response", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ItipViewClass, response), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); +} + +EMailPartItip * +itip_view_get_mail_part (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->itip_part; +} + +ESourceRegistry * +itip_view_get_registry (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->registry; +} + +const gchar * +itip_view_get_extension_name (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->extension_name; +} + +void +itip_view_set_extension_name (ItipView *view, + const gchar *extension_name) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + /* Avoid unnecessary rebuilds. */ + if (g_strcmp0 (extension_name, view->priv->extension_name) == 0) + return; + + g_free (view->priv->extension_name); + view->priv->extension_name = g_strdup (extension_name); + + g_object_notify (G_OBJECT (view), "extension-name"); + + itip_view_rebuild_source_list (view); +} + +void +itip_view_write (GString *buffer) +{ + g_string_append (buffer, + "<html>\n" + "<head>\n" + "<title>ITIP</title>\n" + "<link type=\"text/css\" rel=\"stylesheet\" href=\"evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview.css\" />\n" + "</head>\n" + "<body>\n"); + + g_string_append_printf (buffer, + "<img src=\"gtk-stock://%s?size=%d\" class=\"itip icon\" />\n", + MEETING_ICON, GTK_ICON_SIZE_BUTTON); + + g_string_append (buffer, + "<div class=\"itip content\" id=\"" DIV_ITIP_CONTENT "\">\n"); + + /* The first section listing the sender */ + /* FIXME What to do if the send and organizer do not match */ + g_string_append (buffer, + "<div id=\"" TEXT_ROW_SENDER "\" class=\"itip sender\"></div>\n"); + + g_string_append (buffer, "<hr>\n"); + + /* Elementary event information */ + g_string_append (buffer, + "<table class=\"itip table\" border=\"0\" " + "cellspacing=\"5\" cellpadding=\"0\">\n"); + + append_text_table_row (buffer, TABLE_ROW_SUMMARY, NULL, NULL); + append_text_table_row (buffer, TABLE_ROW_LOCATION, _("Location:"), NULL); + append_text_table_row (buffer, TABLE_ROW_START_DATE, _("Start time:"), NULL); + append_text_table_row (buffer, TABLE_ROW_END_DATE, _("End time:"), NULL); + append_text_table_row (buffer, TABLE_ROW_STATUS, _("Status:"), NULL); + append_text_table_row (buffer, TABLE_ROW_COMMENT, _("Comment:"), NULL); + + g_string_append (buffer, "</table>\n"); + + /* Upper Info items */ + g_string_append (buffer, + "<table class=\"itip info\" id=\"" TABLE_UPPER_ITIP_INFO "\" border=\"0\" " + "cellspacing=\"5\" cellpadding=\"0\">"); + + /* Description */ + g_string_append (buffer, + "<div id=\"" TABLE_ROW_DESCRIPTION "\" class=\"itip description\" hidden=\"\"></div>\n"); + + g_string_append (buffer, "<hr>\n"); + + /* Lower Info items */ + g_string_append (buffer, + "<table class=\"itip info\" id=\"" TABLE_LOWER_ITIP_INFO "\" border=\"0\" " + "cellspacing=\"5\" cellpadding=\"0\">"); + + g_string_append (buffer, + "<table class=\"itip table\" border=\"0\" " + "cellspacing=\"5\" cellpadding=\"0\">\n"); + + g_string_append (buffer, + "<tr id=\"" TABLE_ROW_ESCB "\" hidden=\"\""">" + "<th><label id=\"" TABLE_ROW_ESCB_LABEL "\" for=\"" SELECT_ESOURCE "\"></label></th>" + "<td><select name=\"" SELECT_ESOURCE "\" id=\"" SELECT_ESOURCE "\"></select></td>" + "</tr>\n"); + + /* RSVP area */ + append_checkbox_table_row (buffer, CHECKBOX_RSVP, _("Send reply to sender")); + + /* Comments */ + g_string_append_printf (buffer, + "<tr id=\"" TABLE_ROW_RSVP_COMMENT "\" hidden=\"\">" + "<th>%s</th>" + "<td><textarea name=\"" TEXTAREA_RSVP_COMMENT "\" " + "id=\"" TEXTAREA_RSVP_COMMENT "\" " + "rows=\"3\" cols=\"40\" disabled=\"\">" + "</textarea></td>\n" + "</tr>\n", + _("Comment:")); + + /* Updates */ + append_checkbox_table_row (buffer, CHECKBOX_UPDATE, _("Send _updates to attendees")); + + /* The recurrence check button */ + append_checkbox_table_row (buffer, CHECKBOX_RECUR, _("_Apply to all instances")); + append_checkbox_table_row (buffer, CHECKBOX_FREE_TIME, _("Show time as _free")); + append_checkbox_table_row (buffer, CHECKBOX_KEEP_ALARM, _("_Preserve my reminder")); + append_checkbox_table_row (buffer, CHECKBOX_INHERIT_ALARM, _("_Inherit reminder")); + + g_string_append (buffer, "</table>\n"); + + /* Buttons table */ + append_buttons_table (buffer); + + /* <div class="itip content" > */ + g_string_append (buffer, "</div>\n"); + + g_string_append (buffer, "<div class=\"itip error\" id=\"" DIV_ITIP_ERROR "\"></div>"); + + g_string_append (buffer, "</body></html>"); +} + +void +itip_view_write_for_printing (ItipView *view, + GString *buffer) +{ + if (view->priv->error && *view->priv->error) { + g_string_append (buffer, view->priv->error); + return; + } + + g_string_append (buffer, + "<div class=\"itip print_content\" id=\"" DIV_ITIP_CONTENT "\">\n"); + + /* The first section listing the sender */ + /* FIXME What to do if the send and organizer do not match */ + g_string_append_printf (buffer, + "<div id=\"" TEXT_ROW_SENDER "\" class=\"itip sender\">%s</div>\n", + view->priv->sender ? view->priv->sender : ""); + + g_string_append (buffer, "<hr>\n"); + + /* Elementary event information */ + g_string_append (buffer, + "<table class=\"itip table\" border=\"0\" " + "cellspacing=\"5\" cellpadding=\"0\">\n"); + + append_text_table_row ( + buffer, TABLE_ROW_SUMMARY, + NULL, view->priv->summary); + append_text_table_row ( + buffer, TABLE_ROW_LOCATION, + _("Location:"), view->priv->location); + append_text_table_row ( + buffer, TABLE_ROW_START_DATE, + view->priv->start_header, view->priv->start_label); + append_text_table_row ( + buffer, TABLE_ROW_END_DATE, + view->priv->end_header, view->priv->end_label); + append_text_table_row ( + buffer, TABLE_ROW_STATUS, + _("Status:"), view->priv->status); + append_text_table_row ( + buffer, TABLE_ROW_COMMENT, + _("Comment:"), view->priv->comment); + + g_string_append (buffer, "</table>\n"); + + /* Description */ + g_string_append_printf ( + buffer, + "<div id=\"" TABLE_ROW_DESCRIPTION "\" " + "class=\"itip description\" %s>%s</div>\n", + view->priv->description ? "" : "hidden=\"\"", view->priv->description); + + g_string_append (buffer, "</div>"); +} + +void +itip_view_create_dom_bindings (ItipView *view, + WebKitDOMElement *element) +{ + WebKitDOMElement *el; + WebKitDOMDocument *doc; + + doc = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element)); + view->priv->dom_document = doc; + + el = webkit_dom_document_get_element_by_id (doc, CHECKBOX_RECUR); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (recur_toggled_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, CHECKBOX_RSVP); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (rsvp_toggled_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, CHECKBOX_INHERIT_ALARM); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (alarm_check_toggled_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, CHECKBOX_KEEP_ALARM); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (alarm_check_toggled_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_OPEN_CALENDAR); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_ACCEPT); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_ACCEPT_ALL); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_TENTATIVE); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_TENTATIVE_ALL); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_DECLINE); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_DECLINE_ALL); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_UPDATE); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_UPDATE_ATTENDEE_STATUS); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, BUTTON_SEND_INFORMATION); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } + + el = webkit_dom_document_get_element_by_id (doc, SELECT_ESOURCE); + if (el) { + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "change", + G_CALLBACK (source_changed_cb), FALSE, view); + } +} + +static void +itip_view_init (ItipView *view) +{ + view->priv = ITIP_VIEW_GET_PRIVATE (view); + +} + +ItipView * +itip_view_new (EMailPartItip *puri, + ESourceRegistry *registry) +{ + ItipView *view; + + view = ITIP_VIEW (g_object_new ( + ITIP_TYPE_VIEW, + "registry", registry, + NULL)); + view->priv->itip_part = puri; + + return view; +} + +static void +show_button (ItipView *view, + const gchar *id) +{ + WebKitDOMElement *button; + + button = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, id); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (button), FALSE); +} + +void +itip_view_set_mode (ItipView *view, + ItipViewMode mode) +{ + WebKitDOMElement *row, *cell; + WebKitDOMElement *button; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + view->priv->mode = mode; + + set_sender_text (view); + + if (!view->priv->dom_document) + return; + + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_BUTTONS); + cell = webkit_dom_element_get_first_element_child (row); + do { + button = webkit_dom_element_get_first_element_child (cell); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (button), TRUE); + } while ((cell = webkit_dom_element_get_next_element_sibling (cell)) != NULL); + + view->priv->is_recur_set = itip_view_get_recur_check_state (view); + + /* Always visible */ + show_button (view, BUTTON_OPEN_CALENDAR); + + switch (mode) { + case ITIP_VIEW_MODE_PUBLISH: + if (view->priv->needs_decline) { + show_button (view, BUTTON_DECLINE); + } + show_button (view, BUTTON_ACCEPT); + break; + case ITIP_VIEW_MODE_REQUEST: + show_button (view, view->priv->is_recur_set ? BUTTON_DECLINE_ALL : BUTTON_DECLINE); + show_button (view, view->priv->is_recur_set ? BUTTON_TENTATIVE_ALL : BUTTON_TENTATIVE); + show_button (view, view->priv->is_recur_set ? BUTTON_ACCEPT_ALL : BUTTON_ACCEPT); + break; + case ITIP_VIEW_MODE_ADD: + if (view->priv->type != E_CAL_CLIENT_SOURCE_TYPE_MEMOS) { + show_button (view, BUTTON_DECLINE); + show_button (view, BUTTON_TENTATIVE); + } + show_button (view, BUTTON_ACCEPT); + break; + case ITIP_VIEW_MODE_REFRESH: + show_button (view, BUTTON_SEND_INFORMATION); + break; + case ITIP_VIEW_MODE_REPLY: + show_button (view, BUTTON_UPDATE_ATTENDEE_STATUS); + break; + case ITIP_VIEW_MODE_CANCEL: + show_button (view, BUTTON_UPDATE); + break; + case ITIP_VIEW_MODE_COUNTER: + case ITIP_VIEW_MODE_DECLINECOUNTER: + show_button (view, BUTTON_DECLINE); + show_button (view, BUTTON_TENTATIVE); + show_button (view, BUTTON_ACCEPT); + break; + default: + break; + } +} + +ItipViewMode +itip_view_get_mode (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), ITIP_VIEW_MODE_NONE); + + return view->priv->mode; +} + +void +itip_view_set_item_type (ItipView *view, + ECalClientSourceType type) +{ + WebKitDOMElement *label; + const gchar *header; + gchar *access_key, *html_label; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + view->priv->type = type; + + if (!view->priv->dom_document) + return; + + label = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_ESCB_LABEL); + + switch (view->priv->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + header = _("_Calendar:"); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + header = _("_Tasks:"); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + header = _("_Memos:"); + break; + default: + header = NULL; + break; + } + + if (!header) { + set_sender_text (view); + return; + } + + html_label = parse_html_mnemonics (header, &access_key); + + webkit_dom_html_element_set_access_key ( + WEBKIT_DOM_HTML_ELEMENT (label), access_key); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (label), html_label, NULL); + + g_free (html_label); + + if (access_key) + g_free (access_key); + + set_sender_text (view); +} + +ECalClientSourceType +itip_view_get_item_type (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), ITIP_VIEW_MODE_NONE); + + return view->priv->type; +} + +void +itip_view_set_organizer (ItipView *view, + const gchar *organizer) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->organizer) + g_free (view->priv->organizer); + + view->priv->organizer = e_utf8_ensure_valid (organizer); + + set_sender_text (view); +} + +const gchar * +itip_view_get_organizer (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->organizer; +} + +void +itip_view_set_organizer_sentby (ItipView *view, + const gchar *sentby) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->organizer_sentby) + g_free (view->priv->organizer_sentby); + + view->priv->organizer_sentby = e_utf8_ensure_valid (sentby); + + set_sender_text (view); +} + +const gchar * +itip_view_get_organizer_sentby (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->organizer_sentby; +} + +void +itip_view_set_attendee (ItipView *view, + const gchar *attendee) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->attendee) + g_free (view->priv->attendee); + + view->priv->attendee = e_utf8_ensure_valid (attendee); + + set_sender_text (view); +} + +const gchar * +itip_view_get_attendee (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->attendee; +} + +void +itip_view_set_attendee_sentby (ItipView *view, + const gchar *sentby) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->attendee_sentby) + g_free (view->priv->attendee_sentby); + + view->priv->attendee_sentby = e_utf8_ensure_valid (sentby); + + set_sender_text (view); +} + +const gchar * +itip_view_get_attendee_sentby (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->attendee_sentby; +} + +void +itip_view_set_proxy (ItipView *view, + const gchar *proxy) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->proxy) + g_free (view->priv->proxy); + + view->priv->proxy = e_utf8_ensure_valid (proxy); + + set_sender_text (view); +} + +const gchar * +itip_view_get_proxy (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->proxy; +} + +void +itip_view_set_delegator (ItipView *view, + const gchar *delegator) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->delegator) + g_free (view->priv->delegator); + + view->priv->delegator = e_utf8_ensure_valid (delegator); + + set_sender_text (view); +} + +const gchar * +itip_view_get_delegator (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->delegator; +} + +void +itip_view_set_summary (ItipView *view, + const gchar *summary) +{ + WebKitDOMElement *row, *col; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->summary) + g_free (view->priv->summary); + + view->priv->summary = summary ? g_strstrip (e_utf8_ensure_valid (summary)) : NULL; + + if (!view->priv->dom_document) + return; + + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_SUMMARY); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), (view->priv->summary == NULL)); + + col = webkit_dom_element_get_last_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), + view->priv->summary ? view->priv->summary : "", + NULL); +} + +const gchar * +itip_view_get_summary (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->summary; +} + +void +itip_view_set_location (ItipView *view, + const gchar *location) +{ + WebKitDOMElement *row, *col; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->location) + g_free (view->priv->location); + + view->priv->location = location ? g_strstrip (e_utf8_ensure_valid (location)) : NULL; + + if (!view->priv->dom_document) + return; + + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_LOCATION); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), (view->priv->location == NULL)); + + col = webkit_dom_element_get_last_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), + view->priv->location ? view->priv->location : "", + NULL); +} + +const gchar * +itip_view_get_location (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->location; +} + +void +itip_view_set_status (ItipView *view, + const gchar *status) +{ + WebKitDOMElement *row, *col; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->status) + g_free (view->priv->status); + + view->priv->status = status ? g_strstrip (e_utf8_ensure_valid (status)) : NULL; + + if (!view->priv->dom_document) + return; + + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_STATUS); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), (view->priv->status == NULL)); + + col = webkit_dom_element_get_last_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), + view->priv->status ? view->priv->status : "", + NULL); +} + +const gchar * +itip_view_get_status (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->status; +} + +void +itip_view_set_comment (ItipView *view, + const gchar *comment) +{ + WebKitDOMElement *row, *col; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->comment) + g_free (view->priv->comment); + + view->priv->comment = comment ? g_strstrip (e_utf8_ensure_valid (comment)) : NULL; + + if (!view->priv->dom_document) + return; + + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_COMMENT); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), (view->priv->comment == NULL)); + + col = webkit_dom_element_get_last_element_child (row); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (col), + view->priv->comment ? view->priv->comment : "", + NULL); +} + +const gchar * +itip_view_get_comment (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->comment; +} + +void +itip_view_set_description (ItipView *view, + const gchar *description) +{ + WebKitDOMElement *div; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (view->priv->description) + g_free (view->priv->description); + + view->priv->description = description ? g_strstrip (e_utf8_ensure_valid (description)) : NULL; + + if (!view->priv->dom_document) + return; + + div = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_DESCRIPTION); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (div), (view->priv->description == NULL)); + + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (div), + view->priv->description ? view->priv->description : "", + NULL); +} + +const gchar * +itip_view_get_description (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + return view->priv->description; +} + +void +itip_view_set_start (ItipView *view, + struct tm *start, + gboolean is_date) +{ + ItipViewPrivate *priv; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + if (priv->start_tm && !start) { + g_free (priv->start_tm); + priv->start_tm = NULL; + } else if (start) { + if (!priv->start_tm) + priv->start_tm = g_new0 (struct tm, 1); + + *priv->start_tm = *start; + } + + priv->start_tm_is_date = is_date && start; + + update_start_end_times (view); +} + +const struct tm * +itip_view_get_start (ItipView *view, + gboolean *is_date) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + if (is_date) + *is_date = view->priv->start_tm_is_date; + + return view->priv->start_tm; +} + +void +itip_view_set_end (ItipView *view, + struct tm *end, + gboolean is_date) +{ + ItipViewPrivate *priv; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + if (priv->end_tm && !end) { + g_free (priv->end_tm); + priv->end_tm = NULL; + } else if (end) { + if (!priv->end_tm) + priv->end_tm = g_new0 (struct tm, 1); + + *priv->end_tm = *end; + } + + priv->end_tm_is_date = is_date && end; + + update_start_end_times (view); +} + +const struct tm * +itip_view_get_end (ItipView *view, + gboolean *is_date) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + if (is_date) + *is_date = view->priv->end_tm_is_date; + + return view->priv->end_tm; +} + +guint +itip_view_add_upper_info_item (ItipView *view, + ItipViewInfoItemType type, + const gchar *message) +{ + ItipViewPrivate *priv; + ItipViewInfoItem *item; + + g_return_val_if_fail (ITIP_IS_VIEW (view), 0); + + priv = view->priv; + + item = g_new0 (ItipViewInfoItem, 1); + + item->type = type; + item->message = e_utf8_ensure_valid (message); + item->id = priv->next_info_item_id++; + + priv->upper_info_items = g_slist_append (priv->upper_info_items, item); + + if (!view->priv->dom_document) + return item->id; + + append_info_item_row (view, TABLE_UPPER_ITIP_INFO, item); + + return item->id; +} + +guint +itip_view_add_upper_info_item_printf (ItipView *view, + ItipViewInfoItemType type, + const gchar *format, + ...) +{ + va_list args; + gchar *message; + guint id; + + g_return_val_if_fail (ITIP_IS_VIEW (view), 0); + + va_start (args, format); + message = g_strdup_vprintf (format, args); + va_end (args); + + id = itip_view_add_upper_info_item (view, type, message); + g_free (message); + + return id; +} + +void +itip_view_remove_upper_info_item (ItipView *view, + guint id) +{ + ItipViewPrivate *priv; + GSList *l; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + for (l = priv->upper_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + if (item->id == id) { + priv->upper_info_items = g_slist_remove (priv->upper_info_items, item); + + g_free (item->message); + g_free (item); + + if (!view->priv->dom_document) + remove_info_item_row (view, TABLE_UPPER_ITIP_INFO, id); + + return; + } + } +} + +void +itip_view_clear_upper_info_items (ItipView *view) +{ + ItipViewPrivate *priv; + GSList *l; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + for (l = priv->upper_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + if (view->priv->dom_document) + remove_info_item_row (view, TABLE_UPPER_ITIP_INFO, item->id); + + g_free (item->message); + g_free (item); + } + + g_slist_free (priv->upper_info_items); + priv->upper_info_items = NULL; +} + +guint +itip_view_add_lower_info_item (ItipView *view, + ItipViewInfoItemType type, + const gchar *message) +{ + ItipViewPrivate *priv; + ItipViewInfoItem *item; + + g_return_val_if_fail (ITIP_IS_VIEW (view), 0); + + priv = view->priv; + + item = g_new0 (ItipViewInfoItem, 1); + + item->type = type; + item->message = e_utf8_ensure_valid (message); + item->id = priv->next_info_item_id++; + + priv->lower_info_items = g_slist_append (priv->lower_info_items, item); + + if (!view->priv->dom_document) + return item->id; + + append_info_item_row (view, TABLE_LOWER_ITIP_INFO, item); + + return item->id; +} + +guint +itip_view_add_lower_info_item_printf (ItipView *view, + ItipViewInfoItemType type, + const gchar *format, + ...) +{ + va_list args; + gchar *message; + guint id; + + g_return_val_if_fail (ITIP_IS_VIEW (view), 0); + + va_start (args, format); + message = g_strdup_vprintf (format, args); + va_end (args); + + id = itip_view_add_lower_info_item (view, type, message); + g_free (message); + + return id; +} + +void +itip_view_remove_lower_info_item (ItipView *view, + guint id) +{ + ItipViewPrivate *priv; + GSList *l; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + for (l = priv->lower_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + if (item->id == id) { + priv->lower_info_items = g_slist_remove (priv->lower_info_items, item); + + g_free (item->message); + g_free (item); + + if (view->priv->dom_document) + remove_info_item_row (view, TABLE_LOWER_ITIP_INFO, id); + + return; + } + } +} + +void +itip_view_clear_lower_info_items (ItipView *view) +{ + ItipViewPrivate *priv; + GSList *l; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + for (l = priv->lower_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + if (view->priv->dom_document) + remove_info_item_row (view, TABLE_LOWER_ITIP_INFO, item->id); + + g_free (item->message); + g_free (item); + } + + g_slist_free (priv->lower_info_items); + priv->lower_info_items = NULL; +} + +void +itip_view_set_source (ItipView *view, + ESource *source) +{ + WebKitDOMElement *select; + WebKitDOMElement *row; + ESource *selected_source; + gulong i, len; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + d(printf("Settings default source '%s'\n", e_source_get_display_name (source))); + + if (!view->priv->dom_document) + return; + + row = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_ESCB); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (row), (source == NULL)); + if (source == NULL) + return; + + select = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, SELECT_ESOURCE); + + /* <select> does not emit 'change' event when already selected + * <option> is re-selected, but we need to notify itip formatter, + * so that it would make all the buttons sensitive */ + selected_source = itip_view_ref_source (view); + if (source == selected_source) + source_changed_cb (select, NULL, view); + + if (selected_source != NULL) + g_object_unref (selected_source); + + if (webkit_dom_html_select_element_get_disabled ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select))) { + webkit_dom_html_select_element_set_disabled ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select), FALSE); + } + + len = webkit_dom_html_select_element_get_length ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select)); + for (i = 0; i < len; i++) { + + WebKitDOMNode *node; + WebKitDOMHTMLOptionElement *option; + gchar *value; + + node = webkit_dom_html_select_element_item ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select), i); + option = WEBKIT_DOM_HTML_OPTION_ELEMENT (node); + + value = webkit_dom_html_option_element_get_value (option); + if (g_strcmp0 (value, e_source_get_uid (source)) == 0) { + webkit_dom_html_option_element_set_selected ( + option, TRUE); + + g_free (value); + break; + } + + g_free (value); + } +} + +ESource * +itip_view_ref_source (ItipView *view) +{ + ESourceRegistry *registry; + WebKitDOMElement *select; + gchar *uid; + ESource *source; + gboolean disable = FALSE; + + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + if (!view->priv->dom_document) + return NULL; + + select = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, SELECT_ESOURCE); + if (webkit_dom_html_select_element_get_disabled ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select))) { + webkit_dom_html_select_element_set_disabled ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select), FALSE); + disable = TRUE; + } + + uid = webkit_dom_html_select_element_get_value ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select)); + + registry = itip_view_get_registry (view); + source = e_source_registry_ref_source (registry, uid); + + g_free (uid); + + if (disable) { + webkit_dom_html_select_element_set_disabled ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (select), TRUE); + } + + return source; +} + +void +itip_view_set_rsvp (ItipView *view, + gboolean rsvp) +{ + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RSVP); + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), rsvp); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TEXTAREA_RSVP_COMMENT); + webkit_dom_html_text_area_element_set_disabled ( + WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT (el), !rsvp); +} + +gboolean +itip_view_get_rsvp (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_UPDATE); + return webkit_dom_html_input_element_get_checked (WEBKIT_DOM_HTML_INPUT_ELEMENT (el)); +} + +void +itip_view_set_show_rsvp_check (ItipView *view, + gboolean show) +{ + WebKitDOMElement *label; + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, "table_row_" CHECKBOX_RSVP); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RSVP); + label = webkit_dom_element_get_next_element_sibling (el); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (label), !show); + + if (!show) { + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), FALSE); + } + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_RSVP_COMMENT); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); +} + +gboolean +itip_view_get_show_rsvp_check (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RSVP); + return !webkit_dom_html_element_get_hidden (WEBKIT_DOM_HTML_ELEMENT (el)); +} + +void +itip_view_set_update (ItipView *view, + gboolean update) +{ + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_UPDATE); + + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), update); +} + +gboolean +itip_view_get_update (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_UPDATE); + return webkit_dom_html_input_element_get_checked (WEBKIT_DOM_HTML_INPUT_ELEMENT (el)); +} + +void +itip_view_set_show_update_check (ItipView *view, + gboolean show) +{ + WebKitDOMElement *label; + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, "table_row_" CHECKBOX_UPDATE); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_UPDATE); + label = webkit_dom_element_get_next_element_sibling (el); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (label), !show); + + if (!show) { + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), FALSE); + } +} + +gboolean +itip_view_get_show_update_check (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_UPDATE); + return !webkit_dom_html_element_get_hidden (WEBKIT_DOM_HTML_ELEMENT (el)); +} + +void +itip_view_set_rsvp_comment (ItipView *view, + const gchar *comment) +{ + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TEXTAREA_RSVP_COMMENT); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (el), (comment == NULL)); + + if (comment) { + webkit_dom_html_text_area_element_set_value ( + WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT (el), comment); + } +} + +gchar * +itip_view_get_rsvp_comment (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), NULL); + + if (!view->priv->dom_document) + return NULL; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TEXTAREA_RSVP_COMMENT); + + if (webkit_dom_html_element_get_hidden (WEBKIT_DOM_HTML_ELEMENT (el))) { + return NULL; + } + + return webkit_dom_html_text_area_element_get_value ( + WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT (el)); +} + +void +itip_view_set_needs_decline (ItipView *view, + gboolean needs_decline) +{ + g_return_if_fail (ITIP_IS_VIEW (view)); + + view->priv->needs_decline = needs_decline; +} + +void +itip_view_set_buttons_sensitive (ItipView *view, + gboolean sensitive) +{ + WebKitDOMElement *el, *cell; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + d(printf("Settings buttons %s\n", sensitive ? "sensitive" : "insensitive")); + + view->priv->buttons_sensitive = sensitive; + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_UPDATE); + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RECUR); + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_FREE_TIME); + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_KEEP_ALARM); + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_INHERIT_ALARM); + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RSVP); + webkit_dom_html_input_element_set_disabled ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TEXTAREA_RSVP_COMMENT); + webkit_dom_html_text_area_element_set_disabled ( + WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, SELECT_ESOURCE); + webkit_dom_html_select_element_set_disabled ( + WEBKIT_DOM_HTML_SELECT_ELEMENT (el), !sensitive); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, TABLE_ROW_BUTTONS); + cell = webkit_dom_element_get_first_element_child (el); + do { + WebKitDOMElement *btn; + btn = webkit_dom_element_get_first_element_child (cell); + if (!webkit_dom_html_element_get_hidden ( + WEBKIT_DOM_HTML_ELEMENT (btn))) { + webkit_dom_html_button_element_set_disabled ( + WEBKIT_DOM_HTML_BUTTON_ELEMENT (btn), !sensitive); + } + } while ((cell = webkit_dom_element_get_next_element_sibling (cell)) != NULL); +} + +gboolean +itip_view_get_buttons_sensitive (ItipView *view) +{ + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + return view->priv->buttons_sensitive; +} + +gboolean +itip_view_get_recur_check_state (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RECUR); + return webkit_dom_html_input_element_get_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el)); +} + +void +itip_view_set_show_recur_check (ItipView *view, + gboolean show) +{ + WebKitDOMElement *label; + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, "table_row_" CHECKBOX_RECUR); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_RECUR); + label = webkit_dom_element_get_next_element_sibling (el); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (label), !show); + + if (!show) { + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), FALSE); + } + + /* and update state of the second check */ + alarm_check_toggled_cb ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), + NULL, view); +} + +void +itip_view_set_show_free_time_check (ItipView *view, + gboolean show) +{ + WebKitDOMElement *label; + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, "table_row_" CHECKBOX_FREE_TIME); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_FREE_TIME); + label = webkit_dom_element_get_next_element_sibling (el); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (label), !show); + + if (!show) { + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), FALSE); + } + + /* and update state of the second check */ + alarm_check_toggled_cb ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), + NULL, view); +} + +gboolean +itip_view_get_free_time_check_state (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_FREE_TIME); + return webkit_dom_html_input_element_get_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el)); +} + +void +itip_view_set_show_keep_alarm_check (ItipView *view, + gboolean show) +{ + WebKitDOMElement *label; + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, "table_row_" CHECKBOX_KEEP_ALARM); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_KEEP_ALARM); + label = webkit_dom_element_get_next_element_sibling (el); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (label), !show); + + if (!show) { + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), FALSE); + } + + /* and update state of the second check */ + alarm_check_toggled_cb ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), + NULL, view); +} + +gboolean +itip_view_get_keep_alarm_check_state (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_KEEP_ALARM); + return webkit_dom_html_input_element_get_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el)); +} + +void +itip_view_set_show_inherit_alarm_check (ItipView *view, + gboolean show) +{ + WebKitDOMElement *label; + WebKitDOMElement *el; + + g_return_if_fail (ITIP_IS_VIEW (view)); + + if (!view->priv->dom_document) + return; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, "table_row_" CHECKBOX_INHERIT_ALARM); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (el), !show); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_INHERIT_ALARM); + label = webkit_dom_element_get_next_element_sibling (el); + webkit_dom_html_element_set_hidden (WEBKIT_DOM_HTML_ELEMENT (label), !show); + + if (!show) { + webkit_dom_html_input_element_set_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), FALSE); + } + + /* and update state of the second check */ + alarm_check_toggled_cb ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el), + NULL, view); +} + +gboolean +itip_view_get_inherit_alarm_check_state (ItipView *view) +{ + WebKitDOMElement *el; + + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + if (!view->priv->dom_document) + return FALSE; + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, CHECKBOX_INHERIT_ALARM); + return webkit_dom_html_input_element_get_checked ( + WEBKIT_DOM_HTML_INPUT_ELEMENT (el)); +} + +void +itip_view_set_error (ItipView *view, + const gchar *error_html, + gboolean show_save_btn) +{ + WebKitDOMElement *content, *error; + GString *str; + + g_return_if_fail (ITIP_IS_VIEW (view)); + g_return_if_fail (error_html); + + str = g_string_new (error_html); + + if (show_save_btn) { + g_string_append (str, + "<table border=\"0\" width=\"100%\">" + "<tr width=\"100%\" id=\"" TABLE_ROW_BUTTONS "\">"); + + buttons_table_write_button ( + str, BUTTON_SAVE, _("_Save"), + GTK_STOCK_SAVE, ITIP_VIEW_RESPONSE_SAVE); + + g_string_append (str, "</tr></table>"); + } + + view->priv->error = str->str; + g_string_free (str, FALSE); + + if (!view->priv->dom_document) + return; + + content = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, DIV_ITIP_CONTENT); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (content), TRUE); + + error = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, DIV_ITIP_ERROR); + webkit_dom_html_element_set_hidden ( + WEBKIT_DOM_HTML_ELEMENT (error), FALSE); + + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (error), view->priv->error, NULL); + + if (show_save_btn) { + WebKitDOMElement *el; + + show_button (view, BUTTON_SAVE); + + el = webkit_dom_document_get_element_by_id ( + view->priv->dom_document, BUTTON_SAVE); + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (el), "click", + G_CALLBACK (button_clicked_cb), FALSE, view); + } +} + +/******************************************************************************/ + +typedef struct { + EMailPartItip *puri; + ItipView *view; + GCancellable *cancellable; + gboolean keep_alarm_check; + GHashTable *conflicts; + + gchar *uid; + gchar *rid; + + gchar *sexp; + + gint count; +} FormatItipFindData; + +static gboolean check_is_instance (icalcomponent *icalcomp); + +static icalproperty * +find_attendee (icalcomponent *ical_comp, + const gchar *address) +{ + icalproperty *prop; + + if (address == NULL) + return NULL; + + for (prop = icalcomponent_get_first_property (ical_comp, ICAL_ATTENDEE_PROPERTY); + prop != NULL; + prop = icalcomponent_get_next_property (ical_comp, ICAL_ATTENDEE_PROPERTY)) { + gchar *attendee; + gchar *text; + + attendee = icalproperty_get_value_as_string_r (prop); + + if (!attendee) + continue; + + text = g_strdup (itip_strip_mailto (attendee)); + text = g_strstrip (text); + if (text && !g_ascii_strcasecmp (address, text)) { + g_free (text); + g_free (attendee); + break; + } + g_free (text); + g_free (attendee); + } + + return prop; +} + +static icalproperty * +find_attendee_if_sentby (icalcomponent *ical_comp, + const gchar *address) +{ + icalproperty *prop; + + if (address == NULL) + return NULL; + + for (prop = icalcomponent_get_first_property (ical_comp, ICAL_ATTENDEE_PROPERTY); + prop != NULL; + prop = icalcomponent_get_next_property (ical_comp, ICAL_ATTENDEE_PROPERTY)) { + icalparameter *param; + const gchar *attendee_sentby; + gchar *text; + + param = icalproperty_get_first_parameter (prop, ICAL_SENTBY_PARAMETER); + if (!param) + continue; + + attendee_sentby = icalparameter_get_sentby (param); + + if (!attendee_sentby) + continue; + + text = g_strdup (itip_strip_mailto (attendee_sentby)); + text = g_strstrip (text); + if (text && !g_ascii_strcasecmp (address, text)) { + g_free (text); + break; + } + g_free (text); + } + + return prop; +} + +static void +find_to_address (EMailPartItip *itip_part, + icalcomponent *ical_comp, + icalparameter_partstat *status) +{ + ESourceRegistry *registry; + ESourceMailIdentity *extension; + GList *list, *link; + const gchar *extension_name; + + registry = itip_part->registry; + extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; + + if (itip_part->to_address != NULL) + return; + + if (itip_part->msg != NULL && itip_part->folder != NULL) { + ESource *source; + + source = em_utils_guess_mail_identity ( + registry, itip_part->msg, itip_part->folder); + + if (source != NULL) { + extension = e_source_get_extension (source, extension_name); + + itip_part->to_address = e_source_mail_identity_dup_address (extension); + + g_object_unref (source); + } + } + + if (itip_part->to_address != NULL) + return; + + /* Look through the list of attendees to find the user's address */ + list = e_source_registry_list_sources (registry, extension_name); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + icalproperty *prop = NULL; + icalparameter *param; + const gchar *address; + gchar *text; + + if (!e_source_get_enabled (source)) + continue; + + extension = e_source_get_extension (source, extension_name); + address = e_source_mail_identity_get_address (extension); + + prop = find_attendee (ical_comp, address); + if (prop == NULL) + continue; + + param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER); + if (param != NULL) + itip_part->to_name = g_strdup (icalparameter_get_cn (param)); + + text = icalproperty_get_value_as_string_r (prop); + + itip_part->to_address = g_strdup (itip_strip_mailto (text)); + g_free (text); + g_strstrip (itip_part->to_address); + + itip_part->my_address = g_strdup (address); + + param = icalproperty_get_first_parameter (prop, ICAL_RSVP_PARAMETER); + if (param != NULL && + icalparameter_get_rsvp (param) == ICAL_RSVP_FALSE) + itip_part->no_reply_wanted = TRUE; + + if (status) { + param = icalproperty_get_first_parameter (prop, ICAL_PARTSTAT_PARAMETER); + *status = param ? icalparameter_get_partstat (param) : ICAL_PARTSTAT_NEEDSACTION; + } + + break; + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + if (itip_part->to_address != NULL) + return; + + /* If the user's address was not found in the attendee's list, + * then the user might be responding on behalf of his/her delegator. + * In this case, we would want to go through the SENT-BY fields of + * the attendees to find the user's address. + * + * + * Note: This functionality could have been (easily) implemented + * in the previous loop, but it would hurt the performance for all + * providers in general. Hence, we choose to iterate through the + * accounts list again. + */ + + list = e_source_registry_list_sources (registry, extension_name); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + icalproperty *prop = NULL; + icalparameter *param; + const gchar *address; + gchar *text; + + if (!e_source_get_enabled (source)) + continue; + + extension = e_source_get_extension (source, extension_name); + address = e_source_mail_identity_get_address (extension); + + prop = find_attendee_if_sentby (ical_comp, address); + if (prop == NULL) + continue; + + param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER); + if (param != NULL) + itip_part->to_name = g_strdup (icalparameter_get_cn (param)); + + text = icalproperty_get_value_as_string_r (prop); + + itip_part->to_address = g_strdup (itip_strip_mailto (text)); + g_free (text); + g_strstrip (itip_part->to_address); + + itip_part->my_address = g_strdup (address); + + param = icalproperty_get_first_parameter (prop, ICAL_RSVP_PARAMETER); + if (param != NULL && + ICAL_RSVP_FALSE == icalparameter_get_rsvp (param)) + itip_part->no_reply_wanted = TRUE; + + if (status) { + param = icalproperty_get_first_parameter (prop, ICAL_PARTSTAT_PARAMETER); + *status = param ? icalparameter_get_partstat (param) : ICAL_PARTSTAT_NEEDSACTION; + } + + break; + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); +} + +static void +find_from_address (EMailPartItip *pitip, + icalcomponent *ical_comp) +{ + GList *list, *link; + icalproperty *prop; + gchar *organizer; + icalparameter *param; + const gchar *extension_name; + const gchar *organizer_sentby; + gchar *organizer_clean = NULL; + gchar *organizer_sentby_clean = NULL; + + prop = icalcomponent_get_first_property (ical_comp, ICAL_ORGANIZER_PROPERTY); + + if (!prop) + return; + + organizer = icalproperty_get_value_as_string_r (prop); + if (organizer) { + organizer_clean = g_strdup (itip_strip_mailto (organizer)); + organizer_clean = g_strstrip (organizer_clean); + g_free (organizer); + } + + param = icalproperty_get_first_parameter (prop, ICAL_SENTBY_PARAMETER); + if (param) { + organizer_sentby = icalparameter_get_sentby (param); + if (organizer_sentby) { + organizer_sentby_clean = g_strdup (itip_strip_mailto (organizer_sentby)); + organizer_sentby_clean = g_strstrip (organizer_sentby_clean); + } + } + + if (!(organizer_sentby_clean || organizer_clean)) + return; + + pitip->from_address = g_strdup (organizer_clean); + + param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER); + if (param) + pitip->from_name = g_strdup (icalparameter_get_cn (param)); + + extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; + list = e_source_registry_list_sources (pitip->registry, extension_name); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + ESourceMailIdentity *extension; + const gchar *address; + + if (!e_source_get_enabled (source)) + continue; + + extension = e_source_get_extension (source, extension_name); + address = e_source_mail_identity_get_address (extension); + + if (address == NULL) + continue; + + if ((organizer_clean && !g_ascii_strcasecmp (organizer_clean, address)) + || (organizer_sentby_clean && !g_ascii_strcasecmp (organizer_sentby_clean, address))) { + pitip->my_address = g_strdup (address); + + break; + } + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + g_free (organizer_sentby_clean); + g_free (organizer_clean); +} + +static ECalComponent * +get_real_item (EMailPartItip *pitip) +{ + ECalComponent *comp = NULL; + ESource *source; + + source = e_client_get_source (E_CLIENT (pitip->current_client)); + if (source) + comp = g_hash_table_lookup (pitip->real_comps, e_source_get_uid (source)); + + if (!comp) { + return NULL; + } + + return e_cal_component_clone (comp); +} + +static void +adjust_item (EMailPartItip *pitip, + ECalComponent *comp) +{ + ECalComponent *real_comp; + + real_comp = get_real_item (pitip); + if (real_comp != NULL) { + ECalComponentText text; + const gchar *string; + GSList *l; + + e_cal_component_get_summary (real_comp, &text); + e_cal_component_set_summary (comp, &text); + e_cal_component_get_location (real_comp, &string); + e_cal_component_set_location (comp, string); + e_cal_component_get_description_list (real_comp, &l); + e_cal_component_set_description_list (comp, l); + e_cal_component_free_text_list (l); + + g_object_unref (real_comp); + } else { + ECalComponentText text = {_("Unknown"), NULL}; + + e_cal_component_set_summary (comp, &text); + } +} + +static void +set_buttons_sensitive (EMailPartItip *pitip, + ItipView *view) +{ + gboolean read_only = TRUE; + + if (pitip->current_client) + read_only = e_client_is_readonly (E_CLIENT (pitip->current_client)); + + itip_view_set_buttons_sensitive (view, pitip->current_client != NULL && !read_only); +} + +static void +add_failed_to_load_msg (ItipView *view, + ESource *source, + const GError *error) +{ + gchar *msg; + + g_return_if_fail (view != NULL); + g_return_if_fail (source != NULL); + g_return_if_fail (error != NULL); + + /* Translators: The first '%s' is replaced with a calendar name, + * the second '%s' with an error message */ + msg = g_strdup_printf (_("Failed to load the calendar '%s' (%s)"), e_source_get_display_name (source), error->message); + + itip_view_add_lower_info_item (view, ITIP_VIEW_INFO_ITEM_TYPE_WARNING, msg); + + g_free (msg); +} + +static void +cal_opened_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ESource *source = E_SOURCE (source_object); + ItipView *view = user_data; + EMailPartItip *pitip = itip_view_get_mail_part (view); + ECalClientSourceType source_type; + EClient *client = NULL; + ECalClient *cal_client; + const gchar *uid; + GError *error = NULL; + + e_client_utils_open_new_finish (source, result, &client, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (client == NULL); + g_error_free (error); + return; + + } else if (error != NULL) { + g_warn_if_fail (client == NULL); + add_failed_to_load_msg (view, source, error); + g_error_free (error); + return; + } + + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + cal_client = E_CAL_CLIENT (client); + g_return_if_fail (cal_client != NULL); + + uid = e_source_get_uid (source); + source_type = e_cal_client_get_source_type (cal_client); + g_hash_table_insert ( + pitip->clients[source_type], g_strdup (uid), cal_client); + + if (e_cal_client_check_recurrences_no_master (cal_client)) { + icalcomponent *icalcomp; + gboolean show_recur_check; + + icalcomp = e_cal_component_get_icalcomponent (pitip->comp); + + show_recur_check = check_is_instance (icalcomp); + itip_view_set_show_recur_check (view, show_recur_check); + } + + if (pitip->type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS) { + gboolean needs_decline; + + needs_decline = e_client_check_capability ( + E_CLIENT (client), + CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING); + itip_view_set_needs_decline (view, needs_decline); + itip_view_set_mode (view, ITIP_VIEW_MODE_PUBLISH); + } + + pitip->current_client = cal_client; + + set_buttons_sensitive (pitip, view); +} + +static void +start_calendar_server (EMailPartItip *pitip, + ItipView *view, + ESource *source, + ECalClientSourceType type, + GAsyncReadyCallback func, + gpointer data) +{ + ECalClient *client; + + g_return_if_fail (source != NULL); + + client = g_hash_table_lookup (pitip->clients[type], e_source_get_uid (source)); + if (client) { + pitip->current_client = client; + + itip_view_remove_lower_info_item (view, pitip->progress_info_id); + pitip->progress_info_id = 0; + + set_buttons_sensitive (pitip, view); + + return; + } + + e_client_utils_open_new (source, + type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS ? E_CLIENT_SOURCE_TYPE_EVENTS : + type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS ? E_CLIENT_SOURCE_TYPE_MEMOS : + type == E_CAL_CLIENT_SOURCE_TYPE_TASKS ? E_CLIENT_SOURCE_TYPE_TASKS : E_CLIENT_SOURCE_TYPE_LAST, + TRUE, pitip->cancellable, + func, data); +} + +static void +start_calendar_server_by_uid (EMailPartItip *pitip, + ItipView *view, + const gchar *uid, + ECalClientSourceType type) +{ + ESource *source; + + itip_view_set_buttons_sensitive (view, FALSE); + + source = e_source_registry_ref_source (pitip->registry, uid); + if (source != NULL) { + start_calendar_server ( + pitip, view, source, type, cal_opened_cb, view); + g_object_unref (source); + } +} + +static void +source_selected_cb (ItipView *view, + ESource *source, + gpointer data) +{ + EMailPartItip *pitip = data; + + itip_view_set_buttons_sensitive (view, FALSE); + + g_return_if_fail (source != NULL); + + start_calendar_server (pitip, view, source, pitip->type, cal_opened_cb, view); +} + +static void +find_cal_update_ui (FormatItipFindData *fd, + ECalClient *cal_client) +{ + EMailPartItip *pitip; + ItipView *view; + ESource *source; + + g_return_if_fail (fd != NULL); + + pitip = fd->puri; + view = fd->view; + + /* UI part gone */ + if (g_cancellable_is_cancelled (fd->cancellable)) + return; + + source = cal_client ? e_client_get_source (E_CLIENT (cal_client)) : NULL; + + if (cal_client && g_hash_table_lookup (fd->conflicts, cal_client)) { + itip_view_add_upper_info_item_printf (view, ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + _("An appointment in the calendar '%s' conflicts with this meeting"), e_source_get_display_name (source)); + } + + /* search for a master object if the detached object doesn't exist in the calendar */ + if (pitip->current_client && pitip->current_client == cal_client) { + itip_view_set_show_keep_alarm_check (view, fd->keep_alarm_check); + + pitip->current_client = cal_client; + + /* Provide extra info, since its not in the component */ + /* FIXME Check sequence number of meeting? */ + /* FIXME Do we need to adjust elsewhere for the delegated calendar item? */ + /* FIXME Need to update the fields in the view now */ + if (pitip->method == ICAL_METHOD_REPLY || pitip->method == ICAL_METHOD_REFRESH) + adjust_item (pitip, pitip->comp); + + /* We clear everything because we don't really care + * about any other info/warnings now we found an + * existing versions */ + itip_view_clear_lower_info_items (view); + pitip->progress_info_id = 0; + + /* FIXME Check read only state of calendar? */ + itip_view_add_lower_info_item_printf (view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Found the appointment in the calendar '%s'"), e_source_get_display_name (source)); + + set_buttons_sensitive (pitip, view); + } else if (!pitip->current_client) + itip_view_set_show_keep_alarm_check (view, FALSE); + + if (pitip->current_client && pitip->current_client == cal_client) { + if (e_cal_client_check_recurrences_no_master (pitip->current_client)) { + icalcomponent *icalcomp = e_cal_component_get_icalcomponent (pitip->comp); + + if (check_is_instance (icalcomp)) + itip_view_set_show_recur_check (view, TRUE); + else + itip_view_set_show_recur_check (view, FALSE); + } + + if (pitip->type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS) { + /* TODO The static capability should be made generic to convey that the calendar contains unaccepted items */ + if (e_client_check_capability (E_CLIENT (pitip->current_client), CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING)) + itip_view_set_needs_decline (view, TRUE); + else + itip_view_set_needs_decline (view, FALSE); + + itip_view_set_mode (view, ITIP_VIEW_MODE_PUBLISH); + } + } +} + +static void +decrease_find_data (FormatItipFindData *fd) +{ + g_return_if_fail (fd != NULL); + + fd->count--; + d(printf ("Decreasing itip formatter search count to %d\n", fd->count)); + + if (fd->count == 0 && !g_cancellable_is_cancelled (fd->cancellable)) { + gboolean rsvp_enabled = FALSE; + EMailPartItip *pitip = fd->puri; + ItipView *view = fd->view; + + itip_view_remove_lower_info_item (view, pitip->progress_info_id); + pitip->progress_info_id = 0; + + /* + * Only allow replies if backend doesn't do that automatically. + * * Only enable it for forwarded invitiations (PUBLISH) or direct + * * invitiations (REQUEST), but not replies (REPLY). + * Replies only make sense for events with an organizer. + */ + if ((!pitip->current_client || !e_cal_client_check_save_schedules (pitip->current_client)) && + (pitip->method == ICAL_METHOD_PUBLISH || pitip->method == ICAL_METHOD_REQUEST) && + pitip->has_organizer) { + rsvp_enabled = TRUE; + } + itip_view_set_show_rsvp_check (view, rsvp_enabled); + + /* default is chosen in extract_itip_data() based on content of the VEVENT */ + itip_view_set_rsvp (view, !pitip->no_reply_wanted); + + if ((pitip->method == ICAL_METHOD_PUBLISH || pitip->method == ICAL_METHOD_REQUEST) + && !pitip->current_client) { + /* Reuse already declared one or rename? */ + ESource *source = NULL; + const gchar *extension_name; + + switch (pitip->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + break; + default: + g_return_if_reached (); + } + + source = e_source_registry_ref_default_for_extension_name ( + pitip->registry, extension_name); + + itip_view_set_extension_name (view, extension_name); + + g_signal_connect ( + view, "source_selected", + G_CALLBACK (source_selected_cb), pitip); + + if (source != NULL) { + itip_view_set_source (view, source); + g_object_unref (source); + + /* FIXME Shouldn't the buttons be sensitized here? */ + } else { + itip_view_add_lower_info_item (view, ITIP_VIEW_INFO_ITEM_TYPE_ERROR, _("Unable to find any calendars")); + itip_view_set_buttons_sensitive (view, FALSE); + } + } else if (!pitip->current_client) { + switch (pitip->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + _("Unable to find this meeting in any calendar")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + _("Unable to find this task in any task list")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + _("Unable to find this memo in any memo list")); + break; + default: + g_assert_not_reached (); + break; + } + } + } + + if (fd->count == 0) { + g_hash_table_destroy (fd->conflicts); + g_object_unref (fd->cancellable); + g_free (fd->uid); + g_free (fd->rid); + if (fd->sexp) + g_free (fd->sexp); + g_free (fd); + } +} + +static void +get_object_without_rid_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *cal_client = E_CAL_CLIENT (source_object); + FormatItipFindData *fd = user_data; + icalcomponent *icalcomp = NULL; + GError *error = NULL; + + if (!e_cal_client_get_object_finish (cal_client, result, &icalcomp, &error)) + icalcomp = NULL; + + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || + g_cancellable_is_cancelled (fd->cancellable)) { + g_clear_error (&error); + find_cal_update_ui (fd, cal_client); + decrease_find_data (fd); + return; + } + + g_clear_error (&error); + + if (icalcomp) { + ECalComponent *comp; + + fd->puri->current_client = cal_client; + fd->keep_alarm_check = (fd->puri->method == ICAL_METHOD_PUBLISH || fd->puri->method == ICAL_METHOD_REQUEST) && + (icalcomponent_get_first_component (icalcomp, ICAL_VALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XAUDIOALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XDISPLAYALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XPROCEDUREALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XEMAILALARM_COMPONENT)); + + comp = e_cal_component_new_from_icalcomponent (icalcomp); + if (comp) { + ESource *source = e_client_get_source (E_CLIENT (cal_client)); + + g_hash_table_insert (fd->puri->real_comps, g_strdup (e_source_get_uid (source)), comp); + } + + find_cal_update_ui (fd, cal_client); + decrease_find_data (fd); + return; + } + + find_cal_update_ui (fd, cal_client); + decrease_find_data (fd); +} + +static void +get_object_with_rid_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *cal_client = E_CAL_CLIENT (source_object); + FormatItipFindData *fd = user_data; + icalcomponent *icalcomp = NULL; + GError *error = NULL; + + if (!e_cal_client_get_object_finish (cal_client, result, &icalcomp, &error)) + icalcomp = NULL; + + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || + g_cancellable_is_cancelled (fd->cancellable)) { + g_clear_error (&error); + find_cal_update_ui (fd, cal_client); + decrease_find_data (fd); + return; + } + + g_clear_error (&error); + + if (icalcomp) { + ECalComponent *comp; + + fd->puri->current_client = cal_client; + fd->keep_alarm_check = (fd->puri->method == ICAL_METHOD_PUBLISH || fd->puri->method == ICAL_METHOD_REQUEST) && + (icalcomponent_get_first_component (icalcomp, ICAL_VALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XAUDIOALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XDISPLAYALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XPROCEDUREALARM_COMPONENT) || + icalcomponent_get_first_component (icalcomp, ICAL_XEMAILALARM_COMPONENT)); + + comp = e_cal_component_new_from_icalcomponent (icalcomp); + if (comp) { + ESource *source = e_client_get_source (E_CLIENT (cal_client)); + + g_hash_table_insert (fd->puri->real_comps, g_strdup (e_source_get_uid (source)), comp); + } + + find_cal_update_ui (fd, cal_client); + decrease_find_data (fd); + return; + } + + if (fd->rid && *fd->rid) { + e_cal_client_get_object (cal_client, fd->uid, NULL, fd->cancellable, get_object_without_rid_ready_cb, fd); + return; + } + + find_cal_update_ui (fd, cal_client); + decrease_find_data (fd); +} + +static void +get_object_list_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *cal_client = E_CAL_CLIENT (source_object); + FormatItipFindData *fd = user_data; + GSList *objects = NULL; + GError *error = NULL; + + if (!e_cal_client_get_object_list_finish (cal_client, result, &objects, &error)) + objects = NULL; + + if (g_cancellable_is_cancelled (fd->cancellable)) { + g_clear_error (&error); + decrease_find_data (fd); + return; + } + + if (error) { + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free (error); + decrease_find_data (fd); + return; + } + + g_error_free (error); + } else { + g_hash_table_insert (fd->conflicts, cal_client, GINT_TO_POINTER (g_slist_length (objects))); + e_cal_client_free_icalcomp_slist (objects); + } + + e_cal_client_get_object (cal_client, fd->uid, fd->rid, fd->cancellable, get_object_with_rid_ready_cb, fd); +} + +static void +find_cal_opened_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ESource *source = E_SOURCE (source_object); + FormatItipFindData *fd = user_data; + EMailPartItip *pitip = fd->puri; + ItipView *view = fd->view; + ECalClientSourceType source_type; + EClient *client = NULL; + ECalClient *cal_client; + gboolean search_for_conflicts = FALSE; + const gchar *extension_name; + const gchar *uid; + GError *error = NULL; + + e_client_utils_open_new_finish (source, result, &client, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (client == NULL); + decrease_find_data (fd); + g_error_free (error); + return; + } + + if (g_cancellable_is_cancelled (fd->cancellable)) { + g_clear_error (&error); + decrease_find_data (fd); + return; + } + + if (error) { + /* FIXME Do we really want to warn here? If we fail + * to find the item, this won't be cleared but the + * selector might be shown */ + g_warn_if_fail (client == NULL); + add_failed_to_load_msg (view, source, error); + decrease_find_data (fd); + g_error_free (error); + return; + } + + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + /* Do not process read-only calendars */ + if (e_client_is_readonly (client)) { + g_object_unref (client); + decrease_find_data (fd); + return; + } + + cal_client = E_CAL_CLIENT (client); + source_type = e_cal_client_get_source_type (cal_client); + + uid = e_source_get_uid (source); + g_hash_table_insert ( + pitip->clients[source_type], g_strdup (uid), cal_client); + + extension_name = E_SOURCE_EXTENSION_CONFLICT_SEARCH; + if (e_source_has_extension (source, extension_name)) { + ESourceConflictSearch *extension; + + extension = e_source_get_extension (source, extension_name); + search_for_conflicts = + (pitip->type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) && + e_source_conflict_search_get_include_me (extension); + } + + /* Check for conflicts */ + /* If the query fails, we'll just ignore it */ + /* FIXME What happens for recurring conflicts? */ + if (search_for_conflicts) { + e_cal_client_get_object_list ( + cal_client, fd->sexp, + fd->cancellable, + get_object_list_ready_cb, fd); + return; + } + + if (!pitip->current_client) { + e_cal_client_get_object ( + cal_client, fd->uid, fd->rid, + fd->cancellable, + get_object_with_rid_ready_cb, fd); + return; + } + + decrease_find_data (fd); +} + +static void +find_server (EMailPartItip *pitip, + ItipView *view, + ECalComponent *comp) +{ + FormatItipFindData *fd = NULL; + const gchar *uid; + gchar *rid = NULL; + CamelStore *parent_store; + ESourceRegistry *registry; + ESource *current_source = NULL; + GList *list, *link; + GList *conflict_list = NULL; + const gchar *extension_name; + const gchar *store_uid; + + g_return_if_fail (pitip->folder != NULL); + + switch (pitip->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + break; + default: + g_return_if_reached (); + } + + registry = pitip->registry; + list = e_source_registry_list_sources (registry, extension_name); + + e_cal_component_get_uid (comp, &uid); + rid = e_cal_component_get_recurid_as_string (comp); + + /* XXX Not sure what this was trying to do, + * but it propbably doesn't work anymore. + * Some comments would have been helpful. */ + parent_store = camel_folder_get_parent_store (pitip->folder); + + store_uid = camel_service_get_uid (CAMEL_SERVICE (parent_store)); + + itip_view_set_buttons_sensitive (view, FALSE); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + gboolean search_for_conflicts = FALSE; + const gchar *source_uid; + + extension_name = E_SOURCE_EXTENSION_CONFLICT_SEARCH; + if (e_source_has_extension (source, extension_name)) { + ESourceConflictSearch *extension; + + extension = + e_source_get_extension (source, extension_name); + search_for_conflicts = + e_source_conflict_search_get_include_me (extension); + } + + if (search_for_conflicts) + conflict_list = g_list_prepend ( + conflict_list, g_object_ref (source)); + + if (current_source != NULL) + continue; + + source_uid = e_source_get_uid (source); + if (g_strcmp0 (source_uid, store_uid) == 0) { + current_source = source; + conflict_list = g_list_prepend ( + conflict_list, g_object_ref (source)); + + continue; + } + } + + if (current_source) { + link = conflict_list; + + pitip->progress_info_id = itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS, + _("Opening the calendar. Please wait...")); + } else { + link = list; + pitip->progress_info_id = itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS, + _("Searching for an existing version of this appointment")); + } + + for (; link != NULL; link = g_list_next (link)) { + ESource *source = E_SOURCE (link->data); + + if (!fd) { + gchar *start = NULL, *end = NULL; + + fd = g_new0 (FormatItipFindData, 1); + fd->puri = pitip; + fd->view = view; + fd->cancellable = g_object_ref (pitip->cancellable); + fd->conflicts = g_hash_table_new (g_direct_hash, g_direct_equal); + fd->uid = g_strdup (uid); + fd->rid = rid; + /* avoid free this at the end */ + rid = NULL; + + if (pitip->start_time && pitip->end_time) { + start = isodate_from_time_t (pitip->start_time); + end = isodate_from_time_t (pitip->end_time); + + fd->sexp = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") (make-time \"%s\")) (not (uid? \"%s\")))", + start, end, icalcomponent_get_uid (pitip->ical_comp)); + } + + g_free (start); + g_free (end); + } + fd->count++; + d(printf ("Increasing itip formatter search count to %d\n", fd->count)); + + if (current_source == source) + start_calendar_server ( + pitip, view, source, pitip->type, + find_cal_opened_cb, fd); + else + start_calendar_server ( + pitip, view, source, pitip->type, + find_cal_opened_cb, fd); + } + + g_list_free_full (conflict_list, (GDestroyNotify) g_object_unref); + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + g_free (rid); +} + +static gboolean +change_status (ESourceRegistry *registry, + icalcomponent *ical_comp, + const gchar *address, + icalparameter_partstat status) +{ + icalproperty *prop; + + prop = find_attendee (ical_comp, address); + if (prop) { + icalparameter *param; + + icalproperty_remove_parameter (prop, ICAL_PARTSTAT_PARAMETER); + param = icalparameter_new_partstat (status); + icalproperty_add_parameter (prop, param); + } else { + icalparameter *param; + + if (address != NULL) { + prop = icalproperty_new_attendee (address); + icalcomponent_add_property (ical_comp, prop); + + param = icalparameter_new_role (ICAL_ROLE_OPTPARTICIPANT); + icalproperty_add_parameter (prop, param); + + param = icalparameter_new_partstat (status); + icalproperty_add_parameter (prop, param); + } else { + gchar *default_name = NULL; + gchar *default_address = NULL; + + itip_get_default_name_and_address ( + registry, &default_name, &default_address); + + prop = icalproperty_new_attendee (default_address); + icalcomponent_add_property (ical_comp, prop); + + param = icalparameter_new_cn (default_name); + icalproperty_add_parameter (prop, param); + + param = icalparameter_new_role (ICAL_ROLE_REQPARTICIPANT); + icalproperty_add_parameter (prop, param); + + param = icalparameter_new_partstat (status); + icalproperty_add_parameter (prop, param); + + g_free (default_name); + g_free (default_address); + } + } + + return TRUE; +} + +static void +message_foreach_part (CamelMimePart *part, + GSList **part_list) +{ + CamelDataWrapper *containee; + gint parts, i; + gint go = TRUE; + + if (!part) + return; + + *part_list = g_slist_append (*part_list, part); + + containee = camel_medium_get_content (CAMEL_MEDIUM (part)); + + if (containee == NULL) + return; + + /* using the object types is more accurate than using the mime/types */ + if (CAMEL_IS_MULTIPART (containee)) { + parts = camel_multipart_get_number (CAMEL_MULTIPART (containee)); + for (i = 0; go && i < parts; i++) { + /* Reuse already declared *parts? */ + CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (containee), i); + + message_foreach_part (part, part_list); + } + } else if (CAMEL_IS_MIME_MESSAGE (containee)) { + message_foreach_part ((CamelMimePart *) containee, part_list); + } +} + +static void +attachment_load_finished (EAttachment *attachment, + GAsyncResult *result, + gpointer user_data) +{ + struct { + GFile *file; + gboolean done; + } *status = user_data; + + /* Should be no need to check for error here. */ + e_attachment_load_finish (attachment, result, NULL); + + status->done = TRUE; +} + +static void +attachment_save_finished (EAttachment *attachment, + GAsyncResult *result, + gpointer user_data) +{ + GError *error = NULL; + + struct { + GFile *file; + gboolean done; + } *status = user_data; + + status->file = e_attachment_save_finish (attachment, result, &error); + status->done = TRUE; + + /* XXX Error handling needs improvement. */ + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } +} + +static gchar * +get_uri_for_part (CamelMimePart *mime_part) +{ + EAttachment *attachment; + GFile *temp_directory; + gchar *template; + gchar *path; + + struct { + GFile *file; + gboolean done; + } status; + + /* XXX Error handling leaves much to be desired. */ + + template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ()); + path = e_mkdtemp (template); + g_free (template); + + if (path == NULL) + return NULL; + + temp_directory = g_file_new_for_path (path); + g_free (path); + + attachment = e_attachment_new (); + e_attachment_set_mime_part (attachment, mime_part); + + status.done = FALSE; + + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + attachment_load_finished, &status); + + /* Loading should be instantaneous since we already have + * the full content, but we still have to crank the main + * loop until the callback gets triggered. */ + while (!status.done) + gtk_main_iteration (); + + status.file = NULL; + status.done = FALSE; + + e_attachment_save_async ( + attachment, temp_directory, (GAsyncReadyCallback) + attachment_save_finished, &status); + + /* We can't return until we have results, so crank + * the main loop until the callback gets triggered. */ + while (!status.done) + gtk_main_iteration (); + + if (status.file != NULL) { + path = g_file_get_path (status.file); + g_object_unref (status.file); + } else + path = NULL; + + g_object_unref (attachment); + g_object_unref (temp_directory); + + return path; +} + +static void +update_item_progress_info (EMailPartItip *pitip, + ItipView *view, + const gchar *message) +{ + if (pitip->update_item_progress_info_id) { + itip_view_remove_lower_info_item (view, pitip->update_item_progress_info_id); + pitip->update_item_progress_info_id = 0; + + if (!message) + itip_view_set_buttons_sensitive (view, TRUE); + } + + if (pitip->update_item_error_info_id) { + itip_view_remove_lower_info_item (view, pitip->update_item_error_info_id); + pitip->update_item_error_info_id = 0; + } + + if (message) { + itip_view_set_buttons_sensitive (view, FALSE); + pitip->update_item_progress_info_id = + itip_view_add_lower_info_item ( + view, + ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS, + message); + } +} + +static void +finish_message_delete_with_rsvp (EMailPartItip *pitip, + ItipView *view, + ECalClient *client) +{ + gboolean save_schedules = e_cal_client_check_save_schedules (client); + + if (!save_schedules && pitip->delete_message && pitip->folder) + camel_folder_delete_message (pitip->folder, pitip->uid); + + if (itip_view_get_rsvp (view)) { + ECalComponent *comp = NULL; + icalcomponent *ical_comp; + icalproperty *prop; + icalvalue *value; + const gchar *attendee; + gchar *comment; + GSList *l, *list = NULL; + gboolean found; + + comp = e_cal_component_clone (pitip->comp); + if (comp == NULL) + return; + + if (pitip->to_address == NULL) + find_to_address (pitip, pitip->ical_comp, NULL); + g_assert (pitip->to_address != NULL); + + ical_comp = e_cal_component_get_icalcomponent (comp); + + /* Remove all attendees except the one we are responding as */ + found = FALSE; + for (prop = icalcomponent_get_first_property (ical_comp, ICAL_ATTENDEE_PROPERTY); + prop != NULL; + prop = icalcomponent_get_next_property (ical_comp, ICAL_ATTENDEE_PROPERTY)) { + gchar *text; + + value = icalproperty_get_value (prop); + if (!value) + continue; + + attendee = icalvalue_get_string (value); + + text = g_strdup (itip_strip_mailto (attendee)); + text = g_strstrip (text); + + /* We do this to ensure there is at most one + * attendee in the response */ + if (found || g_ascii_strcasecmp (pitip->to_address, text)) + list = g_slist_prepend (list, prop); + else if (!g_ascii_strcasecmp (pitip->to_address, text)) + found = TRUE; + g_free (text); + } + + for (l = list; l; l = l->next) { + prop = l->data; + icalcomponent_remove_property (ical_comp, prop); + icalproperty_free (prop); + } + g_slist_free (list); + + /* Add a comment if there user set one */ + comment = itip_view_get_rsvp_comment (view); + if (comment) { + GSList comments; + ECalComponentText text; + + text.value = comment; + text.altrep = NULL; + + comments.data = &text; + comments.next = NULL; + + e_cal_component_set_comment_list (comp, &comments); + + g_free (comment); + } + + e_cal_component_rescan (comp); + if (itip_send_comp ( + pitip->registry, + E_CAL_COMPONENT_METHOD_REPLY, + comp, pitip->current_client, + pitip->top_level, NULL, NULL, TRUE, FALSE) && + pitip->folder) { + camel_folder_set_message_flags ( + pitip->folder, pitip->uid, + CAMEL_MESSAGE_ANSWERED, + CAMEL_MESSAGE_ANSWERED); + } + + g_object_unref (comp); + } + + update_item_progress_info (pitip, view, NULL); +} + +static void +receive_objects_ready_cb (GObject *ecalclient, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *client = E_CAL_CLIENT (ecalclient); + ESource *source = e_client_get_source (E_CLIENT (client)); + ItipView *view = user_data; + EMailPartItip *pitip = itip_view_get_mail_part (view); + gboolean save_schedules; + GError *error = NULL; + + if (!e_cal_client_receive_objects_finish (client, result, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED)) { + update_item_progress_info (pitip, view, NULL); + pitip->update_item_error_info_id = + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Unable to send item to calendar '%s'. %s"), + e_source_get_display_name (source), error ? error->message : _("Unknown error")); + } + g_clear_error (&error); + return; + } + + itip_view_set_extension_name (view, NULL); + + itip_view_clear_lower_info_items (view); + + switch (pitip->update_item_response) { + case ITIP_VIEW_RESPONSE_ACCEPT: + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Sent to calendar '%s' as accepted"), e_source_get_display_name (source)); + break; + case ITIP_VIEW_RESPONSE_TENTATIVE: + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Sent to calendar '%s' as tentative"), e_source_get_display_name (source)); + break; + case ITIP_VIEW_RESPONSE_DECLINE: + /* FIXME some calendars just might not save it at all, is this accurate? */ + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Sent to calendar '%s' as declined"), e_source_get_display_name (source)); + break; + case ITIP_VIEW_RESPONSE_CANCEL: + /* FIXME some calendars just might not save it at all, is this accurate? */ + itip_view_add_lower_info_item_printf ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Sent to calendar '%s' as canceled"), e_source_get_display_name (source)); + break; + default: + g_assert_not_reached (); + break; + } + + /*FIXME Save schedules is misused here, remove it */ + save_schedules = e_cal_client_check_save_schedules (client); + + /* FIXME Remove this and handle this at the groupwise mail provider */ + if (save_schedules && pitip->can_delete_invitation_from_cache && pitip->folder) { + CamelFolderChangeInfo *changes = NULL; + const gchar *tag = NULL; + CamelMessageInfo *mi; + mi = camel_folder_summary_get (pitip->folder->summary, pitip->uid); + if (mi) { + changes = camel_folder_change_info_new (); + + if (itip_view_get_recur_check_state (view)) { + /* Recurring appointment and "apply-to-all" is selected */ + tag = camel_message_info_user_tag (mi, "recurrence-key"); + if (tag) { + gint i; + GPtrArray *known_uids; + + known_uids = camel_folder_summary_get_array (pitip->folder->summary); + for (i = 0; known_uids && i < known_uids->len; i++) { + const gchar *uid = g_ptr_array_index (known_uids, i); + CamelMessageInfo *mi2; + + mi2 = camel_folder_summary_get (pitip->folder->summary, uid); + if (!mi2) + continue; + + if (camel_message_info_user_tag (mi2, "recurrence-key") && + g_str_equal (camel_message_info_user_tag (mi2, "recurrence-key"), tag)) { + camel_folder_summary_remove_uid (pitip->folder->summary, mi2->uid); + camel_folder_change_info_remove_uid (changes, mi2->uid); + } + + camel_message_info_free (mi2); + } + } + } else { + /* Either not a recurring appointment or "apply-to-all" is not selected. So just delete this instance alone */ + camel_folder_summary_remove_uid (pitip->folder->summary, pitip->uid); + camel_folder_change_info_remove_uid (changes, pitip->uid); + } + + camel_folder_changed (pitip->folder, changes); + camel_folder_change_info_free (changes); + camel_message_info_free (mi); + } + } + + finish_message_delete_with_rsvp (pitip, view, client); +} + +static void +update_item (EMailPartItip *pitip, + ItipView *view, + ItipViewResponse response) +{ + struct icaltimetype stamp; + icalproperty *prop; + icalcomponent *clone; + ECalComponent *clone_comp; + gchar *str; + + update_item_progress_info (pitip, view, _("Saving changes to the calendar. Please wait...")); + + /* Set X-MICROSOFT-CDO-REPLYTIME to record the time at which + * the user accepted/declined the request. (Outlook ignores + * SEQUENCE in REPLY reponses and instead requires that each + * updated response have a later REPLYTIME than the previous + * one.) This also ends up getting saved in our own copy of + * the meeting, though there's currently no way to see that + * information (unless it's being saved to an Exchange folder + * and you then look at it in Outlook). + */ + stamp = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ()); + str = icaltime_as_ical_string_r (stamp); + prop = icalproperty_new_x (str); + g_free (str); + icalproperty_set_x_name (prop, "X-MICROSOFT-CDO-REPLYTIME"); + icalcomponent_add_property (pitip->ical_comp, prop); + + clone = icalcomponent_new_clone (pitip->ical_comp); + icalcomponent_add_component (pitip->top_level, clone); + icalcomponent_set_method (pitip->top_level, pitip->method); + + if (!itip_view_get_inherit_alarm_check_state (view)) { + icalcomponent *alarm_comp; + icalcompiter alarm_iter; + + alarm_iter = icalcomponent_begin_component (clone, ICAL_VALARM_COMPONENT); + while ((alarm_comp = icalcompiter_deref (&alarm_iter)) != NULL) { + icalcompiter_next (&alarm_iter); + + icalcomponent_remove_component (clone, alarm_comp); + icalcomponent_free (alarm_comp); + } + } + + clone_comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (clone_comp, clone)) { + update_item_progress_info (pitip, view, NULL); + pitip->update_item_error_info_id = itip_view_add_lower_info_item (view, + ITIP_VIEW_INFO_ITEM_TYPE_ERROR, _("Unable to parse item")); + goto cleanup; + } + + if (itip_view_get_keep_alarm_check_state (view)) { + ECalComponent *real_comp; + GList *alarms, *l; + ECalComponentAlarm *alarm; + + real_comp = get_real_item (pitip); + if (real_comp != NULL) { + alarms = e_cal_component_get_alarm_uids (real_comp); + + for (l = alarms; l; l = l->next) { + alarm = e_cal_component_get_alarm (real_comp, (const gchar *) l->data); + + if (alarm) { + ECalComponentAlarm *aclone = e_cal_component_alarm_clone (alarm); + + if (aclone) { + e_cal_component_add_alarm (clone_comp, aclone); + e_cal_component_alarm_free (aclone); + } + + e_cal_component_alarm_free (alarm); + } + } + + cal_obj_uid_list_free (alarms); + g_object_unref (real_comp); + } + } + + if ((response != ITIP_VIEW_RESPONSE_CANCEL) + && (response != ITIP_VIEW_RESPONSE_DECLINE)) { + GSList *attachments = NULL, *new_attachments = NULL, *l; + CamelMimeMessage *msg = pitip->msg; + + e_cal_component_get_attachment_list (clone_comp, &attachments); + + for (l = attachments; l; l = l->next) { + GSList *parts = NULL, *m; + gchar *uri, *new_uri; + CamelMimePart *part; + + uri = l->data; + + if (!g_ascii_strncasecmp (uri, "cid:...", 7)) { + message_foreach_part ((CamelMimePart *) msg, &parts); + + for (m = parts; m; m = m->next) { + part = m->data; + + /* Skip the actual message and the text/calendar part */ + /* FIXME Do we need to skip anything else? */ + if (part == (CamelMimePart *) msg || part == pitip->part) + continue; + + new_uri = get_uri_for_part (part); + if (new_uri != NULL) + new_attachments = g_slist_append (new_attachments, new_uri); + } + + g_slist_free (parts); + + } else if (!g_ascii_strncasecmp (uri, "cid:", 4)) { + part = camel_mime_message_get_part_by_content_id (msg, uri + 4); + if (part) { + new_uri = get_uri_for_part (part); + if (new_uri != NULL) + new_attachments = g_slist_append (new_attachments, new_uri); + } + + } else { + /* Preserve existing non-cid ones */ + new_attachments = g_slist_append (new_attachments, g_strdup (uri)); + } + } + + g_slist_foreach (attachments, (GFunc) g_free, NULL); + g_slist_free (attachments); + + e_cal_component_set_attachment_list (clone_comp, new_attachments); + } + + pitip->update_item_response = response; + + e_cal_client_receive_objects ( + pitip->current_client, + pitip->top_level, + pitip->cancellable, + receive_objects_ready_cb, + view); + + cleanup: + icalcomponent_remove_component (pitip->top_level, clone); + g_object_unref (clone_comp); +} + +/* TODO These operations should be available in e-cal-component.c */ +static void +set_attendee (ECalComponent *comp, + const gchar *address) +{ + icalproperty *prop; + icalcomponent *icalcomp; + gboolean found = FALSE; + + icalcomp = e_cal_component_get_icalcomponent (comp); + + for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY); + prop; + prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) { + const gchar *attendee = icalproperty_get_attendee (prop); + + if (!(g_str_equal (itip_strip_mailto (attendee), address))) + icalcomponent_remove_property (icalcomp, prop); + else + found = TRUE; + } + + if (!found) { + icalparameter *param; + gchar *temp = g_strdup_printf ("MAILTO:%s", address); + + prop = icalproperty_new_attendee ((const gchar *) temp); + icalcomponent_add_property (icalcomp, prop); + + param = icalparameter_new_partstat (ICAL_PARTSTAT_NEEDSACTION); + icalproperty_add_parameter (prop, param); + + param = icalparameter_new_role (ICAL_ROLE_REQPARTICIPANT); + icalproperty_add_parameter (prop, param); + + param = icalparameter_new_cutype (ICAL_CUTYPE_INDIVIDUAL); + icalproperty_add_parameter (prop, param); + + param = icalparameter_new_rsvp (ICAL_RSVP_TRUE); + icalproperty_add_parameter (prop, param); + + g_free (temp); + } + +} + +static gboolean +send_comp_to_attendee (ESourceRegistry *registry, + ECalComponentItipMethod method, + ECalComponent *comp, + const gchar *user, + ECalClient *client, + const gchar *comment) +{ + gboolean status; + ECalComponent *send_comp = e_cal_component_clone (comp); + + set_attendee (send_comp, user); + + if (comment) { + GSList comments; + ECalComponentText text; + + text.value = comment; + text.altrep = NULL; + + comments.data = &text; + comments.next = NULL; + + e_cal_component_set_comment_list (send_comp, &comments); + } + + /* FIXME send the attachments in the request */ + status = itip_send_comp ( + registry, method, send_comp, + client, NULL, NULL, NULL, TRUE, FALSE); + + g_object_unref (send_comp); + + return status; +} + +static void +remove_delegate (EMailPartItip *pitip, + ItipView *view, + const gchar *delegate, + const gchar *delegator, + ECalComponent *comp) +{ + gboolean status; + gchar *comment = g_strdup_printf (_("Organizer has removed the delegate %s "), itip_strip_mailto (delegate)); + + /* send cancellation notice to delegate */ + status = send_comp_to_attendee ( + pitip->registry, + E_CAL_COMPONENT_METHOD_CANCEL, pitip->comp, + delegate, pitip->current_client, comment); + if (status) + send_comp_to_attendee ( + pitip->registry, + E_CAL_COMPONENT_METHOD_REQUEST, pitip->comp, + delegator, pitip->current_client, comment); + if (status) { + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Sent a cancelation notice to the delegate")); + } else + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Could not send the cancelation notice to the delegate")); + + g_free (comment); + +} + +static void +update_x (ECalComponent *pitip_comp, + ECalComponent *comp) +{ + icalcomponent *itip_icalcomp = e_cal_component_get_icalcomponent (pitip_comp); + icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp); + + icalproperty *prop = icalcomponent_get_first_property (itip_icalcomp, ICAL_X_PROPERTY); + while (prop) { + const gchar *name = icalproperty_get_x_name (prop); + if (!g_ascii_strcasecmp (name, "X-EVOLUTION-IS-REPLY")) { + icalproperty *new_prop = icalproperty_new_x (icalproperty_get_x (prop)); + icalproperty_set_x_name (new_prop, "X-EVOLUTION-IS-REPLY"); + icalcomponent_add_property (icalcomp, new_prop); + } + prop = icalcomponent_get_next_property (itip_icalcomp, ICAL_X_PROPERTY); + } + + e_cal_component_set_icalcomponent (comp, icalcomp); +} + +static void +modify_object_cb (GObject *ecalclient, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *client = E_CAL_CLIENT (ecalclient); + ItipView *view = user_data; + EMailPartItip *pitip = itip_view_get_mail_part (view); + GError *error = NULL; + + if (!e_cal_client_modify_object_finish (client, result, &error)) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || + g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED)) { + g_clear_error (&error); + return; + } + + update_item_progress_info (pitip, view, NULL); + pitip->update_item_error_info_id = + itip_view_add_lower_info_item_printf ( + view, + ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + _("Unable to update attendee. %s"), + error ? error->message : _("Unknown error")); + + g_clear_error (&error); + } else { + update_item_progress_info (pitip, view, NULL); + itip_view_add_lower_info_item (view, + ITIP_VIEW_INFO_ITEM_TYPE_INFO, _("Attendee status updated")); + } +} + +static void +update_attendee_status_icalcomp (EMailPartItip *pitip, + ItipView *view, + icalcomponent *icalcomp) +{ + ECalComponent *comp; + const gchar *uid = NULL; + gchar *rid; + GSList *attendees; + + e_cal_component_get_uid (pitip->comp, &uid); + rid = e_cal_component_get_recurid_as_string (pitip->comp); + + comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (comp, icalcomp)) { + icalcomponent_free (icalcomp); + + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + _("The meeting is invalid and cannot be updated")); + } else { + icalcomponent *org_icalcomp; + const gchar *delegate; + + org_icalcomp = e_cal_component_get_icalcomponent (pitip->comp); + + e_cal_component_get_attendee_list (pitip->comp, &attendees); + if (attendees != NULL) { + ECalComponentAttendee *a = attendees->data; + icalproperty *prop, *del_prop; + EShell *shell = e_shell_get_default (); + + prop = find_attendee (icalcomp, itip_strip_mailto (a->value)); + if ((a->status == ICAL_PARTSTAT_DELEGATED) && (del_prop = find_attendee (org_icalcomp, itip_strip_mailto (a->delto))) && !(find_attendee (icalcomp, itip_strip_mailto (a->delto)))) { + gint response; + delegate = icalproperty_get_attendee (del_prop); + response = e_alert_run_dialog_for_args (e_shell_get_active_window (shell), + "org.gnome.itip-formatter:add-delegate", + itip_strip_mailto (a->value), + itip_strip_mailto (delegate), NULL); + if (response == GTK_RESPONSE_YES) { + icalcomponent_add_property (icalcomp, icalproperty_new_clone (del_prop)); + e_cal_component_rescan (comp); + } else if (response == GTK_RESPONSE_NO) { + remove_delegate (pitip, view, delegate, itip_strip_mailto (a->value), comp); + goto cleanup; + } else { + goto cleanup; + } + } + + if (prop == NULL) { + gint response; + + if (a->delfrom && *a->delfrom) { + response = e_alert_run_dialog_for_args (e_shell_get_active_window (shell), + "org.gnome.itip-formatter:add-delegate", + itip_strip_mailto (a->delfrom), + itip_strip_mailto (a->value), NULL); + if (response == GTK_RESPONSE_YES) { + /* Already declared in this function */ + icalproperty *prop = find_attendee (icalcomp, itip_strip_mailto (a->value)); + icalcomponent_add_property (icalcomp,icalproperty_new_clone (prop)); + e_cal_component_rescan (comp); + } else if (response == GTK_RESPONSE_NO) { + remove_delegate (pitip, + view, + itip_strip_mailto (a->value), + itip_strip_mailto (a->delfrom), + comp); + goto cleanup; + } else { + goto cleanup; + } + } + + response = e_alert_run_dialog_for_args ( + e_shell_get_active_window (shell), + "org.gnome.itip-formatter:add-unknown-attendee", NULL); + + if (response == GTK_RESPONSE_YES) { + change_status ( + pitip->registry, icalcomp, + itip_strip_mailto (a->value), + a->status); + e_cal_component_rescan (comp); + } else { + goto cleanup; + } + } else if (a->status == ICAL_PARTSTAT_NONE || a->status == ICAL_PARTSTAT_X) { + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + _("Attendee status could not be updated because the status is invalid")); + goto cleanup; + } else { + if (a->status == ICAL_PARTSTAT_DELEGATED) { + /* *prop already declared in this function */ + icalproperty *prop, *new_prop; + + prop = find_attendee (icalcomp, itip_strip_mailto (a->value)); + icalcomponent_remove_property (icalcomp, prop); + + new_prop = find_attendee (org_icalcomp, itip_strip_mailto (a->value)); + icalcomponent_add_property (icalcomp, icalproperty_new_clone (new_prop)); + } else { + change_status ( + pitip->registry, icalcomp, + itip_strip_mailto (a->value), + a->status); + } + + e_cal_component_rescan (comp); + } + } + } + + update_x (pitip->comp, comp); + + if (itip_view_get_update (view)) { + e_cal_component_commit_sequence (comp); + itip_send_comp ( + pitip->registry, + E_CAL_COMPONENT_METHOD_REQUEST, + comp, pitip->current_client, + NULL, NULL, NULL, TRUE, FALSE); + } + + update_item_progress_info (pitip, view, _("Saving changes to the calendar. Please wait...")); + + e_cal_client_modify_object ( + pitip->current_client, + icalcomp, rid ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL, + pitip->cancellable, + modify_object_cb, + view); + + cleanup: + g_object_unref (comp); +} + +static void +update_attendee_status_get_object_without_rid_cb (GObject *ecalclient, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *client = E_CAL_CLIENT (ecalclient); + ItipView *view = user_data; + EMailPartItip *pitip = itip_view_get_mail_part (view); + icalcomponent *icalcomp = NULL; + GError *error = NULL; + + if (!e_cal_client_get_object_finish (client, result, &icalcomp, &error)) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || + g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED)) { + g_clear_error (&error); + return; + } + + g_clear_error (&error); + + update_item_progress_info (pitip, view, NULL); + pitip->update_item_error_info_id = itip_view_add_lower_info_item ( + view, + ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + _("Attendee status can not be updated because the item no longer exists")); + return; + } + + update_attendee_status_icalcomp (pitip, view, icalcomp); +} + +static void +update_attendee_status_get_object_with_rid_cb (GObject *ecalclient, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *client = E_CAL_CLIENT (ecalclient); + ItipView *view = user_data; + EMailPartItip *pitip = itip_view_get_mail_part (view); + icalcomponent *icalcomp = NULL; + GError *error = NULL; + + if (!e_cal_client_get_object_finish (client, result, &icalcomp, &error)) { + const gchar *uid; + gchar *rid; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || + g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED)) { + g_clear_error (&error); + return; + } + + g_clear_error (&error); + + e_cal_component_get_uid (pitip->comp, &uid); + rid = e_cal_component_get_recurid_as_string (pitip->comp); + + if (!rid || !*rid) { + g_free (rid); + + update_item_progress_info (pitip, view, NULL); + pitip->update_item_error_info_id = itip_view_add_lower_info_item ( + view, + ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + _("Attendee status can not be updated because the item no longer exists")); + return; + } + + e_cal_client_get_object ( + pitip->current_client, + uid, + NULL, + pitip->cancellable, + update_attendee_status_get_object_without_rid_cb, + view); + + g_free (rid); + return; + } + + update_attendee_status_icalcomp (pitip, view, icalcomp); +} + +static void +update_attendee_status (EMailPartItip *pitip, + ItipView *view) +{ + const gchar *uid = NULL; + gchar *rid; + + /* Obtain our version */ + e_cal_component_get_uid (pitip->comp, &uid); + rid = e_cal_component_get_recurid_as_string (pitip->comp); + + update_item_progress_info (pitip, view, _("Saving changes to the calendar. Please wait...")); + + /* search for a master object if the detached object doesn't exist in the calendar */ + e_cal_client_get_object ( + pitip->current_client, + uid, rid, + pitip->cancellable, + update_attendee_status_get_object_with_rid_cb, + view); + + g_free (rid); +} + +static void +send_item (EMailPartItip *pitip, + ItipView *view) +{ + ECalComponent *comp; + + comp = get_real_item (pitip); + + if (comp != NULL) { + itip_send_comp ( + pitip->registry, + E_CAL_COMPONENT_METHOD_REQUEST, + comp, pitip->current_client, + NULL, NULL, NULL, TRUE, FALSE); + g_object_unref (comp); + + switch (pitip->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Meeting information sent")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Task information sent")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, + _("Memo information sent")); + break; + default: + g_assert_not_reached (); + break; + } + } else { + switch (pitip->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + _("Unable to send meeting information, the meeting does not exist")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + _("Unable to send task information, the task does not exist")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + itip_view_add_lower_info_item ( + view, ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + _("Unable to send memo information, the memo does not exist")); + break; + default: + g_assert_not_reached (); + break; + } + } +} + +static icalcomponent * +get_next (icalcompiter *iter) +{ + icalcomponent *ret = NULL; + icalcomponent_kind kind; + + do { + icalcompiter_next (iter); + ret = icalcompiter_deref (iter); + if (ret == NULL) + break; + kind = icalcomponent_isa (ret); + } while (ret != NULL + && kind != ICAL_VEVENT_COMPONENT + && kind != ICAL_VTODO_COMPONENT + && kind != ICAL_VFREEBUSY_COMPONENT); + + return ret; +} + +static void +attachment_load_finish (EAttachment *attachment, + GAsyncResult *result, + GFile *file) +{ + EShell *shell; + GtkWindow *parent; + + /* XXX Theoretically, this should never fail. */ + e_attachment_load_finish (attachment, result, NULL); + + shell = e_shell_get_default (); + parent = e_shell_get_active_window (shell); + + e_attachment_save_async ( + attachment, file, (GAsyncReadyCallback) + e_attachment_save_handle_error, parent); + + g_object_unref (file); +} + +static void +save_vcalendar_cb (EMailPartItip *pitip) +{ + EAttachment *attachment; + EShell *shell; + GFile *file; + const gchar *suggestion; + + g_return_if_fail (pitip != NULL); + g_return_if_fail (pitip->vcalendar != NULL); + g_return_if_fail (pitip->part != NULL); + + suggestion = camel_mime_part_get_filename (pitip->part); + if (suggestion == NULL) { + /* Translators: This is a default filename for a calendar. */ + suggestion = _("calendar.ics"); + } + + shell = e_shell_get_default (); + file = e_shell_run_save_dialog ( + shell, _("Save Calendar"), suggestion, "*.ics:text/calendar", NULL, NULL); + if (file == NULL) + return; + + attachment = e_attachment_new (); + e_attachment_set_mime_part (attachment, pitip->part); + + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + attachment_load_finish, file); +} + +static void +set_itip_error (ItipView *view, + const gchar *primary, + const gchar *secondary, + gboolean save_btn) +{ + gchar *error; + + error = g_strdup_printf ( + "<div class=\"error\">" + "<p><b>%s</b></p>" + "<p>%s</p>", + primary, secondary); + + itip_view_set_error (view, error, save_btn); + + g_free (error); +} + +static gboolean +extract_itip_data (EMailPartItip *pitip, + ItipView *view, + gboolean *have_alarms) +{ + EShell *shell; + EShellSettings *shell_settings; + icalproperty *prop; + icalcomponent_kind kind = ICAL_NO_COMPONENT; + icalcomponent *tz_comp; + icalcompiter tz_iter; + icalcomponent *alarm_comp; + icalcompiter alarm_iter; + ECalComponent *comp; + gboolean use_default_reminder; + + shell = e_shell_get_default (); + shell_settings = e_shell_get_shell_settings (shell); + + if (!pitip->vcalendar) { + set_itip_error (view, + _("The calendar attached is not valid"), + _("The message claims to contain a calendar, but the calendar is not a valid iCalendar."), + FALSE); + + return FALSE; + } + + pitip->top_level = e_cal_util_new_top_level (); + + pitip->main_comp = icalparser_parse_string (pitip->vcalendar); + if (pitip->main_comp == NULL || !is_icalcomp_valid (pitip->main_comp)) { + set_itip_error (view, + _("The calendar attached is not valid"), + _("The message claims to contain a calendar, but the calendar is not a valid iCalendar."), + FALSE); + + if (pitip->main_comp) { + icalcomponent_free (pitip->main_comp); + pitip->main_comp = NULL; + } + + return FALSE; + } + + prop = icalcomponent_get_first_property (pitip->main_comp, ICAL_METHOD_PROPERTY); + if (prop == NULL) { + pitip->method = ICAL_METHOD_PUBLISH; + } else { + pitip->method = icalproperty_get_method (prop); + } + + tz_iter = icalcomponent_begin_component (pitip->main_comp, ICAL_VTIMEZONE_COMPONENT); + while ((tz_comp = icalcompiter_deref (&tz_iter)) != NULL) { + icalcomponent *clone; + + clone = icalcomponent_new_clone (tz_comp); + icalcomponent_add_component (pitip->top_level, clone); + + icalcompiter_next (&tz_iter); + } + + pitip->iter = icalcomponent_begin_component (pitip->main_comp, ICAL_ANY_COMPONENT); + pitip->ical_comp = icalcompiter_deref (&pitip->iter); + if (pitip->ical_comp != NULL) { + kind = icalcomponent_isa (pitip->ical_comp); + if (kind != ICAL_VEVENT_COMPONENT + && kind != ICAL_VTODO_COMPONENT + && kind != ICAL_VFREEBUSY_COMPONENT + && kind != ICAL_VJOURNAL_COMPONENT) + pitip->ical_comp = get_next (&pitip->iter); + } + + if (pitip->ical_comp == NULL) { + set_itip_error (view, + _("The item in the calendar is not valid"), + _("The message does contain a calendar, but the calendar contains no events, tasks or free/busy information"), + FALSE); + + return FALSE; + } + + switch (icalcomponent_isa (pitip->ical_comp)) { + case ICAL_VEVENT_COMPONENT: + pitip->type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + pitip->has_organizer = icalcomponent_get_first_property (pitip->ical_comp, ICAL_ORGANIZER_PROPERTY) != NULL; + if (icalcomponent_get_first_property (pitip->ical_comp, ICAL_ATTENDEE_PROPERTY) == NULL) { + /* no attendees: assume that that this is not a meeting and organizer doesn't want a reply */ + pitip->no_reply_wanted = TRUE; + } else { + /* + * if we have attendees, then find_to_address() will check for our RSVP + * and set no_reply_wanted=TRUE if RSVP=FALSE for the current user + */ + } + break; + case ICAL_VTODO_COMPONENT: + pitip->type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + break; + case ICAL_VJOURNAL_COMPONENT: + pitip->type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS; + break; + default: + set_itip_error (view, + _("The item in the calendar is not valid"), + _("The message does contain a calendar, but the calendar contains no events, tasks or free/busy information"), + FALSE); + + return FALSE; + } + + pitip->total = icalcomponent_count_components (pitip->main_comp, ICAL_VEVENT_COMPONENT); + pitip->total += icalcomponent_count_components (pitip->main_comp, ICAL_VTODO_COMPONENT); + pitip->total += icalcomponent_count_components (pitip->main_comp, ICAL_VFREEBUSY_COMPONENT); + pitip->total += icalcomponent_count_components (pitip->main_comp, ICAL_VJOURNAL_COMPONENT); + + if (pitip->total > 1) { + + set_itip_error (view, + _("The calendar attached contains multiple items"), + _("To process all of these items, the file should be saved and the calendar imported"), + TRUE); + + } if (pitip->total > 0) { + pitip->current = 1; + } else { + pitip->current = 0; + } + + if (icalcomponent_isa (pitip->ical_comp) != ICAL_VJOURNAL_COMPONENT) { + gchar *my_address; + + prop = NULL; + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (pitip->ical_comp)); + my_address = itip_get_comp_attendee (pitip->registry, comp, NULL); + g_object_unref (comp); + comp = NULL; + + if (!prop) + prop = find_attendee (pitip->ical_comp, my_address); + if (!prop) + prop = find_attendee_if_sentby (pitip->ical_comp, my_address); + if (prop) { + icalparameter *param; + const gchar * delfrom; + + if ((param = icalproperty_get_first_parameter (prop, ICAL_DELEGATEDFROM_PARAMETER))) { + delfrom = icalparameter_get_delegatedfrom (param); + + pitip->delegator_address = g_strdup (itip_strip_mailto (delfrom)); + } + } + g_free (my_address); + prop = NULL; + + /* Determine any delegate sections */ + prop = icalcomponent_get_first_property (pitip->ical_comp, ICAL_X_PROPERTY); + while (prop) { + const gchar *x_name, *x_val; + + x_name = icalproperty_get_x_name (prop); + x_val = icalproperty_get_x (prop); + + if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-CALENDAR-UID")) + pitip->calendar_uid = g_strdup (x_val); + else if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-CALENDAR-URI")) + g_warning (G_STRLOC ": X-EVOLUTION-DELEGATOR-CALENDAR-URI used"); + else if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-ADDRESS")) + pitip->delegator_address = g_strdup (x_val); + else if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-NAME")) + pitip->delegator_name = g_strdup (x_val); + + prop = icalcomponent_get_next_property (pitip->ical_comp, ICAL_X_PROPERTY); + } + + /* Strip out procedural alarms for security purposes */ + alarm_iter = icalcomponent_begin_component (pitip->ical_comp, ICAL_VALARM_COMPONENT); + while ((alarm_comp = icalcompiter_deref (&alarm_iter)) != NULL) { + icalproperty *p; + + icalcompiter_next (&alarm_iter); + + p = icalcomponent_get_first_property (alarm_comp, ICAL_ACTION_PROPERTY); + if (!p || icalproperty_get_action (p) == ICAL_ACTION_PROCEDURE) + icalcomponent_remove_component (pitip->ical_comp, alarm_comp); + + icalcomponent_free (alarm_comp); + } + + if (have_alarms) { + alarm_iter = icalcomponent_begin_component (pitip->ical_comp, ICAL_VALARM_COMPONENT); + *have_alarms = icalcompiter_deref (&alarm_iter) != NULL; + } + } + + pitip->comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (pitip->comp, pitip->ical_comp)) { + g_object_unref (pitip->comp); + pitip->comp = NULL; + + set_itip_error (view, + _("The item in the calendar is not valid"), + _("The message does contain a calendar, but the calendar contains no events, tasks or free/busy information"), + FALSE); + + return FALSE; + }; + + /* Add default reminder if the config says so */ + + use_default_reminder = e_shell_settings_get_boolean ( + shell_settings, "cal-use-default-reminder"); + + if (use_default_reminder) { + ECalComponentAlarm *acomp; + gint interval; + EDurationType units; + ECalComponentAlarmTrigger trigger; + + interval = e_shell_settings_get_int ( + shell_settings, "cal-default-reminder-interval"); + units = e_shell_settings_get_int ( + shell_settings, "cal-default-reminder-units"); + + acomp = e_cal_component_alarm_new (); + + e_cal_component_alarm_set_action (acomp, E_CAL_COMPONENT_ALARM_DISPLAY); + + trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START; + memset (&trigger.u.rel_duration, 0, sizeof (trigger.u.rel_duration)); + + trigger.u.rel_duration.is_neg = TRUE; + + switch (units) { + case E_DURATION_MINUTES: + trigger.u.rel_duration.minutes = interval; + break; + case E_DURATION_HOURS: + trigger.u.rel_duration.hours = interval; + break; + case E_DURATION_DAYS: + trigger.u.rel_duration.days = interval; + break; + default: + g_assert_not_reached (); + } + + e_cal_component_alarm_set_trigger (acomp, trigger); + e_cal_component_add_alarm (pitip->comp, acomp); + + e_cal_component_alarm_free (acomp); + } + + find_from_address (pitip, pitip->ical_comp); + find_to_address (pitip, pitip->ical_comp, NULL); + + return TRUE; +} + +struct _opencal_msg { + MailMsg base; + + gchar *command; /* command line to run */ +}; + +static gchar * +open_calendar__desc (struct _opencal_msg *m, + gint complete) +{ + return g_strdup (_("Opening calendar")); +} + +static void +open_calendar__exec (struct _opencal_msg *m, + GCancellable *cancellable, + GError **error) +{ + if (!g_spawn_command_line_async (m->command, NULL)) { + g_warning ("Could not launch %s", m->command); + } +} + +static void +open_calendar__free (struct _opencal_msg *m) +{ + g_free (m->command); + m->command = NULL; +} + +static MailMsgInfo open_calendar_info = { + sizeof (struct _opencal_msg), + (MailMsgDescFunc) open_calendar__desc, + (MailMsgExecFunc) open_calendar__exec, + (MailMsgDoneFunc) NULL, + (MailMsgFreeFunc) open_calendar__free, +}; + +static gboolean +idle_open_cb (gpointer data) +{ + EMailPartItip *pitip = data; + struct _opencal_msg *m; + gchar *start, *end; + + start = isodate_from_time_t (pitip->start_time); + end = isodate_from_time_t (pitip->end_time); + m = mail_msg_new (&open_calendar_info); + m->command = g_strdup_printf ("evolution \"calendar:///?startdate=%s&enddate=%s\"", start, end); + mail_msg_slow_ordered_push (m); + + g_free (start); + g_free (end); + + return FALSE; +} + +static void +view_response_cb (ItipView *view, + ItipViewResponse response, + gpointer data) +{ + EMailPartItip *pitip = data; + gboolean status = FALSE; + icalproperty *prop; + ECalComponentTransparency trans; + + if (response == ITIP_VIEW_RESPONSE_SAVE) { + save_vcalendar_cb (pitip); + return; + } + + pitip->can_delete_invitation_from_cache = FALSE; + if (pitip->method == ICAL_METHOD_PUBLISH || pitip->method == ICAL_METHOD_REQUEST) { + if (itip_view_get_free_time_check_state (view)) + e_cal_component_set_transparency (pitip->comp, E_CAL_COMPONENT_TRANSP_TRANSPARENT); + else + e_cal_component_set_transparency (pitip->comp, E_CAL_COMPONENT_TRANSP_OPAQUE); + } else { + e_cal_component_get_transparency (pitip->comp, &trans); + + if (trans == E_CAL_COMPONENT_TRANSP_NONE) + e_cal_component_set_transparency (pitip->comp, E_CAL_COMPONENT_TRANSP_OPAQUE); + } + + if (!pitip->to_address && pitip->current_client != NULL) + e_client_get_backend_property_sync (E_CLIENT (pitip->current_client), CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, &pitip->to_address, NULL, NULL); + + /* check if it is a recur instance (no master object) and + * add a property */ + if (itip_view_get_recur_check_state (view)) { + prop = icalproperty_new_x ("All"); + icalproperty_set_x_name (prop, "X-GW-RECUR-INSTANCES-MOD-TYPE"); + icalcomponent_add_property (pitip->ical_comp, prop); + } + + switch (response) { + case ITIP_VIEW_RESPONSE_ACCEPT: + if (pitip->type != E_CAL_CLIENT_SOURCE_TYPE_MEMOS) + status = change_status ( + pitip->registry, + pitip->ical_comp, + pitip->to_address, + ICAL_PARTSTAT_ACCEPTED); + else + status = TRUE; + if (status) { + e_cal_component_rescan (pitip->comp); + pitip->can_delete_invitation_from_cache = TRUE; + update_item (pitip, view, response); + } + break; + case ITIP_VIEW_RESPONSE_TENTATIVE: + status = change_status ( + pitip->registry, + pitip->ical_comp, + pitip->to_address, + ICAL_PARTSTAT_TENTATIVE); + if (status) { + e_cal_component_rescan (pitip->comp); + pitip->can_delete_invitation_from_cache = TRUE; + update_item (pitip, view, response); + } + break; + case ITIP_VIEW_RESPONSE_DECLINE: + if (pitip->type != E_CAL_CLIENT_SOURCE_TYPE_MEMOS) + status = change_status ( + pitip->registry, + pitip->ical_comp, + pitip->to_address, + ICAL_PARTSTAT_DECLINED); + else { + prop = icalproperty_new_x ("1"); + icalproperty_set_x_name (prop, "X-GW-DECLINED"); + icalcomponent_add_property (pitip->ical_comp, prop); + status = TRUE; + } + + if (status) { + e_cal_component_rescan (pitip->comp); + pitip->can_delete_invitation_from_cache = TRUE; + update_item (pitip, view, response); + } + break; + case ITIP_VIEW_RESPONSE_UPDATE: + update_attendee_status (pitip, view); + break; + case ITIP_VIEW_RESPONSE_CANCEL: + update_item (pitip, view, response); + break; + case ITIP_VIEW_RESPONSE_REFRESH: + send_item (pitip, view); + break; + case ITIP_VIEW_RESPONSE_OPEN: + g_idle_add (idle_open_cb, pitip); + return; + default: + break; + } +} + +static gboolean +check_is_instance (icalcomponent *icalcomp) +{ + icalproperty *icalprop; + + icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY); + while (icalprop) { + const gchar *x_name; + + x_name = icalproperty_get_x_name (icalprop); + if (!strcmp (x_name, "X-GW-RECURRENCE-KEY")) { + return TRUE; + } + icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY); + } + + return FALSE; +} + +static gboolean +in_proper_folder (ESourceRegistry *registry, + CamelFolder *folder) +{ + EShell *shell; + EShellBackend *shell_backend; + EMailBackend *backend; + EMailSession *session; + MailFolderCache *folder_cache; + gboolean res = TRUE; + CamelFolderInfoFlags flags = 0; + + if (!folder) + return FALSE; + + shell = e_shell_get_default (); + shell_backend = e_shell_get_backend_by_name (shell, "mail"); + backend = E_MAIL_BACKEND (shell_backend); + session = e_mail_backend_get_session (backend); + folder_cache = e_mail_session_get_folder_cache (session); + + if (mail_folder_cache_get_folder_info_flags (folder_cache, folder, &flags)) { + /* it should be neither trash nor junk folder, */ + res = ((flags & CAMEL_FOLDER_TYPE_MASK) != CAMEL_FOLDER_TYPE_TRASH && + (flags & CAMEL_FOLDER_TYPE_MASK) != CAMEL_FOLDER_TYPE_JUNK && + /* it can be Inbox */ + ( (flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX || + /* or any other virtual folder */ + CAMEL_IS_VEE_FOLDER (folder) || + /* or anything else except of sent, outbox or drafts folder */ + (!em_utils_folder_is_sent (registry, folder) && + !em_utils_folder_is_outbox (registry, folder) && + !em_utils_folder_is_drafts (registry, folder)) + )); + } else { + /* cannot check for Inbox folder here */ + res = (folder->folder_flags & (CAMEL_FOLDER_IS_TRASH | CAMEL_FOLDER_IS_JUNK)) == 0 && ( + (CAMEL_IS_VEE_FOLDER (folder)) || ( + !em_utils_folder_is_sent (registry, folder) && + !em_utils_folder_is_outbox (registry, folder) && + !em_utils_folder_is_drafts (registry, folder))); + } + + return res; +} + +void +itip_view_init_view (ItipView *view) +{ + EShell *shell; + EShellSettings *shell_settings; + ESourceRegistry *registry; + ECalComponentText text; + ECalComponentOrganizer organizer; + ECalComponentDateTime datetime; + icaltimezone *from_zone, *to_zone; + GString *gstring = NULL; + GSList *list, *l; + icalcomponent *icalcomp; + const gchar *string, *org; + gint i; + gboolean response_enabled; + gboolean have_alarms = FALSE; + EMailPartItip *info; + + info = view->priv->itip_part; + g_return_if_fail (info != NULL); + + shell = e_shell_get_default (); + registry = e_shell_get_registry (shell); + shell_settings = e_shell_get_shell_settings (shell); + + info->registry = g_object_ref (registry); + + /* Reset current client before initializing view */ + info->current_client = NULL; + + /* Initialize the ecal hashes */ + for (i = 0; i < E_CAL_CLIENT_SOURCE_TYPE_LAST; i++) + info->clients[i] = g_hash_table_new_full ( + (GHashFunc) g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + /* FIXME Handle multiple VEVENTS with the same UID, ie detached instances */ + if (!extract_itip_data (info, view, &have_alarms)) + return; + + response_enabled = in_proper_folder (registry, info->folder); + + if (!response_enabled) { + itip_view_set_mode (view, ITIP_VIEW_MODE_HIDE_ALL); + } else { + itip_view_set_show_inherit_alarm_check ( + view, + have_alarms && (info->method == ICAL_METHOD_PUBLISH || info->method == ICAL_METHOD_REQUEST)); + + switch (info->method) { + case ICAL_METHOD_PUBLISH: + case ICAL_METHOD_REQUEST: + /* + * Treat meeting request (sent by organizer directly) and + * published evend (forwarded by organizer or attendee) alike: + * if the event has an organizer, then it can be replied to and + * we show the "accept/tentative/decline" choice. + * Otherwise only show "accept". + */ + itip_view_set_mode (view, + info->has_organizer ? + ITIP_VIEW_MODE_REQUEST : + ITIP_VIEW_MODE_PUBLISH); + break; + case ICAL_METHOD_REPLY: + itip_view_set_mode (view, ITIP_VIEW_MODE_REPLY); + break; + case ICAL_METHOD_ADD: + itip_view_set_mode (view, ITIP_VIEW_MODE_ADD); + break; + case ICAL_METHOD_CANCEL: + itip_view_set_mode (view, ITIP_VIEW_MODE_CANCEL); + break; + case ICAL_METHOD_REFRESH: + itip_view_set_mode (view, ITIP_VIEW_MODE_REFRESH); + break; + case ICAL_METHOD_COUNTER: + itip_view_set_mode (view, ITIP_VIEW_MODE_COUNTER); + break; + case ICAL_METHOD_DECLINECOUNTER: + itip_view_set_mode (view, ITIP_VIEW_MODE_DECLINECOUNTER); + break; + case ICAL_METHOD_X : + /* Handle appointment requests from Microsoft Live. This is + * a best-at-hand-now handling. Must be revisited when we have + * better access to the source of such meetings */ + info->method = ICAL_METHOD_REQUEST; + itip_view_set_mode (view, ITIP_VIEW_MODE_REQUEST); + break; + default: + return; + } + } + + itip_view_set_item_type (view, info->type); + + if (response_enabled) { + switch (info->method) { + case ICAL_METHOD_REQUEST: + /* FIXME What about the name? */ + itip_view_set_delegator (view, info->delegator_name ? info->delegator_name : info->delegator_address); + case ICAL_METHOD_PUBLISH: + case ICAL_METHOD_ADD: + case ICAL_METHOD_CANCEL: + case ICAL_METHOD_DECLINECOUNTER: + itip_view_set_show_update_check (view, FALSE); + + /* An organizer sent this */ + e_cal_component_get_organizer (info->comp, &organizer); + org = organizer.cn ? organizer.cn : itip_strip_mailto (organizer.value); + + itip_view_set_organizer (view, org); + if (organizer.sentby) + itip_view_set_organizer_sentby ( + view, itip_strip_mailto (organizer.sentby)); + + if (info->my_address) { + if (!(organizer.value && !g_ascii_strcasecmp (itip_strip_mailto (organizer.value), info->my_address)) + && !(organizer.sentby && !g_ascii_strcasecmp (itip_strip_mailto (organizer.sentby), info->my_address)) + && (info->to_address && g_ascii_strcasecmp (info->to_address, info->my_address))) + itip_view_set_proxy (view, info->to_name ? info->to_name : info->to_address); + } + break; + case ICAL_METHOD_REPLY: + case ICAL_METHOD_REFRESH: + case ICAL_METHOD_COUNTER: + itip_view_set_show_update_check (view, TRUE); + + /* An attendee sent this */ + e_cal_component_get_attendee_list (info->comp, &list); + if (list != NULL) { + ECalComponentAttendee *attendee; + + attendee = list->data; + + itip_view_set_attendee (view, attendee->cn ? attendee->cn : itip_strip_mailto (attendee->value)); + + if (attendee->sentby) + itip_view_set_attendee_sentby (view, itip_strip_mailto (attendee->sentby)); + + if (info->my_address) { + if (!(attendee->value && !g_ascii_strcasecmp (itip_strip_mailto (attendee->value), info->my_address)) + && !(attendee->sentby && !g_ascii_strcasecmp (itip_strip_mailto (attendee->sentby), info->my_address)) + && (info->from_address && g_ascii_strcasecmp (info->from_address, info->my_address))) + itip_view_set_proxy (view, info->from_name ? info->from_name : info->from_address); + } + + e_cal_component_free_attendee_list (list); + } + break; + default: + g_assert_not_reached (); + break; + } + } + + e_cal_component_get_summary (info->comp, &text); + itip_view_set_summary (view, text.value ? text.value : C_("cal-itip", "None")); + + e_cal_component_get_location (info->comp, &string); + itip_view_set_location (view, string); + + /* Status really only applies for REPLY */ + if (response_enabled && info->method == ICAL_METHOD_REPLY) { + e_cal_component_get_attendee_list (info->comp, &list); + if (list != NULL) { + ECalComponentAttendee *a = list->data; + + switch (a->status) { + case ICAL_PARTSTAT_ACCEPTED: + itip_view_set_status (view, _("Accepted")); + break; + case ICAL_PARTSTAT_TENTATIVE: + itip_view_set_status (view, _("Tentatively Accepted")); + break; + case ICAL_PARTSTAT_DECLINED: + itip_view_set_status (view, _("Declined")); + break; + case ICAL_PARTSTAT_DELEGATED: + itip_view_set_status (view, _("Delegated")); + break; + default: + itip_view_set_status (view, _("Unknown")); + } + } + e_cal_component_free_attendee_list (list); + } + + if (info->method == ICAL_METHOD_REPLY + || info->method == ICAL_METHOD_COUNTER + || info->method == ICAL_METHOD_DECLINECOUNTER) { + /* FIXME Check spec to see if multiple comments are actually valid */ + /* Comments for iTIP are limited to one per object */ + e_cal_component_get_comment_list (info->comp, &list); + if (list) { + ECalComponentText *text = list->data; + + if (text->value) { + gchar *html; + + html = camel_text_to_html ( + text->value, + CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | + CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, + 0); + + itip_view_set_comment (view, html); + + g_free (html); + } + } + e_cal_component_free_text_list (list); + } + + e_cal_component_get_description_list (info->comp, &list); + for (l = list; l; l = l->next) { + ECalComponentText *text = l->data; + + if (!gstring && text->value) + gstring = g_string_new (text->value); + else if (text->value) + g_string_append_printf (gstring, "\n\n%s", text->value); + } + + e_cal_component_free_text_list (list); + + if (gstring) { + gchar *html; + + html = camel_text_to_html ( + gstring->str, + CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | + CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | + CAMEL_MIME_FILTER_TOHTML_MARK_CITATION | + CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, + 0); + + itip_view_set_description (view, html); + g_string_free (gstring, TRUE); + g_free (html); + } + + to_zone = e_shell_settings_get_pointer (shell_settings, "cal-timezone"); + + e_cal_component_get_dtstart (info->comp, &datetime); + info->start_time = 0; + if (datetime.value) { + struct tm start_tm; + + /* If the timezone is not in the component, guess the local time */ + /* Should we guess if the timezone is an olsen name somehow? */ + if (datetime.value->is_utc) + from_zone = icaltimezone_get_utc_timezone (); + else if (!datetime.value->is_utc && datetime.tzid) + from_zone = icalcomponent_get_timezone (info->top_level, datetime.tzid); + else + from_zone = NULL; + + start_tm = icaltimetype_to_tm_with_zone (datetime.value, from_zone, to_zone); + + itip_view_set_start (view, &start_tm, datetime.value->is_date); + info->start_time = icaltime_as_timet_with_zone (*datetime.value, from_zone); + } + + icalcomp = e_cal_component_get_icalcomponent (info->comp); + + /* Set the recurrence id */ + if (check_is_instance (icalcomp) && datetime.value) { + ECalComponentRange *recur_id; + struct icaltimetype icaltime = icaltime_convert_to_zone (*datetime.value, to_zone); + + recur_id = g_new0 (ECalComponentRange, 1); + recur_id->type = E_CAL_COMPONENT_RANGE_SINGLE; + recur_id->datetime.value = &icaltime; + recur_id->datetime.tzid = icaltimezone_get_tzid (to_zone); + e_cal_component_set_recurid (info->comp, recur_id); + g_free (recur_id); /* it's ok to call g_free here */ + } + e_cal_component_free_datetime (&datetime); + + e_cal_component_get_dtend (info->comp, &datetime); + info->end_time = 0; + if (datetime.value) { + struct tm end_tm; + + /* If the timezone is not in the component, guess the local time */ + /* Should we guess if the timezone is an olsen name somehow? */ + if (datetime.value->is_utc) + from_zone = icaltimezone_get_utc_timezone (); + else if (!datetime.value->is_utc && datetime.tzid) + from_zone = icalcomponent_get_timezone (info->top_level, datetime.tzid); + else + from_zone = NULL; + + if (datetime.value->is_date) { + /* RFC says the DTEND is not inclusive, thus subtract one day + * if we have a date */ + + icaltime_adjust (datetime.value, -1, 0, 0, 0); + } + + end_tm = icaltimetype_to_tm_with_zone (datetime.value, from_zone, to_zone); + + itip_view_set_end (view, &end_tm, datetime.value->is_date); + info->end_time = icaltime_as_timet_with_zone (*datetime.value, from_zone); + } + e_cal_component_free_datetime (&datetime); + + /* Recurrence info */ + /* FIXME Better recurring description */ + if (e_cal_component_has_recurrences (info->comp)) { + /* FIXME Tell the user we don't support recurring tasks */ + switch (info->type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + itip_view_add_upper_info_item (view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, _("This meeting recurs")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + itip_view_add_upper_info_item (view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, _("This task recurs")); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + itip_view_add_upper_info_item (view, ITIP_VIEW_INFO_ITEM_TYPE_INFO, _("This memo recurs")); + break; + default: + g_assert_not_reached (); + break; + } + } + + if (response_enabled) { + g_signal_connect ( + view, "response", + G_CALLBACK (view_response_cb), info); + + itip_view_set_show_free_time_check (view, info->type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS && (info->method == ICAL_METHOD_PUBLISH || info->method == ICAL_METHOD_REQUEST)); + + if (info->calendar_uid) { + start_calendar_server_by_uid (info, view, info->calendar_uid, info->type); + } else { + find_server (info, view, info->comp); + set_buttons_sensitive (info, view); + } + } +} diff --git a/modules/itip-formatter/itip-view.h b/modules/itip-formatter/itip-view.h new file mode 100644 index 0000000000..60b8b1d4ba --- /dev/null +++ b/modules/itip-formatter/itip-view.h @@ -0,0 +1,270 @@ +/* + * + * 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: + * JP Rosevear <jpr@novell.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef _ITIP_VIEW_H_ +#define _ITIP_VIEW_H_ + +#include <stdarg.h> +#include <unistd.h> +#include <gtk/gtk.h> +#include <webkit/webkitdom.h> +#include <libecal/libecal.h> +#include <libedataserver/libedataserver.h> + +G_BEGIN_DECLS + +#define ITIP_TYPE_VIEW (itip_view_get_type ()) +#define ITIP_VIEW(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ITIP_TYPE_VIEW, ItipView)) +#define ITIP_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ITIP_TYPE_VIEW, ItipViewClass)) +#define ITIP_IS_VIEW(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ITIP_TYPE_VIEW)) +#define ITIP_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ITIP_TYPE_VIEW)) +#define ITIP_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ITIP_TYPE_VIEW, ItipViewClass)) + +typedef struct _ItipView ItipView; +typedef struct _ItipViewPrivate ItipViewPrivate; +typedef struct _ItipViewClass ItipViewClass; +typedef struct _EMailPartItip EMailPartItip; + +typedef enum { + ITIP_VIEW_MODE_NONE, + ITIP_VIEW_MODE_PUBLISH, + ITIP_VIEW_MODE_REQUEST, + ITIP_VIEW_MODE_COUNTER, + ITIP_VIEW_MODE_DECLINECOUNTER, + ITIP_VIEW_MODE_ADD, + ITIP_VIEW_MODE_REPLY, + ITIP_VIEW_MODE_REFRESH, + ITIP_VIEW_MODE_CANCEL, + ITIP_VIEW_MODE_HIDE_ALL +} ItipViewMode; + +typedef enum { + ITIP_VIEW_RESPONSE_NONE, + ITIP_VIEW_RESPONSE_ACCEPT, + ITIP_VIEW_RESPONSE_TENTATIVE, + ITIP_VIEW_RESPONSE_DECLINE, + ITIP_VIEW_RESPONSE_UPDATE, + ITIP_VIEW_RESPONSE_CANCEL, + ITIP_VIEW_RESPONSE_REFRESH, + ITIP_VIEW_RESPONSE_OPEN, + ITIP_VIEW_RESPONSE_SAVE +} ItipViewResponse; + +typedef enum { + ITIP_VIEW_INFO_ITEM_TYPE_NONE, + ITIP_VIEW_INFO_ITEM_TYPE_INFO, + ITIP_VIEW_INFO_ITEM_TYPE_WARNING, + ITIP_VIEW_INFO_ITEM_TYPE_ERROR, + ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS +} ItipViewInfoItemType; + +struct _ItipView { + GObject parent_instance; + + ItipViewPrivate *priv; +}; + +struct _ItipViewClass { + GObjectClass parent_class; + + void (* source_selected) (ItipView *view, + ESource *selected_source); + + void (* response) (ItipView *view, + gint response); +}; + +GType itip_view_get_type (void); + +ItipView * itip_view_new (EMailPartItip *puri, + ESourceRegistry *registry); +void itip_view_init_view (ItipView *view); + +void itip_view_write (GString *buffer); + +void itip_view_write_for_printing (ItipView *view, + GString *buffer); + +void itip_view_create_dom_bindings (ItipView *view, + WebKitDOMElement *element); + +EMailPartItip * itip_view_get_mail_part (ItipView *view); +ESourceRegistry * + itip_view_get_registry (ItipView *view); +const gchar * itip_view_get_extension_name (ItipView *view); +void itip_view_set_extension_name (ItipView *view, + const gchar *extension_name); + +void itip_view_set_mode (ItipView *view, + ItipViewMode mode); +ItipViewMode itip_view_get_mode (ItipView *view); + +void itip_view_set_item_type (ItipView *view, + ECalClientSourceType type); +ECalClientSourceType + itip_view_get_item_type (ItipView *view); + +void itip_view_set_organizer (ItipView *view, + const gchar *organizer); +const gchar * itip_view_get_organizer (ItipView *view); + +void itip_view_set_organizer_sentby (ItipView *view, + const gchar *sentby); +const gchar * itip_view_get_organizer_sentby (ItipView *view); + +void itip_view_set_attendee (ItipView *view, + const gchar *attendee); +const gchar * itip_view_get_attendee (ItipView *view); + +void itip_view_set_attendee_sentby (ItipView *view, + const gchar *sentby); +const gchar * itip_view_get_attendee_sentby (ItipView *view); + +void itip_view_set_delegator (ItipView *view, + const gchar *delegator); +const gchar * itip_view_get_delegator (ItipView *view); + +void itip_view_set_proxy (ItipView *view, + const gchar *proxy); +const gchar * itip_view_get_proxy (ItipView *view); + +void itip_view_set_summary (ItipView *view, + const gchar *summary); +const gchar * itip_view_get_summary (ItipView *view); + +void itip_view_set_location (ItipView *view, + const gchar *location); +const gchar * itip_view_get_location (ItipView *view); + +void itip_view_set_status (ItipView *view, + const gchar *status); +const gchar * itip_view_get_status (ItipView *view); + +void itip_view_set_comment (ItipView *view, + const gchar *comment); +const gchar * itip_view_get_comment (ItipView *view); + +void itip_view_set_description (ItipView *view, + const gchar *description); +const gchar * itip_view_get_description (ItipView *view); + +void itip_view_set_start (ItipView *view, + struct tm *start, + gboolean is_date); +const struct tm * + itip_view_get_start (ItipView *view, + gboolean *is_date); + +void itip_view_set_end (ItipView *view, + struct tm *end, + gboolean is_date); +const struct tm * + itip_view_get_end (ItipView *view, + gboolean *is_date); + +guint itip_view_add_upper_info_item (ItipView *view, + ItipViewInfoItemType type, + const gchar *message); +guint itip_view_add_upper_info_item_printf + (ItipView *view, + ItipViewInfoItemType, + const gchar *format, ...) G_GNUC_PRINTF (3, 4); +void itip_view_remove_upper_info_item + (ItipView *view, + guint id); +void itip_view_clear_upper_info_items + (ItipView *view); + +guint itip_view_add_lower_info_item (ItipView *view, + ItipViewInfoItemType type, + const gchar *message); +guint itip_view_add_lower_info_item_printf + (ItipView *view, + ItipViewInfoItemType type, + const gchar *format, ...) G_GNUC_PRINTF (3, 4); +void itip_view_remove_lower_info_item + (ItipView *view, + guint id); +void itip_view_clear_lower_info_items + (ItipView *view); + +void itip_view_set_source (ItipView *view, + ESource *source); +ESource * itip_view_ref_source (ItipView *view); + +void itip_view_set_rsvp (ItipView *view, + gboolean rsvp); +gboolean itip_view_get_rsvp (ItipView *view); + +void itip_view_set_show_rsvp_check (ItipView *view, + gboolean show); +gboolean itip_view_get_show_rsvp_check (ItipView *view); + +void itip_view_set_update (ItipView *view, + gboolean update); +gboolean itip_view_get_update (ItipView *view); + +void itip_view_set_show_update_check (ItipView *view, + gboolean show); +gboolean itip_view_get_show_update_check (ItipView *view); + +void itip_view_set_rsvp_comment (ItipView *view, + const gchar *comment); +gchar * itip_view_get_rsvp_comment (ItipView *view); + +void itip_view_set_buttons_sensitive (ItipView *view, + gboolean sensitive); +gboolean itip_view_get_buttons_sensitive (ItipView *view); + +void itip_view_set_show_recur_check (ItipView *view, + gboolean show); +gboolean itip_view_get_recur_check_state (ItipView *view); + +void itip_view_set_needs_decline (ItipView *view, + gboolean needs_decline); + +void itip_view_set_show_free_time_check + (ItipView *view, + gboolean show); +gboolean itip_view_get_free_time_check_state + (ItipView *view); + +void itip_view_set_show_keep_alarm_check + (ItipView *view, + gboolean show); +gboolean itip_view_get_keep_alarm_check_state + (ItipView *view); + +void itip_view_set_show_inherit_alarm_check + (ItipView *view, + gboolean show); +gboolean itip_view_get_inherit_alarm_check_state + (ItipView *view); + +void itip_view_set_error (ItipView *view, + const gchar *error_html, + gboolean show_save_btn); + +G_END_DECLS + +#endif diff --git a/modules/itip-formatter/org-gnome-itip-formatter.error.xml b/modules/itip-formatter/org-gnome-itip-formatter.error.xml new file mode 100644 index 0000000000..e5d84c67af --- /dev/null +++ b/modules/itip-formatter/org-gnome-itip-formatter.error.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<error-list domain="org.gnome.itip-formatter"> + + <error id="add-unknown-attendee" type="question"> + <_primary>This response is not from a current attendee. Add the sender as an attendee?</_primary> + <button stock="gtk-yes" response="GTK_RESPONSE_YES"/> + <button stock="gtk-no" response="GTK_RESPONSE_NO"/> + </error> + + <error id="add-delegate" type="question"> + <_primary>This meeting has been delegated</_primary> + <_secondary>'{0}' has delegated the meeting. Do you want to add the delegate '{1}'?</_secondary> + <button stock="gtk-yes" response="GTK_RESPONSE_YES"/> + <button stock="gtk-no" response="GTK_RESPONSE_NO"/> + </error> + +</error-list> diff --git a/modules/itip-formatter/plugin/Makefile.am b/modules/itip-formatter/plugin/Makefile.am new file mode 100644 index 0000000000..b84059c2b4 --- /dev/null +++ b/modules/itip-formatter/plugin/Makefile.am @@ -0,0 +1,36 @@ +@EVO_PLUGIN_RULE@ + +plugin_DATA = org-gnome-itip-formatter.eplug +plugin_LTLIBRARIES = liborg-gnome-itip-formatter.la + +liborg_gnome_itip_formatter_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -I$(top_srcdir)/modules/itip-formatter \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +liborg_gnome_itip_formatter_la_SOURCES = \ + config-ui.c + +liborg_gnome_itip_formatter_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED) + +liborg_gnome_itip_formatter_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/calendar/gui/libevolution-calendar.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/shell/libeshell.la \ + $(top_builddir)/em-format/libemformat.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ + $(top_builddir)/modules/itip-formatter/module-itip-formatter.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +BUILT_SOURCES = $(plugin_DATA) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = \ + org-gnome-itip-formatter.eplug.xml diff --git a/modules/itip-formatter/plugin/config-ui.c b/modules/itip-formatter/plugin/config-ui.c new file mode 100644 index 0000000000..9ba6f05b0e --- /dev/null +++ b/modules/itip-formatter/plugin/config-ui.c @@ -0,0 +1,159 @@ +/* + * 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/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include <mail/em-config.h> + +#include <shell/e-shell.h> +#include <libedataserver/libedataserver.h> +#include <libecal/libecal.h> +#include <modules/itip-formatter/e-conflict-search-selector.h> +#include <modules/itip-formatter/e-source-conflict-search.h> + +#define CONF_KEY_DELETE "delete-processed" + +GtkWidget *itip_formatter_page_factory (EPlugin *ep, EConfigHookItemFactoryData *hook_data); +gint e_plugin_lib_enable (EPlugin *ep, gint enable); + +gint +e_plugin_lib_enable (EPlugin *ep, + gint enable) +{ + return 0; +} + +static void +delete_toggled_cb (GtkWidget *widget) +{ + GSettings *settings; + gboolean active; + + settings = g_settings_new ("org.gnome.evolution.plugin.itip"); + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + g_settings_set_boolean (settings, CONF_KEY_DELETE, active); + g_object_unref (settings); +} + +GtkWidget * +itip_formatter_page_factory (EPlugin *ep, + EConfigHookItemFactoryData *hook_data) +{ + EShell *shell; + ESourceRegistry *registry; + GtkWidget *page; + GtkWidget *tab_label; + GtkWidget *frame; + GtkWidget *frame_label; + GtkWidget *padding_label; + GtkWidget *hbox; + GtkWidget *inner_vbox; + GtkWidget *check; + GtkWidget *label; + GtkWidget *ess; + GtkWidget *scrolledwin; + gchar *str; + GSettings *settings; + + shell = e_shell_get_default (); + registry = e_shell_get_registry (shell); + + /* Create a new notebook page */ + page = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (page), 12); + tab_label = gtk_label_new (_("Meeting Invitations")); + gtk_notebook_append_page (GTK_NOTEBOOK (hook_data->parent), page, tab_label); + + /* Frame */ + frame = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0); + + /* "General" */ + frame_label = gtk_label_new (""); + str = g_strdup_printf ("<span weight=\"bold\">%s</span>", _("General")); + gtk_label_set_markup (GTK_LABEL (frame_label), str); + g_free (str); + gtk_misc_set_alignment (GTK_MISC (frame_label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (frame), frame_label, FALSE, FALSE, 0); + + /* Indent/padding */ + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (frame), hbox, FALSE, TRUE, 0); + padding_label = gtk_label_new (""); + gtk_box_pack_start (GTK_BOX (hbox), padding_label, FALSE, FALSE, 0); + inner_vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox), inner_vbox, FALSE, FALSE, 0); + + /* Delete message after acting */ + settings = g_settings_new ("org.gnome.evolution.plugin.itip"); + + check = gtk_check_button_new_with_mnemonic (_("_Delete message after acting")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), g_settings_get_boolean (settings, CONF_KEY_DELETE)); + g_signal_connect ( + check, "toggled", + G_CALLBACK (delete_toggled_cb), NULL); + gtk_box_pack_start (GTK_BOX (inner_vbox), check, FALSE, FALSE, 0); + + g_object_unref (settings); + + /* "Conflict searching" */ + frame = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (page), frame, TRUE, TRUE, 24); + + frame_label = gtk_label_new (""); + str = g_strdup_printf ("<span weight=\"bold\">%s</span>", _("Conflict Search")); + gtk_label_set_markup (GTK_LABEL (frame_label), str); + g_free (str); + gtk_misc_set_alignment (GTK_MISC (frame_label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (frame), frame_label, FALSE, FALSE, 0); + + /* Indent/padding */ + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (frame), hbox, TRUE, TRUE, 0); + padding_label = gtk_label_new (""); + gtk_box_pack_start (GTK_BOX (hbox), padding_label, FALSE, FALSE, 0); + inner_vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox), inner_vbox, TRUE, TRUE, 0); + + /* Source selector */ + label = gtk_label_new (_("Select the calendars to search for meeting conflicts")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_box_pack_start (GTK_BOX (inner_vbox), label, FALSE, FALSE, 0); + + scrolledwin = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (scrolledwin), + GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (inner_vbox), scrolledwin, TRUE, TRUE, 0); + + ess = e_conflict_search_selector_new (registry); + atk_object_set_name (gtk_widget_get_accessible (ess), _("Conflict Search")); + gtk_container_add (GTK_CONTAINER (scrolledwin), ess); + + gtk_widget_show_all (page); + + return page; +} diff --git a/modules/itip-formatter/plugin/org-gnome-itip-formatter.eplug.xml b/modules/itip-formatter/plugin/org-gnome-itip-formatter.eplug.xml new file mode 100644 index 0000000000..b60b0e129f --- /dev/null +++ b/modules/itip-formatter/plugin/org-gnome-itip-formatter.eplug.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<e-plugin-list> + <e-plugin id="org.gnome.evolution.itip_formatter" + type="shlib" _name="Itip Formatter" + location="@PLUGINDIR@/liborg-gnome-itip-formatter@SOEXT@"> + <_description>Display "text/calendar" MIME parts in mail messages.</_description> + <author name="JP Rosevear" email="jpr@novell.com"/> + + <hook class="org.gnome.evolution.calendar.config:1.0"> + <group id="org.gnome.evolution.calendar.prefs" target="prefs"> + <item type="page" path="90.itip" label="itip" factory="itip_formatter_page_factory"/> + </group> + </hook> + </e-plugin> +</e-plugin-list>
\ No newline at end of file diff --git a/modules/prefer-plain/Makefile.am b/modules/prefer-plain/Makefile.am new file mode 100644 index 0000000000..e4e6b56d02 --- /dev/null +++ b/modules/prefer-plain/Makefile.am @@ -0,0 +1,28 @@ +SUBDIRS=plugin + +module_LTLIBRARIES = module-prefer-plain.la + +module_prefer_plain_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + -DG_LOG_DOMAIN=\"evolution-module-prefer-plain\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +module_prefer_plain_la_SOURCES = \ + e-mail-parser-prefer-plain.c \ + e-mail-parser-prefer-plain.h \ + evolution-module-prefer-plain.c + +module_prefer_plain_la_LIBADD = \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/em-format/libemformat.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +module_prefer_plain_la_LDFLAGS = \ + -avoid-version -module $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/prefer-plain/e-mail-parser-prefer-plain.c b/modules/prefer-plain/e-mail-parser-prefer-plain.c new file mode 100644 index 0000000000..37da8d3a43 --- /dev/null +++ b/modules/prefer-plain/e-mail-parser-prefer-plain.c @@ -0,0 +1,553 @@ +/* + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "e-mail-parser-prefer-plain.h" + +#include <em-format/e-mail-extension-registry.h> +#include <em-format/e-mail-parser-extension.h> +#include <em-format/e-mail-part.h> +#include <em-format/e-mail-part-utils.h> + +#include <libebackend/libebackend.h> + +#define d(x) + +typedef struct _EMailParserPreferPlain { + EExtension parent; + + GSettings *settings; + gint mode; + gboolean show_suppressed; +} EMailParserPreferPlain; + +typedef struct _EMailParserPreferPlainClass { + EExtensionClass parent_class; +} EMailParserPreferPlainClass; + +GType e_mail_parser_prefer_plain_get_type (void); +static void e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface); +static void e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface); + +enum { + EPP_NORMAL, + EPP_PREFER, + EPP_TEXT +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailParserPreferPlain, + e_mail_parser_prefer_plain, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_parser_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_PARSER_EXTENSION, + e_mail_parser_parser_extension_interface_init)); + +static const gchar* parser_mime_types[] = { "multipart/alternative", + "text/html", + NULL }; + +static struct { + const gchar *key; + const gchar *label; + const gchar *description; +} epp_options[] = { + { "normal", + N_("Show HTML if present"), + N_("Let Evolution choose the best part to show.") }, + + { "prefer_plain", + N_("Show plain text if present"), + N_("Show plain text part, if present, otherwise " + "let Evolution choose the best part to show.") }, + + { "only_plain", + N_("Only ever show plain text"), + N_("Always show plain text part and make attachments " + "from other parts, if requested.") }, +}; + +enum { + PROP_0, + PROP_MODE, + PROP_SHOW_SUPPRESSED +}; + +static GSList * +make_part_attachment (EMailParser *parser, + CamelMimePart *part, + GString *part_id, + gboolean force_html, + GCancellable *cancellable) +{ + GSList *parts; + + if (camel_content_type_is (camel_mime_part_get_content_type (part), "text", "html")) { + EMailPart *mail_part; + gint len; + /* always show HTML as attachments and not inline */ + camel_mime_part_set_disposition (part, "attachment"); + + if (!camel_mime_part_get_filename (part)) { + gchar *str = g_strdup_printf ("%s.html", _("attachment")); + camel_mime_part_set_filename (part, str); + g_free (str); + } + + len = part_id->len; + g_string_append (part_id, ".text_html"); + mail_part = e_mail_part_new (part, part_id->str); + mail_part->mime_type = g_strdup ("text/html"); + g_string_truncate (part_id, len); + + parts = e_mail_parser_wrap_as_attachment ( + parser, part, g_slist_append (NULL, mail_part), + part_id, cancellable); + + } else if (force_html && CAMEL_IS_MIME_MESSAGE (part)) { + /* message was asked to be formatted as text/html; + * might be for cases where message itself is a text/html part */ + CamelMimePart *new_part; + CamelDataWrapper *content; + + content = camel_medium_get_content (CAMEL_MEDIUM (part)); + g_return_val_if_fail (content != NULL, NULL); + + new_part = camel_mime_part_new (); + camel_medium_set_content (CAMEL_MEDIUM (new_part), content); + + parts = e_mail_parser_parse_part ( + parser, new_part, part_id, cancellable); + + g_object_unref (new_part); + } else { + parts = e_mail_parser_parse_part ( + parser, part, part_id, cancellable); + } + + return parts; +} + +static GSList * +export_as_attachments (CamelMultipart *mp, + EMailParser *parser, + CamelMimePart *except, + GString *part_id, + GCancellable *cancellable) +{ + gint i, nparts; + CamelMimePart *part; + gint len; + GSList *parts; + + if (!mp || !CAMEL_IS_MULTIPART (mp)) + return NULL; + + len = part_id->len; + nparts = camel_multipart_get_number (mp); + parts = NULL; + for (i = 0; i < nparts; i++) { + part = camel_multipart_get_part (mp, i); + + if (part != except) { + CamelMultipart *multipart = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + g_string_append_printf (part_id, ".aleternative-prefer-plain.%d", i); + if (CAMEL_IS_MULTIPART (multipart)) { + parts = g_slist_concat (parts, + export_as_attachments ( + multipart, parser, + except, part_id, + cancellable)); + } else { + parts = g_slist_concat (parts, + make_part_attachment ( + parser, part, part_id, + FALSE, cancellable)); + } + g_string_truncate (part_id, len); + } + } + + return parts; +} + +static GSList * +empe_prefer_plain_parse (EMailParserExtension *extension, + EMailParser *parser, + CamelMimePart *part, + GString *part_id, + GCancellable *cancellable) +{ + EMailParserPreferPlain *emp_pp; + CamelMultipart *mp; + CamelMimePart *display_part = NULL, *calendar_part = NULL; + gint i, nparts, partidlen, displayid = 0, calendarid = 0; + GSList *parts; + + emp_pp = (EMailParserPreferPlain *) extension; + + /* We 'can' parse HTML as well! + * The reason simply is to convert the HTML part to attachment in some + * cases, otherwise we will return NULL and fallback to "normal" parser. */ + if (camel_content_type_is (camel_mime_part_get_content_type (part), "text", "html")) { + GQueue *extensions; + EMailExtensionRegistry *reg; + + reg = e_mail_parser_get_extension_registry (parser); + extensions = e_mail_extension_registry_get_for_mime_type ( + reg, "text/html"); + + if (emp_pp->mode != EPP_TEXT + || strstr (part_id->str, ".alternative-prefer-plain.") != NULL + || e_mail_part_is_inline (part, extensions)) { + + return NULL; + + } else if (emp_pp->show_suppressed) { + return make_part_attachment ( + parser, part, part_id, + TRUE, cancellable); + } + + /* Return an empty item. We MUST return something, otherwise + * the parser would think we have failed to parse the part + * and would let a fallback extension to parse it and we don't + * want that... */ + /* FIXME: In theory we could parse it anyway and just set + * is_hidden to TRUE....? */ + return g_slist_alloc (); + } + + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + partidlen = part_id->len; + + parts = NULL; + if (emp_pp->mode == EPP_NORMAL) { + gboolean have_plain = FALSE; + + /* Try to find text/html part even when not as last and force + * to show it. Old handler will show the last part of + * multipart/alternate, but if we can offer HTML, then + * offer it, regardless of position in multipart. But do + * this when have only text/plain and text/html parts, + * not more. */ + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelContentType *content_type; + + part = camel_multipart_get_part (mp, i); + + if (!part) + continue; + + content_type = camel_mime_part_get_content_type (part); + + if (camel_content_type_is (content_type, "text", "html")) { + displayid = i; + display_part = part; + + if (have_plain) + break; + } else if (camel_content_type_is (content_type, "text", "plain")) { + have_plain = TRUE; + + if (display_part) + break; + } + } + + if (display_part && have_plain && nparts == 2) { + g_string_append_printf (part_id, ".alternative-prefer-plain.%d", displayid); + /* FIXME Not passing a GCancellable here. */ + parts = e_mail_parser_parse_part_as ( + parser, display_part, part_id, + "text/html", cancellable); + + g_string_truncate (part_id, partidlen); + } else { + /* Parser will automatically fallback to next extension */ + return NULL; + + } + + return parts; + + } else if (!CAMEL_IS_MULTIPART (mp)) { + return e_mail_parser_parse_part_as ( + parser, part, part_id, + "application/vnd.evolution.source", cancellable); + } + + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelContentType *ct; + + part = camel_multipart_get_part (mp, i); + + if (!part) + continue; + + ct = camel_mime_part_get_content_type (part); + if (!display_part && camel_content_type_is (ct, "text", "plain")) { + displayid = i; + display_part = part; + } else if (!calendar_part && (camel_content_type_is (ct, "text", "calendar") || camel_content_type_is (ct, "text", "x-calendar"))) { + calendarid = i; + calendar_part = part; + } + } + + /* if we found a text part, show it */ + if (display_part) { + g_string_append_printf(part_id, ".alternative-prefer-plain.%d", displayid); + parts = g_slist_concat (parts, + e_mail_parser_parse_part_as ( + parser, display_part, part_id, + "text/plain", cancellable)); + + g_string_truncate (part_id, partidlen); + } + + /* all other parts are attachments */ + if (emp_pp->show_suppressed) { + parts = g_slist_concat (parts, + export_as_attachments ( + mp, parser, display_part, part_id, + cancellable)); + } else if (calendar_part) { + g_string_append_printf(part_id, ".alternative-prefer-plain.%d", calendarid); + parts = g_slist_concat (parts, + make_part_attachment ( + parser, calendar_part, part_id, + FALSE, NULL)); + } + + g_string_truncate (part_id, partidlen); + + return parts; +} + +static const gchar ** +empe_mime_types (EMailExtension *extension) +{ + return parser_mime_types; +} + +void +e_mail_parser_prefer_plain_type_register (GTypeModule *type_module) +{ + e_mail_parser_prefer_plain_register_type (type_module); +} + +static void +e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = empe_mime_types; +} + +static void +e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface) +{ + iface->parse = empe_prefer_plain_parse; +} + +static void +e_mail_parser_prefer_plain_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_parser_prefer_plain_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EMailParserPreferPlain *parser; + + parser = (EMailParserPreferPlain *) object; + + switch (property_id) { + case PROP_MODE: + g_value_set_int (value, parser->mode); + return; + case PROP_SHOW_SUPPRESSED: + g_value_set_boolean (value, parser->show_suppressed); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_mail_parser_prefer_plain_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EMailParserPreferPlain *parser; + + parser = (EMailParserPreferPlain *) object; + + switch (property_id) { + case PROP_MODE: + parser->mode = g_value_get_int (value); + return; + case PROP_SHOW_SUPPRESSED: + parser->show_suppressed = g_value_get_boolean (value); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_mail_parser_prefer_plain_finalize (GObject *object) +{ + EMailParserPreferPlain *parser; + + parser = (EMailParserPreferPlain *) object; + + g_clear_object (&parser->settings); + + G_OBJECT_CLASS (e_mail_parser_prefer_plain_parent_class)->finalize (object); +} + +static void +e_mail_parser_prefer_plain_class_init (EMailParserPreferPlainClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_parser_prefer_plain_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_parser_prefer_plain_constructed; + object_class->get_property = e_mail_parser_prefer_plain_get_property; + object_class->set_property = e_mail_parser_prefer_plain_set_property; + object_class->finalize = e_mail_parser_prefer_plain_finalize; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY; + + g_object_class_install_property ( + object_class, + PROP_MODE, + g_param_spec_int ( + "mode", + "Mode", + NULL, + EPP_NORMAL, + EPP_TEXT, + EPP_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_SHOW_SUPPRESSED, + g_param_spec_boolean ( + "show-suppressed", + "Show Suppressed", + NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); +} + +void +e_mail_parser_prefer_plain_class_finalize (EMailParserPreferPlainClass *klass) +{ +} + +static gboolean +parser_mode_get_mapping (GValue *value, + GVariant *variant, + gpointer user_data) +{ + gint i; + + const gchar *key = g_variant_get_string (variant, NULL); + if (key) { + for (i = 0; i < G_N_ELEMENTS (epp_options); i++) { + if (!strcmp (epp_options[i].key, key)) { + g_value_set_int (value, i); + return TRUE; + } + } + } else { + g_value_set_int (value, 0); + } + + return TRUE; +} + +static GVariant * +parser_mode_set_mapping (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + return g_variant_new_string (epp_options[g_value_get_int (value)].key); +} + +static void +e_mail_parser_prefer_plain_init (EMailParserPreferPlain *parser) +{ + gchar *key; + gint i; + + parser->settings = g_settings_new ("org.gnome.evolution.plugin.prefer-plain"); + g_settings_bind_with_mapping ( + parser->settings, "mode", + parser, "mode", G_SETTINGS_BIND_DEFAULT, + parser_mode_get_mapping, + parser_mode_set_mapping, + NULL, NULL); + g_settings_bind ( + parser->settings, "show-suppressed", + parser, "show-suppressed", G_SETTINGS_BIND_DEFAULT); + + /* Initialize the settings */ + key = g_settings_get_string (parser->settings, "mode"); + if (key) { + for (i = 0; i < G_N_ELEMENTS (epp_options); i++) { + if (!strcmp (epp_options[i].key, key)) { + parser->mode = i; + break; + } + } + g_free (key); + } else { + parser->mode = 0; + } + + parser->show_suppressed = g_settings_get_boolean (parser->settings, "show-suppressed"); +} diff --git a/modules/prefer-plain/e-mail-parser-prefer-plain.h b/modules/prefer-plain/e-mail-parser-prefer-plain.h new file mode 100644 index 0000000000..4cfb8226f9 --- /dev/null +++ b/modules/prefer-plain/e-mail-parser-prefer-plain.h @@ -0,0 +1,30 @@ +/* + * e-mail-parser-prefer-plain.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/> + * + */ + +#ifndef E_MAIL_PARSER_PREFER_PLAIN_H +#define E_MAIL_PARSER_PREFER_PLAIN_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_parser_prefer_plain_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_PARSER_PREFER_PLAIN_H */ diff --git a/modules/prefer-plain/evolution-module-prefer-plain.c b/modules/prefer-plain/evolution-module-prefer-plain.c new file mode 100644 index 0000000000..cb81932594 --- /dev/null +++ b/modules/prefer-plain/evolution-module-prefer-plain.c @@ -0,0 +1,73 @@ +/* + * evolution-module-prefer-plain.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/> + * + */ + +#include "e-mail-parser-prefer-plain.h" + +#include <gmodule.h> +#include <gio/gio.h> + +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) +{ + GSettings *settings; + gchar **disabled_plugins; + gint i = 0; + + settings = g_settings_new ("org.gnome.evolution"); + disabled_plugins = g_settings_get_strv (settings, "disabled-eplugins"); + + for (i = 0; disabled_plugins && disabled_plugins[i] != NULL; i++) { + + if (g_strcmp0 ( + disabled_plugins[i], + "org.gnome.evolution.plugin.preferPlain") == 0) { + + g_strfreev (disabled_plugins); + g_object_unref (settings); + return; + } + + } + + e_mail_parser_prefer_plain_type_register (type_module); + + g_strfreev (disabled_plugins); + g_object_unref (settings); +} + +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/prefer-plain/plugin/Makefile.am b/modules/prefer-plain/plugin/Makefile.am new file mode 100644 index 0000000000..3a0e16d29a --- /dev/null +++ b/modules/prefer-plain/plugin/Makefile.am @@ -0,0 +1,28 @@ +@EVO_PLUGIN_RULE@ + +plugin_DATA = org-gnome-prefer-plain.eplug +plugin_LTLIBRARIES = liborg-gnome-prefer-plain.la + +liborg_gnome_prefer_plain_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +liborg_gnome_prefer_plain_la_SOURCES = \ + config-ui.c + +liborg_gnome_prefer_plain_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED) + +liborg_gnome_prefer_plain_la_LIBADD = \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +BUILT_SOURCES = $(plugin_DATA) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = \ + org-gnome-prefer-plain.eplug.xml diff --git a/modules/prefer-plain/plugin/config-ui.c b/modules/prefer-plain/plugin/config-ui.c new file mode 100644 index 0000000000..29b81a60fe --- /dev/null +++ b/modules/prefer-plain/plugin/config-ui.c @@ -0,0 +1,192 @@ +/* + * 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/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include <mail/em-config.h> + +#include <libedataserverui/libedataserverui.h> + +GtkWidget *prefer_plain_page_factory (EPlugin *ep, EConfigHookItemFactoryData *hook_data); + +enum { + EPP_NORMAL, + EPP_PREFER, + EPP_TEXT +}; + +static GSettings *epp_settings = NULL; +static gint epp_mode = -1; +static gboolean epp_show_suppressed = TRUE; + +static struct { + const gchar *key; + const gchar *label; + const gchar *description; +} epp_options[] = { + { "normal", + N_("Show HTML if present"), + N_("Let Evolution choose the best part to show.") }, + + { "prefer_plain", + N_("Show plain text if present"), + N_("Show plain text part, if present, otherwise " + "let Evolution choose the best part to show.") }, + + { "only_plain", + N_("Only ever show plain text"), + N_("Always show plain text part and make attachments " + "from other parts, if requested.") }, +}; + +static void +update_info_label (GtkWidget *info_label, + guint mode) +{ + gchar *str = g_strconcat ("<i>", _(epp_options[mode > 2 ? 0 : mode].description), "</i>", NULL); + + gtk_label_set_markup (GTK_LABEL (info_label), str); + + g_free (str); +} + +static void +epp_mode_changed (GtkComboBox *dropdown, + GtkWidget *info_label) +{ + epp_mode = gtk_combo_box_get_active (dropdown); + if (epp_mode > 2) + epp_mode = 0; + + g_settings_set_string (epp_settings, "mode", epp_options[epp_mode].key); + update_info_label (info_label, epp_mode); +} + +static void +epp_show_suppressed_toggled (GtkToggleButton *check, + gpointer data) +{ + g_return_if_fail (check != NULL); + + epp_show_suppressed = gtk_toggle_button_get_active (check); + g_settings_set_boolean (epp_settings, "show-suppressed", epp_show_suppressed); +} + +GtkWidget * +prefer_plain_page_factory (EPlugin *epl, + struct _EConfigHookItemFactoryData *data) +{ + GtkComboBox *dropdown; + GtkCellRenderer *cell; + GtkListStore *store; + GtkWidget *dropdown_label, *info, *check; + guint i; + GtkTreeIter iter; + + if (data->old) + return data->old; + + check = gtk_check_button_new_with_mnemonic (_("Show s_uppressed HTML parts as attachments")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), epp_show_suppressed); + gtk_widget_show (check); + g_signal_connect ( + check, "toggled", + G_CALLBACK (epp_show_suppressed_toggled), NULL); + + dropdown = (GtkComboBox *) gtk_combo_box_new (); + cell = gtk_cell_renderer_text_new (); + store = gtk_list_store_new (1, G_TYPE_STRING); + for (i = 0; i < G_N_ELEMENTS (epp_options); i++) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _(epp_options[i].label), -1); + } + + 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, epp_mode); + gtk_widget_show ((GtkWidget *) dropdown); + + dropdown_label = gtk_label_new_with_mnemonic (_("HTML _Mode")); + gtk_widget_show (dropdown_label); + gtk_label_set_mnemonic_widget (GTK_LABEL (dropdown_label), (GtkWidget *) dropdown); + + info = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (info), 0.0, 0.5); + gtk_label_set_line_wrap (GTK_LABEL (info), TRUE); + + gtk_widget_show (info); + update_info_label (info, epp_mode); + + g_signal_connect ( + dropdown, "changed", + G_CALLBACK (epp_mode_changed), info); + + g_object_get (data->parent, "n-rows", &i, NULL); + gtk_table_attach ((GtkTable *) data->parent, check, 0, 2, i, i + 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_table_attach ((GtkTable *) data->parent, dropdown_label, 0, 1, i + 1, i + 2, 0, 0, 0, 0); + gtk_table_attach ((GtkTable *) data->parent, (GtkWidget *) dropdown, 1, 2, i + 1, i + 2, GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_table_attach ((GtkTable *) data->parent, info, 1, 2, i + 2, i + 3, GTK_FILL | GTK_EXPAND, 0, 0, 0); + + /* since this isnt dynamic, we don't need to track each item */ + + return (GtkWidget *) dropdown; +} + +gint e_plugin_lib_enable (EPlugin *ep, gint enable); + +gint +e_plugin_lib_enable (EPlugin *ep, + gint enable) +{ + gchar *key; + gint i; + + if (epp_settings || epp_mode != -1) + return 0; + + if (enable) { + + epp_settings = g_settings_new ("org.gnome.evolution.plugin.prefer-plain"); + key = g_settings_get_string (epp_settings, "mode"); + if (key) { + for (i = 0; i < G_N_ELEMENTS (epp_options); i++) { + if (!strcmp (epp_options[i].key, key)) { + epp_mode = i; + break; + } + } + g_free (key); + } else { + epp_mode = 0; + } + + epp_show_suppressed = g_settings_get_boolean (epp_settings, "show-suppressed"); + } else { + if (epp_settings) { + g_object_unref (epp_settings); + epp_settings = NULL; + } + } + + return 0; +} diff --git a/modules/prefer-plain/plugin/org-gnome-prefer-plain.eplug.xml b/modules/prefer-plain/plugin/org-gnome-prefer-plain.eplug.xml new file mode 100644 index 0000000000..43948e3a69 --- /dev/null +++ b/modules/prefer-plain/plugin/org-gnome-prefer-plain.eplug.xml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<e-plugin-list> + <e-plugin + type="shlib" + id="org.gnome.evolution.plugin.preferPlain" + location="@PLUGINDIR@/liborg-gnome-prefer-plain@SOEXT@" + _name="Prefer Plain Text"> + + + <_description>View mail messages as plain text, even if they contain HTML content.</_description> + <author name="Michael Zucchi" email="NotZed@Ximian.com"/> + + + <!-- hook into the 'html mail' preferences page --> + <hook class="org.gnome.evolution.mail.config:1.0"> + <group target="prefs" id="org.gnome.evolution.mail.prefs"> + <!-- we could also just insert our own items from a section factory, --> + <!-- but then we also need to create our own section frame --> + <item type="section_table" path="10.html/80.mode" _label="Plain Text Mode"/> + <item type="item_table" path="10.html/80.mode/00.mode" factory="prefer_plain_page_factory"/> + </group> + </hook> + + </e-plugin> +</e-plugin-list>
\ No newline at end of file diff --git a/modules/text-highlight/Makefile.am b/modules/text-highlight/Makefile.am new file mode 100644 index 0000000000..9b27298474 --- /dev/null +++ b/modules/text-highlight/Makefile.am @@ -0,0 +1,27 @@ +module_LTLIBRARIES = module-text-highlight.la + +module_text_highlight_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + -DG_LOG_DOMAIN=\"evolution-module-text-highlight\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +module_text_highlight_la_SOURCES = \ + text-highlight.c \ + text-highlight.h \ + evolution-module-text-highlight.c + +module_text_highlight_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/em-format/libemformat.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +module_text_highlight_la_LDFLAGS = \ + -avoid-version -module $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/text-highlight/evolution-module-text-highlight.c b/modules/text-highlight/evolution-module-text-highlight.c new file mode 100644 index 0000000000..de6e469bde --- /dev/null +++ b/modules/text-highlight/evolution-module-text-highlight.c @@ -0,0 +1,51 @@ +/* + * evolution-module-text-highlight.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/> + * + */ + +#include "text-highlight.h" + +#include <gmodule.h> + +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_formatter_text_highlight_type_register (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/text-highlight/text-highlight.c b/modules/text-highlight/text-highlight.c new file mode 100644 index 0000000000..462e5f0dc3 --- /dev/null +++ b/modules/text-highlight/text-highlight.c @@ -0,0 +1,314 @@ +/* + * text-highlight.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/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "text-highlight.h" + +#include <em-format/e-mail-formatter-extension.h> +#include <em-format/e-mail-formatter.h> +#include <em-format/e-mail-part-utils.h> +#include <e-util/e-util.h> + +#include <libebackend/libebackend.h> +#include <libedataserver/libedataserver.h> + +#include <glib/gi18n-lib.h> +#include <X11/Xlib.h> +#include <camel/camel.h> + +typedef struct _EMailFormatterTextHighlight EMailFormatterTextHighlight; +typedef struct _EMailFormatterTextHighlightClass EMailFormatterTextHighlightClass; + +struct _EMailFormatterTextHighlight { + EExtension parent; +}; + +struct _EMailFormatterTextHighlightClass { + EExtensionClass parent_class; +}; + +GType e_mail_formatter_text_highlight_get_type (void); +static void e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface); +static void e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailFormatterTextHighlight, + e_mail_formatter_text_highlight, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_formatter_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_FORMATTER_EXTENSION, + e_mail_formatter_formatter_extension_interface_init)); + +static gpointer emfe_parent_class = 0; + +static const gchar *formatter_mime_types[] = { "text/x-diff", + "text/x-patch", + NULL }; + +static gchar * get_default_font (void) +{ + gchar *font; + GSettings *settings; + + settings = g_settings_new ("org.gnome.desktop.interface"); + + font = g_settings_get_string (settings, "monospace-font-name"); + + return font ? font : g_strdup ("monospace 10"); +} + +static gboolean +emfe_text_highlight_format (EMailFormatterExtension *extension, + EMailFormatter *formatter, + EMailFormatterContext *context, + EMailPart *part, + CamelStream *stream, + GCancellable *cancellable) +{ + if (context->mode == E_MAIL_FORMATTER_MODE_PRINTING) { + + CamelDataWrapper *dw; + CamelStream *filter_stream; + CamelMimeFilter *mime_filter; + + dw = camel_medium_get_content (CAMEL_MEDIUM (part->part)); + if (!dw) { + return FALSE; + } + + camel_stream_write_string ( + stream, "<pre><div class=\"pre\">", cancellable, NULL); + + filter_stream = camel_stream_filter_new (stream); + mime_filter = camel_mime_filter_tohtml_new ( + CAMEL_MIME_FILTER_TOHTML_PRE | + CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES, + 0x7a7a7a); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filter_stream), mime_filter); + g_object_unref (mime_filter); + + e_mail_formatter_format_text ( + formatter, part, filter_stream, cancellable); + + camel_stream_flush (filter_stream, cancellable, NULL); + g_object_unref (filter_stream); + + camel_stream_write_string ( + stream, "</div></pre>", cancellable, NULL); + + return TRUE; + + } else if (context->mode == E_MAIL_FORMATTER_MODE_RAW) { + gint stdin, stdout; + GPid pid; + CamelStream *read, *write; + CamelDataWrapper *dw; + gchar *font_family, *font_size; + gboolean use_custom_font; + GSettings *settings; + PangoFontDescription *fd; + const gchar *argv[] = { "highlight", + NULL, /* don't move these! */ + NULL, + "--out-format=html", + "--include-style", + "--inline-css", + "--style=bclear", + "--syntax=diff", + "--failsafe", + NULL }; + + dw = camel_medium_get_content (CAMEL_MEDIUM (part->part)); + if (!dw) { + return FALSE; + } + + fd = NULL; + settings = g_settings_new ("org.gnome.evolution.mail"); + use_custom_font = g_settings_get_boolean (settings, "use-custom-font"); + if (!use_custom_font) { + gchar *font; + + font = get_default_font (); + fd = pango_font_description_from_string (font); + g_free (font); + + g_object_unref (settings); + + } else { + gchar *font; + + font = g_settings_get_string (settings, "monospace-font"); + if (!font) + font = get_default_font (); + + fd = pango_font_description_from_string (font); + + g_free (font); + } + + font_family = g_strdup_printf ("--font='%s'", + pango_font_description_get_family (fd)); + font_size = g_strdup_printf ("--font-size=%d", + pango_font_description_get_size (fd) / PANGO_SCALE); + + argv[1] = font_family; + argv[2] = font_size; + + if (!g_spawn_async_with_pipes ( + NULL, (gchar **) argv, NULL, + G_SPAWN_SEARCH_PATH | + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &pid, &stdin, &stdout, NULL, NULL)) { + return FALSE; + } + + write = camel_stream_fs_new_with_fd (stdin); + read = camel_stream_fs_new_with_fd (stdout); + + camel_data_wrapper_decode_to_stream_sync ( + dw, write, cancellable, NULL); + g_object_unref (write); + + g_spawn_close_pid (pid); + + g_seekable_seek (G_SEEKABLE (read), 0, G_SEEK_SET, cancellable, NULL); + camel_stream_write_to_stream (read, stream, cancellable, NULL); + camel_stream_flush (read, cancellable, NULL); + g_object_unref (read); + + g_free (font_family); + g_free (font_size); + pango_font_description_free (fd); + + } else { + gchar *uri, *str; + + uri = e_mail_part_build_uri ( + context->folder, context->message_uid, + "part_id", G_TYPE_STRING, part->id, + "mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_RAW, + NULL); + + str = g_strdup_printf ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n" + "<iframe width=\"100%%\" height=\"10\"" + " name=\"%s\" frameborder=\"0\" src=\"%s\"></iframe>" + "</div></div>", + e_color_to_value ((GdkColor *) + e_mail_formatter_get_color ( + formatter, E_MAIL_FORMATTER_COLOR_FRAME)), + e_color_to_value ((GdkColor *) + e_mail_formatter_get_color ( + formatter, E_MAIL_FORMATTER_COLOR_CONTENT)), + part->id, uri); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + g_free (uri); + + } + + return TRUE; +} + +static const gchar * +emfe_text_highlight_get_display_name (EMailFormatterExtension *extension) +{ + return _("Patch"); +} + +static const gchar * +emfe_text_highlight_get_description (EMailFormatterExtension *extension) +{ + return _("Format part as a patch"); +} + +static const gchar ** +emfe_text_highlight_mime_types (EMailExtension *extension) +{ + return formatter_mime_types; +} + +static void +emfe_text_highlight_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_formatter_text_highlight_init (EMailFormatterTextHighlight *object) +{ +} + +static void +e_mail_formatter_text_highlight_class_init (EMailFormatterTextHighlightClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + emfe_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = emfe_text_highlight_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_FORMATTER_EXTENSION_REGISTRY; +} + +static void +e_mail_formatter_text_highlight_class_finalize (EMailFormatterTextHighlightClass *klass) +{ +} + +void +e_mail_formatter_text_highlight_type_register (GTypeModule *type_module) +{ + e_mail_formatter_text_highlight_register_type (type_module); +} + +static void +e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface) +{ + iface->format = emfe_text_highlight_format; + iface->get_display_name = emfe_text_highlight_get_display_name; + iface->get_description = emfe_text_highlight_get_description; +} + +static void +e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = emfe_text_highlight_mime_types; +} diff --git a/modules/text-highlight/text-highlight.h b/modules/text-highlight/text-highlight.h new file mode 100644 index 0000000000..af10da4c84 --- /dev/null +++ b/modules/text-highlight/text-highlight.h @@ -0,0 +1,30 @@ +/* + * text-highlight.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/> + * + */ + +#ifndef TEXT_HIGHLIGHT_H +#define TEXT_HIGHLIGHT_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_formatter_text_highlight_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* TEXT_HIGHLIGHT_H */ diff --git a/modules/tnef-attachment/Makefile.am b/modules/tnef-attachment/Makefile.am new file mode 100644 index 0000000000..ff5b412275 --- /dev/null +++ b/modules/tnef-attachment/Makefile.am @@ -0,0 +1,34 @@ +if OS_WIN32 +NO_UNDEFINED_REQUIRED_LIBS = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/mail/libevolution-mail.la +endif + +module_LTLIBRARIES = module-tnef-attachment.la + +module_tnef_attachment_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + -DG_LOG_DOMAIN=\"evolution-module-tnef-attachment\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) \ + $(TNEF_CFLAGS) + +module_tnef_attachment_la_SOURCES = \ + e-mail-parser-tnef-attachment.c \ + e-mail-parser-tnef-attachment.h \ + evolution-module-tnef-attachment.c + +module_tnef_attachment_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/em-format/libemformat.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + -lytnef + +module_tnef_attachment_la_LDFLAGS = \ + -avoid-version -module $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/tnef-attachment/e-mail-parser-tnef-attachment.c b/modules/tnef-attachment/e-mail-parser-tnef-attachment.c new file mode 100644 index 0000000000..854a17765a --- /dev/null +++ b/modules/tnef-attachment/e-mail-parser-tnef-attachment.c @@ -0,0 +1,1446 @@ +/* + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glib/gprintf.h> +#include <stdio.h> + +#include "e-mail-parser-tnef-attachment.h" + +#include <em-format/e-mail-extension-registry.h> +#include <em-format/e-mail-parser-extension.h> +#include <em-format/e-mail-part.h> +#include <em-format/e-mail-part-utils.h> + +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <stdlib.h> +#ifdef HAVE_YTNEF_H +#include <ytnef.h> +#elif defined HAVE_LIBYTNEF_YTNEF_H +#include <libytnef/ytnef.h> +#endif + +#include <mail/em-utils.h> +#include <e-util/e-mktemp.h> + +#include <libebackend/libebackend.h> + +#define d(x) + +typedef struct _EMailParserTnefAttachment { + EExtension parent; + + GSettings *settings; + gint mode; + gboolean show_suppressed; +} EMailParserTnefAttachment; + +typedef struct _EMailParserTnefAttachmentClass { + EExtensionClass parent_class; +} EMailParserTnefAttachmentClass; + +GType e_mail_parser_tnef_attachment_get_type (void); +static void e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface); +static void e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailParserTnefAttachment, + e_mail_parser_tnef_attachment, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_parser_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_PARSER_EXTENSION, + e_mail_parser_parser_extension_interface_init)); + +static const gchar* parser_mime_types[] = { "application/vnd.ms-tnef", + "application/ms-tnefl", + NULL }; + +gint verbose = 0; +gint saveRTF = 0; +gint saveintermediate = 0; +gboolean loaded = FALSE; +void processTnef (TNEFStruct *tnef, const gchar *tmpdir); +void saveVCalendar (TNEFStruct *tnef, const gchar *tmpdir); +void saveVCard (TNEFStruct *tnef, const gchar *tmpdir); +void saveVTask (TNEFStruct *tnef, const gchar *tmpdir); + +/* Other Prototypes */ +void fprintProperty (TNEFStruct *tnef, FILE *fptr, DWORD proptype, DWORD propid, const gchar text[]); +void fprintUserProp (TNEFStruct *tnef, FILE *fptr, DWORD proptype, DWORD propid, const gchar text[]); +void quotedfprint (FILE *fptr, variableLength *vl); +void cstylefprint (FILE *fptr, variableLength *vl); +void printRtf (FILE *fptr, variableLength *vl); +void printRrule (FILE *fptr, gchar *recur_data, gint size, TNEFStruct *tnef); +guchar getRruleCount (guchar a, guchar b); +guchar getRruleMonthNum (guchar a, guchar b); +gchar * getRruleDayname (guchar a); + +static gchar * +sanitize_filename (const gchar *filename) +{ + gchar * sanitized_name; + sanitized_name = g_path_get_basename (filename); + if (sanitized_name == NULL || !g_strcmp0 (sanitized_name, ".")) { + g_free (sanitized_name); + return NULL; + } else { + return g_strdelimit (sanitized_name, " ", '_'); + } +} + +static GSList * +empe_tnef_attachment_parse (EMailParserExtension *extension, + EMailParser *parser, + CamelMimePart *part, + GString *part_id, + GCancellable *cancellable) +{ + gchar *tmpdir, *name; + CamelStream *out; + struct dirent *d; + DIR *dir; + CamelMultipart *mp; + CamelMimePart *mainpart; + CamelDataWrapper *content; + gint len; + TNEFStruct tnef; + GSList *parts; + + tmpdir = e_mkdtemp("tnef-attachment-XXXXXX"); + if (tmpdir == NULL) + return NULL; + + name = g_build_filename(tmpdir, ".evo-attachment.tnef", NULL); + + out = camel_stream_fs_new_with_name (name, O_RDWR | O_CREAT, 0666, NULL); + if (out == NULL) { + g_free (name); + return NULL; + } + content = camel_medium_get_content ((CamelMedium *) part); + if (content == NULL) { + g_free (name); + g_object_unref (out); + return NULL; + } + if (camel_data_wrapper_decode_to_stream_sync (content, out, NULL, NULL) == -1 + || camel_stream_close (out, NULL, NULL) == -1) { + g_object_unref (out); + g_free (name); + return NULL; + } + g_object_unref (out); + + /* Extracting the winmail.dat */ + TNEFInitialize (&tnef); + tnef.Debug = verbose; + if (TNEFParseFile (name, &tnef) == -1) { + printf("ERROR processing file\n"); + } + processTnef (&tnef, tmpdir); + + TNEFFree (&tnef); + /* Extraction done */ + + dir = opendir (tmpdir); + if (dir == NULL) { + g_object_unref (out); + g_free (name); + return NULL; + } + + mainpart = camel_mime_part_new (); + + mp = camel_multipart_new (); + camel_data_wrapper_set_mime_type((CamelDataWrapper *)mp, "multipart/mixed"); + camel_multipart_set_boundary (mp, NULL); + + camel_medium_set_content ((CamelMedium *) mainpart, (CamelDataWrapper *) mp); + + while ((d = readdir (dir))) { + CamelMimePart *part; + CamelDataWrapper *content; + CamelStream *stream; + gchar *path; + const gchar *type; + + if (!strcmp(d->d_name, ".") + || !strcmp(d->d_name, "..") + || !strcmp(d->d_name, ".evo-attachment.tnef")) + continue; + + path = g_build_filename (tmpdir, d->d_name, NULL); + + stream = camel_stream_fs_new_with_name (path, O_RDONLY, 0, NULL); + content = camel_data_wrapper_new (); + camel_data_wrapper_construct_from_stream_sync ( + content, stream, NULL, NULL); + g_object_unref (stream); + + part = camel_mime_part_new (); + camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BINARY); + + camel_medium_set_content ((CamelMedium *) part, content); + g_object_unref (content); + + type = e_mail_part_snoop_type (part); + if (type) + camel_data_wrapper_set_mime_type ((CamelDataWrapper *) part, type); + + camel_mime_part_set_filename (part, d->d_name); + + g_free (path); + + camel_multipart_add_part (mp, part); + g_object_unref (part); + } + + closedir (dir); + + len = part_id->len; + g_string_append_printf(part_id, ".tnef"); + + parts = NULL; + if (camel_multipart_get_number (mp) > 0) { + + CamelMimePart *part = camel_mime_part_new (); + + camel_medium_set_content ((CamelMedium *) part, + CAMEL_DATA_WRAPPER (mp)); + + parts = e_mail_parser_parse_part_as (parser, part, + part_id, "multipart/mixed", cancellable); + + g_object_unref (part); + } + + g_string_truncate (part_id, len); + + if (parts) { + parts = e_mail_parser_wrap_as_attachment (parser, + part, parts, part_id, cancellable); + } + + g_object_unref (mp); + g_object_unref (mainpart); + + g_free (name); + g_free (tmpdir); + + return parts; +} + +static const gchar ** +empe_mime_types (EMailExtension *extension) +{ + return parser_mime_types; +} + +void +e_mail_parser_tnef_attachment_type_register (GTypeModule *type_module) +{ + e_mail_parser_tnef_attachment_register_type (type_module); +} + +static void +e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = empe_mime_types; +} + +static void +e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface) +{ + iface->parse = empe_tnef_attachment_parse; +} + +static void +e_mail_parser_tnef_attachment_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_parser_tnef_attachment_class_init (EMailParserTnefAttachmentClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_parser_tnef_attachment_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_parser_tnef_attachment_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY; +} + +void +e_mail_parser_tnef_attachment_class_finalize (EMailParserTnefAttachmentClass *klass) +{ +} + +static void +e_mail_parser_tnef_attachment_init (EMailParserTnefAttachment *parser) +{ +} + +void +processTnef (TNEFStruct *tnef, + const gchar *tmpdir) +{ + variableLength *filename; + variableLength *filedata; + Attachment *p; + gint RealAttachment; + gint object; + gchar *ifilename = NULL; + gchar *absfilename, *file; + gint count; + gint foundCal = 0; + + FILE *fptr; + + /* First see if this requires special processing. */ + /* ie: it's a Contact Card, Task, or Meeting request (vCal/vCard) */ + if (tnef->messageClass[0] != 0) { + if (strcmp(tnef->messageClass, "IPM.Contact") == 0) { + saveVCard (tnef, tmpdir); + } + if (strcmp(tnef->messageClass, "IPM.Task") == 0) { + saveVTask (tnef, tmpdir); + } + if (strcmp(tnef->messageClass, "IPM.Appointment") == 0) { + saveVCalendar (tnef, tmpdir); + foundCal = 1; + } + } + + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8,0x24))) != MAPI_UNDEFINED) { + if (strcmp(filename->data, "IPM.Appointment") == 0) { + /* If it's "indicated" twice, we don't want to save 2 calendar entries. */ + if (foundCal == 0) { + saveVCalendar (tnef, tmpdir); + } + } + } + + if (strcmp(tnef->messageClass, "IPM.Microsoft Mail.Note") == 0) { + if ((saveRTF == 1) && (tnef->subject.size > 0)) { + /* Description */ + if ((filename = MAPIFindProperty (&(tnef->MapiProperties), + PROP_TAG (PT_BINARY, PR_RTF_COMPRESSED))) + != MAPI_UNDEFINED) { + variableLength buf; + if ((buf.data = (gchar *) DecompressRTF (filename, &buf.size)) != NULL) { + file = sanitize_filename (tnef->subject.data); + if (!file) + return; + absfilename = g_strconcat (file, ".rtf", NULL); + ifilename = g_build_filename (tmpdir, file, NULL); + g_free (absfilename); + g_free (file); + + if ((fptr = fopen(ifilename, "wb"))==NULL) { + printf("ERROR: Error writing file to disk!"); + } else { + fwrite (buf.data, + sizeof (BYTE), + buf.size, + fptr); + fclose (fptr); + } + free (buf.data); + } + } + } + } + + /* Now process each attachment */ + p = tnef->starting_attach.next; + count = 0; + while (p != NULL) { + count++; + /* Make sure it has a size. */ + if (p->FileData.size > 0) { + object = 1; + + /* See if the contents are stored as "attached data" */ + /* Inside the MAPI blocks. */ + if ((filedata = MAPIFindProperty (&(p->MAPI), + PROP_TAG (PT_OBJECT, PR_ATTACH_DATA_OBJ))) + == MAPI_UNDEFINED) { + if ((filedata = MAPIFindProperty (&(p->MAPI), + PROP_TAG (PT_BINARY, PR_ATTACH_DATA_OBJ))) + == MAPI_UNDEFINED) { + /* Nope, standard TNEF stuff. */ + filedata = &(p->FileData); + object = 0; + } + } + /* See if this is an embedded TNEF stream. */ + RealAttachment = 1; + if (object == 1) { + /* This is an "embedded object", so skip the */ + /* 16-byte identifier first. */ + TNEFStruct emb_tnef; + DWORD signature; + memcpy (&signature, filedata->data + 16, sizeof (DWORD)); + if (TNEFCheckForSignature (signature) == 0) { + /* Has a TNEF signature, so process it. */ + TNEFInitialize (&emb_tnef); + emb_tnef.Debug = tnef->Debug; + if (TNEFParseMemory ((guchar *) filedata->data + 16, + filedata->size - 16, &emb_tnef) != -1) { + processTnef (&emb_tnef, tmpdir); + RealAttachment = 0; + } + TNEFFree (&emb_tnef); + } + } else { + TNEFStruct emb_tnef; + DWORD signature; + memcpy (&signature, filedata->data, sizeof (DWORD)); + if (TNEFCheckForSignature (signature) == 0) { + /* Has a TNEF signature, so process it. */ + TNEFInitialize (&emb_tnef); + emb_tnef.Debug = tnef->Debug; + if (TNEFParseMemory ((guchar *) filedata->data, + filedata->size, &emb_tnef) != -1) { + processTnef (&emb_tnef, tmpdir); + RealAttachment = 0; + } + TNEFFree (&emb_tnef); + } + } + if ((RealAttachment == 1) || (saveintermediate == 1)) { + gchar tmpname[20]; + /* Ok, it's not an embedded stream, so now we */ + /* process it. */ + if ((filename = MAPIFindProperty (&(p->MAPI), + PROP_TAG (PT_STRING8, PR_ATTACH_LONG_FILENAME))) + == MAPI_UNDEFINED) { + if ((filename = MAPIFindProperty (&(p->MAPI), + PROP_TAG (PT_STRING8, PR_DISPLAY_NAME))) + == MAPI_UNDEFINED) { + filename = &(p->Title); + } + } + if (filename->size == 1) { + filename->size = 20; + g_sprintf(tmpname, "file_%03i.dat", count); + filename->data = tmpname; + } + absfilename = sanitize_filename (filename->data); + if (!absfilename) + return; + ifilename = g_build_filename (tmpdir, absfilename, NULL); + g_free (absfilename); + + if ((fptr = fopen(ifilename, "wb"))==NULL) { + printf("ERROR: Error writing file to disk!"); + } else { + if (object == 1) { + fwrite (filedata->data + 16, + sizeof (BYTE), + filedata->size - 16, + fptr); + } else { + fwrite (filedata->data, + sizeof (BYTE), + filedata->size, + fptr); + } + fclose (fptr); + } + } + } /* if size>0 */ + p = p->next; + } /* while p!= null */ + g_free (ifilename); +} + +void +saveVCard (TNEFStruct *tnef, + const gchar *tmpdir) +{ + gchar *ifilename; + gchar *absfilename, *file = NULL; + FILE *fptr; + variableLength *vl; + variableLength *pobox, *street, *city, *state, *zip, *country; + dtr thedate; + gint boolean; + + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_DISPLAY_NAME))) == MAPI_UNDEFINED) { + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_COMPANY_NAME))) == MAPI_UNDEFINED) { + if (tnef->subject.size > 0) { + file = sanitize_filename (tnef->subject.data); + if (!file) + return; + absfilename = g_strconcat (file, ".vcard", NULL); + } else + absfilename = g_strdup ("unknown.vcard"); + } else { + file = sanitize_filename (vl->data); + if (!file) + return; + absfilename = g_strconcat (file, ".vcard", NULL); + } + } else { + file = sanitize_filename (vl->data); + if (!file) + return; + absfilename = g_strconcat (file, ".vcard", NULL); + } + + ifilename = g_build_filename (tmpdir, absfilename, NULL); + g_free (file); + g_free (absfilename); + + if ((fptr = fopen(ifilename, "wb"))==NULL) { + printf("Error writing file to disk!"); + } else { + fprintf(fptr, "BEGIN:VCARD\n"); + fprintf(fptr, "VERSION:2.1\n"); + if (vl != MAPI_UNDEFINED) { + fprintf(fptr, "FN:%s\n", vl->data); + } + fprintProperty(tnef, fptr, PT_STRING8, PR_NICKNAME, "NICKNAME:%s\n"); + fprintUserProp(tnef, fptr, PT_STRING8, 0x8554, "MAILER:Microsoft Outlook %s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_SPOUSE_NAME, "X-EVOLUTION-SPOUSE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_MANAGER_NAME, "X-EVOLUTION-MANAGER:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_ASSISTANT, "X-EVOLUTION-ASSISTANT:%s\n"); + + /* Organizational */ + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_COMPANY_NAME))) != MAPI_UNDEFINED) { + if (vl->size > 0) { + if ((vl->size == 1) && (vl->data[0] == 0)) { + } else { + fprintf(fptr,"ORG:%s", vl->data); + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_DEPARTMENT_NAME))) != MAPI_UNDEFINED) { + fprintf(fptr,";%s", vl->data); + } + fprintf(fptr, "\n"); + } + } + } + + fprintProperty(tnef, fptr, PT_STRING8, PR_OFFICE_LOCATION, "X-EVOLUTION-OFFICE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_TITLE, "TITLE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_PROFESSION, "ROLE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_BODY, "NOTE:%s\n"); + if (tnef->body.size > 0) { + fprintf(fptr, "NOTE;QUOTED-PRINTABLE:"); + quotedfprint (fptr, &(tnef->body)); + fprintf(fptr,"\n"); + } + + /* Business Address */ + boolean = 0; + if ((pobox = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_POST_OFFICE_BOX))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((street = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_STREET_ADDRESS))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((city = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_LOCALITY))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((state = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_STATE_OR_PROVINCE))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((zip = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_POSTAL_CODE))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((country = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_COUNTRY))) != MAPI_UNDEFINED) { + boolean = 1; + } + if (boolean == 1) { + fprintf(fptr, "ADR;QUOTED-PRINTABLE;WORK:"); + if (pobox != MAPI_UNDEFINED) { + quotedfprint (fptr, pobox); + } + fprintf(fptr, ";;"); + if (street != MAPI_UNDEFINED) { + quotedfprint (fptr, street); + } + fprintf(fptr, ";"); + if (city != MAPI_UNDEFINED) { + quotedfprint (fptr, city); + } + fprintf(fptr, ";"); + if (state != MAPI_UNDEFINED) { + quotedfprint (fptr, state); + } + fprintf(fptr, ";"); + if (zip != MAPI_UNDEFINED) { + quotedfprint (fptr, zip); + } + fprintf(fptr, ";"); + if (country != MAPI_UNDEFINED) { + quotedfprint (fptr, country); + } + fprintf(fptr,"\n"); + if ((vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x801b))) != MAPI_UNDEFINED) { + fprintf(fptr, "LABEL;QUOTED-PRINTABLE;WORK:"); + quotedfprint (fptr, vl); + fprintf(fptr,"\n"); + } + } + + /* Home Address */ + boolean = 0; + if ((pobox = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_HOME_ADDRESS_POST_OFFICE_BOX))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((street = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_HOME_ADDRESS_STREET))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((city = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_HOME_ADDRESS_CITY))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((state = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_HOME_ADDRESS_STATE_OR_PROVINCE))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((zip = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_HOME_ADDRESS_POSTAL_CODE))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((country = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_HOME_ADDRESS_COUNTRY))) != MAPI_UNDEFINED) { + boolean = 1; + } + if (boolean == 1) { + fprintf(fptr, "ADR;QUOTED-PRINTABLE;HOME:"); + if (pobox != MAPI_UNDEFINED) { + quotedfprint (fptr, pobox); + } + fprintf(fptr, ";;"); + if (street != MAPI_UNDEFINED) { + quotedfprint (fptr, street); + } + fprintf(fptr, ";"); + if (city != MAPI_UNDEFINED) { + quotedfprint (fptr, city); + } + fprintf(fptr, ";"); + if (state != MAPI_UNDEFINED) { + quotedfprint (fptr, state); + } + fprintf(fptr, ";"); + if (zip != MAPI_UNDEFINED) { + quotedfprint (fptr, zip); + } + fprintf(fptr, ";"); + if (country != MAPI_UNDEFINED) { + quotedfprint (fptr, country); + } + fprintf(fptr,"\n"); + if ((vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x801a))) != MAPI_UNDEFINED) { + fprintf(fptr, "LABEL;QUOTED-PRINTABLE;WORK:"); + quotedfprint (fptr, vl); + fprintf(fptr,"\n"); + } + } + + /* Other Address */ + boolean = 0; + if ((pobox = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_OTHER_ADDRESS_POST_OFFICE_BOX))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((street = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_OTHER_ADDRESS_STREET))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((city = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_OTHER_ADDRESS_CITY))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((state = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_OTHER_ADDRESS_STATE_OR_PROVINCE))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((zip = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_OTHER_ADDRESS_POSTAL_CODE))) != MAPI_UNDEFINED) { + boolean = 1; + } + if ((country = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_OTHER_ADDRESS_COUNTRY))) != MAPI_UNDEFINED) { + boolean = 1; + } + if (boolean == 1) { + fprintf(fptr, "ADR;QUOTED-PRINTABLE;OTHER:"); + if (pobox != MAPI_UNDEFINED) { + quotedfprint (fptr, pobox); + } + fprintf(fptr, ";;"); + if (street != MAPI_UNDEFINED) { + quotedfprint (fptr, street); + } + fprintf(fptr, ";"); + if (city != MAPI_UNDEFINED) { + quotedfprint (fptr, city); + } + fprintf(fptr, ";"); + if (state != MAPI_UNDEFINED) { + quotedfprint (fptr, state); + } + fprintf(fptr, ";"); + if (zip != MAPI_UNDEFINED) { + quotedfprint (fptr, zip); + } + fprintf(fptr, ";"); + if (country != MAPI_UNDEFINED) { + quotedfprint (fptr, country); + } + fprintf(fptr,"\n"); + } + + fprintProperty(tnef, fptr, PT_STRING8, PR_CALLBACK_TELEPHONE_NUMBER, "TEL;X-EVOLUTION-CALLBACK:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_PRIMARY_TELEPHONE_NUMBER, "TEL;PREF:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_MOBILE_TELEPHONE_NUMBER, "TEL;CELL:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_RADIO_TELEPHONE_NUMBER, "TEL;X-EVOLUTION-RADIO:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_CAR_TELEPHONE_NUMBER, "TEL;CAR:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_OTHER_TELEPHONE_NUMBER, "TEL;VOICE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_PAGER_TELEPHONE_NUMBER, "TEL;PAGER:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_TELEX_NUMBER, "TEL;X-EVOLUTION-TELEX:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_ISDN_NUMBER, "TEL;ISDN:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_HOME2_TELEPHONE_NUMBER, "TEL;HOME:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_TTYTDD_PHONE_NUMBER, "TEL;X-EVOLUTION-TTYTDD:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_HOME_TELEPHONE_NUMBER, "TEL;HOME;VOICE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_ASSISTANT_TELEPHONE_NUMBER, "TEL;X-EVOLUTION-ASSISTANT:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_COMPANY_MAIN_PHONE_NUMBER, "TEL;WORK:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_BUSINESS_TELEPHONE_NUMBER, "TEL;WORK:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_BUSINESS2_TELEPHONE_NUMBER, "TEL;WORK;VOICE:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_PRIMARY_FAX_NUMBER, "TEL;PREF;FAX:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_BUSINESS_FAX_NUMBER, "TEL;WORK;FAX:%s\n"); + fprintProperty(tnef, fptr, PT_STRING8, PR_HOME_FAX_NUMBER, "TEL;HOME;FAX:%s\n"); + + /* Email addresses */ + if ((vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x8083))) == MAPI_UNDEFINED) { + vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x8084)); + } + if (vl != MAPI_UNDEFINED) { + if (vl->size > 0) + fprintf(fptr, "EMAIL:%s\n", vl->data); + } + if ((vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x8093))) == MAPI_UNDEFINED) { + vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x8094)); + } + if (vl != MAPI_UNDEFINED) { + if (vl->size > 0) + fprintf(fptr, "EMAIL:%s\n", vl->data); + } + if ((vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x80a3))) == MAPI_UNDEFINED) { + vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x80a4)); + } + if (vl != MAPI_UNDEFINED) { + if (vl->size > 0) + fprintf(fptr, "EMAIL:%s\n", vl->data); + } + + fprintProperty(tnef, fptr, PT_STRING8, PR_BUSINESS_HOME_PAGE, "URL:%s\n"); + fprintUserProp(tnef, fptr, PT_STRING8, 0x80d8, "FBURL:%s\n"); + + /* Birthday */ + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_SYSTIME, PR_BIRTHDAY))) != MAPI_UNDEFINED) { + fprintf(fptr, "BDAY:"); + MAPISysTimetoDTR ((guchar *) vl->data, &thedate); + fprintf(fptr, "%i-%02i-%02i\n", thedate.wYear, thedate.wMonth, thedate.wDay); + } + + /* Anniversary */ + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_SYSTIME, PR_WEDDING_ANNIVERSARY))) != MAPI_UNDEFINED) { + fprintf(fptr, "X-EVOLUTION-ANNIVERSARY:"); + MAPISysTimetoDTR ((guchar *) vl->data, &thedate); + fprintf(fptr, "%i-%02i-%02i\n", thedate.wYear, thedate.wMonth, thedate.wDay); + } + fprintf(fptr, "END:VCARD\n"); + fclose (fptr); + } + g_free (ifilename); +} + +guchar getRruleCount (guchar a, guchar b) { + return ((a << 8) | b); +} + +guchar getRruleMonthNum (guchar a, guchar b) { + switch (a) { + case 0x00: + switch (b) { + case 0x00: + /* Jan */ + return (1); + case 0xA3: + /* May */ + return (5); + case 0xAE: + /* Nov */ + return (11); + } + break; + case 0x60: + switch (b) { + case 0xAE: + /* Feb */ + return (2); + case 0x51: + /* Jun */ + return (6); + } + break; + case 0xE0: + switch (b) { + case 0x4B: + /* Mar */ + return (3); + case 0x56: + /* Sep */ + return (9); + } + break; + case 0x40: + switch (b) { + case 0xFA: + /* Apr */ + return (4); + } + break; + case 0x20: + if (b == 0xFA) { + /* Jul */ + return (7); + } + break; + case 0x80: + if (b == 0xA8) { + /* Aug */ + return (8); + } + break; + case 0xA0: + if (b == 0xFF) { + /* Oct */ + return (10); + } + break; + case 0xC0: + if (b == 0x56) { + return (12); + } + } + + /* Error */ + return (0); +} + +gchar * getRruleDayname (guchar a) { + static gchar daystring[25]; + + *daystring = 0; + + if (a & 0x01) { + strcat(daystring, "SU,"); + } + if (a & 0x02) { + strcat(daystring, "MO,"); + } + if (a & 0x04) { + strcat(daystring, "TU,"); + } + if (a & 0x08) { + strcat(daystring, "WE,"); + } + if (a & 0x10) { + strcat(daystring, "TH,"); + } + if (a & 0x20) { + strcat(daystring, "FR,"); + } + if (a & 0x40) { + strcat(daystring, "SA,"); + } + + if (strlen (daystring)) { + daystring[strlen (daystring) - 1] = 0; + } + + return (daystring); +} + +void printRrule (FILE *fptr, gchar *recur_data, gint size, TNEFStruct *tnef) +{ + variableLength *filename; + + if (size < 0x1F) { + return; + } + + fprintf(fptr, "RRULE:FREQ="); + + if (recur_data[0x04] == 0x0A) { + fprintf(fptr, "DAILY"); + + if (recur_data[0x16] == 0x23 || recur_data[0x16] == 0x22 || + recur_data[0x16] == 0x21) { + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_I2, 0x0011))) != MAPI_UNDEFINED) { + fprintf(fptr, ";INTERVAL=%d", *(filename->data)); + } + if (recur_data[0x16] == 0x22 || recur_data[0x16] == 0x21) { + fprintf(fptr, ";COUNT=%d", + getRruleCount (recur_data[0x1B], recur_data[0x1A])); + } + } else if (recur_data[0x16] == 0x3E) { + fprintf(fptr, ";BYDAY=MO,TU,WE,TH,FR"); + if (recur_data[0x1A] == 0x22 || recur_data[0x1A] == 0x21) { + fprintf(fptr, ";COUNT=%d", + getRruleCount (recur_data[0x1F], recur_data[0x1E])); + } + } + } else if (recur_data[0x04] == 0x0B) { + fprintf(fptr, "WEEKLY;INTERVAL=%d;BYDAY=%s", + recur_data[0x0E], getRruleDayname (recur_data[0x16])); + if (recur_data[0x1A] == 0x22 || recur_data[0x1A] == 0x21) { + fprintf(fptr, ";COUNT=%d", + getRruleCount (recur_data[0x1F], recur_data[0x1E])); + } + } else if (recur_data[0x04] == 0x0C) { + fprintf(fptr, "MONTHLY"); + if (recur_data[0x06] == 0x02) { + fprintf(fptr, ";INTERVAL=%d;BYMONTHDAY=%d", recur_data[0x0E], + recur_data[0x16]); + if (recur_data[0x1A] == 0x22 || recur_data[0x1A] == 0x21) { + fprintf(fptr, ";COUNT=%d", getRruleCount(recur_data[0x1F], + recur_data[0x1E])); + } + } else if (recur_data[0x06] == 0x03) { + fprintf(fptr, ";BYDAY=%s;BYSETPOS=%d;INTERVAL=%d", + getRruleDayname (recur_data[0x16]), + recur_data[0x1A] == 0x05 ? -1 : recur_data[0x1A], + recur_data[0x0E]); + if (recur_data[0x1E] == 0x22 || recur_data[0x1E] == 0x21) { + fprintf(fptr, ";COUNT=%d", getRruleCount(recur_data[0x23], + recur_data[0x22])); + } + } + } else if (recur_data[0x04] == 0x0D) { + fprintf(fptr, "YEARLY;BYMONTH=%d", + getRruleMonthNum (recur_data[0x0A], recur_data[0x0B])); + if (recur_data[0x06] == 0x02) { + fprintf(fptr, ";BYMONTHDAY=%d", recur_data[0x16]); + } else if (recur_data[0x06] == 0x03) { + fprintf(fptr, ";BYDAY=%s;BYSETPOS=%d", + getRruleDayname (recur_data[0x16]), + recur_data[0x1A] == 0x05 ? -1 : recur_data[0x1A]); + } + if (recur_data[0x1E] == 0x22 || recur_data[0x1E] == 0x21) { + fprintf(fptr, ";COUNT=%d", getRruleCount(recur_data[0x23], + recur_data[0x22])); + } + } + fprintf(fptr, "\n"); +} + +void saveVCalendar (TNEFStruct *tnef, const gchar *tmpdir) { + gchar *ifilename; + variableLength *filename; + gchar *charptr, *charptr2; + FILE *fptr; + gint index; + DWORD *dword_ptr; + dtr thedate; + + ifilename = g_build_filename (tmpdir, "calendar.vcf", NULL); + printf("%s\n", ifilename); + + if ((fptr = fopen(ifilename, "wb"))==NULL) { + printf("Error writing file to disk!"); + } else { + fprintf(fptr, "BEGIN:VCALENDAR\n"); + if (tnef->messageClass[0] != 0) { + charptr2 = tnef->messageClass; + charptr = charptr2; + while (*charptr != 0) { + if (*charptr == '.') { + charptr2 = charptr; + } + charptr++; + } + if (strcmp(charptr2, ".MtgCncl") == 0) { + fprintf(fptr, "METHOD:CANCEL\n"); + } else { + fprintf(fptr, "METHOD:REQUEST\n"); + } + } else { + fprintf(fptr, "METHOD:REQUEST\n"); + } + fprintf(fptr, "VERSION:2.0\n"); + fprintf(fptr, "BEGIN:VEVENT\n"); + + /* UID + * After alot of comparisons, I'm reasonably sure this is totally + * wrong. But it's not really necessary. */ + + /* I think it only exists to connect future modification entries to + * this entry. so as long as it's incorrectly interpreted the same way + * every time, it should be ok :) */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_BINARY, 0x3))) == MAPI_UNDEFINED) { + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_BINARY, 0x23))) == MAPI_UNDEFINED) { + filename = NULL; + } + } + if (filename != NULL) { + fprintf(fptr, "UID:"); + for (index = 0; index < filename->size; index++) { + fprintf(fptr,"%02X", (guchar)filename->data[index]); + } + fprintf(fptr,"\n"); + } + + /* Sequence */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_LONG, 0x8201))) != MAPI_UNDEFINED) { + dword_ptr = (DWORD *) filename->data; + fprintf(fptr, "SEQUENCE:%i\n", (gint) *dword_ptr); + } + if ((filename = MAPIFindProperty (&(tnef->MapiProperties), + PROP_TAG (PT_BINARY, PR_SENDER_SEARCH_KEY))) + != MAPI_UNDEFINED) { + charptr = filename->data; + charptr2 = strstr(charptr, ":"); + if (charptr2 == NULL) + charptr2 = charptr; + else + charptr2++; + fprintf(fptr, "ORGANIZER;CN=\"%s\":MAILTO:%s\n", + charptr2, charptr2); + } + + /* Required Attendees */ + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8, 0x823b))) != MAPI_UNDEFINED) { + /* We have a list of required participants, so + write them out. */ + if (filename->size > 1) { + charptr = filename->data - 1; + while (charptr != NULL) { + charptr++; + charptr2 = strstr(charptr, ";"); + if (charptr2 != NULL) { + *charptr2 = 0; + } + while (*charptr == ' ') + charptr++; + fprintf(fptr, "ATTENDEE;PARTSTAT=NEEDS-ACTION;"); + fprintf(fptr, "ROLE=REQ-PARTICIPANT;RSVP=TRUE;"); + fprintf(fptr, "CN=\"%s\":MAILTO:%s\n", + charptr, charptr); + charptr = charptr2; + } + } + /* Optional attendees */ + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8, 0x823c))) != MAPI_UNDEFINED) { + /* The list of optional participants */ + if (filename->size > 1) { + charptr = filename->data - 1; + while (charptr != NULL) { + charptr++; + charptr2 = strstr(charptr, ";"); + if (charptr2 != NULL) { + *charptr2 = 0; + } + while (*charptr == ' ') + charptr++; + fprintf(fptr, "ATTENDEE;PARTSTAT=NEEDS-ACTION;"); + fprintf(fptr, "ROLE=OPT-PARTICIPANT;RSVP=TRUE;"); + fprintf(fptr, "CN=\"%s\":MAILTO:%s\n", + charptr, charptr); + charptr = charptr2; + } + } + } + } else if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8, 0x8238))) != MAPI_UNDEFINED) { + if (filename->size > 1) { + charptr = filename->data - 1; + while (charptr != NULL) { + charptr++; + charptr2 = strstr(charptr, ";"); + if (charptr2 != NULL) { + *charptr2 = 0; + } + while (*charptr == ' ') + charptr++; + fprintf(fptr, "ATTENDEE;PARTSTAT=NEEDS-ACTION;"); + fprintf(fptr, "ROLE=REQ-PARTICIPANT;RSVP=TRUE;"); + fprintf(fptr, "CN=\"%s\":MAILTO:%s\n", + charptr, charptr); + charptr = charptr2; + } + } + + } + /* Summary */ + filename = NULL; + if ((filename = MAPIFindProperty (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8, PR_CONVERSATION_TOPIC))) + != MAPI_UNDEFINED) { + fprintf(fptr, "SUMMARY:"); + cstylefprint (fptr, filename); + fprintf(fptr, "\n"); + } + + /* Description */ + if ((filename = MAPIFindProperty (&(tnef->MapiProperties), + PROP_TAG (PT_BINARY, PR_RTF_COMPRESSED))) + != MAPI_UNDEFINED) { + variableLength buf; + if ((buf.data = (gchar *) DecompressRTF (filename, &buf.size)) != NULL) { + fprintf(fptr, "DESCRIPTION:"); + printRtf (fptr, &buf); + free (buf.data); + } + + } + + /* Location */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8, 0x0002))) == MAPI_UNDEFINED) { + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_STRING8, 0x8208))) == MAPI_UNDEFINED) { + filename = NULL; + } + } + if (filename != NULL) { + fprintf(fptr,"LOCATION: %s\n", filename->data); + } + /* Date Start */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_SYSTIME, 0x820d))) == MAPI_UNDEFINED) { + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_SYSTIME, 0x8516))) == MAPI_UNDEFINED) { + filename = NULL; + } + } + if (filename != NULL) { + fprintf(fptr, "DTSTART:"); + MAPISysTimetoDTR ((guchar *) filename->data, &thedate); + fprintf(fptr,"%04i%02i%02iT%02i%02i%02iZ\n", + thedate.wYear, thedate.wMonth, thedate.wDay, + thedate.wHour, thedate.wMinute, thedate.wSecond); + } + /* Date End */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_SYSTIME, 0x820e))) == MAPI_UNDEFINED) { + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_SYSTIME, 0x8517))) == MAPI_UNDEFINED) { + filename = NULL; + } + } + if (filename != NULL) { + fprintf(fptr, "DTEND:"); + MAPISysTimetoDTR ((guchar *) filename->data, &thedate); + fprintf(fptr,"%04i%02i%02iT%02i%02i%02iZ\n", + thedate.wYear, thedate.wMonth, thedate.wDay, + thedate.wHour, thedate.wMinute, thedate.wSecond); + } + /* Date Stamp */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_SYSTIME, 0x8202))) != MAPI_UNDEFINED) { + fprintf(fptr, "CREATED:"); + MAPISysTimetoDTR ((guchar *) filename->data, &thedate); + fprintf(fptr,"%04i%02i%02iT%02i%02i%02iZ\n", + thedate.wYear, thedate.wMonth, thedate.wDay, + thedate.wHour, thedate.wMinute, thedate.wSecond); + } + /* Class */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_BOOLEAN, 0x8506))) != MAPI_UNDEFINED) { + dword_ptr = (DWORD *) filename->data; + fprintf(fptr, "CLASS:" ); + if (*dword_ptr == 1) { + fprintf(fptr,"PRIVATE\n"); + } else { + fprintf(fptr,"PUBLIC\n"); + } + } + /* Recurrence */ + filename = NULL; + if ((filename = MAPIFindUserProp (&(tnef->MapiProperties), + PROP_TAG (PT_BINARY, 0x8216))) != MAPI_UNDEFINED) { + printRrule (fptr, filename->data, filename->size, tnef); + } + + /* Wrap it up */ + fprintf(fptr, "END:VEVENT\n"); + fprintf(fptr, "END:VCALENDAR\n"); + fclose (fptr); + } + g_free (ifilename); +} + +void saveVTask (TNEFStruct *tnef, const gchar *tmpdir) { + variableLength *vl; + variableLength *filename; + gint index; + gchar *ifilename; + gchar *absfilename, *file; + gchar *charptr, *charptr2; + dtr thedate; + FILE *fptr; + DWORD *dword_ptr; + + vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_CONVERSATION_TOPIC)); + + if (vl == MAPI_UNDEFINED) { + return; + } + + index = strlen (vl->data); + while (vl->data[index] == ' ') + vl->data[index--] = 0; + + file = sanitize_filename (vl->data); + if (!file) + return; + absfilename = g_strconcat (file, ".vcf", NULL); + ifilename = g_build_filename (tmpdir, absfilename, NULL); + g_free (file); + g_free (absfilename); + + printf("%s\n", ifilename); + + if ((fptr = fopen(ifilename, "wb"))==NULL) { + printf("Error writing file to disk!"); + } else { + fprintf(fptr, "BEGIN:VCALENDAR\n"); + fprintf(fptr, "VERSION:2.0\n"); + fprintf(fptr, "METHOD:PUBLISH\n"); + filename = NULL; + + fprintf(fptr, "BEGIN:VTODO\n"); + if (tnef->messageID[0] != 0) { + fprintf(fptr,"UID:%s\n", tnef->messageID); + } + filename = MAPIFindUserProp (&(tnef->MapiProperties), \ + PROP_TAG (PT_STRING8, 0x8122)); + if (filename != MAPI_UNDEFINED) { + fprintf(fptr, "ORGANIZER:%s\n", filename->data); + } + + if ((filename = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, PR_DISPLAY_TO))) != MAPI_UNDEFINED) { + filename = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (PT_STRING8, 0x811f)); + } + if ((filename != MAPI_UNDEFINED) && (filename->size > 1)) { + charptr = filename->data - 1; + while (charptr != NULL) { + charptr++; + charptr2 = strstr(charptr, ";"); + if (charptr2 != NULL) { + *charptr2 = 0; + } + while (*charptr == ' ') + charptr++; + fprintf(fptr, "ATTENDEE;CN=%s;ROLE=REQ-PARTICIPANT:%s\n", charptr, charptr); + charptr = charptr2; + } + } + + if (tnef->subject.size > 0) { + fprintf(fptr,"SUMMARY:"); + cstylefprint (fptr,&(tnef->subject)); + fprintf(fptr,"\n"); + } + + if (tnef->body.size > 0) { + fprintf(fptr,"DESCRIPTION:"); + cstylefprint (fptr,&(tnef->body)); + fprintf(fptr,"\n"); + } + + filename = MAPIFindProperty (&(tnef->MapiProperties), \ + PROP_TAG (PT_SYSTIME, PR_CREATION_TIME)); + if (filename != MAPI_UNDEFINED) { + fprintf(fptr, "DTSTAMP:"); + MAPISysTimetoDTR ((guchar *) filename->data, &thedate); + fprintf(fptr,"%04i%02i%02iT%02i%02i%02iZ\n", + thedate.wYear, thedate.wMonth, thedate.wDay, + thedate.wHour, thedate.wMinute, thedate.wSecond); + } + + filename = MAPIFindUserProp (&(tnef->MapiProperties), \ + PROP_TAG (PT_SYSTIME, 0x8517)); + if (filename != MAPI_UNDEFINED) { + fprintf(fptr, "DUE:"); + MAPISysTimetoDTR ((guchar *) filename->data, &thedate); + fprintf(fptr,"%04i%02i%02iT%02i%02i%02iZ\n", + thedate.wYear, thedate.wMonth, thedate.wDay, + thedate.wHour, thedate.wMinute, thedate.wSecond); + } + filename = MAPIFindProperty (&(tnef->MapiProperties), \ + PROP_TAG (PT_SYSTIME, PR_LAST_MODIFICATION_TIME)); + if (filename != MAPI_UNDEFINED) { + fprintf(fptr, "LAST-MODIFIED:"); + MAPISysTimetoDTR ((guchar *) filename->data, &thedate); + fprintf(fptr,"%04i%02i%02iT%02i%02i%02iZ\n", + thedate.wYear, thedate.wMonth, thedate.wDay, + thedate.wHour, thedate.wMinute, thedate.wSecond); + } + /* Class */ + filename = MAPIFindUserProp (&(tnef->MapiProperties), \ + PROP_TAG (PT_BOOLEAN, 0x8506)); + if (filename != MAPI_UNDEFINED) { + dword_ptr = (DWORD *) filename->data; + fprintf(fptr, "CLASS:" ); + if (*dword_ptr == 1) { + fprintf(fptr,"PRIVATE\n"); + } else { + fprintf(fptr,"PUBLIC\n"); + } + } + fprintf(fptr, "END:VTODO\n"); + fprintf(fptr, "END:VCALENDAR\n"); + fclose (fptr); + } + g_free (ifilename); +} + +void fprintProperty (TNEFStruct *tnef, FILE *fptr, DWORD proptype, DWORD propid, const gchar text[]) { + variableLength *vl; + if ((vl = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG (proptype, propid))) != MAPI_UNDEFINED) { + if (vl->size > 0) { + if ((vl->size == 1) && (vl->data[0] == 0)) { + } else { + fprintf (fptr, text, vl->data); + } + } + } +} + +void fprintUserProp (TNEFStruct *tnef, FILE *fptr, DWORD proptype, DWORD propid, const gchar text[]) { + variableLength *vl; + if ((vl = MAPIFindUserProp (&(tnef->MapiProperties), PROP_TAG (proptype, propid))) != MAPI_UNDEFINED) { + if (vl->size > 0) { + if ((vl->size == 1) && (vl->data[0] == 0)) { + } else { + fprintf (fptr, text, vl->data); + } + } + } +} + +void quotedfprint (FILE *fptr, variableLength *vl) { + gint index; + + for (index = 0; index < vl->size - 1; index++) { + if (vl->data[index] == '\n') { + fprintf(fptr, "=0A"); + } else if (vl->data[index] == '\r') { + } else { + fprintf(fptr, "%c", vl->data[index]); + } + } +} + +void cstylefprint (FILE *fptr, variableLength *vl) { + gint index; + + for (index = 0; index < vl->size - 1; index++) { + if (vl->data[index] == '\n') { + fprintf(fptr, "\\n"); + } else if (vl->data[index] == '\r') { + /* Print nothing. */ + } else if (vl->data[index] == ';') { + fprintf(fptr, "\\;"); + } else if (vl->data[index] == ',') { + fprintf(fptr, "\\,"); + } else if (vl->data[index] == '\\') { + fprintf(fptr, "\\"); + } else { + fprintf(fptr, "%c", vl->data[index]); + } + } +} + +void printRtf (FILE *fptr, variableLength *vl) { + gint index; + gchar *byte; + gint brace_ct; + gint key; + + key = 0; + brace_ct = 0; + + for (index = 0, byte = vl->data; index < vl->size; index++, byte++) { + if (*byte == '}') { + brace_ct--; + key = 0; + continue; + } + if (*byte == '{') { + brace_ct++; + continue; + } + if (*byte == '\\') { + key = 1; + } + if (isspace (*byte)) { + key = 0; + } + if ((brace_ct == 1) && (key == 0)) { + if (*byte == '\n') { + fprintf(fptr, "\\n"); + } else if (*byte == '\r') { + /* Print nothing. */ + } else if (*byte == ';') { + fprintf(fptr, "\\;"); + } else if (*byte == ',') { + fprintf(fptr, "\\,"); + } else if (*byte == '\\') { + fprintf(fptr, "\\"); + } else { + fprintf(fptr, "%c", *byte); + } + } + } + fprintf(fptr, "\n"); +} diff --git a/modules/tnef-attachment/e-mail-parser-tnef-attachment.h b/modules/tnef-attachment/e-mail-parser-tnef-attachment.h new file mode 100644 index 0000000000..360c66ad9a --- /dev/null +++ b/modules/tnef-attachment/e-mail-parser-tnef-attachment.h @@ -0,0 +1,30 @@ +/* + * e-mail-parser-tnef-attachment.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/> + * + */ + +#ifndef E_MAIL_PARSER_TNEF_ATTACHMENT_H +#define E_MAIL_PARSER_TNEF_ATTACHMENT_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_parser_tnef_attachment_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_PARSER_TNEF_ATTACHMENT_H */ diff --git a/modules/tnef-attachment/evolution-module-tnef-attachment.c b/modules/tnef-attachment/evolution-module-tnef-attachment.c new file mode 100644 index 0000000000..df31b97a13 --- /dev/null +++ b/modules/tnef-attachment/evolution-module-tnef-attachment.c @@ -0,0 +1,51 @@ +/* + * evolution-module-tnef-attachment.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/> + * + */ + +#include "e-mail-parser-tnef-attachment.h" + +#include <gmodule.h> + +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_parser_tnef_attachment_type_register (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/vcard-inline/Makefile.am b/modules/vcard-inline/Makefile.am new file mode 100644 index 0000000000..fc0b610000 --- /dev/null +++ b/modules/vcard-inline/Makefile.am @@ -0,0 +1,32 @@ +module_LTLIBRARIES = module-vcard-inline.la + +module_vcard_inline_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ + -DG_LOG_DOMAIN=\"evolution-module-vcard-inline\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +module_vcard_inline_la_SOURCES = \ + e-mail-formatter-vcard-inline.c \ + e-mail-formatter-vcard-inline.h \ + e-mail-parser-vcard-inline.c \ + e-mail-parser-vcard-inline.h \ + evolution-module-vcard-inline.c + +module_vcard_inline_la_LIBADD = \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/em-format/libemformat.la \ + $(top_builddir)/addressbook/util/libeabutil.la \ + $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \ + $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \ + $(top_builddir)/addressbook/printing/libecontactprint.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +module_vcard_inline_la_LDFLAGS = \ + -avoid-version -module $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/vcard-inline/e-mail-formatter-vcard-inline.c b/modules/vcard-inline/e-mail-formatter-vcard-inline.c new file mode 100644 index 0000000000..25cdacb116 --- /dev/null +++ b/modules/vcard-inline/e-mail-formatter-vcard-inline.c @@ -0,0 +1,249 @@ +/* + * e-mail-formatter-vcard-inline.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/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-mail-formatter-vcard-inline.h" +#include "e-mail-part-vcard-inline.h" + +#include <glib/gi18n-lib.h> + +#include <libebackend/libebackend.h> + +#include <em-format/e-mail-formatter-extension.h> +#include <em-format/e-mail-formatter.h> +#include <em-format/e-mail-part-utils.h> + +#include <camel/camel.h> + +#define d(x) + +typedef struct _EMailFormatterVCardInline { + EExtension parent; +} EMailFormatterVCardInline; + +typedef struct _EMailFormatterVCardInlineClass { + EExtensionClass parent_class; +} EMailFormatterVCardInlineClass; + +GType e_mail_formatter_vcard_inline_get_type (void); +static void e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface); +static void e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailFormatterVCardInline, + e_mail_formatter_vcard_inline, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_formatter_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_FORMATTER_EXTENSION, + e_mail_formatter_formatter_extension_interface_init)); + +static const gchar* formatter_mime_types[] = { "text/vcard", "text/x-vcard", + "text/directory", NULL }; + +static gboolean +emfe_vcard_inline_format (EMailFormatterExtension *extension, + EMailFormatter *formatter, + EMailFormatterContext *context, + EMailPart *part, + CamelStream *stream, + GCancellable *cancellable) +{ + EMailPartVCardInline *vcard_part; + + g_return_val_if_fail (E_MAIL_PART_IS (part, EMailPartVCardInline), FALSE); + vcard_part = (EMailPartVCardInline *) part; + + if (context->mode == E_MAIL_FORMATTER_MODE_RAW) { + + EContact *contact; + + if (vcard_part->contact_list != NULL) + contact = E_CONTACT (vcard_part->contact_list->data); + else + contact = NULL; + + eab_contact_formatter_format_contact_sync ( + vcard_part->formatter, contact, stream, cancellable); + + } else { + gchar *str, *uri; + gint length; + const gchar *label = NULL; + EABContactDisplayMode mode; + const gchar *info = NULL; + + length = g_slist_length (vcard_part->contact_list); + if (length < 1) + return FALSE; + + if (!vcard_part->message_uid && context->message_uid) + vcard_part->message_uid = g_strdup (context->message_uid); + + if (!vcard_part->folder && context->folder) + vcard_part->folder = g_object_ref (context->folder); + + uri = e_mail_part_build_uri ( + context->folder, context->message_uid, + "part_id", G_TYPE_STRING, part->id, + "mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_RAW, + NULL); + + mode = eab_contact_formatter_get_display_mode (vcard_part->formatter); + if (mode == EAB_CONTACT_DISPLAY_RENDER_COMPACT) { + mode = EAB_CONTACT_DISPLAY_RENDER_NORMAL; + label =_("Show Full vCard"); + } else { + mode = EAB_CONTACT_DISPLAY_RENDER_COMPACT; + label = _("Show Compact vCard"); + } + + str = g_strdup_printf ( + "<div id=\"%s\">" + "<button type=\"button\" " + "name=\"set-display-mode\" " + "class=\"org-gnome-vcard-inline-display-mode-button\" " + "value=\"%d\">%s</button>" + "<button type=\"button\" " + "name=\"save-to-addressbook\" " + "class=\"org-gnome-vcard-inline-save-button\" " + "value=\"%s\">%s</button><br/>" + "<iframe width=\"100%%\" height=\"auto\" frameborder=\"0\"" + "src=\"%s\" name=\"%s\"></iframe>" + "</div>", + part->id, + mode, label, + part->id, _("Save To Addressbook"), + uri, part->id); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + + if (length == 2) { + + info = _("There is one other contact."); + + } else if (length > 2) { + + /* Translators: This will always be two or more. */ + info = g_strdup_printf (ngettext ( + "There is %d other contact.", + "There are %d other contacts.", + length - 1), length - 1); + } + + if (info) { + + str = g_strdup_printf ( + "<div class=\"attachment-info\">%s</div>", + info); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + } + + g_free (uri); + } + + return TRUE; +} + +static const gchar * +emfe_vcard_inline_get_display_name (EMailFormatterExtension *extension) +{ + return _("Addressbok Contact"); +} + +static const gchar * +emfe_vcard_inline_get_description (EMailFormatterExtension *extension) +{ + return _("Display the part as an addressbook contact"); +} + +static const gchar ** +emfe_vcard_inline_mime_types (EMailExtension *extension) +{ + return formatter_mime_types; +} + +static void +e_mail_formatter_vcard_inline_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_formatter_vcard_inline_class_init (EMailFormatterVCardInlineClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_formatter_vcard_inline_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_formatter_vcard_inline_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_FORMATTER_EXTENSION_REGISTRY; +} + +static void +e_mail_formatter_formatter_extension_interface_init (EMailFormatterExtensionInterface *iface) +{ + iface->format = emfe_vcard_inline_format; + iface->get_display_name = emfe_vcard_inline_get_display_name; + iface->get_description = emfe_vcard_inline_get_description; +} + +static void +e_mail_formatter_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = emfe_vcard_inline_mime_types; +} + +static void +e_mail_formatter_vcard_inline_init (EMailFormatterVCardInline *formatter) +{ + +} + +void +e_mail_formatter_vcard_inline_type_register (GTypeModule *type_module) +{ + e_mail_formatter_vcard_inline_register_type (type_module); +} + +static void +e_mail_formatter_vcard_inline_class_finalize (EMailFormatterVCardInlineClass *klass) +{ + +} diff --git a/modules/vcard-inline/e-mail-formatter-vcard-inline.h b/modules/vcard-inline/e-mail-formatter-vcard-inline.h new file mode 100644 index 0000000000..1dcec839f3 --- /dev/null +++ b/modules/vcard-inline/e-mail-formatter-vcard-inline.h @@ -0,0 +1,30 @@ +/* + * e-mail-formatter-vcard-inline.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/> + * + */ + +#ifndef E_MAIL_FORMATTER_VCARD_INLINE_H +#define E_MAIL_FORMATTER_VCARD_INLINE_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_formatter_vcard_inline_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_FORMATTER_VCARD_INLINE_H */ diff --git a/modules/vcard-inline/e-mail-parser-vcard-inline.c b/modules/vcard-inline/e-mail-parser-vcard-inline.c new file mode 100644 index 0000000000..562ca75c72 --- /dev/null +++ b/modules/vcard-inline/e-mail-parser-vcard-inline.c @@ -0,0 +1,410 @@ +/* + * e-mail-parser-vcard-inline.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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include "e-mail-parser-vcard-inline.h" +#include "e-mail-part-vcard-inline.h" + +#include <camel/camel.h> + +#include <em-format/e-mail-extension-registry.h> +#include <em-format/e-mail-parser-extension.h> +#include <em-format/e-mail-part.h> +#include <em-format/e-mail-part-utils.h> +#include <em-format/e-mail-formatter.h> + +#include <libebook/libebook.h> +#include <libedataserver/libedataserver.h> +#include <libedataserverui/libedataserverui.h> + +#include <shell/e-shell.h> +#include <addressbook/gui/merging/eab-contact-merging.h> +#include <addressbook/util/eab-book-util.h> + +#include <libebackend/libebackend.h> + +#define d(x) + +typedef struct _EMailParserVCardInline { + EExtension parent; +} EMailParserVCardInline; + +typedef struct _EMailParserVCardInlineClass { + EExtensionClass parent_class; +} EMailParserVCardInlineClass; + +GType e_mail_parser_vcard_inline_get_type (void); +static void e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface); +static void e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMailParserVCardInline, + e_mail_parser_vcard_inline, + E_TYPE_EXTENSION, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_EXTENSION, + e_mail_parser_mail_extension_interface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_MAIL_PARSER_EXTENSION, + e_mail_parser_parser_extension_interface_init)); + +static const gchar* parser_mime_types[] = { "text/vcard", "text/x-vcard", + "text/directory", NULL }; + +static void +mail_part_vcard_inline_free (EMailPart *mail_part) +{ + EMailPartVCardInline *vi_part = (EMailPartVCardInline *) mail_part; + + g_clear_object (&vi_part->contact_display); + g_clear_object (&vi_part->message_label); + g_clear_object (&vi_part->formatter); + g_clear_object (&vi_part->iframe); + g_clear_object (&vi_part->save_button); + g_clear_object (&vi_part->toggle_button); + g_clear_object (&vi_part->folder); + + if (vi_part->message_uid) { + g_free (vi_part->message_uid); + vi_part->message_uid = NULL; + } +} + +static void +client_loaded_cb (ESource *source, + GAsyncResult *result, + GSList *contact_list) +{ + EShell *shell; + EClient *client = NULL; + EBookClient *book_client; + ESourceRegistry *registry; + GSList *iter; + GError *error = NULL; + + e_client_utils_open_new_finish (source, result, &client, &error); + + if (error != NULL) { + g_warn_if_fail (client == NULL); + g_warning ( + "%s: Failed to open book client: %s", + G_STRFUNC, error->message); + g_error_free (error); + goto exit; + } + + g_return_if_fail (E_IS_BOOK_CLIENT (client)); + + book_client = E_BOOK_CLIENT (client); + + shell = e_shell_get_default (); + registry = e_shell_get_registry (shell); + + for (iter = contact_list; iter != NULL; iter = iter->next) { + EContact *contact; + + contact = E_CONTACT (iter->data); + eab_merging_book_add_contact ( + registry, book_client, contact, NULL, NULL); + } + + g_object_unref (client); + + exit: + e_client_util_free_object_slist (contact_list); +} + +static void +save_vcard_cb (WebKitDOMEventTarget *button, + WebKitDOMEvent *event, + EMailPartVCardInline *vcard_part) +{ + EShell *shell; + ESource *source; + ESourceRegistry *registry; + ESourceSelector *selector; + GSList *contact_list; + const gchar *extension_name; + GtkWidget *dialog; + + shell = e_shell_get_default (); + registry = e_shell_get_registry (shell); + extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK; + + dialog = e_source_selector_dialog_new (NULL, registry, extension_name); + + selector = e_source_selector_dialog_get_selector ( + E_SOURCE_SELECTOR_DIALOG (dialog)); + + source = e_source_registry_ref_default_address_book (registry); + e_source_selector_set_primary_selection (selector, source); + g_object_unref (source); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) { + gtk_widget_destroy (dialog); + return; + } + + source = e_source_selector_dialog_peek_primary_selection ( + E_SOURCE_SELECTOR_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + g_return_if_fail (source != NULL); + + contact_list = e_client_util_copy_object_slist (NULL, vcard_part->contact_list); + + e_client_utils_open_new ( + source, E_CLIENT_SOURCE_TYPE_CONTACTS, + FALSE, NULL, (GAsyncReadyCallback) client_loaded_cb, + contact_list); +} + +static void +display_mode_toggle_cb (WebKitDOMEventTarget *button, + WebKitDOMEvent *event, + EMailPartVCardInline *vcard_part) +{ + EABContactDisplayMode mode; + gchar *uri; + + mode = eab_contact_formatter_get_display_mode (vcard_part->formatter); + if (mode == EAB_CONTACT_DISPLAY_RENDER_NORMAL) { + mode = EAB_CONTACT_DISPLAY_RENDER_COMPACT; + + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (button), + _("Show Full vCard"), NULL); + + } else { + mode = EAB_CONTACT_DISPLAY_RENDER_NORMAL; + + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (button), + _("Show Compact vCard"), NULL); + } + + eab_contact_formatter_set_display_mode (vcard_part->formatter, mode); + + uri = e_mail_part_build_uri ( + vcard_part->folder, vcard_part->message_uid, + "part_id", G_TYPE_STRING, vcard_part->parent.id, + "mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_RAW, NULL); + + webkit_dom_html_iframe_element_set_src ( + WEBKIT_DOM_HTML_IFRAME_ELEMENT (vcard_part->iframe), uri); + + g_free (uri); +} + +static void +bind_dom (EMailPartVCardInline *vcard_part, + WebKitDOMElement *attachment) +{ + WebKitDOMNodeList *list; + WebKitDOMElement *iframe, *toggle_button, *save_button; + + /* IFRAME */ + list = webkit_dom_element_get_elements_by_tag_name (attachment, "iframe"); + if (webkit_dom_node_list_get_length (list) != 1) + return; + iframe = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (list, 0)); + if (vcard_part->iframe) + g_object_unref (vcard_part->iframe); + vcard_part->iframe = g_object_ref (iframe); + + /* TOGGLE DISPLAY MODE BUTTON */ + list = webkit_dom_element_get_elements_by_class_name ( + attachment, "org-gnome-vcard-inline-display-mode-button"); + if (webkit_dom_node_list_get_length (list) != 1) + return; + toggle_button = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (list, 0)); + if (vcard_part->toggle_button) + g_object_unref (vcard_part->toggle_button); + vcard_part->toggle_button = g_object_ref (toggle_button); + + /* SAVE TO ADDRESSBOOK BUTTON */ + list = webkit_dom_element_get_elements_by_class_name ( + attachment, "org-gnome-vcard-inline-save-button"); + if (webkit_dom_node_list_get_length (list) != 1) + return; + save_button = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (list, 0)); + if (vcard_part->save_button) + g_object_unref (vcard_part->save_button); + vcard_part->save_button = g_object_ref (save_button); + + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (toggle_button), + "click", G_CALLBACK (display_mode_toggle_cb), + FALSE, vcard_part); + + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (save_button), + "click", G_CALLBACK (save_vcard_cb), + FALSE, vcard_part); + + /* Bind collapse buttons for contact lists. */ + eab_contact_formatter_bind_dom ( + webkit_dom_html_iframe_element_get_content_document ( + WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe))); +} + +static void +decode_vcard (EMailPartVCardInline *vcard_part, + CamelMimePart *mime_part) +{ + CamelDataWrapper *data_wrapper; + CamelMedium *medium; + CamelStream *stream; + GSList *contact_list; + GByteArray *array; + const gchar *string; + const guint8 padding[2] = {0}; + + array = g_byte_array_new (); + medium = CAMEL_MEDIUM (mime_part); + + /* Stream takes ownership of the byte array. */ + stream = camel_stream_mem_new_with_byte_array (array); + data_wrapper = camel_medium_get_content (medium); + camel_data_wrapper_decode_to_stream_sync ( + data_wrapper, stream, NULL, NULL); + + /* because the result is not NULL-terminated */ + g_byte_array_append (array, padding, 2); + + string = (gchar *) array->data; + contact_list = eab_contact_list_from_string (string); + vcard_part->contact_list = contact_list; + + g_object_unref (mime_part); + g_object_unref (stream); +} + +static GSList * +empe_vcard_inline_parse (EMailParserExtension *extension, + EMailParser *parser, + CamelMimePart *part, + GString *part_id, + GCancellable *cancellable) +{ + EMailPartVCardInline *vcard_part; + gint len; + + len = part_id->len; + g_string_append (part_id, ".org-gnome-vcard-inline-display"); + + vcard_part = (EMailPartVCardInline *) e_mail_part_subclass_new ( + part, part_id->str, sizeof (EMailPartVCardInline), + (GFreeFunc) mail_part_vcard_inline_free); + vcard_part->parent.mime_type = camel_content_type_simple ( + camel_mime_part_get_content_type (part)); + vcard_part->parent.bind_func = (EMailPartDOMBindFunc) bind_dom; + vcard_part->parent.is_attachment = TRUE; + vcard_part->formatter = g_object_new ( + EAB_TYPE_CONTACT_FORMATTER, + "display-mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT, + "render-maps", FALSE, NULL); + g_object_ref (part); + + decode_vcard (vcard_part, part); + + g_string_truncate (part_id, len); + + return e_mail_parser_wrap_as_attachment ( + parser, part, g_slist_append (NULL, vcard_part), + part_id, cancellable); +} + +static guint32 +empe_vcard_inline_get_flags (EMailParserExtension *extension) +{ + return E_MAIL_PARSER_EXTENSION_INLINE_DISPOSITION; +} + +static const gchar ** +empe_mime_types (EMailExtension *extension) +{ + return parser_mime_types; +} + +void +e_mail_parser_vcard_inline_type_register (GTypeModule *type_module) +{ + e_mail_parser_vcard_inline_register_type (type_module); +} + +static void +e_mail_parser_mail_extension_interface_init (EMailExtensionInterface *iface) +{ + iface->mime_types = empe_mime_types; +} + +static void +e_mail_parser_parser_extension_interface_init (EMailParserExtensionInterface *iface) +{ + iface->parse = empe_vcard_inline_parse; + iface->get_flags = empe_vcard_inline_get_flags; +} + +static void +e_mail_parser_vcard_inline_constructed (GObject *object) +{ + EExtensible *extensible; + EMailExtensionRegistry *reg; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + reg = E_MAIL_EXTENSION_REGISTRY (extensible); + + e_mail_extension_registry_add_extension (reg, E_MAIL_EXTENSION (object)); +} + +static void +e_mail_parser_vcard_inline_class_init (EMailParserVCardInlineClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + e_mail_parser_vcard_inline_parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = e_mail_parser_vcard_inline_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY; +} + +static void +e_mail_parser_vcard_inline_class_finalize (EMailParserVCardInlineClass *klass) +{ + +} + +static void +e_mail_parser_vcard_inline_init (EMailParserVCardInline *self) +{ +} diff --git a/modules/vcard-inline/e-mail-parser-vcard-inline.h b/modules/vcard-inline/e-mail-parser-vcard-inline.h new file mode 100644 index 0000000000..76ec5fe206 --- /dev/null +++ b/modules/vcard-inline/e-mail-parser-vcard-inline.h @@ -0,0 +1,30 @@ +/* + * e-mail-parser-vcard-inline.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/> + * + */ + +#ifndef E_MAIL_PARSER_VCARD_INLINE_H +#define E_MAIL_PARSER_VCARD_INLINE_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_mail_parser_vcard_inline_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MAIL_PARSER_VCARD_INLINE_H */ diff --git a/modules/vcard-inline/e-mail-part-vcard-inline.h b/modules/vcard-inline/e-mail-part-vcard-inline.h new file mode 100644 index 0000000000..8272d2f672 --- /dev/null +++ b/modules/vcard-inline/e-mail-part-vcard-inline.h @@ -0,0 +1,50 @@ +/* + * e-mail-part-vcard-inline.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/> + * + */ + +#ifndef E_MAIL_PART_VCARD_INLINE_H +#define E_MAIL_PART_VCARD_INLINE_H + +#include <em-format/e-mail-part.h> + +#include <addressbook/gui/widgets/eab-contact-formatter.h> +#include <webkit/webkitdom.h> + +G_BEGIN_DECLS + +typedef struct _EMailPartVCardInline EMailPartVCardInline; + +struct _EMailPartVCardInline { + EMailPart parent; + + GSList *contact_list; + GtkWidget *contact_display; + GtkWidget *message_label; + + EABContactFormatter *formatter; + WebKitDOMElement *iframe; + WebKitDOMElement *toggle_button; + WebKitDOMElement *save_button; + + CamelFolder *folder; + gchar *message_uid; +}; + +G_END_DECLS + +#endif /* E_MAIL_PART_VCARD_INLINE_H */ + diff --git a/modules/vcard-inline/evolution-module-vcard-inline.c b/modules/vcard-inline/evolution-module-vcard-inline.c new file mode 100644 index 0000000000..4e95aba312 --- /dev/null +++ b/modules/vcard-inline/evolution-module-vcard-inline.c @@ -0,0 +1,53 @@ +/* + * evolution-module-vcard-inline.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/> + * + */ + +#include "e-mail-formatter-vcard-inline.h" +#include "e-mail-parser-vcard-inline.h" + +#include <gmodule.h> + +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_formatter_vcard_inline_type_register (type_module); + e_mail_parser_vcard_inline_type_register (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; +} |