diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2010-10-06 22:55:27 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2010-10-13 01:59:00 +0800 |
commit | 4118d671d44b71592f0e91abb63f2468baaa9318 (patch) | |
tree | e70f787f68034a16df1c59f75c8869618b02146b | |
parent | a06e4484b8df804124b5bcf88d94dec5acfba270 (diff) | |
download | gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.tar gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.tar.gz gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.tar.bz2 gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.tar.lz gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.tar.xz gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.tar.zst gsoc2013-evolution-4118d671d44b71592f0e91abb63f2468baaa9318.zip |
Composer: Show cancellable operations and errors inline.
'Send' and 'Save Draft' are now asynchronous and run outside of
Evolution's MailMsg infrastructure.
Add an EActivityBar to the composer window so these asynchronous
operations can be tracked and cancelled even in the absense of a main
window. Also add an EAlertBar to the composer window so error messages
can be shown directly in the window.
Instead of calling e_alert_dialog_run_for_args(), call e_alert_submit()
and pass the EMsgComposer as the widget argument. The EMsgComposer will
decide whether to show an EAlertDialog or use the EAlertBar, depending
on the GtkMessageType of the alert.
33 files changed, 4423 insertions, 1642 deletions
diff --git a/composer/Makefile.am b/composer/Makefile.am index 5caea41ee7..5cd2eab05c 100644 --- a/composer/Makefile.am +++ b/composer/Makefile.am @@ -9,15 +9,16 @@ privsolib_LTLIBRARIES = libcomposer.la libcomposerincludedir = $(privincludedir)/composer libcomposerinclude_HEADERS = \ - e-composer-header.h \ - e-composer-header-table.h \ + e-composer-actions.h \ + e-composer-activity.h \ + e-composer-common.h \ e-composer-from-header.h \ + e-composer-header-table.h \ + e-composer-header.h \ e-composer-name-header.h \ e-composer-post-header.h \ e-composer-private.h \ e-composer-text-header.h \ - e-composer-common.h \ - e-composer-actions.h \ e-msg-composer.h libcomposer_la_CPPFLAGS = \ @@ -42,9 +43,10 @@ libcomposer_la_CPPFLAGS = \ libcomposer_la_SOURCES = \ $(libcomposerinclude_HEADERS) \ e-composer-actions.c \ - e-composer-header.c \ - e-composer-header-table.c \ + e-composer-activity.c \ e-composer-from-header.c \ + e-composer-header-table.c \ + e-composer-header.c \ e-composer-name-header.c \ e-composer-post-header.c \ e-composer-private.c \ diff --git a/composer/e-composer-actions.c b/composer/e-composer-actions.c index a49567cea2..13fe48495e 100644 --- a/composer/e-composer-actions.c +++ b/composer/e-composer-actions.c @@ -130,8 +130,8 @@ action_save_cb (GtkAction *action, if (response != GTK_RESPONSE_OK) return; } else { - e_alert_run_dialog_for_args ( - GTK_WINDOW (composer), + e_alert_submit ( + GTK_WIDGET (composer), E_ALERT_NO_SAVE_FILE, filename, g_strerror (errno_saved), NULL); return; @@ -140,8 +140,8 @@ action_save_cb (GtkAction *action, close (fd); if (!gtkhtml_editor_save (editor, filename, TRUE, &error)) { - e_alert_run_dialog_for_args ( - GTK_WINDOW (composer), + e_alert_submit ( + GTK_WIDGET (composer), E_ALERT_NO_SAVE_FILE, filename, error->message, NULL); g_error_free (error); @@ -252,20 +252,6 @@ static GtkActionEntry entries[] = { N_("Close the current file"), G_CALLBACK (action_close_cb) }, - { "print", - GTK_STOCK_PRINT, - N_("_Print..."), - "<Control>p", - NULL, - G_CALLBACK (action_print_cb) }, - - { "print-preview", - GTK_STOCK_PRINT_PREVIEW, - N_("Print Pre_view"), - "<Shift><Control>p", - NULL, - G_CALLBACK (action_print_preview_cb) }, - { "save", GTK_STOCK_SAVE, N_("_Save"), @@ -280,20 +266,6 @@ static GtkActionEntry entries[] = { N_("Save the current file with a different name"), G_CALLBACK (action_save_as_cb) }, - { "save-draft", - GTK_STOCK_SAVE, - N_("Save as _Draft"), - "<Control>s", - N_("Save as draft"), - G_CALLBACK (action_save_draft_cb) }, - - { "send", - "mail-send", - N_("S_end"), - "<Control>Return", - N_("Send this message"), - G_CALLBACK (action_send_cb) }, - { "new-message", "mail-message-new", N_("New _Message"), @@ -318,6 +290,37 @@ static GtkActionEntry entries[] = { NULL } }; +static GtkActionEntry async_entries[] = { + + { "print", + GTK_STOCK_PRINT, + N_("_Print..."), + "<Control>p", + NULL, + G_CALLBACK (action_print_cb) }, + + { "print-preview", + GTK_STOCK_PRINT_PREVIEW, + N_("Print Pre_view"), + "<Shift><Control>p", + NULL, + G_CALLBACK (action_print_preview_cb) }, + + { "save-draft", + GTK_STOCK_SAVE, + N_("Save as _Draft"), + "<Control>s", + N_("Save as draft"), + G_CALLBACK (action_save_draft_cb) }, + + { "send", + "mail-send", + N_("S_end"), + "<Control>Return", + N_("Send this message"), + G_CALLBACK (action_send_cb) }, +}; + static GtkToggleActionEntry toggle_entries[] = { { "pgp-encrypt", @@ -416,6 +419,15 @@ e_composer_actions_init (EMsgComposer *composer) G_N_ELEMENTS (toggle_entries), composer); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + /* Asynchronous Actions */ + action_group = composer->priv->async_actions; + gtk_action_group_set_translation_domain ( + action_group, GETTEXT_PACKAGE); + gtk_action_group_add_actions ( + action_group, async_entries, + G_N_ELEMENTS (async_entries), composer); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + /* Character Set Actions */ action_group = composer->priv->charset_actions; gtk_action_group_set_translation_domain ( diff --git a/composer/e-composer-activity.c b/composer/e-composer-activity.c new file mode 100644 index 0000000000..d565bbc1fb --- /dev/null +++ b/composer/e-composer-activity.c @@ -0,0 +1,156 @@ +/* + * e-composer-activity.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-composer-private.h" + +#define E_COMPOSER_ACTIVITY_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityPrivate)) + +struct _EComposerActivityPrivate { + EMsgComposer *composer; +}; + +enum { + PROP_0, + PROP_COMPOSER +}; + +G_DEFINE_TYPE ( + EComposerActivity, + e_composer_activity, + E_TYPE_ACTIVITY) + +static void +composer_activity_set_sensitive (EMsgComposer *composer, + gboolean sensitive) +{ + GtkActionGroup *action_group; + + action_group = composer->priv->async_actions; + gtk_action_group_set_sensitive (action_group, sensitive); +} + +static void +composer_activity_set_composer (EComposerActivity *activity, + EMsgComposer *composer) +{ + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + g_return_if_fail (activity->priv->composer == NULL); + + activity->priv->composer = g_object_ref (composer); + + composer_activity_set_sensitive (composer, FALSE); +} + +static void +composer_activity_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_COMPOSER: + composer_activity_set_composer ( + E_COMPOSER_ACTIVITY (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +composer_activity_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_COMPOSER: + g_value_set_object ( + value, e_composer_activity_get_composer ( + E_COMPOSER_ACTIVITY (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +composer_activity_dispose (GObject *object) +{ + EComposerActivityPrivate *priv; + + priv = E_COMPOSER_ACTIVITY_GET_PRIVATE (object); + + if (priv->composer != NULL) { + composer_activity_set_sensitive (priv->composer, TRUE); + g_object_unref (priv->composer); + priv->composer = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_composer_activity_parent_class)->dispose (object); +} + +static void +e_composer_activity_class_init (EComposerActivityClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (EComposerActivityPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = composer_activity_set_property; + object_class->get_property = composer_activity_get_property; + object_class->dispose = composer_activity_dispose; + + g_object_class_install_property ( + object_class, + PROP_COMPOSER, + g_param_spec_object ( + "composer", + NULL, + NULL, + E_TYPE_MSG_COMPOSER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +e_composer_activity_init (EComposerActivity *activity) +{ + activity->priv = E_COMPOSER_ACTIVITY_GET_PRIVATE (activity); +} + +EActivity * +e_composer_activity_new (EMsgComposer *composer) +{ + return g_object_new ( + E_TYPE_COMPOSER_ACTIVITY, + "composer", composer, NULL); +} + +EMsgComposer * +e_composer_activity_get_composer (EComposerActivity *activity) +{ + g_return_val_if_fail (E_IS_COMPOSER_ACTIVITY (activity), NULL); + + return activity->priv->composer; +} diff --git a/composer/e-composer-activity.h b/composer/e-composer-activity.h new file mode 100644 index 0000000000..431e390062 --- /dev/null +++ b/composer/e-composer-activity.h @@ -0,0 +1,66 @@ +/* + * e-composer-activity.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_COMPOSER_ACTIVITY_H +#define E_COMPOSER_ACTIVITY_H + +#include <e-util/e-activity.h> +#include <composer/e-msg-composer.h> + +/* Standard GObject macros */ +#define E_TYPE_COMPOSER_ACTIVITY \ + (e_composer_activity_get_type ()) +#define E_COMPOSER_ACTIVITY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivity)) +#define E_COMPOSER_ACTIVITY_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityClass)) +#define E_IS_COMPOSER_ACTIVITY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_COMPOSER_ACTIVITY)) +#define E_IS_COMPOSER_ACTIVITY_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_COMPOSER_ACTIVITY)) +#define E_COMPOSER_ACTIVITY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityClass)) + +G_BEGIN_DECLS + +typedef struct _EComposerActivity EComposerActivity; +typedef struct _EComposerActivityClass EComposerActivityClass; +typedef struct _EComposerActivityPrivate EComposerActivityPrivate; + +struct _EComposerActivity { + EActivity parent; + EComposerActivityPrivate *priv; +}; + +struct _EComposerActivityClass { + EActivityClass parent_class; +}; + +GType e_composer_activity_get_type (void); +EActivity * e_composer_activity_new (EMsgComposer *composer); +EMsgComposer * e_composer_activity_get_composer + (EComposerActivity *activity); + +G_END_DECLS + +#endif /* E_COMPOSER_ACTIVITY_H */ diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c index 01ffda6501..d96832d887 100644 --- a/composer/e-composer-private.c +++ b/composer/e-composer-private.c @@ -176,6 +176,7 @@ e_composer_private_constructed (EMsgComposer *composer) priv->window_group = gtk_window_group_new (); gtk_window_group_add_window (priv->window_group, window); + priv->async_actions = gtk_action_group_new ("async"); priv->charset_actions = gtk_action_group_new ("charset"); priv->composer_actions = gtk_action_group_new ("composer"); @@ -253,17 +254,31 @@ e_composer_private_constructed (EMsgComposer *composer) priv->focus_tracker = focus_tracker; - /* Construct the header table. */ - container = editor->vbox; + /* Construct the activity bar. */ + + widget = e_activity_bar_new (); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + priv->activity_bar = g_object_ref (widget); + /* EActivityBar controls its own visibility. */ + + /* Construct the alert bar for errors. */ + + widget = e_alert_bar_new (); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + priv->alert_bar = g_object_ref (widget); + /* EAlertBar controls its own visibility. */ + + /* Construct the header table. */ + widget = e_composer_header_table_new (shell); gtk_container_set_border_width (GTK_CONTAINER (widget), 6); - gtk_box_pack_start (GTK_BOX (editor->vbox), widget, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); if (small_screen_mode) - gtk_box_reorder_child (GTK_BOX (editor->vbox), widget, 1); + gtk_box_reorder_child (GTK_BOX (container), widget, 1); else - gtk_box_reorder_child (GTK_BOX (editor->vbox), widget, 2); + gtk_box_reorder_child (GTK_BOX (container), widget, 2); priv->header_table = g_object_ref (widget); gtk_widget_show (widget); @@ -399,8 +414,6 @@ e_composer_private_constructed (EMsgComposer *composer) g_signal_connect ( html, "url-requested", G_CALLBACK (msg_composer_url_requested_cb), composer); - - priv->mail_sent = FALSE; } void @@ -436,6 +449,16 @@ e_composer_private_dispose (EMsgComposer *composer) composer->priv->header_table = NULL; } + if (composer->priv->activity_bar != NULL) { + g_object_unref (composer->priv->activity_bar); + composer->priv->activity_bar = NULL; + } + + if (composer->priv->alert_bar != NULL) { + g_object_unref (composer->priv->alert_bar); + composer->priv->alert_bar = NULL; + } + if (composer->priv->attachment_paned != NULL) { g_object_unref (composer->priv->attachment_paned); composer->priv->attachment_paned = NULL; @@ -451,6 +474,11 @@ e_composer_private_dispose (EMsgComposer *composer) composer->priv->window_group = NULL; } + if (composer->priv->async_actions != NULL) { + g_object_unref (composer->priv->async_actions); + composer->priv->async_actions = NULL; + } + if (composer->priv->charset_actions != NULL) { g_object_unref (composer->priv->charset_actions); composer->priv->charset_actions = NULL; diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h index 026ed96954..1e9130b196 100644 --- a/composer/e-composer-private.h +++ b/composer/e-composer-private.h @@ -24,20 +24,44 @@ #include <errno.h> -#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + #include <glib/gstdio.h> +#include <glib/gi18n-lib.h> + +#include <gconf/gconf.h> +#include <gconf/gconf-client.h> #include "e-composer-actions.h" +#include "e-composer-activity.h" #include "e-composer-header-table.h" +#include "e-util/e-alert-sink.h" #include "e-util/e-binding.h" #include "e-util/e-charset.h" +#include "e-util/e-extensible.h" +#include "e-util/e-marshal.h" #include "e-util/e-mktemp.h" +#include "e-util/e-plugin-ui.h" #include "e-util/e-selection.h" #include "e-util/e-util.h" #include "e-util/gconf-bridge.h" +#include "widgets/misc/e-activity-bar.h" +#include "widgets/misc/e-alert-bar.h" +#include "widgets/misc/e-attachment.h" #include "widgets/misc/e-attachment-icon-view.h" #include "widgets/misc/e-attachment-paned.h" #include "widgets/misc/e-attachment-store.h" +#include "widgets/misc/e-signature-combo-box.h" +#include "widgets/misc/e-web-view.h" +#include "shell/e-shell.h" + +#ifdef HAVE_XFREE +#include <X11/XF86keysym.h> +#endif + +/* backward-compatibility cruft */ +#include "e-util/gtk-compat.h" #define E_MSG_COMPOSER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -92,15 +116,19 @@ struct _EMsgComposerPrivate { GtkWidget *html_editor; GtkWidget *header_table; + GtkWidget *activity_bar; + GtkWidget *alert_bar; GtkWidget *attachment_paned; EFocusTracker *focus_tracker; GtkWindowGroup *window_group; + GtkActionGroup *async_actions; GtkActionGroup *charset_actions; GtkActionGroup *composer_actions; - GPtrArray *extra_hdr_names, *extra_hdr_values; + GPtrArray *extra_hdr_names; + GPtrArray *extra_hdr_values; GArray *gconf_bridge_binding_ids; GtkWidget *focused_entry; @@ -111,9 +139,10 @@ struct _EMsgComposerPrivate { GHashTable *inline_images_by_url; GList *current_images; - gchar *mime_type, *mime_body, *charset; + gchar *mime_type; + gchar *mime_body; + gchar *charset; - guint32 is_alternative : 1; guint32 autosaved : 1; guint32 mode_post : 1; guint32 in_signature_insert : 1; @@ -122,12 +151,6 @@ struct _EMsgComposerPrivate { CamelMimeMessage *redirect; gboolean is_from_message; - - /* The mail composed has been sent. This bit will be set when - * the mail passed sanity checking and is sent out, which - * indicates that the composer can be destroyed. This bit can - * be set/get by using API e_msg_composer_{set,get}_mail_sent (). */ - gboolean mail_sent; }; void e_composer_private_constructed (EMsgComposer *composer); diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c index 0ea116f6dc..cc4b3c3b08 100644 --- a/composer/e-msg-composer.c +++ b/composer/e-msg-composer.c @@ -37,45 +37,53 @@ #include <ctype.h> #include <fcntl.h> -#include <glib.h> - -#include <gtk/gtk.h> -#include <gdk/gdkkeysyms.h> - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - #include "e-util/e-account-utils.h" #include "e-util/e-alert-dialog.h" -#include "e-util/e-alert-sink.h" #include "e-util/e-dialog-utils.h" -#include "e-util/e-extensible.h" -#include "e-util/e-plugin-ui.h" #include "e-util/e-signature-utils.h" #include "e-util/e-util-private.h" -#include "e-signature-combo-box.h" -#include "shell/e-shell.h" #include "em-format/em-format.h" #include "em-format/em-format-quote.h" -#include "misc/e-web-view.h" -#include "e-msg-composer.h" -#include "e-attachment.h" #include "e-composer-private.h" -#include "e-composer-header-table.h" -#ifdef HAVE_XFREE -#include <X11/XF86keysym.h> -#endif +typedef struct _AsyncContext AsyncContext; + +struct _AsyncContext { + EActivity *activity; + + CamelMimeMessage *message; + CamelDataWrapper *top_level_part; + CamelDataWrapper *text_plain_part; + + EAccount *account; + CamelSession *session; + CamelInternetAddress *from; -/* backward-compatibility cruft */ -#include "e-util/gtk-compat.h" + CamelTransferEncoding plain_encoding; + GtkPrintOperationAction print_action; + + GPtrArray *recipients; -#define d(x) + guint skip_content : 1; + guint need_thread : 1; + guint pgp_sign : 1; + guint pgp_encrypt : 1; + guint smime_sign : 1; + guint smime_encrypt : 1; +}; -#define E_MSG_COMPOSER_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_MSG_COMPOSER, EMsgComposerPrivate)) +/* Flags for building a message. */ +typedef enum { + COMPOSER_FLAG_HTML_CONTENT = 1 << 0, + COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1, + COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2, + COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3, + COMPOSER_FLAG_PGP_SIGN = 1 << 4, + COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5, + COMPOSER_FLAG_SMIME_SIGN = 1 << 6, + COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7 +} ComposerFlags; enum { PROP_0, @@ -84,6 +92,7 @@ enum { }; enum { + PRESEND, SEND, SAVE_DRAFT, PRINT, @@ -92,11 +101,6 @@ enum { static guint signals[LAST_SIGNAL]; -/* local prototypes */ -static GList *add_recipients (GList *list, const gchar *recips); - -static void handle_mailto (EMsgComposer *composer, const gchar *mailto); - /* used by e_msg_composer_add_message_attachments () */ static void add_attachments_from_multipart (EMsgComposer *composer, CamelMultipart *multipart, @@ -121,13 +125,46 @@ static void handle_multipart_signed (EMsgComposer *composer, GCancellable *cancellable, gint depth); +static void e_msg_composer_alert_sink_init (EAlertSinkInterface *interface); + G_DEFINE_TYPE_WITH_CODE ( EMsgComposer, e_msg_composer, GTKHTML_TYPE_EDITOR, - G_IMPLEMENT_INTERFACE (E_TYPE_ALERT_SINK, NULL) + G_IMPLEMENT_INTERFACE ( + E_TYPE_ALERT_SINK, e_msg_composer_alert_sink_init) G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) +static void +async_context_free (AsyncContext *context) +{ + if (context->activity != NULL) + g_object_unref (context->activity); + + if (context->message != NULL) + g_object_unref (context->message); + + if (context->top_level_part != NULL) + g_object_unref (context->top_level_part); + + if (context->text_plain_part != NULL) + g_object_unref (context->text_plain_part); + + if (context->account != NULL) + g_object_unref (context->account); + + if (context->session != NULL) + g_object_unref (context->session); + + if (context->from != NULL) + g_object_unref (context->from); + + if (context->recipients != NULL) + g_ptr_array_free (context->recipients, TRUE); + + g_slice_free (AsyncContext, context); +} + /** * emcu_part_to_html: * @part: @@ -181,52 +218,6 @@ emcu_part_to_html (CamelMimePart *part, return text; } -/* copy of em_utils_prompt_user from mailer */ -static gboolean -emcu_prompt_user (GtkWindow *parent, const gchar *promptkey, const gchar *tag, ...) -{ - GtkDialog *mbox; - GtkWidget *check = NULL; - GtkWidget *container; - va_list ap; - gint button; - GConfClient *gconf = gconf_client_get_default (); - EAlert *alert = NULL; - - if (promptkey - && !gconf_client_get_bool (gconf, promptkey, NULL)) { - g_object_unref (gconf); - return TRUE; - } - - va_start (ap, tag); - alert = e_alert_new_valist (tag, ap); - va_end (ap); - - mbox = (GtkDialog*) e_alert_dialog_new (parent, alert); - g_object_unref (alert); - - if (promptkey) { - check = gtk_check_button_new_with_mnemonic (_("_Do not show this message again.")); - gtk_container_set_border_width ((GtkContainer *)check, 12); - container = gtk_dialog_get_content_area (mbox); - gtk_box_pack_start (GTK_BOX (container), check, TRUE, TRUE, 0); - gtk_widget_show (check); - } - - button = gtk_dialog_run (mbox); - if (promptkey) - gconf_client_set_bool ( - gconf, promptkey, - !gtk_toggle_button_get_active ( - GTK_TOGGLE_BUTTON (check)), NULL); - - gtk_widget_destroy ((GtkWidget*) mbox); - g_object_unref (gconf); - - return button == GTK_RESPONSE_YES; -} - /* copy of mail_tool_remove_xevolution_headers */ static struct _camel_header_raw * emcu_remove_xevolution_headers (CamelMimeMessage *message) @@ -607,42 +598,413 @@ account_hash_algo_to_camel_hash (const gchar *hash_algo) return res; } -static CamelMimeMessage * -build_message (EMsgComposer *composer, - gboolean html_content, - gboolean save_html_object_data, - GCancellable *cancellable, - GError **error) +static void +composer_add_charset_filter (CamelStream *stream, + const gchar *charset) { - GtkhtmlEditor *editor; - EMsgComposerPrivate *p = composer->priv; + CamelMimeFilter *filter; + + filter = camel_mime_filter_charset_new ("UTF-8", charset); + camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter); + g_object_unref (filter); +} + +static void +composer_add_quoted_printable_filter (CamelStream *stream) +{ + CamelMimeFilter *filter; + + filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC); + camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter); + g_object_unref (filter); + + filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_FROM); + camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter); + g_object_unref (filter); +} + +/* Helper for composer_build_message_thread() */ +static gboolean +composer_build_message_pgp (AsyncContext *context, + GCancellable *cancellable, + GError **error) +{ + CamelCipherContext *cipher; + CamelDataWrapper *content; + CamelMimePart *mime_part; + const gchar *pgp_userid; + gboolean have_pgp_key; + + /* Return silently if we're not signing or encrypting with PGP. */ + if (!context->pgp_sign && !context->pgp_encrypt) + return TRUE; + + have_pgp_key = + (context->account != NULL) && + (context->account->pgp_key != NULL) && + (context->account->pgp_key[0] != '\0'); + + mime_part = camel_mime_part_new (); + + camel_medium_set_content ( + CAMEL_MEDIUM (mime_part), + context->top_level_part); + + if (context->top_level_part == context->text_plain_part) + camel_mime_part_set_encoding ( + mime_part, context->plain_encoding); + + g_object_unref (context->top_level_part); + context->top_level_part = NULL; + + if (have_pgp_key) + pgp_userid = context->account->pgp_key; + else + camel_internet_address_get ( + context->from, 0, NULL, &pgp_userid); + + if (context->pgp_sign) { + CamelMimePart *npart; + gboolean success; + + npart = camel_mime_part_new (); + + cipher = camel_gpg_context_new (context->session); + if (context->account != NULL) + camel_gpg_context_set_always_trust ( + CAMEL_GPG_CONTEXT (cipher), + context->account->pgp_always_trust); + + success = camel_cipher_context_sign_sync ( + cipher, pgp_userid, + account_hash_algo_to_camel_hash ( + (context->account != NULL) ? + e_account_get_string (context->account, + E_ACCOUNT_PGP_HASH_ALGORITHM) : NULL), + mime_part, npart, cancellable, error); + + g_object_unref (cipher); + + g_object_unref (mime_part); + + if (!success) { + g_object_unref (npart); + return FALSE; + } + + mime_part = npart; + } + + if (context->pgp_encrypt) { + CamelMimePart *npart; + gboolean encrypt_to_self; + gboolean success; + + encrypt_to_self = + (context->account != NULL) && + (context->account->pgp_encrypt_to_self) && + (pgp_userid != NULL); + + npart = camel_mime_part_new (); + + /* Check to see if we should encrypt to self. + * NB gets removed immediately after use */ + if (encrypt_to_self) + g_ptr_array_add ( + context->recipients, + g_strdup (pgp_userid)); + + cipher = camel_gpg_context_new (context->session); + if (context->account != NULL) + camel_gpg_context_set_always_trust ( + CAMEL_GPG_CONTEXT (cipher), + context->account->pgp_always_trust); + success = camel_cipher_context_encrypt_sync ( + cipher, pgp_userid, context->recipients, + mime_part, npart, cancellable, error); + + g_object_unref (cipher); + + if (encrypt_to_self) + g_ptr_array_set_size ( + context->recipients, + context->recipients->len - 1); + + g_object_unref (mime_part); + + if (!success) { + g_object_unref (npart); + return FALSE; + } + + mime_part = npart; + } + + content = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); + context->top_level_part = g_object_ref (content); + + g_object_unref (mime_part); + + return TRUE; +} + +#ifdef HAVE_SSL +static gboolean +composer_build_message_smime (AsyncContext *context, + GCancellable *cancellable, + GError **error) +{ + CamelCipherContext *cipher; + CamelMimePart *mime_part; + gboolean have_signing_certificate; + gboolean have_encryption_certificate; + + /* Return silently if we're not signing or encrypting with S/MIME. */ + if (!context->smime_sign && !context->smime_encrypt) + return TRUE; + + have_signing_certificate = + (context->account != NULL) && + (context->account->smime_sign_key != NULL) && + (context->account->smime_sign_key[0] != '\0'); + + have_encryption_certificate = + (context->account != NULL) && + (context->account->smime_encrypt_key != NULL) && + (context->account->smime_encrypt_key[0] != '\0'); + + if (context->smime_sign && !have_signing_certificate) { + g_set_error ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Cannot sign outgoing message: " + "No signing certificate set for " + "this account")); + return FALSE; + } + + if (context->smime_encrypt && !have_encryption_certificate) { + g_set_error ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Cannot encrypt outgoing message: " + "No encryption certificate set for " + "this account")); + return FALSE; + } + + mime_part = camel_mime_part_new (); + + camel_medium_set_content ( + CAMEL_MEDIUM (mime_part), + context->top_level_part); + + if (context->top_level_part == context->text_plain_part) + camel_mime_part_set_encoding ( + mime_part, context->plain_encoding); + + g_object_unref (context->top_level_part); + context->top_level_part = NULL; + + if (context->smime_sign) { + CamelMimePart *npart; + gboolean success; + + npart = camel_mime_part_new (); + + cipher = camel_smime_context_new (context->session); + + /* if we're also encrypting, envelope-sign rather than clear-sign */ + if (context->smime_encrypt) { + camel_smime_context_set_sign_mode ( + (CamelSMIMEContext *) cipher, + CAMEL_SMIME_SIGN_ENVELOPED); + camel_smime_context_set_encrypt_key ( + (CamelSMIMEContext *) cipher, TRUE, + context->account->smime_encrypt_key); + } else if (have_encryption_certificate) { + camel_smime_context_set_encrypt_key ( + (CamelSMIMEContext *) cipher, TRUE, + context->account->smime_encrypt_key); + } + + success = camel_cipher_context_sign_sync ( + cipher, context->account->smime_sign_key, + account_hash_algo_to_camel_hash ( + (context->account != NULL) ? + e_account_get_string (context->account, + E_ACCOUNT_SMIME_HASH_ALGORITHM) : NULL), + mime_part, npart, cancellable, error); + + g_object_unref (cipher); + + g_object_unref (mime_part); + + if (!success) { + g_object_unref (npart); + return FALSE; + } + + mime_part = npart; + } + + if (context->smime_encrypt) { + gboolean success; + + /* check to see if we should encrypt to self, NB removed after use */ + if (context->account->smime_encrypt_to_self) + g_ptr_array_add ( + context->recipients, g_strdup ( + context->account->smime_encrypt_key)); + + cipher = camel_smime_context_new (context->session); + camel_smime_context_set_encrypt_key ( + (CamelSMIMEContext *) cipher, TRUE, + context->account->smime_encrypt_key); + + success = camel_cipher_context_encrypt_sync ( + cipher, NULL, + context->recipients, mime_part, + CAMEL_MIME_PART (context->message), + cancellable, error); + + g_object_unref (cipher); + + if (!success) + return FALSE; + + if (context->account->smime_encrypt_to_self) + g_ptr_array_set_size ( + context->recipients, + context->recipients->len - 1); + } + + /* we replaced the message directly, we don't want to do reparenting foo */ + if (context->smime_encrypt) { + context->skip_content = TRUE; + } else { + CamelDataWrapper *content; + + content = camel_medium_get_content ( + CAMEL_MEDIUM (mime_part)); + context->top_level_part = g_object_ref (content); + } + + g_object_unref (mime_part); + + return TRUE; +} +#endif + +static void +composer_build_message_thread (GSimpleAsyncResult *simple, + EMsgComposer *composer, + GCancellable *cancellable) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + /* Setup working recipient list if we're encrypting. */ + if (context->pgp_encrypt || context->smime_encrypt) { + gint ii, jj; + + const gchar *types[] = { + CAMEL_RECIPIENT_TYPE_TO, + CAMEL_RECIPIENT_TYPE_CC, + CAMEL_RECIPIENT_TYPE_BCC + }; + + context->recipients = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_free); + for (ii = 0; ii < G_N_ELEMENTS (types); ii++) { + CamelInternetAddress *addr; + const gchar *address; + + addr = camel_mime_message_get_recipients ( + context->message, types[ii]); + for (jj = 0; camel_internet_address_get (addr, jj, NULL, &address); jj++) + g_ptr_array_add ( + context->recipients, + g_strdup (address)); + } + } + + if (!composer_build_message_pgp (context, cancellable, &error)) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + return; + } + +#if defined (HAVE_NSS) + if (!composer_build_message_smime (context, cancellable, &error)) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + return; + } +#endif /* HAVE_NSS */ +} + +static void +composer_add_evolution_format_header (CamelMedium *medium, + ComposerFlags flags) +{ + GString *string; + + string = g_string_sized_new (128); + + if (flags & COMPOSER_FLAG_HTML_CONTENT) + g_string_append (string, "text/html"); + else + g_string_append (string, "text/plain"); + + if (flags & COMPOSER_FLAG_PGP_SIGN) + g_string_append (string, ", pgp-sign"); + + if (flags & COMPOSER_FLAG_PGP_ENCRYPT) + g_string_append (string, ", pgp-encrypt"); + + if (flags & COMPOSER_FLAG_SMIME_SIGN) + g_string_append (string, ", smime-sign"); + + if (flags & COMPOSER_FLAG_SMIME_ENCRYPT) + g_string_append (string, ", smime-encrypt"); + + camel_medium_add_header ( + medium, "X-Evolution-Format", string->str); + + g_string_free (string, TRUE); +} + +static void +composer_build_message (EMsgComposer *composer, + ComposerFlags flags, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EMsgComposerPrivate *priv; + GSimpleAsyncResult *simple; + AsyncContext *context; + GtkhtmlEditor *editor; EAttachmentView *view; EAttachmentStore *store; EComposerHeaderTable *table; - GtkToggleAction *action; - CamelDataWrapper *plain, *html, *current; - CamelTransferEncoding plain_encoding; + CamelDataWrapper *html; const gchar *iconv_charset = NULL; - GPtrArray *recipients = NULL; CamelMultipart *body = NULL; CamelContentType *type; - CamelMimeMessage *new; CamelSession *session; CamelStream *stream; + CamelStream *mem_stream; CamelMimePart *part; GByteArray *data; EAccount *account; gchar *charset; - gboolean pgp_sign; - gboolean pgp_encrypt; - gboolean smime_sign; - gboolean smime_encrypt; gint i; - GError *local_error = NULL; - - g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + priv = composer->priv; editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); account = e_composer_header_table_get_account (table); @@ -650,55 +1012,129 @@ build_message (EMsgComposer *composer, store = e_attachment_view_get_store (view); session = e_msg_composer_get_session (composer); - /* evil kludgy hack for Redirect */ - if (p->redirect) { - build_message_headers (composer, p->redirect, TRUE); - g_object_ref (p->redirect); - return p->redirect; + /* Do all the non-blocking work here, and defer + * any blocking operations to a separate thread. */ + + context = g_slice_new0 (AsyncContext); + context->account = g_object_ref (account); + context->session = g_object_ref (session); + context->from = e_msg_composer_get_from (composer); + + if (flags & COMPOSER_FLAG_PGP_SIGN) + context->pgp_sign = TRUE; + + if (flags & COMPOSER_FLAG_PGP_ENCRYPT) + context->pgp_encrypt = TRUE; + + if (flags & COMPOSER_FLAG_SMIME_SIGN) + context->smime_sign = TRUE; + + if (flags & COMPOSER_FLAG_SMIME_ENCRYPT) + context->smime_encrypt = TRUE; + + context->need_thread = + context->pgp_sign || context->pgp_encrypt || + context->smime_sign || context->smime_encrypt; + + simple = g_simple_async_result_new ( + G_OBJECT (composer), callback, + user_data, composer_build_message); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + /* If this is a redirected message, just tweak the headers. */ + if (priv->redirect) { + context->message = g_object_ref (priv->redirect); + build_message_headers (composer, context->message, TRUE); + g_simple_async_result_complete (simple); + g_object_unref (simple); + return; } - new = camel_mime_message_new (); - build_message_headers (composer, new, FALSE); - for (i = 0; i < p->extra_hdr_names->len; i++) { + context->message = camel_mime_message_new (); + + build_message_headers (composer, context->message, FALSE); + for (i = 0; i < priv->extra_hdr_names->len; i++) { camel_medium_add_header ( - CAMEL_MEDIUM (new), - p->extra_hdr_names->pdata[i], - p->extra_hdr_values->pdata[i]); + CAMEL_MEDIUM (context->message), + priv->extra_hdr_names->pdata[i], + priv->extra_hdr_values->pdata[i]); } - /* Message Disposition Notification */ - action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT)); - if (gtk_toggle_action_get_active (action)) { + /* Disposition-Notification-To */ + if (flags & COMPOSER_FLAG_REQUEST_READ_RECEIPT) { gchar *mdn_address = account->id->reply_to; if (!mdn_address || !*mdn_address) mdn_address = account->id->address; camel_medium_add_header ( - CAMEL_MEDIUM (new), + CAMEL_MEDIUM (context->message), "Disposition-Notification-To", mdn_address); } - /* Message Priority */ - action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE)); - if (gtk_toggle_action_get_active (action)) + /* X-Priority */ + if (flags & COMPOSER_FLAG_PRIORITIZE_MESSAGE) camel_medium_add_header ( - CAMEL_MEDIUM (new), "X-Priority", "1"); + CAMEL_MEDIUM (context->message), + "X-Priority", "1"); - if (p->mime_body) { - if (text_requires_quoted_printable (p->mime_body, -1)) { - plain_encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; + if (account != NULL) { + /* X-Evolution-Account */ + camel_medium_set_header ( + CAMEL_MEDIUM (context->message), + "X-Evolution-Account", account->uid); + + /* X-Evolution-Fcc */ + camel_medium_set_header ( + CAMEL_MEDIUM (context->message), + "X-Evolution-Fcc", account->sent_folder_uri); + + /* X-Evolution-Transport */ + camel_medium_set_header ( + CAMEL_MEDIUM (context->message), + "X-Evolution-Transport", account->transport->url); + + /* Organization */ + if (account->id->organization != NULL) { + gchar *organization; + + organization = camel_header_encode_string ( + (const guchar *) account->id->organization); + camel_medium_set_header ( + CAMEL_MEDIUM (context->message), + "Organization", organization); + g_free (organization); + } + } + + /* X-Evolution-Format */ + composer_add_evolution_format_header ( + CAMEL_MEDIUM (context->message), flags); + + /* Build the text/plain part. */ + + if (priv->mime_body) { + if (text_requires_quoted_printable (priv->mime_body, -1)) { + context->plain_encoding = + CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; } else { - plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT; - for (i = 0; p->mime_body[i]; i++) { - if ((guchar) p->mime_body[i] > 127) { - plain_encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; + context->plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT; + for (i = 0; priv->mime_body[i]; i++) { + if ((guchar) priv->mime_body[i] > 127) { + context->plain_encoding = + CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; break; } } } + data = g_byte_array_new (); - g_byte_array_append (data, (const guint8 *)p->mime_body, strlen (p->mime_body)); - type = camel_content_type_decode (p->mime_type); + g_byte_array_append ( + data, (const guint8 *) priv->mime_body, + strlen (priv->mime_body)); + type = camel_content_type_decode (priv->mime_type); + } else { gchar *text; gsize length; @@ -708,78 +1144,70 @@ build_message (EMsgComposer *composer, g_byte_array_append (data, (guint8 *) text, (guint) length); g_free (text); - /* FIXME: we may want to do better than this... */ - type = camel_content_type_new ("text", "plain"); - if ((charset = best_charset (data, p->charset, &plain_encoding))) { + charset = best_charset ( + data, priv->charset, &context->plain_encoding); + if (charset != NULL) { camel_content_type_set_param (type, "charset", charset); iconv_charset = camel_iconv_charset_name (charset); g_free (charset); } } - stream = camel_stream_mem_new_with_byte_array (data); - - /* convert the stream to the appropriate charset */ - if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0) { - CamelStream *filter_stream; - CamelMimeFilter *filter; + mem_stream = camel_stream_mem_new_with_byte_array (data); + stream = camel_stream_filter_new (mem_stream); + g_object_unref (mem_stream); - filter_stream = camel_stream_filter_new (stream); - g_object_unref (stream); - - stream = filter_stream; - filter = camel_mime_filter_charset_new ("UTF-8", iconv_charset); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filter_stream), filter); - g_object_unref (filter); - } + /* Convert the stream to the appropriate charset. */ + if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0) + composer_add_charset_filter (stream, iconv_charset); - if (plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) { - /* encode to quoted-printable by ourself, together with - * taking care of "\nFrom " text */ - CamelStream *filter_stream; - CamelMimeFilter *mf, *qp; + /* Encode the stream to quoted-printable if necessary. */ + if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) + composer_add_quoted_printable_filter (stream); - if (!CAMEL_IS_STREAM_FILTER (stream)) { - filter_stream = camel_stream_filter_new (stream); - g_object_unref (stream); - - stream = filter_stream; - } - - qp = camel_mime_filter_basic_new ( - CAMEL_MIME_FILTER_BASIC_QP_ENC); - camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), qp); - g_object_unref (qp); - - mf = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_FROM); - camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), mf); - g_object_unref (mf); - } - - /* construct the content object */ - plain = camel_data_wrapper_new (); + /* Construct the content object. This does not block since + * we're constructing the data wrapper from a memory stream. */ + context->top_level_part = camel_data_wrapper_new (); camel_data_wrapper_construct_from_stream_sync ( - plain, stream, NULL, NULL); + context->top_level_part, stream, NULL, NULL); g_object_unref (stream); - if (plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) { - /* to not re-encode the data when pushing it to a part */ - plain->encoding = plain_encoding; - } + context->text_plain_part = g_object_ref (context->top_level_part); + + /* Avoid re-encoding the data when adding it to a MIME part. */ + if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) + context->top_level_part->encoding = context->plain_encoding; + + camel_data_wrapper_set_mime_type_field ( + context->top_level_part, type); - camel_data_wrapper_set_mime_type_field (plain, type); camel_content_type_unref (type); - if (html_content) { + /* Build the text/html part, and wrap it and the text/plain part + * in a multipart/alternative part. Additionally, if there are + * inline images then wrap the multipart/alternative part along + * with the images in a multipart/related part. + * + * So the structure of all this will be: + * + * multipart/related + * multipart/alternative + * text/plain + * text/html + * image/<<whatever>> + * image/<<whatever>> + * ... + */ + + if (flags & COMPOSER_FLAG_HTML_CONTENT) { gchar *text; gsize length; gboolean pre_encode; clear_current_images (composer); - if (save_html_object_data) + if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA) gtkhtml_editor_run_command (editor, "save-data-on"); data = g_byte_array_new (); @@ -788,45 +1216,30 @@ build_message (EMsgComposer *composer, pre_encode = text_requires_quoted_printable (text, length); g_free (text); - if (save_html_object_data) + if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA) gtkhtml_editor_run_command (editor, "save-data-off"); - html = camel_data_wrapper_new (); - - stream = camel_stream_mem_new_with_byte_array (data); - - if (pre_encode) { - /* encode to quoted-printable by ourself, together with - * taking care of "\nFrom " text */ - CamelStream *filter_stream; - CamelMimeFilter *mf, *qp; + mem_stream = camel_stream_mem_new_with_byte_array (data); + stream = camel_stream_filter_new (mem_stream); + g_object_unref (mem_stream); - if (!CAMEL_IS_STREAM_FILTER (stream)) { - filter_stream = camel_stream_filter_new (stream); - g_object_unref (stream); - - stream = filter_stream; - } - - qp = camel_mime_filter_basic_new ( - CAMEL_MIME_FILTER_BASIC_QP_ENC); - camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), qp); - g_object_unref (qp); - - mf = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_FROM); - camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), mf); - g_object_unref (mf); - } + if (pre_encode) + composer_add_quoted_printable_filter (stream); + /* Construct the content object. This does not block since + * we're constructing the data wrapper from a memory stream. */ + html = camel_data_wrapper_new (); camel_data_wrapper_construct_from_stream_sync ( html, stream, NULL, NULL); g_object_unref (stream); - camel_data_wrapper_set_mime_type (html, "text/html; charset=utf-8"); - if (pre_encode) { - /* to not re-encode the data when pushing it to a part */ - html->encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; - } + camel_data_wrapper_set_mime_type ( + html, "text/html; charset=utf-8"); + + /* Avoid re-encoding the data when adding it to a MIME part. */ + if (pre_encode) + html->encoding = + CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; /* Build the multipart/alternative */ body = camel_multipart_new (); @@ -834,357 +1247,124 @@ build_message (EMsgComposer *composer, CAMEL_DATA_WRAPPER (body), "multipart/alternative"); camel_multipart_set_boundary (body, NULL); + /* Add the text/plain part. */ part = camel_mime_part_new (); - camel_medium_set_content (CAMEL_MEDIUM (part), plain); - g_object_unref (plain); - camel_mime_part_set_encoding (part, plain_encoding); + camel_medium_set_content ( + CAMEL_MEDIUM (part), context->top_level_part); + camel_mime_part_set_encoding (part, context->plain_encoding); camel_multipart_add_part (body, part); g_object_unref (part); + /* Add the text/html part. */ part = camel_mime_part_new (); camel_medium_set_content (CAMEL_MEDIUM (part), html); - g_object_unref (html); - camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE); + camel_mime_part_set_encoding ( + part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE); camel_multipart_add_part (body, part); g_object_unref (part); - /* If there are inlined images, construct a - * multipart/related containing the - * multipart/alternative and the images. - */ - if (p->current_images) { + g_object_unref (context->top_level_part); + g_object_unref (html); + + /* If there are inlined images, construct a multipart/related + * containing the multipart/alternative and the images. */ + if (priv->current_images) { CamelMultipart *html_with_images; html_with_images = camel_multipart_new (); camel_data_wrapper_set_mime_type ( CAMEL_DATA_WRAPPER (html_with_images), - "multipart/related; type=\"multipart/alternative\""); + "multipart/related; " + "type=\"multipart/alternative\""); camel_multipart_set_boundary (html_with_images, NULL); part = camel_mime_part_new (); - camel_medium_set_content (CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (body)); - g_object_unref (body); + camel_medium_set_content ( + CAMEL_MEDIUM (part), + CAMEL_DATA_WRAPPER (body)); camel_multipart_add_part (html_with_images, part); g_object_unref (part); + g_object_unref (body); + add_inlined_images (composer, html_with_images); clear_current_images (composer); - current = CAMEL_DATA_WRAPPER (html_with_images); + context->top_level_part = + CAMEL_DATA_WRAPPER (html_with_images); } else - current = CAMEL_DATA_WRAPPER (body); - } else - current = plain; + context->top_level_part = + CAMEL_DATA_WRAPPER (body); + } + /* If there are attachments, wrap what we've built so far + * along with the attachments in a multipart/mixed part. */ if (e_attachment_store_get_num_attachments (store) > 0) { CamelMultipart *multipart = camel_multipart_new (); - if (p->is_alternative) { - camel_data_wrapper_set_mime_type ( - CAMEL_DATA_WRAPPER (multipart), - "multipart/alternative"); - } - /* Generate a random boundary. */ camel_multipart_set_boundary (multipart, NULL); part = camel_mime_part_new (); - camel_medium_set_content (CAMEL_MEDIUM (part), current); - if (current == plain) - camel_mime_part_set_encoding (part, plain_encoding); - g_object_unref (current); + camel_medium_set_content ( + CAMEL_MEDIUM (part), + context->top_level_part); + if (context->top_level_part == context->text_plain_part) + camel_mime_part_set_encoding ( + part, context->plain_encoding); camel_multipart_add_part (multipart, part); g_object_unref (part); e_attachment_store_add_to_multipart ( - store, multipart, p->charset); + store, multipart, priv->charset); - if (p->is_alternative) { - for (i = camel_multipart_get_number (multipart); i > 1; i--) { - part = camel_multipart_get_part (multipart, i - 1); - camel_medium_remove_header (CAMEL_MEDIUM (part), "Content-Disposition"); - } - } - - current = CAMEL_DATA_WRAPPER (multipart); + g_object_unref (context->top_level_part); + context->top_level_part = CAMEL_DATA_WRAPPER (multipart); } - action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); - pgp_sign = gtk_toggle_action_get_active (action); - - action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); - pgp_encrypt = gtk_toggle_action_get_active (action); - -#if defined (HAVE_NSS) - action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); - smime_sign = gtk_toggle_action_get_active (action); - - action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); - smime_encrypt = gtk_toggle_action_get_active (action); -#else - smime_sign = FALSE; - smime_encrypt = FALSE; -#endif - - /* Setup working recipient list if we're encrypting */ - if (pgp_encrypt || smime_encrypt) { - gint j; - const gchar *types[] = { - CAMEL_RECIPIENT_TYPE_TO, - CAMEL_RECIPIENT_TYPE_CC, - CAMEL_RECIPIENT_TYPE_BCC - }; - - recipients = g_ptr_array_new (); - for (i = 0; i < G_N_ELEMENTS (types); i++) { - CamelInternetAddress *addr; - const gchar *address; - - addr = camel_mime_message_get_recipients (new, types[i]); - for (j=0;camel_internet_address_get (addr, j, NULL, &address); j++) - g_ptr_array_add (recipients, g_strdup (address)); - - } - } - - if (pgp_sign || pgp_encrypt) { - const gchar *pgp_userid; - CamelInternetAddress *from = NULL; - CamelCipherContext *cipher; - - part = camel_mime_part_new (); - camel_medium_set_content (CAMEL_MEDIUM (part), current); - if (current == plain) - camel_mime_part_set_encoding (part, plain_encoding); - g_object_unref (current); - - if (account && account->pgp_key && *account->pgp_key) { - pgp_userid = account->pgp_key; - } else { - from = e_msg_composer_get_from (composer); - camel_internet_address_get (from, 0, NULL, &pgp_userid); - } - - if (pgp_sign) { - CamelMimePart *npart = camel_mime_part_new (); - - cipher = camel_gpg_context_new (session); - if (account != NULL) - camel_gpg_context_set_always_trust ( - CAMEL_GPG_CONTEXT (cipher), - account->pgp_always_trust); - - camel_cipher_context_sign_sync ( - cipher, pgp_userid, - account_hash_algo_to_camel_hash ( - account ? e_account_get_string (account, E_ACCOUNT_PGP_HASH_ALGORITHM) : NULL), - part, npart, cancellable, &local_error); - - g_object_unref (cipher); - - if (local_error != NULL) { - g_object_unref (npart); - goto exception; - } - - g_object_unref (part); - part = npart; - } - - if (pgp_encrypt) { - CamelMimePart *npart = camel_mime_part_new (); - - /* Check to see if we should encrypt to self. - * NB gets removed immediately after use */ - if (account && account->pgp_encrypt_to_self && pgp_userid) - g_ptr_array_add (recipients, g_strdup (pgp_userid)); - - cipher = camel_gpg_context_new (session); - if (account != NULL) - camel_gpg_context_set_always_trust ( - CAMEL_GPG_CONTEXT (cipher), - account->pgp_always_trust); - - camel_cipher_context_encrypt_sync ( - cipher, pgp_userid, recipients, - part, npart, cancellable, &local_error); - - g_object_unref (cipher); - - if (account && account->pgp_encrypt_to_self && pgp_userid) - g_ptr_array_set_size (recipients, recipients->len - 1); - - if (local_error != NULL) { - g_object_unref (npart); - goto exception; - } - - g_object_unref (part); - part = npart; - } - - if (from) - g_object_unref (from); - - current = camel_medium_get_content (CAMEL_MEDIUM (part)); - g_object_ref (current); - g_object_unref (part); - } - -#if defined (HAVE_NSS) - if (smime_sign || smime_encrypt) { - CamelInternetAddress *from = NULL; - CamelCipherContext *cipher; - - part = camel_mime_part_new (); - camel_medium_set_content ((CamelMedium *)part, current); - if (current == plain) - camel_mime_part_set_encoding (part, plain_encoding); - g_object_unref (current); - - if (smime_sign && (account == NULL || - account->smime_sign_key == NULL || - account->smime_sign_key[0] == 0)) { - g_set_error ( - &local_error, - CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("Cannot sign outgoing message: " - "No signing certificate set for " - "this account")); - goto exception; - } - - if (smime_encrypt && (account == NULL || - account->smime_sign_key == NULL || - account->smime_sign_key[0] == 0)) { - g_set_error ( - &local_error, - CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("Cannot encrypt outgoing message: " - "No encryption certificate set for " - "this account")); - goto exception; - } - - if (smime_sign) { - CamelMimePart *npart = camel_mime_part_new (); - - cipher = camel_smime_context_new (session); - - /* if we're also encrypting, envelope-sign rather than clear-sign */ - if (smime_encrypt) { - camel_smime_context_set_sign_mode ( - (CamelSMIMEContext *) cipher, - CAMEL_SMIME_SIGN_ENVELOPED); - camel_smime_context_set_encrypt_key ( - (CamelSMIMEContext *) cipher, - TRUE, account->smime_encrypt_key); - } else if (account && - account->smime_encrypt_key && - *account->smime_encrypt_key) { - camel_smime_context_set_encrypt_key ( - (CamelSMIMEContext *) cipher, - TRUE, account->smime_encrypt_key); - } - - camel_cipher_context_sign_sync ( - cipher, account->smime_sign_key, - account_hash_algo_to_camel_hash ( - (account != NULL) ? - e_account_get_string (account, - E_ACCOUNT_SMIME_HASH_ALGORITHM) : NULL), - part, npart, cancellable, &local_error); - - g_object_unref (cipher); - - if (local_error != NULL) { - g_object_unref (npart); - goto exception; - } - - g_object_unref (part); - part = npart; - } - - if (smime_encrypt) { - - /* check to see if we should encrypt to self, NB removed after use */ - if (account->smime_encrypt_to_self) - g_ptr_array_add ( - recipients, g_strdup ( - account->smime_encrypt_key)); - - cipher = camel_smime_context_new (session); - camel_smime_context_set_encrypt_key ( - (CamelSMIMEContext *) cipher, TRUE, - account->smime_encrypt_key); - - camel_cipher_context_encrypt_sync ( - cipher, NULL, recipients, part, - (CamelMimePart *) new, cancellable, - &local_error); - - g_object_unref (cipher); - - if (local_error != NULL) - goto exception; - - if (account->smime_encrypt_to_self) - g_ptr_array_set_size (recipients, recipients->len - 1); - } - - if (from) - g_object_unref (from); - - /* we replaced the message directly, we don't want to do reparenting foo */ - if (smime_encrypt) { - g_object_unref (part); - goto skip_content; - } else { - current = camel_medium_get_content ((CamelMedium *)part); - g_object_ref (current); - g_object_unref (part); - } - } -#endif /* HAVE_NSS */ - - camel_medium_set_content (CAMEL_MEDIUM (new), current); - if (current == plain) - camel_mime_part_set_encoding (CAMEL_MIME_PART (new), plain_encoding); - g_object_unref (current); + /* Run any blocking operations in a separate thread. */ + if (context->need_thread) + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + composer_build_message_thread, + io_priority, cancellable); + else + g_simple_async_result_complete (simple); -#if defined (HAVE_NSS) -skip_content: -#endif - if (recipients) { - for (i=0; i<recipients->len; i++) - g_free (recipients->pdata[i]); - g_ptr_array_free (recipients, TRUE); - } + g_object_unref (simple); +} - /* Attach whether this message was written in HTML */ - camel_medium_set_header ( - CAMEL_MEDIUM (new), "X-Evolution-Format", - html_content ? "text/html" : "text/plain"); +static CamelMimeMessage * +composer_build_message_finish (EMsgComposer *composer, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; - return new; + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (composer), composer_build_message), NULL); -exception: + simple = G_SIMPLE_ASYNC_RESULT (result); + context = g_simple_async_result_get_op_res_gpointer (simple); - if (part != CAMEL_MIME_PART (new)) - g_object_unref (part); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; - g_object_unref (new); + /* Finalize some details before returning. */ - if (recipients) { - for (i=0; i<recipients->len; i++) - g_free (recipients->pdata[i]); - g_ptr_array_free (recipients, TRUE); - } + if (!context->skip_content) + camel_medium_set_content ( + CAMEL_MEDIUM (context->message), + context->top_level_part); - g_propagate_error (error, local_error); + if (context->top_level_part == context->text_plain_part) + camel_mime_part_set_encoding ( + CAMEL_MIME_PART (context->message), + context->plain_encoding); - return NULL; + return g_object_ref (context->message); } /* Signatures */ @@ -1678,6 +1858,11 @@ msg_composer_delete_event_cb (EMsgComposer *composer) shell = e_msg_composer_get_shell (composer); + /* If the "async" action group is insensitive, it means an + * asynchronous operation is in progress. Block the event. */ + if (!gtk_action_group_get_sensitive (composer->priv->async_actions)) + return TRUE; + if (g_list_length (e_shell_get_watched_windows (shell)) == 1) { /* This is the last watched window, use the quit * mechanism to have a draft saved properly */ @@ -2219,6 +2404,56 @@ msg_composer_object_deleted (GtkhtmlEditor *editor) gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); } +static gboolean +msg_composer_presend (EMsgComposer *composer) +{ + /* This keeps the signal accumulator at TRUE. */ + return TRUE; +} + +static void +msg_composer_submit_alert (EAlertSink *alert_sink, + EAlert *alert) +{ + EMsgComposerPrivate *priv; + EAlertBar *alert_bar; + GtkWidget *dialog; + GtkWindow *parent; + + priv = E_MSG_COMPOSER_GET_PRIVATE (alert_sink); + + switch (e_alert_get_message_type (alert)) { + case GTK_MESSAGE_INFO: + case GTK_MESSAGE_WARNING: + case GTK_MESSAGE_ERROR: + alert_bar = E_ALERT_BAR (priv->alert_bar); + e_alert_bar_add_alert (alert_bar, alert); + break; + + default: + parent = GTK_WINDOW (alert_sink); + dialog = e_alert_dialog_new (parent, alert); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + break; + } +} + +static gboolean +msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean v_boolean; + + v_boolean = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, v_boolean); + + /* FALSE means abort the signal emission. */ + return v_boolean; +} + static void e_msg_composer_class_init (EMsgComposerClass *class) { @@ -2250,6 +2485,8 @@ e_msg_composer_class_init (EMsgComposerClass *class) editor_class->link_clicked = msg_composer_link_clicked; editor_class->object_deleted = msg_composer_object_deleted; + class->presend = msg_composer_presend; + g_object_class_install_property ( object_class, PROP_FOCUS_TRACKER, @@ -2271,30 +2508,54 @@ e_msg_composer_class_init (EMsgComposerClass *class) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + signals[PRESEND] = g_signal_new ( + "presend", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMsgComposerClass, presend), + msg_composer_accumulator_false_abort, + NULL, + e_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + signals[SEND] = g_signal_new ( "send", G_OBJECT_CLASS_TYPE (class), G_SIGNAL_RUN_LAST, - 0, NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + G_STRUCT_OFFSET (EMsgComposerClass, send), + NULL, NULL, + e_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + CAMEL_TYPE_MIME_MESSAGE, + E_TYPE_ACTIVITY); signals[SAVE_DRAFT] = g_signal_new ( "save-draft", G_OBJECT_CLASS_TYPE (class), G_SIGNAL_RUN_LAST, - 0, NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + G_STRUCT_OFFSET (EMsgComposerClass, save_draft), + NULL, NULL, + e_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + CAMEL_TYPE_MIME_MESSAGE, + E_TYPE_ACTIVITY); signals[PRINT] = g_signal_new ( "print", G_OBJECT_CLASS_TYPE (class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__ENUM, - G_TYPE_NONE, 1, - GTK_TYPE_PRINT_OPERATION_ACTION); + e_marshal_VOID__ENUM_OBJECT_OBJECT, + G_TYPE_NONE, 3, + GTK_TYPE_PRINT_OPERATION_ACTION, + CAMEL_TYPE_MIME_MESSAGE, + E_TYPE_ACTIVITY); +} + +static void +e_msg_composer_alert_sink_init (EAlertSinkInterface *interface) +{ + interface->submit_alert = msg_composer_submit_alert; } static void @@ -3214,6 +3475,51 @@ e_msg_composer_get_shell (EMsgComposer *composer) return E_SHELL (composer->priv->shell); } +static void +msg_composer_send_cb (EMsgComposer *composer, + GAsyncResult *result, + AsyncContext *context) +{ + CamelMimeMessage *message; + GtkhtmlEditor *editor; + GError *error = NULL; + + message = e_msg_composer_get_message_finish (composer, result, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (message == NULL); + async_context_free (context); + g_error_free (error); + return; + } + + if (error != NULL) { + g_warn_if_fail (message == NULL); + async_context_free (context); + e_alert_submit ( + GTK_WIDGET (composer), + "mail-composer:no-build-message", + error->message, NULL); + g_error_free (error); + return; + } + + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + g_signal_emit ( + composer, signals[SEND], 0, + message, context->activity); + + g_object_unref (message); + + async_context_free (context); + + /* XXX This should be elsewhere. */ + editor = GTKHTML_EDITOR (composer); + gtkhtml_editor_set_changed (editor, FALSE); +} + /** * e_msg_composer_send: * @composer: an #EMsgComposer @@ -3223,15 +3529,84 @@ e_msg_composer_get_shell (EMsgComposer *composer) void e_msg_composer_send (EMsgComposer *composer) { - GtkhtmlEditor *editor; + AsyncContext *context; + GtkAction *action; + EActivityBar *activity_bar; + GCancellable *cancellable; + gboolean proceed_with_send = TRUE; + const gchar *icon_name; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - editor = GTKHTML_EDITOR (composer); + /* This gives the user a chance to abort the send. */ + g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send); + + if (!proceed_with_send) + return; + + context = g_slice_new0 (AsyncContext); + context->activity = e_composer_activity_new (composer); + + cancellable = camel_operation_new (); + e_activity_set_cancellable (context->activity, cancellable); + g_object_unref (cancellable); + + action = ACTION (SEND); + icon_name = gtk_action_get_icon_name (action); + e_activity_set_icon_name (context->activity, icon_name); + + activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); + e_activity_bar_set_activity (activity_bar, context->activity); + + e_msg_composer_get_message ( + composer, G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) msg_composer_send_cb, + context); +} + +static void +msg_composer_save_draft_cb (EMsgComposer *composer, + GAsyncResult *result, + AsyncContext *context) +{ + CamelMimeMessage *message; + GtkhtmlEditor *editor; + GError *error = NULL; + + message = e_msg_composer_get_message_draft_finish ( + composer, result, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (message == NULL); + async_context_free (context); + g_error_free (error); + return; + } + + if (error != NULL) { + g_warn_if_fail (message == NULL); + async_context_free (context); + e_alert_submit ( + GTK_WIDGET (composer), + "mail-composer:no-build-message", + error->message, NULL); + g_error_free (error); + return; + } + + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + g_signal_emit ( + composer, signals[SAVE_DRAFT], 0, + message, context->activity); + + g_object_unref (message); - g_signal_emit (composer, signals[SEND], 0); + async_context_free (context); /* XXX This should be elsewhere. */ + editor = GTKHTML_EDITOR (composer); gtkhtml_editor_set_changed (editor, FALSE); } @@ -3244,32 +3619,113 @@ e_msg_composer_send (EMsgComposer *composer) void e_msg_composer_save_draft (EMsgComposer *composer) { - GtkhtmlEditor *editor; + AsyncContext *context; + GtkAction *action; + EActivityBar *activity_bar; + GCancellable *cancellable; + const gchar *icon_name; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - editor = GTKHTML_EDITOR (composer); + context = g_slice_new0 (AsyncContext); + context->activity = e_composer_activity_new (composer); - g_signal_emit (composer, signals[SAVE_DRAFT], 0); + cancellable = camel_operation_new (); + e_activity_set_cancellable (context->activity, cancellable); + g_object_unref (cancellable); - /* XXX This should be elsewhere. */ - gtkhtml_editor_set_changed (editor, FALSE); + action = ACTION (SAVE_DRAFT); + icon_name = gtk_action_get_icon_name (action); + e_activity_set_icon_name (context->activity, icon_name); + + activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); + e_activity_bar_set_activity (activity_bar, context->activity); + + e_msg_composer_get_message_draft ( + composer, G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) msg_composer_save_draft_cb, + context); +} + +static void +msg_composer_print_cb (EMsgComposer *composer, + GAsyncResult *result, + AsyncContext *context) +{ + CamelMimeMessage *message; + GError *error = NULL; + + message = e_msg_composer_get_message_print_finish ( + composer, result, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (message == NULL); + async_context_free (context); + g_error_free (error); + return; + } + + if (error != NULL) { + g_warn_if_fail (message == NULL); + async_context_free (context); + e_alert_submit ( + GTK_WIDGET (composer), + "mail-composer:no-build-message", + error->message, NULL); + g_error_free (error); + return; + } + + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + g_signal_emit ( + composer, signals[PRINT], 0, + context->print_action, message, context->activity); + + g_object_unref (message); + + async_context_free (context); } /** * e_msg_composer_print: * @composer: an #EMsgComposer - * @action: the print action to start + * @print_action: the print action to start * * Print the message in @composer. **/ void e_msg_composer_print (EMsgComposer *composer, - GtkPrintOperationAction action) + GtkPrintOperationAction print_action) { + AsyncContext *context; + GtkAction *action; + EActivityBar *activity_bar; + GCancellable *cancellable; + const gchar *icon_name; + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - g_signal_emit (composer, signals[PRINT], 0, action); + context = g_slice_new0 (AsyncContext); + context->activity = e_composer_activity_new (composer); + context->print_action = print_action; + + cancellable = camel_operation_new (); + e_activity_set_cancellable (context->activity, cancellable); + g_object_unref (cancellable); + + action = ACTION (PRINT); + icon_name = gtk_action_get_icon_name (action); + e_activity_set_icon_name (context->activity, icon_name); + + activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); + e_activity_bar_set_activity (activity_bar, context->activity); + + e_msg_composer_get_message_print ( + composer, G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) msg_composer_print_cb, + context); } static GList * @@ -3542,81 +3998,164 @@ e_msg_composer_set_body (EMsgComposer *composer, /** * e_msg_composer_add_header: - * @composer: a composer object - * @name: the header name - * @value: the header value + * @composer: an #EMsgComposer + * @name: the header's name + * @value: the header's value * - * Adds a header with @name and @value to the message. This header - * may not be displayed by the composer, but will be included in - * the message it outputs. + * Adds a new custom header created from @name and @value. The header + * is not shown in the user interface but will be added to the resulting + * MIME message when sending or saving. **/ void e_msg_composer_add_header (EMsgComposer *composer, const gchar *name, const gchar *value) { - EMsgComposerPrivate *p = composer->priv; + EMsgComposerPrivate *priv; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (name != NULL); g_return_if_fail (value != NULL); - g_ptr_array_add (p->extra_hdr_names, g_strdup (name)); - g_ptr_array_add (p->extra_hdr_values, g_strdup (value)); + priv = composer->priv; + + g_ptr_array_add (priv->extra_hdr_names, g_strdup (name)); + g_ptr_array_add (priv->extra_hdr_values, g_strdup (value)); } /** - * e_msg_composer_modify_header : - * @composer : a composer object - * @name: the header name - * @change_value: the header value to put in place of the previous - * value + * e_msg_composer_set_header: + * @composer: an #EMsgComposer + * @name: the header's name + * @value: the header's value * - * Searches for a header with name=@name ,if found it removes - * that header and adds a new header with the @name and @change_value . - * If not found then it creates a new header with @name and @change_value . + * Replaces all custom headers matching @name that were added with + * e_msg_composer_add_header() or e_msg_composer_set_header(), with + * a new custom header created from @name and @value. The header is + * not shown in the user interface but will be added to the resulting + * MIME message when sending or saving. **/ void -e_msg_composer_modify_header (EMsgComposer *composer, - const gchar *name, - const gchar *change_value) +e_msg_composer_set_header (EMsgComposer *composer, + const gchar *name, + const gchar *value) { g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (name != NULL); - g_return_if_fail (change_value != NULL); + g_return_if_fail (value != NULL); e_msg_composer_remove_header (composer, name); - e_msg_composer_add_header (composer, name, change_value); + e_msg_composer_add_header (composer, name, value); } /** - * e_msg_composer_modify_header : - * @composer : a composer object - * @name: the header name + * e_msg_composer_remove_header: + * @composer: an #EMsgComposer + * @name: the header's name * - * Searches for the header and if found it removes it . + * Removes all custom headers matching @name that were added with + * e_msg_composer_add_header() or e_msg_composer_set_header(). **/ void e_msg_composer_remove_header (EMsgComposer *composer, const gchar *name) { - EMsgComposerPrivate *p; - gint i; + EMsgComposerPrivate *priv; + guint ii; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (name != NULL); - p = composer->priv; + priv = composer->priv; - for (i = 0; i < p->extra_hdr_names->len; i++) { - if (strcmp (p->extra_hdr_names->pdata[i], name) == 0) { - g_ptr_array_remove_index (p->extra_hdr_names, i); - g_ptr_array_remove_index (p->extra_hdr_values, i); + for (ii = 0; ii < priv->extra_hdr_names->len; ii++) { + if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) { + g_free (priv->extra_hdr_names->pdata[ii]); + g_free (priv->extra_hdr_values->pdata[ii]); + g_ptr_array_remove_index (priv->extra_hdr_names, ii); + g_ptr_array_remove_index (priv->extra_hdr_values, ii); } } } /** + * e_msg_composer_set_draft_headers: + * @composer: an #EMsgComposer + * @folder_uri: folder URI of the last saved draft + * @message_uid: message UID of the last saved draft + * + * Add special X-Evolution-Draft headers to remember the most recently + * saved draft message, even across Evolution sessions. These headers + * can be used to mark the draft message for deletion after saving a + * newer draft or sending the composed message. + **/ +void +e_msg_composer_set_draft_headers (EMsgComposer *composer, + const gchar *folder_uri, + const gchar *message_uid) +{ + const gchar *header_name; + + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + g_return_if_fail (folder_uri != NULL); + g_return_if_fail (message_uid != NULL); + + header_name = "X-Evolution-Draft-Folder"; + e_msg_composer_set_header (composer, header_name, folder_uri); + + header_name = "X-Evolution-Draft-Message"; + e_msg_composer_set_header (composer, header_name, message_uid); +} + +/** + * e_msg_composer_set_source_headers: + * @composer: an #EMsgComposer + * @folder_uri: folder URI of the source message + * @message_uid: message UID of the source message + * @flags: flags to set on the source message after sending + * + * Add special X-Evolution-Source headers to remember the message being + * forwarded or replied to, even across Evolution sessions. These headers + * can be used to set appropriate flags on the source message after sending + * the composed message. + **/ +void +e_msg_composer_set_source_headers (EMsgComposer *composer, + const gchar *folder_uri, + const gchar *message_uid, + CamelMessageFlags flags) +{ + GString *buffer; + const gchar *header_name; + + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + g_return_if_fail (folder_uri != NULL); + g_return_if_fail (message_uid != NULL); + + buffer = g_string_sized_new (32); + + if (flags & CAMEL_MESSAGE_ANSWERED) + g_string_append (buffer, "ANSWERED "); + if (flags & CAMEL_MESSAGE_ANSWERED_ALL) + g_string_append (buffer, "ANSWERED_ALL "); + if (flags & CAMEL_MESSAGE_FORWARDED) + g_string_append (buffer, "FORWARDED "); + if (flags & CAMEL_MESSAGE_SEEN) + g_string_append (buffer, "SEEN "); + + header_name = "X-Evolution-Source-Folder"; + e_msg_composer_set_header (composer, header_name, folder_uri); + + header_name = "X-Evolution-Source-Message"; + e_msg_composer_set_header (composer, header_name, message_uid); + + header_name = "X-Evolution-Source-Flags"; + e_msg_composer_set_header (composer, header_name, buffer->str); + + g_string_free (buffer, TRUE); +} + +/** * e_msg_composer_attach: * @composer: a composer object * @mime_part: the #CamelMimePart to attach @@ -3745,207 +4284,211 @@ e_msg_composer_add_inline_image_from_mime_part (EMsgComposer *composer, g_strdup (location), part); } +static void +composer_get_message_ready (EMsgComposer *composer, + GAsyncResult *result, + GSimpleAsyncResult *simple) +{ + CamelMimeMessage *message; + GError *error = NULL; + + message = composer_build_message_finish (composer, result, &error); + + if (message != NULL) + g_simple_async_result_set_op_res_gpointer ( + simple, message, (GDestroyNotify) g_object_unref); + + if (error != NULL) { + g_warn_if_fail (message == NULL); + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + g_simple_async_result_complete (simple); + + g_object_unref (simple); +} + /** * e_msg_composer_get_message: - * @composer: A message composer widget + * @composer: an #EMsgComposer * - * Retrieve the message edited by the user as a CamelMimeMessage. The - * CamelMimeMessage object is created on the fly; subsequent calls to this + * Retrieve the message edited by the user as a #CamelMimeMessage. The + * #CamelMimeMessage object is created on the fly; subsequent calls to this * function will always create new objects from scratch. - * - * Returns: A pointer to the new CamelMimeMessage object **/ -CamelMimeMessage * +void e_msg_composer_get_message (EMsgComposer *composer, - gboolean save_html_object_data, + gint io_priority, GCancellable *cancellable, - GError **error) + GAsyncReadyCallback callback, + gpointer user_data) { - EAttachmentView *view; - EAttachmentStore *store; - GtkhtmlEditor *editor; - gboolean html_content; + GSimpleAsyncResult *simple; + GtkAction *action; + ComposerFlags flags = 0; - g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - view = e_msg_composer_get_attachment_view (composer); - store = e_attachment_view_get_store (view); + simple = g_simple_async_result_new ( + G_OBJECT (composer), callback, + user_data, e_msg_composer_get_message); - if (e_attachment_store_get_num_loading (store) > 0) { - if (!emcu_prompt_user (GTK_WINDOW (composer), NULL, - "mail-composer:ask-send-message-pending-download", NULL)) { - return NULL; - } - } + if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) + flags |= COMPOSER_FLAG_HTML_CONTENT; - editor = GTKHTML_EDITOR (composer); - html_content = gtkhtml_editor_get_html_mode (editor); + action = ACTION (PRIORITIZE_MESSAGE); + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE; - return build_message ( - composer, html_content, - save_html_object_data, cancellable, error); -} + action = ACTION (REQUEST_READ_RECEIPT); + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT; -static gchar * -msg_composer_get_message_print_helper (EMsgComposer *composer, - gboolean html_content) -{ - GtkToggleAction *action; - GString *string; + action = ACTION (PGP_SIGN); + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + flags |= COMPOSER_FLAG_PGP_SIGN; - string = g_string_sized_new (128); + action = ACTION (PGP_ENCRYPT); + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + flags |= COMPOSER_FLAG_PGP_ENCRYPT; - if (html_content) - g_string_append (string, "text/html"); - else - g_string_append (string, "text/plain"); - - action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); - if (gtk_toggle_action_get_active (action)) - g_string_append (string, ", pgp-sign"); - gtk_toggle_action_set_active (action, FALSE); - - action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); - if (gtk_toggle_action_get_active (action)) - g_string_append (string, ", pgp-encrypt"); - gtk_toggle_action_set_active (action, FALSE); +#ifdef HAVE_NSS + action = ACTION (SMIME_SIGN); + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + flags |= COMPOSER_FLAG_SMIME_SIGN; - action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); - if (gtk_toggle_action_get_active (action)) - g_string_append (string, ", smime-sign"); - gtk_toggle_action_set_active (action, FALSE); - - action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); - if (gtk_toggle_action_get_active (action)) - g_string_append (string, ", smime-encrypt"); - gtk_toggle_action_set_active (action, FALSE); + action = ACTION (SMIME_ENCRYPT); + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + flags |= COMPOSER_FLAG_SMIME_ENCRYPT; +#endif - return g_string_free (string, FALSE); + composer_build_message ( + composer, flags, io_priority, + cancellable, (GAsyncReadyCallback) + composer_get_message_ready, simple); } CamelMimeMessage * -e_msg_composer_get_message_print (EMsgComposer *composer, - gboolean save_html_object_data, - GCancellable *cancellable) +e_msg_composer_get_message_finish (EMsgComposer *composer, + GAsyncResult *result, + GError **error) { - EShell *shell; - GtkhtmlEditor *editor; - EMsgComposer *temp_composer; - CamelMimeMessage *msg; - gboolean html_content; - gchar *flags; + GSimpleAsyncResult *simple; + CamelMimeMessage *message; - g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (composer), + e_msg_composer_get_message), NULL); - editor = GTKHTML_EDITOR (composer); - html_content = gtkhtml_editor_get_html_mode (editor); + simple = G_SIMPLE_ASYNC_RESULT (result); + message = g_simple_async_result_get_op_res_gpointer (simple); - msg = build_message ( - composer, html_content, save_html_object_data, - cancellable, NULL); - if (msg == NULL) + if (g_simple_async_result_propagate_error (simple, error)) return NULL; - shell = e_msg_composer_get_shell (composer); - temp_composer = e_msg_composer_new_with_message ( - shell, msg, cancellable); - g_object_unref (msg); - - /* Override composer flags. */ - flags = msg_composer_get_message_print_helper ( - temp_composer, html_content); - - msg = build_message ( - temp_composer, TRUE, save_html_object_data, - cancellable, NULL); - if (msg != NULL) - camel_medium_set_header ( - CAMEL_MEDIUM (msg), "X-Evolution-Format", flags); - - gtk_widget_destroy (GTK_WIDGET (temp_composer)); - g_free (flags); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - return msg; + return g_object_ref (message); } -CamelMimeMessage * -e_msg_composer_get_message_draft (EMsgComposer *composer, +void +e_msg_composer_get_message_print (EMsgComposer *composer, + gint io_priority, GCancellable *cancellable, - GError **error) + GAsyncReadyCallback callback, + gpointer user_data) { - GtkhtmlEditor *editor; - EComposerHeaderTable *table; - GtkToggleAction *action; - CamelMimeMessage *msg; - EAccount *account; - gboolean html_content; - gboolean pgp_encrypt; - gboolean pgp_sign; - gboolean smime_encrypt; - gboolean smime_sign; - GString *flags; + GSimpleAsyncResult *simple; + ComposerFlags flags = 0; - editor = GTKHTML_EDITOR (composer); - table = e_msg_composer_get_header_table (composer); - html_content = gtkhtml_editor_get_html_mode (editor); + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); - pgp_sign = gtk_toggle_action_get_active (action); - gtk_toggle_action_set_active (action, FALSE); + simple = g_simple_async_result_new ( + G_OBJECT (composer), callback, + user_data, e_msg_composer_get_message_print); - action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); - pgp_encrypt = gtk_toggle_action_get_active (action); - gtk_toggle_action_set_active (action, FALSE); + flags |= COMPOSER_FLAG_HTML_CONTENT; + flags |= COMPOSER_FLAG_SAVE_OBJECT_DATA; - action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); - smime_sign = gtk_toggle_action_get_active (action); - gtk_toggle_action_set_active (action, FALSE); + composer_build_message ( + composer, flags, io_priority, + cancellable, (GAsyncReadyCallback) + composer_get_message_ready, simple); +} - action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); - smime_encrypt = gtk_toggle_action_get_active (action); - gtk_toggle_action_set_active (action, FALSE); +CamelMimeMessage * +e_msg_composer_get_message_print_finish (EMsgComposer *composer, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + CamelMimeMessage *message; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (composer), + e_msg_composer_get_message_print), NULL); - msg = build_message (composer, TRUE, TRUE, cancellable, error); - if (msg == NULL) + simple = G_SIMPLE_ASYNC_RESULT (result); + message = g_simple_async_result_get_op_res_gpointer (simple); + + if (g_simple_async_result_propagate_error (simple, error)) return NULL; - action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); - gtk_toggle_action_set_active (action, pgp_sign); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); - gtk_toggle_action_set_active (action, pgp_encrypt); + return g_object_ref (message); +} - action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); - gtk_toggle_action_set_active (action, smime_sign); +void +e_msg_composer_get_message_draft (EMsgComposer *composer, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + ComposerFlags flags = 0; - action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); - gtk_toggle_action_set_active (action, smime_encrypt); + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - /* Attach account info to the draft. */ - account = e_composer_header_table_get_account (table); - if (account && account->name) - camel_medium_set_header ( - CAMEL_MEDIUM (msg), - "X-Evolution-Account", account->uid); + simple = g_simple_async_result_new ( + G_OBJECT (composer), callback, + user_data, e_msg_composer_get_message_draft); + + if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) + flags |= COMPOSER_FLAG_HTML_CONTENT; - flags = g_string_new (html_content ? "text/html" : "text/plain"); + composer_build_message ( + composer, flags, io_priority, + cancellable, (GAsyncReadyCallback) + composer_get_message_ready, simple); +} - /* This should probably only save the setting if it is - * different from the from-account default? */ - if (pgp_sign) - g_string_append (flags, ", pgp-sign"); - if (pgp_encrypt) - g_string_append (flags, ", pgp-encrypt"); - if (smime_sign) - g_string_append (flags, ", smime-sign"); - if (smime_encrypt) - g_string_append (flags, ", smime-encrypt"); +CamelMimeMessage * +e_msg_composer_get_message_draft_finish (EMsgComposer *composer, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + CamelMimeMessage *message; - camel_medium_set_header ( - CAMEL_MEDIUM (msg), "X-Evolution-Format", flags->str); - g_string_free (flags, TRUE); + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (composer), + e_msg_composer_get_message_draft), NULL); + + simple = G_SIMPLE_ASYNC_RESULT (result); + message = g_simple_async_result_get_op_res_gpointer (simple); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - return msg; + return g_object_ref (message); } /** @@ -4153,50 +4696,6 @@ e_msg_composer_can_close (EMsgComposer *composer, return res; } -EMsgComposer * -e_msg_composer_load_from_file (EShell *shell, - const gchar *filename, - GCancellable *cancellable) -{ - CamelStream *stream; - CamelMimeMessage *message; - EMsgComposer *composer; - - g_return_val_if_fail (E_IS_SHELL (shell), NULL); - g_return_val_if_fail (filename != NULL, NULL); - - stream = camel_stream_fs_new_with_name ( - filename, O_RDONLY, 0, NULL); - if (stream == NULL) - return NULL; - - message = camel_mime_message_new (); - camel_data_wrapper_construct_from_stream_sync ( - CAMEL_DATA_WRAPPER (message), stream, NULL, NULL); - g_object_unref (stream); - - composer = e_msg_composer_new_with_message ( - shell, message, cancellable); - if (composer != NULL) - gtk_widget_show (GTK_WIDGET (composer)); - - return composer; -} - -void -e_msg_composer_set_alternative (EMsgComposer *composer, - gboolean alt) -{ - GtkhtmlEditor *editor; - - g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - - editor = GTKHTML_EDITOR (composer); - - composer->priv->is_alternative = alt; - gtkhtml_editor_set_html_mode (editor, !alt); -} - void e_msg_composer_reply_indent (EMsgComposer *composer) { @@ -4330,20 +4829,3 @@ e_save_spell_languages (GList *spell_languages) g_error_free (error); } } - -void -e_msg_composer_set_mail_sent (EMsgComposer *composer, - gboolean mail_sent) -{ - g_return_if_fail (composer != NULL); - - composer->priv->mail_sent = mail_sent; -} - -gboolean -e_msg_composer_get_mail_sent (EMsgComposer *composer) -{ - g_return_val_if_fail (composer != NULL, FALSE); - - return composer->priv->mail_sent; -} diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h index 866774ed9c..5cf781022e 100644 --- a/composer/e-msg-composer.h +++ b/composer/e-msg-composer.h @@ -66,6 +66,19 @@ struct _EMsgComposer { struct _EMsgComposerClass { GtkhtmlEditorClass parent_class; + + /* Signals */ + gboolean (*presend) (EMsgComposer *composer); + void (*print) (EMsgComposer *composer, + GtkPrintOperationAction print_action, + CamelMimeMessage *message, + EActivity *activity); + void (*save_draft) (EMsgComposer *composer, + CamelMimeMessage *message, + EActivity *activity); + void (*send) (EMsgComposer *composer, + CamelMimeMessage *message, + EActivity *activity); }; GType e_msg_composer_get_type (void); @@ -87,10 +100,7 @@ EShell * e_msg_composer_get_shell (EMsgComposer *composer); void e_msg_composer_send (EMsgComposer *composer); void e_msg_composer_save_draft (EMsgComposer *composer); void e_msg_composer_print (EMsgComposer *composer, - GtkPrintOperationAction action); - -void e_msg_composer_set_alternative (EMsgComposer *composer, - gboolean alt); + GtkPrintOperationAction print_action); void e_msg_composer_set_body_text (EMsgComposer *composer, const gchar *text, @@ -101,11 +111,20 @@ void e_msg_composer_set_body (EMsgComposer *composer, void e_msg_composer_add_header (EMsgComposer *composer, const gchar *name, const gchar *value); -void e_msg_composer_modify_header (EMsgComposer *composer, +void e_msg_composer_set_header (EMsgComposer *composer, const gchar *name, const gchar *value); void e_msg_composer_remove_header (EMsgComposer *composer, const gchar *name); +void e_msg_composer_set_draft_headers + (EMsgComposer *composer, + const gchar *folder_uri, + const gchar *message_uid); +void e_msg_composer_set_source_headers + (EMsgComposer *composer, + const gchar *folder_uri, + const gchar *message_uid, + CamelMessageFlags flags); void e_msg_composer_attach (EMsgComposer *composer, CamelMimePart *mime_part); CamelMimePart * e_msg_composer_add_inline_image_from_file @@ -114,20 +133,37 @@ CamelMimePart * e_msg_composer_add_inline_image_from_file void e_msg_composer_add_inline_image_from_mime_part (EMsgComposer *composer, CamelMimePart *part); -CamelMimeMessage * - e_msg_composer_get_message (EMsgComposer *composer, - gboolean save_html_object_data, +void e_msg_composer_get_message (EMsgComposer *composer, + gint io_priority, GCancellable *cancellable, - GError **error); + GAsyncReadyCallback callback, + gpointer user_data); CamelMimeMessage * - e_msg_composer_get_message_print + e_msg_composer_get_message_finish (EMsgComposer *composer, - gboolean save_html_object_data, - GCancellable *cancellable); + GAsyncResult *result, + GError **error); +void e_msg_composer_get_message_print + (EMsgComposer *composer, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); CamelMimeMessage * - e_msg_composer_get_message_draft + e_msg_composer_get_message_print_finish + (EMsgComposer *composer, + GAsyncResult *result, + GError **error); +void e_msg_composer_get_message_draft (EMsgComposer *composer, + gint io_priority, GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +CamelMimeMessage * + e_msg_composer_get_message_draft_finish + (EMsgComposer *composer, + GAsyncResult *result, GError **error); void e_msg_composer_show_sig_file (EMsgComposer *composer); @@ -147,10 +183,6 @@ void e_msg_composer_request_close (EMsgComposer *composer); gboolean e_msg_composer_can_close (EMsgComposer *composer, gboolean can_save_draft); -EMsgComposer * e_msg_composer_load_from_file (EShell *shell, - const gchar *filename, - GCancellable *cancellable); - void e_msg_composer_reply_indent (EMsgComposer *composer); EComposerHeaderTable * @@ -166,10 +198,6 @@ gboolean e_msg_composer_is_exiting (EMsgComposer *composer); GList * e_load_spell_languages (void); void e_save_spell_languages (GList *spell_languages); -gboolean e_msg_composer_get_mail_sent (EMsgComposer *composer); -void e_msg_composer_set_mail_sent (EMsgComposer *composer, - gboolean mail_sent); - G_END_DECLS #endif /* E_MSG_COMPOSER_H */ diff --git a/composer/mail-composer.error.xml b/composer/mail-composer.error.xml index 262eee459c..90f0187b0e 100644 --- a/composer/mail-composer.error.xml +++ b/composer/mail-composer.error.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <error-list domain="mail-composer"> - <error id="no-attach" type="error" modal="true"> + <error id="no-attach" type="error"> <_primary>You cannot attach the file "{0}" to this message.</_primary> <!--For Translators: '{1}' is the exception description,describing why the file could not be attached to the message --> <secondary>{1}</secondary> @@ -35,7 +35,7 @@ <button _label="_Send" response="GTK_RESPONSE_YES"/> </error> - <error id="exit-unsaved" modal="true" type="warning" default="GTK_RESPONSE_YES"> + <error id="exit-unsaved" type="warning" default="GTK_RESPONSE_YES"> <_primary>Are you sure you want to discard the message, titled '{0}', you are composing?</_primary> <_secondary>Closing this composer window will discard the message permanently, unless you choose to save the message in your Drafts folder. This will allow you to continue the message at a later date.</_secondary> <button _label="_Discard Changes" response="GTK_RESPONSE_NO"/> @@ -43,30 +43,39 @@ <button _label="_Save Draft" response="GTK_RESPONSE_YES"/> </error> - <error id="no-build-message" type="error" modal="true"> + <error id="no-build-message" type="error"> <_primary>Could not create message.</_primary> <_secondary>Because "{0}", you may need to select different mail options.</_secondary> </error> - <error id="no-sig-file" type="warning" modal="true"> + <error id="no-sig-file" type="warning"> <_primary>Could not read signature file "{0}".</_primary> <_secondary>Because "{1}".</_secondary> </error> - <error id="all-accounts-deleted" type="warning" modal="true"> + <error id="all-accounts-deleted" type="warning"> <_primary>All accounts have been removed.</_primary> <_secondary>You need to configure an account before you can compose mail.</_secondary> </error> - <error id="no-address-control" type="error" modal="true"> - <_primary>Could not create composer window.</_primary> - <_secondary>Unable to activate the address selector control.</_secondary> + <error id="append-to-outbox-error" type="error"> + <_primary>An error occurred while saving to your Outbox folder.</_primary> + <_secondary>The reported error was "{0}". The message has not been sent.</_secondary> </error> - <error id="no-editor-control" type="error" modal="true"> - <_primary>Could not create composer window.</_primary> - <_secondary xml:space="preserve">Unable to activate the HTML editor control. + <error id="save-draft-error" type="error"> + <_primary>An error occurred while saving to your Drafts folder.</_primary> + <_secondary>The reported error was "{0}". The message has most likely not been saved.</_secondary> + </error> + + <error id="send-error" type="error"> + <_primary>An error occurred while sending.</_primary> + <_secondary>The reported error was "{0}".</_secondary> + </error> -Please make sure that you have the correct version of gtkhtml and libgtkhtml installed.</_secondary> + <error id="saved-to-outbox" type="info"> + <_primary>Message saved to Outbox.</_primary> + <_secondary>Because you are working offline, the message has been saved to your local Outbox folder. When you are back online you can send the message by clicking the Send/Receive button in Evolution's toolbar.</_secondary> </error> + </error-list> diff --git a/e-util/e-alert-activity.c b/e-util/e-alert-activity.c index 3a97a4eab4..7d7e5a7999 100644 --- a/e-util/e-alert-activity.c +++ b/e-util/e-alert-activity.c @@ -124,8 +124,6 @@ alert_activity_constructed (GObject *object) secondary_text = e_alert_get_secondary_text (alert); e_activity_set_secondary_text (activity, secondary_text); - g_object_unref (alert); - /* This is a constructor property, so can't do it in init(). * XXX What we really want to do is override the property's * default value, but GObject does not support that. */ diff --git a/e-util/e-alert-dialog.c b/e-util/e-alert-dialog.c index 0057ed91aa..4d6fcd8e6e 100644 --- a/e-util/e-alert-dialog.c +++ b/e-util/e-alert-dialog.c @@ -24,95 +24,90 @@ #include "e-alert-dialog.h" #include "e-util.h" -G_DEFINE_TYPE ( - EAlertDialog, - e_alert_dialog, - GTK_TYPE_DIALOG) - -#define ALERT_DIALOG_PRIVATE(o) \ +#define E_ALERT_DIALOG_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_TYPE_ALERT_DIALOG, EAlertDialogPrivate)) -struct _EAlertDialogPrivate -{ +struct _EAlertDialogPrivate { GtkWindow *parent; EAlert *alert; }; -enum -{ +enum { PROP_0, - PROP_PARENT, PROP_ALERT }; +G_DEFINE_TYPE ( + EAlertDialog, + e_alert_dialog, + GTK_TYPE_DIALOG) + static void -e_alert_dialog_set_property (GObject *object, guint property_id, - const GValue *value, GParamSpec *pspec) +alert_dialog_set_alert (EAlertDialog *dialog, + EAlert *alert) { - EAlertDialog *dialog = (EAlertDialog*) object; + g_return_if_fail (E_IS_ALERT (alert)); + g_return_if_fail (dialog->priv->alert == NULL); - switch (property_id) - { - case PROP_PARENT: - dialog->priv->parent = g_value_dup_object (value); - break; + dialog->priv->alert = g_object_ref (alert); +} + +static void +alert_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { case PROP_ALERT: - dialog->priv->alert = g_value_dup_object (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + alert_dialog_set_alert ( + E_ALERT_DIALOG (object), + g_value_get_object (value)); + return; } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void -e_alert_dialog_get_property (GObject *object, guint property_id, - GValue *value, GParamSpec *pspec) +alert_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) { - EAlertDialog *dialog = (EAlertDialog*) object; - - switch (property_id) - { - case PROP_PARENT: - g_value_set_object (value, dialog->priv->parent); - break; + switch (property_id) { case PROP_ALERT: - g_value_set_object (value, dialog->priv->alert); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + g_value_set_object ( + value, e_alert_dialog_get_alert ( + E_ALERT_DIALOG (object))); + return; } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void -e_alert_dialog_dispose (GObject *object) +alert_dialog_dispose (GObject *object) { - EAlertDialog *dialog = (EAlertDialog*) object; + EAlertDialogPrivate *priv; - if (dialog->priv->parent) { - g_object_unref (dialog->priv->parent); - dialog->priv->parent = NULL; - } + priv = E_ALERT_DIALOG_GET_PRIVATE (object); - if (dialog->priv->alert) { - g_object_unref (dialog->priv->alert); - dialog->priv->alert = NULL; + if (priv->alert) { + g_object_unref (priv->alert); + priv->alert = NULL; } + /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_alert_dialog_parent_class)->dispose (object); } static void -e_alert_dialog_init (EAlertDialog *self) -{ - self->priv = ALERT_DIALOG_PRIVATE (self); -} - -static void -e_alert_dialog_constructed (GObject *obj) +alert_dialog_constructed (GObject *object) { - EAlertDialog *self = (EAlertDialog*) obj; + EAlertDialog *self = (EAlertDialog*) object; EAlert *alert; - struct _e_alert_button *b; + EAlertButton *b; GtkWidget *action_area; GtkWidget *content_area; GtkWidget *container; @@ -123,58 +118,49 @@ e_alert_dialog_constructed (GObject *obj) g_return_if_fail (self != NULL); - self->priv = ALERT_DIALOG_PRIVATE (self); - alert = self->priv->alert; + alert = e_alert_dialog_get_alert (E_ALERT_DIALOG (self)); gtk_window_set_title (GTK_WINDOW (self), " "); - action_area = gtk_dialog_get_action_area ((GtkDialog*) self); - content_area = gtk_dialog_get_content_area ((GtkDialog*) self); + action_area = gtk_dialog_get_action_area (GTK_DIALOG (self)); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (self)); #if !GTK_CHECK_VERSION(2,90,7) g_object_set (self, "has-separator", FALSE, NULL); #endif - gtk_widget_ensure_style ((GtkWidget *)self); + gtk_widget_ensure_style (GTK_WIDGET (self)); gtk_container_set_border_width (GTK_CONTAINER (action_area), 12); gtk_container_set_border_width (GTK_CONTAINER (content_area), 0); - if (self->priv->parent) - gtk_window_set_transient_for ((GtkWindow *)self, self->priv->parent); - else - g_warning ( - "Something called %s() with a NULL parent window. " - "This is no longer legal, please fix it.", G_STRFUNC); - - gtk_window_set_destroy_with_parent ((GtkWindow *)self, TRUE); + gtk_window_set_destroy_with_parent (GTK_WINDOW (self), TRUE); b = e_alert_peek_buttons (alert); if (b == NULL) { - gtk_dialog_add_button ((GtkDialog*) self, GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_add_button ( + GTK_DIALOG (self), GTK_STOCK_OK, GTK_RESPONSE_OK); } else { for (; b; b=b->next) { if (b->stock) { - if (b->label) { -#if 0 - /* FIXME: So although this looks like it will work, it wont. - Need to do it the hard way ... it also breaks the - default_response stuff */ - w = gtk_button_new_from_stock (b->stock); - gtk_button_set_label ((GtkButton *)w, b->label); - gtk_widget_show (w); - gtk_dialog_add_action_widget (self, w, b->response); -#endif - gtk_dialog_add_button ((GtkDialog*) self, b->label, b->response); - } else - gtk_dialog_add_button ((GtkDialog*) self, b->stock, b->response); + if (b->label != NULL) + gtk_dialog_add_button ( + GTK_DIALOG (self), + b->label, b->response); + else + gtk_dialog_add_button ( + GTK_DIALOG (self), + b->stock, b->response); } else - gtk_dialog_add_button ((GtkDialog*) self, b->label, b->response); + gtk_dialog_add_button ( + GTK_DIALOG (self), + b->label, b->response); } } if (e_alert_get_default_response (alert)) - gtk_dialog_set_default_response ((GtkDialog*) self, - e_alert_get_default_response (alert)); + gtk_dialog_set_default_response ( + GTK_DIALOG (self), + e_alert_get_default_response (alert)); widget = gtk_hbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (widget), 12); @@ -224,90 +210,118 @@ e_alert_dialog_constructed (GObject *obj) } static void -e_alert_dialog_class_init (EAlertDialogClass *klass) +alert_dialog_response (GtkDialog *dialog, + gint response_id) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - g_type_class_add_private (klass, sizeof (EAlertDialogPrivate)); - - object_class->dispose = e_alert_dialog_dispose; - object_class->get_property = e_alert_dialog_get_property; - object_class->set_property = e_alert_dialog_set_property; - object_class->constructed = e_alert_dialog_constructed; - - g_object_class_install_property (object_class, - PROP_PARENT, - g_param_spec_object ("parent", - "parent window", - "A parent window to be transient for", - GTK_TYPE_WINDOW, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (object_class, - PROP_ALERT, - g_param_spec_object ("alert", - "alert", - "EAlert to be displayed", - E_TYPE_ALERT, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); + EAlert *alert; + + alert = e_alert_dialog_get_alert (E_ALERT_DIALOG (dialog)); + e_alert_response (alert, response_id); } -GtkWidget* +static void +e_alert_dialog_class_init (EAlertDialogClass *class) +{ + GObjectClass *object_class; + GtkDialogClass *dialog_class; + + g_type_class_add_private (class, sizeof (EAlertDialogPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = alert_dialog_set_property; + object_class->get_property = alert_dialog_get_property; + object_class->dispose = alert_dialog_dispose; + object_class->constructed = alert_dialog_constructed; + + dialog_class = GTK_DIALOG_CLASS (class); + dialog_class->response = alert_dialog_response; + + g_object_class_install_property ( + object_class, + PROP_ALERT, + g_param_spec_object ( + "alert", + "Alert", + "Alert to be displayed", + E_TYPE_ALERT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +static void +e_alert_dialog_init (EAlertDialog *self) +{ + self->priv = E_ALERT_DIALOG_GET_PRIVATE (self); +} + +GtkWidget * e_alert_dialog_new (GtkWindow *parent, EAlert *alert) { + g_return_val_if_fail (E_IS_ALERT (alert), NULL); + return g_object_new ( E_TYPE_ALERT_DIALOG, - "parent", parent, "alert", alert, NULL); + "alert", alert, "transient-for", parent, NULL); } -GtkWidget* -e_alert_dialog_new_for_args (GtkWindow *parent, const gchar *tag, ...) +GtkWidget * +e_alert_dialog_new_for_args (GtkWindow *parent, + const gchar *tag, + ...) { - GtkWidget *d; - EAlert *e; + GtkWidget *dialog; + EAlert *alert; va_list ap; + g_return_val_if_fail (tag != NULL, NULL); + va_start (ap, tag); - e = e_alert_new_valist (tag, ap); + alert = e_alert_new_valist (tag, ap); va_end (ap); - d = e_alert_dialog_new (parent, e); - g_object_unref (e); + dialog = e_alert_dialog_new (parent, alert); + + g_object_unref (alert); - return d; + return dialog; } gint -e_alert_run_dialog (GtkWindow *parent, EAlert *alert) +e_alert_run_dialog (GtkWindow *parent, + EAlert *alert) { GtkWidget *dialog; - gint res; + gint response; - dialog = e_alert_dialog_new (parent, alert); + g_return_val_if_fail (E_IS_ALERT (alert), 0); - res = gtk_dialog_run ((GtkDialog *)dialog); + dialog = e_alert_dialog_new (parent, alert); + response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); - return res; + return response; } gint -e_alert_run_dialog_for_args (GtkWindow *parent, const gchar *tag, ...) +e_alert_run_dialog_for_args (GtkWindow *parent, + const gchar *tag, + ...) { - EAlert *e; - va_list ap; + EAlert *alert; gint response; + va_list ap; + + g_return_val_if_fail (tag != NULL, 0); va_start (ap, tag); - e = e_alert_new_valist (tag, ap); + alert = e_alert_new_valist (tag, ap); va_end (ap); - response = e_alert_run_dialog (parent, e); - g_object_unref (e); + response = e_alert_run_dialog (parent, alert); + + g_object_unref (alert); return response; } @@ -329,7 +343,7 @@ e_alert_dialog_count_buttons (EAlertDialog *dialog) g_return_val_if_fail (E_IS_ALERT_DIALOG (dialog), 0); - container = gtk_dialog_get_action_area ((GtkDialog*) dialog); + container = gtk_dialog_get_action_area (GTK_DIALOG (dialog)); children = gtk_container_get_children (GTK_CONTAINER (container)); /* Iterate over the children looking for buttons. */ @@ -346,19 +360,14 @@ e_alert_dialog_count_buttons (EAlertDialog *dialog) * e_alert_dialog_get_alert: * @dialog: a #EAlertDialog * - * Convenience API for getting the #EAlert associated with @dialog + * Returns the #EAlert associated with @dialog. * - * Return value: the #EAlert associated with @dialog. The alert should be - * unreffed when no longer needed. - */ + * Returns: the #EAlert associated with @dialog + **/ EAlert * e_alert_dialog_get_alert (EAlertDialog *dialog) { - EAlert *alert = NULL; - - g_return_val_if_fail (dialog != NULL, NULL); + g_return_val_if_fail (E_IS_ALERT_DIALOG (dialog), NULL); - g_object_get (dialog, "alert", &alert, - NULL); - return alert; + return dialog->priv->alert; } diff --git a/e-util/e-alert-dialog.h b/e-util/e-alert-dialog.h index 54235a757b..96253a582e 100644 --- a/e-util/e-alert-dialog.h +++ b/e-util/e-alert-dialog.h @@ -21,63 +21,60 @@ * Copyright (C) 2009 Intel Corporation */ -#ifndef _E_ALERT_DIALOG_H -#define _E_ALERT_DIALOG_H +#ifndef E_ALERT_DIALOG_H +#define E_ALERT_DIALOG_H #include <gtk/gtk.h> #include <e-util/e-alert.h> -G_BEGIN_DECLS - -#define E_TYPE_ALERT_DIALOG e_alert_dialog_get_type() - +/* Standard GObject macros */ +#define E_TYPE_ALERT_DIALOG \ + (e_alert_dialog_get_type ()) #define E_ALERT_DIALOG(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - E_TYPE_ALERT_DIALOG, EAlertDialog)) - -#define E_ALERT_DIALOG_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), \ - E_TYPE_ALERT_DIALOG, EAlertDialogClass)) - + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_ALERT_DIALOG, EAlertDialog)) +#define E_ALERT_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_ALERT_DIALOG, EAlertDialogClass)) #define E_IS_ALERT_DIALOG(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - E_TYPE_ALERT_DIALOG)) - -#define E_IS_ALERT_DIALOG_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), \ - E_TYPE_ALERT_DIALOG)) - + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_ALERT_DIALOG)) +#define E_IS_ALERT_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_ALERT_DIALOG)) #define E_ALERT_DIALOG_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), \ - E_TYPE_ALERT_DIALOG, EAlertDialogClass)) + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_ALERT_DIALOG, EAlertDialogClass)) + +G_BEGIN_DECLS typedef struct _EAlertDialog EAlertDialog; typedef struct _EAlertDialogClass EAlertDialogClass; typedef struct _EAlertDialogPrivate EAlertDialogPrivate; -struct _EAlertDialog -{ - GtkDialog parent; - EAlertDialogPrivate *priv; +struct _EAlertDialog { + GtkDialog parent; + EAlertDialogPrivate *priv; }; -struct _EAlertDialogClass -{ - GtkDialogClass parent_class; +struct _EAlertDialogClass { + GtkDialogClass parent_class; }; -GType e_alert_dialog_get_type (void); - -GtkWidget* e_alert_dialog_new (GtkWindow* parent, EAlert *alert); -GtkWidget* e_alert_dialog_new_for_args (GtkWindow* parent, const gchar *tag, ...) G_GNUC_NULL_TERMINATED; - -/* Convenience functions for displaying the alert in a GtkDialog */ -gint e_alert_run_dialog (GtkWindow *parent, EAlert *alert); -gint e_alert_run_dialog_for_args (GtkWindow *parent, const gchar *tag, ...) G_GNUC_NULL_TERMINATED; - -guint e_alert_dialog_count_buttons (EAlertDialog *dialog); -EAlert *e_alert_dialog_get_alert (EAlertDialog *dialog); +GType e_alert_dialog_get_type (void); +GtkWidget * e_alert_dialog_new (GtkWindow *parent, + EAlert *alert); +GtkWidget * e_alert_dialog_new_for_args (GtkWindow *parent, + const gchar *tag, + ...) G_GNUC_NULL_TERMINATED; +gint e_alert_run_dialog (GtkWindow *parent, + EAlert *alert); +gint e_alert_run_dialog_for_args (GtkWindow *parent, + const gchar *tag, + ...) G_GNUC_NULL_TERMINATED; +guint e_alert_dialog_count_buttons (EAlertDialog *dialog); +EAlert * e_alert_dialog_get_alert (EAlertDialog *dialog); G_END_DECLS -#endif /* _E_ALERT_DIALOG_H */ +#endif /* E_ALERT_DIALOG_H */ diff --git a/e-util/e-alert.c b/e-util/e-alert.c index ced83ddc0e..1846e6d1fa 100644 --- a/e-util/e-alert.c +++ b/e-util/e-alert.c @@ -52,7 +52,7 @@ struct _e_alert { gint default_response; const gchar *primary_text; const gchar *secondary_text; - struct _e_alert_button *buttons; + EAlertButton *buttons; }; struct _e_alert_table { @@ -65,7 +65,7 @@ static GHashTable *alert_table; /* ********************************************************************** */ -static struct _e_alert_button default_ok_button = { +static EAlertButton default_ok_button = { NULL, "gtk-ok", NULL, GTK_RESPONSE_OK }; @@ -78,31 +78,48 @@ static struct _e_alert default_alerts[] = { /* ********************************************************************** */ -static struct { - const gchar *name; - gint id; -} response_map[] = { - { "GTK_RESPONSE_REJECT", GTK_RESPONSE_REJECT }, - { "GTK_RESPONSE_ACCEPT", GTK_RESPONSE_ACCEPT }, - { "GTK_RESPONSE_OK", GTK_RESPONSE_OK }, - { "GTK_RESPONSE_CANCEL", GTK_RESPONSE_CANCEL }, - { "GTK_RESPONSE_CLOSE", GTK_RESPONSE_CLOSE }, - { "GTK_RESPONSE_YES", GTK_RESPONSE_YES }, - { "GTK_RESPONSE_NO", GTK_RESPONSE_NO }, - { "GTK_RESPONSE_APPLY", GTK_RESPONSE_APPLY }, - { "GTK_RESPONSE_HELP", GTK_RESPONSE_HELP }, +struct _EAlertPrivate { + gchar *tag; + GPtrArray *args; + gchar *primary_text; + gchar *secondary_text; + struct _e_alert *definition; + GtkMessageType message_type; + gint default_response; }; +enum { + PROP_0, + PROP_ARGS, + PROP_TAG, + PROP_MESSAGE_TYPE, + PROP_PRIMARY_TEXT, + PROP_SECONDARY_TEXT +}; + +enum { + RESPONSE, + LAST_SIGNAL +}; + +static gulong signals[LAST_SIGNAL]; + +G_DEFINE_TYPE ( + EAlert, + e_alert, + G_TYPE_OBJECT) + static gint map_response (const gchar *name) { - gint i; + GEnumClass *class; + GEnumValue *value; - for (i = 0; i < G_N_ELEMENTS (response_map); i++) - if (!strcmp (name, response_map[i].name)) - return response_map[i].id; + class = g_type_class_ref (GTK_TYPE_RESPONSE_TYPE); + value = g_enum_get_value_by_name (class, name); + g_type_class_unref (class); - return 0; + return (value != NULL) ? value->value : 0; } static GtkMessageType @@ -111,36 +128,13 @@ map_type (const gchar *nick) GEnumClass *class; GEnumValue *value; - class = g_type_class_peek (GTK_TYPE_MESSAGE_TYPE); + class = g_type_class_ref (GTK_TYPE_MESSAGE_TYPE); value = g_enum_get_value_by_nick (class, nick); + g_type_class_unref (class); return (value != NULL) ? value->value : GTK_MESSAGE_ERROR; } -G_DEFINE_TYPE ( - EAlert, - e_alert, - G_TYPE_OBJECT) - -enum { - PROP_0, - PROP_ARGS, - PROP_TAG, - PROP_MESSAGE_TYPE, - PROP_PRIMARY_TEXT, - PROP_SECONDARY_TEXT -}; - -struct _EAlertPrivate { - gchar *tag; - GPtrArray *args; - gchar *primary_text; - gchar *secondary_text; - struct _e_alert *definition; - GtkMessageType message_type; - gint default_response; -}; - /* XML format: @@ -152,9 +146,6 @@ struct _EAlertPrivate { response="response_id"? /> * </error> - The tool e-error-tool is used to extract the translatable strings for - translation. - */ static void e_alert_load (const gchar *path) @@ -162,7 +153,7 @@ e_alert_load (const gchar *path) xmlDocPtr doc = NULL; xmlNodePtr root, error, scan; struct _e_alert *e; - struct _e_alert_button *lastbutton; + EAlertButton *lastbutton; struct _e_alert_table *table; gchar *tmp; @@ -217,7 +208,7 @@ e_alert_load (const gchar *path) e->id = g_strdup (tmp); xmlFree (tmp); - lastbutton = (struct _e_alert_button *)&e->buttons; + lastbutton = (EAlertButton *)&e->buttons; tmp = (gchar *)xmlGetProp(error, (const guchar *)"type"); e->message_type = map_type (tmp); @@ -242,7 +233,7 @@ e_alert_load (const gchar *path) xmlFree (tmp); } } else if (!strcmp((gchar *)scan->name, "button")) { - struct _e_alert_button *b; + EAlertButton *b; gchar *label = NULL; gchar *stock = NULL; @@ -557,6 +548,16 @@ e_alert_class_init (EAlertClass *class) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + signals[RESPONSE] = g_signal_new ( + "response", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EAlertClass, response), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + e_alert_load_tables (); } @@ -730,16 +731,8 @@ e_alert_set_secondary_text (EAlert *alert, g_object_notify (G_OBJECT (alert), "secondary-text"); } -struct _e_alert_button * -e_alert_peek_buttons (EAlert *alert) -{ - g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL); - return alert->priv->definition->buttons; -} - -GtkWidget * -e_alert_create_image (EAlert *alert, - GtkIconSize size) +const gchar * +e_alert_get_stock_id (EAlert *alert) { const gchar *stock_id; @@ -764,10 +757,39 @@ e_alert_create_image (EAlert *alert, break; } + return stock_id; +} + +EAlertButton * +e_alert_peek_buttons (EAlert *alert) +{ + g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL); + return alert->priv->definition->buttons; +} + +GtkWidget * +e_alert_create_image (EAlert *alert, + GtkIconSize size) +{ + const gchar *stock_id; + + g_return_val_if_fail (E_IS_ALERT (alert), NULL); + + stock_id = e_alert_get_stock_id (alert); + return gtk_image_new_from_stock (stock_id, size); } void +e_alert_response (EAlert *alert, + gint response_id) +{ + g_return_if_fail (E_IS_ALERT (alert)); + + g_signal_emit (alert, signals[RESPONSE], 0, response_id); +} + +void e_alert_submit (GtkWidget *widget, const gchar *tag, ...) diff --git a/e-util/e-alert.h b/e-util/e-alert.h index fe705c5036..e0ad92c7d2 100644 --- a/e-util/e-alert.h +++ b/e-util/e-alert.h @@ -61,8 +61,10 @@ typedef struct _EAlert EAlert; typedef struct _EAlertClass EAlertClass; typedef struct _EAlertPrivate EAlertPrivate; -struct _e_alert_button { - struct _e_alert_button *next; +typedef struct _EAlertButton EAlertButton; + +struct _EAlertButton { + EAlertButton *next; const gchar *stock; const gchar *label; gint response; @@ -75,6 +77,10 @@ struct _EAlert { struct _EAlertClass { GObjectClass parent_class; + + /* Signals */ + void (*response) (EAlert *alert, + gint response_id); }; GType e_alert_get_type (void); @@ -96,10 +102,12 @@ void e_alert_set_primary_text (EAlert *alert, const gchar * e_alert_get_secondary_text (EAlert *alert); void e_alert_set_secondary_text (EAlert *alert, const gchar *secondary_text); -struct _e_alert_button * - e_alert_peek_buttons (EAlert *alert); +const gchar * e_alert_get_stock_id (EAlert *alert); +EAlertButton * e_alert_peek_buttons (EAlert *alert); GtkWidget * e_alert_create_image (EAlert *alert, GtkIconSize size); +void e_alert_response (EAlert *alert, + gint response_id); void e_alert_submit (GtkWidget *widget, const gchar *tag, diff --git a/e-util/e-marshal.list b/e-util/e-marshal.list index 9cc975f3b4..d4e6f43f15 100644 --- a/e-util/e-marshal.list +++ b/e-util/e-marshal.list @@ -20,6 +20,7 @@ INT:INT,INT,BOXED INT:INT,POINTER,INT,BOXED INT:OBJECT,BOXED INT:POINTER +NONE:ENUM,OBJECT,OBJECT NONE:INT,INT NONE:INT,INT,BOXED NONE:INT,INT,OBJECT diff --git a/mail/Makefile.am b/mail/Makefile.am index f30de3eea9..6b1615ec91 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -45,6 +45,7 @@ mailinclude_HEADERS = \ e-mail-backend.h \ e-mail-browser.h \ e-mail-display.h \ + e-mail-folder-utils.h \ e-mail-label-action.h \ e-mail-label-dialog.h \ e-mail-label-list-store.h \ @@ -55,6 +56,7 @@ mailinclude_HEADERS = \ e-mail-reader.h \ e-mail-reader-utils.h \ e-mail-session.h \ + e-mail-session-utils.h \ e-mail-sidebar.h \ e-mail-store.h \ e-mail-tag-editor.h \ @@ -114,6 +116,7 @@ libevolution_mail_la_SOURCES = \ e-mail-backend.c \ e-mail-browser.c \ e-mail-display.c \ + e-mail-folder-utils.c \ e-mail-label-action.c \ e-mail-label-dialog.c \ e-mail-label-list-store.c \ @@ -124,6 +127,7 @@ libevolution_mail_la_SOURCES = \ e-mail-reader.c \ e-mail-reader-utils.c \ e-mail-session.c \ + e-mail-session-utils.c \ e-mail-sidebar.c \ e-mail-store.c \ e-mail-tag-editor.c \ diff --git a/mail/e-mail-folder-utils.c b/mail/e-mail-folder-utils.c new file mode 100644 index 0000000000..7186589849 --- /dev/null +++ b/mail/e-mail-folder-utils.c @@ -0,0 +1,142 @@ +/* + * e-mail-folder-utils.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-folder-utils.h" + +#include <config.h> +#include <glib/gi18n-lib.h> + +/* X-Mailer header value */ +#define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT) + +typedef struct _AsyncContext AsyncContext; + +struct _AsyncContext { + GCancellable *cancellable; + gchar *message_uid; +}; + +static void +async_context_free (AsyncContext *context) +{ + if (context->cancellable != NULL) + g_object_unref (context->cancellable); + + g_free (context->message_uid); + + g_slice_free (AsyncContext, context); +} + +static void +mail_folder_append_message_ready (CamelFolder *folder, + GAsyncResult *result, + GSimpleAsyncResult *simple) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + camel_folder_append_message_finish ( + folder, result, &context->message_uid, &error); + + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + camel_operation_pop_message (context->cancellable); + + g_simple_async_result_complete (simple); + + g_object_unref (simple); +} + +void +e_mail_folder_append_message (CamelFolder *folder, + CamelMimeMessage *message, + CamelMessageInfo *info, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + CamelMedium *medium; + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + medium = CAMEL_MEDIUM (message); + + context = g_slice_new0 (AsyncContext); + + if (G_IS_CANCELLABLE (cancellable)) + context->cancellable = g_object_ref (cancellable); + + simple = g_simple_async_result_new ( + G_OBJECT (folder), callback, user_data, + e_mail_folder_append_message); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + camel_operation_push_message ( + context->cancellable, + _("Saving message to folder '%s'"), + camel_folder_get_full_name (folder)); + + if (camel_medium_get_header (medium, "X-Mailer") == NULL) + camel_medium_set_header (medium, "X-Mailer", X_MAILER); + + camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0); + + camel_folder_append_message ( + folder, message, info, io_priority, + context->cancellable, (GAsyncReadyCallback) + mail_folder_append_message_ready, simple); +} + +gboolean +e_mail_folder_append_message_finish (CamelFolder *folder, + GAsyncResult *result, + gchar **appended_uid, + GError **error) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (folder), + e_mail_folder_append_message), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + context = g_simple_async_result_get_op_res_gpointer (simple); + + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + if (appended_uid != NULL) { + *appended_uid = context->message_uid; + context->message_uid = NULL; + } + + return TRUE; +} diff --git a/mail/e-mail-folder-utils.h b/mail/e-mail-folder-utils.h new file mode 100644 index 0000000000..c5d3fefe13 --- /dev/null +++ b/mail/e-mail-folder-utils.h @@ -0,0 +1,43 @@ +/* + * e-mail-folder-utils.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_FOLDER_UTILS_H +#define E_MAIL_FOLDER_UTILS_H + +/* CamelFolder wrappers with Evolution-specific policies. */ + +#include <camel/camel.h> + +G_BEGIN_DECLS + +void e_mail_folder_append_message (CamelFolder *folder, + CamelMimeMessage *message, + CamelMessageInfo *info, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_mail_folder_append_message_finish + (CamelFolder *folder, + GAsyncResult *result, + gchar **appended_uid, + GError **error); + +G_END_DECLS + +#endif /* E_MAIL_FOLDER_UTILS_H */ diff --git a/mail/e-mail-reader.c b/mail/e-mail-reader.c index 0e28d06c88..184cc757df 100644 --- a/mail/e-mail-reader.c +++ b/mail/e-mail-reader.c @@ -462,9 +462,12 @@ check_close_browser_reader (EMailReader *reader) GtkWindow *parent; gint response; EShell *shell; + EMailBackend *backend; EShellBackend *shell_backend; - shell_backend = e_mail_reader_get_shell_backend (reader); + backend = e_mail_reader_get_backend (reader); + + shell_backend = E_SHELL_BACKEND (backend); shell = e_shell_backend_get_shell (shell_backend); parent = e_shell_get_active_window (shell); @@ -714,16 +717,20 @@ action_mail_message_edit_cb (GtkAction *action, EMailBackend *backend; EShellBackend *shell_backend; CamelFolder *folder; + const gchar *folder_uri; GPtrArray *uids; + gboolean replace; backend = e_mail_reader_get_backend (reader); folder = e_mail_reader_get_folder (reader); + folder_uri = e_mail_reader_get_folder_uri (reader); uids = e_mail_reader_get_selected_uids (reader); shell_backend = E_SHELL_BACKEND (backend); shell = e_shell_backend_get_shell (shell_backend); - em_utils_edit_messages (shell, folder, uids, FALSE); + replace = em_utils_folder_is_drafts (folder, folder_uri); + em_utils_edit_messages (shell, folder, uids, replace); } static void diff --git a/mail/e-mail-session-utils.c b/mail/e-mail-session-utils.c new file mode 100644 index 0000000000..e9cdd83914 --- /dev/null +++ b/mail/e-mail-session-utils.c @@ -0,0 +1,846 @@ +/* + * e-mail-session-utils.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-session-utils.h" + +#include <config.h> +#include <glib/gi18n-lib.h> + +#include <mail/mail-tools.h> +#include <mail/e-mail-local.h> +#include <e-util/e-account-utils.h> +#include <filter/e-filter-rule.h> + +/* X-Mailer header value */ +#define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT) + +typedef struct _AsyncContext AsyncContext; + +struct _AsyncContext { + CamelFolder *sent_folder; + CamelFolder *outbox_folder; + + CamelMimeMessage *message; + CamelMessageInfo *info; + + CamelAddress *from; + CamelAddress *recipients; + + CamelFilterDriver *driver; + + GCancellable *cancellable; + gint io_priority; + + /* X-Evolution headers */ + struct _camel_header_raw *xev; + + GPtrArray *post_to_uris; + + gchar *destination; + gchar *message_uid; + gchar *transport_uri; + gchar *sent_folder_uri; +}; + +static void +async_context_free (AsyncContext *context) +{ + if (context->sent_folder != NULL) + g_object_unref (context->sent_folder); + + if (context->outbox_folder != NULL) + g_object_unref (context->outbox_folder); + + if (context->message != NULL) + g_object_unref (context->message); + + if (context->info != NULL) + camel_message_info_free (context->info); + + if (context->from != NULL) + g_object_unref (context->from); + + if (context->recipients != NULL) + g_object_unref (context->recipients); + + if (context->driver != NULL) + g_object_unref (context->driver); + + if (context->cancellable != NULL) { + camel_operation_pop_message (context->cancellable); + g_object_unref (context->cancellable); + } + + if (context->xev != NULL) + camel_header_raw_clear (&context->xev); + + if (context->post_to_uris != NULL) { + g_ptr_array_foreach ( + context->post_to_uris, (GFunc) g_free, NULL); + g_ptr_array_free (context->post_to_uris, TRUE); + } + + g_free (context->destination); + g_free (context->message_uid); + g_free (context->transport_uri); + g_free (context->sent_folder_uri); + + g_slice_free (AsyncContext, context); +} + +static void +mail_session_handle_draft_headers_thread (GSimpleAsyncResult *simple, + EMailSession *session, + GCancellable *cancellable) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + e_mail_session_handle_draft_headers_sync ( + session, context->message, cancellable, &error); + + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } +} + +gboolean +e_mail_session_handle_draft_headers_sync (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder; + CamelMedium *medium; + const gchar *folder_uri; + const gchar *message_uid; + const gchar *header_name; + gboolean success; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE); + + medium = CAMEL_MEDIUM (message); + + header_name = "X-Evolution-Draft-Folder"; + folder_uri = camel_medium_get_header (medium, header_name); + + header_name = "X-Evolution-Draft-Message"; + message_uid = camel_medium_get_header (medium, header_name); + + /* Don't report errors about missing X-Evolution-Draft + * headers. These headers are optional, so their absence + * is handled by doing nothing. */ + if (folder_uri == NULL || message_uid == NULL) + return TRUE; + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, error); + + if (folder == NULL) + return FALSE; + + camel_folder_set_message_flags ( + folder, message_uid, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); + + success = camel_folder_synchronize_message_sync ( + folder, message_uid, cancellable, error); + + g_object_unref (folder); + + return success; +} + +void +e_mail_session_handle_draft_headers (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, user_data, + e_mail_session_handle_draft_headers); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + mail_session_handle_draft_headers_thread, + io_priority, cancellable); + + g_object_unref (simple); +} + +gboolean +e_mail_session_handle_draft_headers_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_handle_draft_headers), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} + +static void +mail_session_handle_source_headers_thread (GSimpleAsyncResult *simple, + EMailSession *session, + GCancellable *cancellable) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + e_mail_session_handle_source_headers_sync ( + session, context->message, cancellable, &error); + + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } +} + +gboolean +e_mail_session_handle_source_headers_sync (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder; + CamelMedium *medium; + CamelMessageFlags flags = 0; + const gchar *folder_uri; + const gchar *message_uid; + const gchar *flag_string; + const gchar *header_name; + gboolean success; + guint length, ii; + gchar **tokens; + gchar *string; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE); + + medium = CAMEL_MEDIUM (message); + + header_name = "X-Evolution-Source-Folder"; + folder_uri = camel_medium_get_header (medium, header_name); + + header_name = "X-Evolution-Source-Message"; + message_uid = camel_medium_get_header (medium, header_name); + + header_name = "X-Evolution-Source-Flags"; + flag_string = camel_medium_get_header (medium, header_name); + + /* Don't report errors about missing X-Evolution-Source + * headers. These headers are optional, so their absence + * is handled by doing nothing. */ + if (folder_uri == NULL || message_uid == NULL || flag_string == NULL) + return TRUE; + + /* Convert the flag string to CamelMessageFlags. */ + + string = g_strstrip (g_strdup (flag_string)); + tokens = g_strsplit (string, " ", 0); + g_free (string); + + /* If tokens is NULL, a length of 0 will skip the loop. */ + length = (tokens != NULL) ? g_strv_length (tokens) : 0; + + for (ii = 0; ii < length; ii++) { + /* Note: We're only checking for flags known to + * be used in X-Evolution-Source-Flags headers. + * Add more as needed. */ + if (g_strcmp0 (tokens[ii], "ANSWERED") == 0) + flags |= CAMEL_MESSAGE_ANSWERED; + else if (g_strcmp0 (tokens[ii], "ANSWERED_ALL") == 0) + flags |= CAMEL_MESSAGE_ANSWERED_ALL; + else if (g_strcmp0 (tokens[ii], "FORWARDED") == 0) + flags |= CAMEL_MESSAGE_FORWARDED; + else if (g_strcmp0 (tokens[ii], "SEEN") == 0) + flags |= CAMEL_MESSAGE_SEEN; + else + g_warning ( + "Unknown flag '%s' in %s", + tokens[ii], header_name); + } + + g_strfreev (tokens); + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, error); + + if (folder == NULL) + return FALSE; + + camel_folder_set_message_flags ( + folder, message_uid, flags, flags); + + success = camel_folder_synchronize_message_sync ( + folder, message_uid, cancellable, error); + + g_object_unref (folder); + + return success; +} + +void +e_mail_session_handle_source_headers (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, user_data, + e_mail_session_handle_source_headers); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + mail_session_handle_source_headers_thread, + io_priority, cancellable); + + g_object_unref (simple); +} + +gboolean +e_mail_session_handle_source_headers_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_handle_draft_headers), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} + +static void +mail_session_send_to_thread (GSimpleAsyncResult *simple, + EMailSession *session, + GCancellable *cancellable) +{ + AsyncContext *context; + CamelFolder *local_sent_folder; + GString *error_messages; + gboolean copy_to_sent = TRUE; + guint ii; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + /* Send the message to all recipients. */ + if (camel_address_length (context->recipients) > 0) { + CamelTransport *transport; + CamelProviderFlags flags; + + /* XXX This API does not allow for cancellation. */ + transport = camel_session_get_transport ( + CAMEL_SESSION (session), + context->transport_uri, &error); + + if (error != NULL) { + g_warn_if_fail (transport == NULL); + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + return; + } + + g_return_if_fail (CAMEL_IS_TRANSPORT (transport)); + + flags = CAMEL_SERVICE (transport)->provider->flags; + if (flags & CAMEL_PROVIDER_DISABLE_SENT_FOLDER) + copy_to_sent = FALSE; + + camel_transport_send_to_sync ( + transport, context->message, + context->from, context->recipients, + cancellable, &error); + + g_object_unref (transport); + + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + return; + } + } + + /* Post the message to requested folders. */ + for (ii = 0; ii < context->post_to_uris->len; ii++) { + CamelFolder *folder; + const gchar *folder_uri; + + folder_uri = g_ptr_array_index (context->post_to_uris, ii); + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, &error); + + if (error != NULL) { + g_warn_if_fail (folder == NULL); + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + return; + } + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + + camel_folder_append_message_sync ( + folder, context->message, context->info, + NULL, cancellable, &error); + + g_object_unref (folder); + + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + return; + } + } + + /*** Post Processing ***/ + + /* This accumulates error messages during post-processing. */ + error_messages = g_string_sized_new (256); + + mail_tool_restore_xevolution_headers (context->message, context->xev); + + /* Run filters on the outgoing message. */ + if (context->driver != NULL) { + camel_filter_driver_filter_message ( + context->driver, context->message, context->info, + NULL, NULL, NULL, "", cancellable, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto exit; + + if (error != NULL) { + g_string_append_printf ( + error_messages, + _("Failed to apply outgoing filters: %s"), + error->message); + g_clear_error (&error); + } + } + + if (!copy_to_sent) + goto cleanup; + + /* Append the sent message to a Sent folder. */ + + local_sent_folder = e_mail_local_get_folder (E_MAIL_FOLDER_SENT); + + /* Try to extract a CamelFolder from the Sent folder URI. */ + if (context->sent_folder_uri != NULL) { + context->sent_folder = e_mail_session_uri_to_folder_sync ( + session, context->sent_folder_uri, 0, + cancellable, &error); + if (error != NULL) { + g_warn_if_fail (context->sent_folder == NULL); + if (error_messages->len > 0) + g_string_append (error_messages, "\n\n"); + g_string_append_printf ( + error_messages, + _("Failed to append to %s: %s\n" + "Appending to local 'Sent' folder instead."), + context->sent_folder_uri, error->message); + g_clear_error (&error); + } + } + + /* Fall back to the local Sent folder. */ + if (context->sent_folder == NULL) + context->sent_folder = g_object_ref (local_sent_folder); + + /* Append the message. */ + camel_folder_append_message_sync ( + context->sent_folder, context->message, + context->info, NULL, cancellable, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto exit; + + if (error == NULL) + goto cleanup; + + /* If appending to a remote Sent folder failed, + * try appending to the local Sent folder. */ + if (context->sent_folder != local_sent_folder) { + const gchar *description; + + description = camel_folder_get_description ( + context->sent_folder); + + if (error_messages->len > 0) + g_string_append (error_messages, "\n\n"); + g_string_append_printf ( + error_messages, + _("Failed to append to %s: %s\n" + "Appending to local 'Sent' folder instead."), + description, error->message); + g_clear_error (&error); + + camel_folder_append_message_sync ( + local_sent_folder, context->message, + context->info, NULL, cancellable, &error); + } + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto exit; + + /* We can't even append to the local Sent folder? + * In that case just leave the message in Outbox. */ + if (error != NULL) { + if (error_messages->len > 0) + g_string_append (error_messages, "\n\n"); + g_string_append_printf ( + error_messages, + _("Failed to append to local 'Sent' folder: %s"), + error->message); + g_clear_error (&error); + goto exit; + } + +cleanup: + + /* The send operation was successful; ignore cleanup errors. */ + + /* Mark the Outbox message for deletion. */ + camel_folder_set_message_flags ( + context->outbox_folder, context->message_uid, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, ~0); + + /* Synchronize the Outbox folder. */ + camel_folder_synchronize_sync ( + context->outbox_folder, FALSE, cancellable, &error); + if (error != NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + } + + /* Mark the draft message for deletion, if present. */ + e_mail_session_handle_draft_headers_sync ( + session, context->message, cancellable, &error); + if (error != NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + } + + /* Set flags on the original source message, if present. + * Source message refers to the message being forwarded + * or replied to. */ + e_mail_session_handle_source_headers_sync ( + session, context->message, cancellable, &error); + if (error != NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + } + +exit: + + /* If we were cancelled, disregard any other errors. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + + /* Stuff the accumulated error messages in a GError. */ + } else if (error_messages->len > 0) { + g_simple_async_result_set_error ( + simple, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + "%s", error_messages->str); + } + + /* Synchronize the Sent folder. */ + if (context->sent_folder != NULL) + camel_folder_synchronize_sync ( + context->sent_folder, FALSE, cancellable, NULL); + + g_string_free (error_messages, TRUE); +} + +static void +mail_session_send_to_prepare (CamelFolder *outbox_folder, + GAsyncResult *result, + GSimpleAsyncResult *simple) +{ + AsyncContext *context; + CamelAddress *from; + CamelAddress *recipients; + CamelMedium *medium; + CamelMessageInfo *info; + CamelMimeMessage *message; + EAccount *account = NULL; + GPtrArray *post_to_uris; + struct _camel_header_raw *xev; + struct _camel_header_raw *header; + const gchar *string; + const gchar *resent_from; + gchar *transport_uri = NULL; + gchar *sent_folder_uri = NULL; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + message = camel_folder_get_message_finish ( + outbox_folder, result, &error); + + if (error != NULL) { + g_warn_if_fail (message == NULL); + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + g_error_free (error); + return; + } + + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + medium = CAMEL_MEDIUM (message); + + camel_medium_set_header (medium, "X-Mailer", X_MAILER); + + xev = mail_tool_remove_xevolution_headers (message); + + /* Extract directives from X-Evolution headers. */ + + string = camel_header_raw_find (&xev, "X-Evolution-Account", NULL); + if (string != NULL) { + gchar *account_uid; + + account_uid = g_strstrip (g_strdup (string)); + account = e_get_account_by_uid (account_uid); + g_free (account_uid); + } + + if (account != NULL) { + if (account->transport != NULL) + transport_uri = g_strdup (account->transport->url); + sent_folder_uri = g_strdup (account->sent_folder_uri); + } + + string = camel_header_raw_find (&xev, "X-Evolution-Transport", NULL); + if (transport_uri == NULL && string != NULL) + transport_uri = g_strstrip (g_strdup (string)); + + string = camel_header_raw_find (&xev, "X-Evolution-Fcc", NULL); + if (sent_folder_uri == NULL && string != NULL) + sent_folder_uri = g_strstrip (g_strdup (string)); + + if (transport_uri == NULL) + transport_uri = g_strdup (context->destination); + + post_to_uris = g_ptr_array_new (); + for (header = xev; header != NULL; header = header->next) { + gchar *folder_uri; + + if (g_strcmp0 (header->name, "X-Evolution-PostTo") != 0) + continue; + + folder_uri = g_strstrip (g_strdup (header->name)); + g_ptr_array_add (post_to_uris, folder_uri); + } + + /* Collect sender and recipients from headers. */ + + from = (CamelAddress *) camel_internet_address_new (); + recipients = (CamelAddress *) camel_internet_address_new (); + resent_from = camel_medium_get_header (medium, "Resent-From"); + + if (resent_from != NULL) { + const CamelInternetAddress *addr; + const gchar *type; + + camel_address_decode (from, resent_from); + + type = CAMEL_RECIPIENT_TYPE_RESENT_TO; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_RESENT_CC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_RESENT_BCC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + } else { + const CamelInternetAddress *addr; + const gchar *type; + + addr = camel_mime_message_get_from (message); + camel_address_copy (from, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_TO; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_CC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_BCC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + } + + /* Miscellaneous preparations. */ + + info = camel_message_info_new (NULL); + camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0); + + /* The rest of the processing happens in a thread. */ + + context->from = from; + context->recipients = recipients; + context->message = g_object_ref (message); + context->info = info; + context->xev = xev; + context->post_to_uris = post_to_uris; + context->transport_uri = transport_uri; + context->sent_folder_uri = sent_folder_uri; + + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + mail_session_send_to_thread, + context->io_priority, + context->cancellable); + + g_object_unref (simple); +} + +void +e_mail_session_send_to (EMailSession *session, + CamelFolder *outbox_folder, + const gchar *message_uid, + const gchar *destination, + gint io_priority, + GCancellable *cancellable, + CamelFilterGetFolderFunc get_folder_func, + gpointer get_folder_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + GError *error = NULL; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_FOLDER (outbox_folder)); + g_return_if_fail (message_uid != NULL); + + context = g_slice_new0 (AsyncContext); + context->outbox_folder = g_object_ref (outbox_folder); + context->message_uid = g_strdup (message_uid); + context->destination = g_strdup (destination); + context->io_priority = io_priority; + + if (G_IS_CANCELLABLE (cancellable)) + context->cancellable = g_object_ref (cancellable); + + /* More convenient to do this here than in the prepare function. + * Failure here emits a runtime warning but is non-fatal. */ + context->driver = camel_session_get_filter_driver ( + CAMEL_SESSION (session), E_FILTER_SOURCE_OUTGOING, &error); + if (context->driver != NULL) + camel_filter_driver_set_folder_func ( + context->driver, get_folder_func, get_folder_data); + if (error != NULL) { + g_warn_if_fail (context->driver == NULL); + g_warning ("%s", error->message); + g_error_free (error); + } + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, + user_data, e_mail_session_send_to); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + /* This gets popped in async_context_free(). */ + camel_operation_push_message ( + context->cancellable, _("Sending message")); + + camel_folder_get_message ( + outbox_folder, message_uid, io_priority, + context->cancellable, (GAsyncReadyCallback) + mail_session_send_to_prepare, simple); +} + +gboolean +e_mail_session_send_to_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_send_to), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} diff --git a/mail/e-mail-session-utils.h b/mail/e-mail-session-utils.h new file mode 100644 index 0000000000..fcbc2636f7 --- /dev/null +++ b/mail/e-mail-session-utils.h @@ -0,0 +1,76 @@ +/* + * e-mail-session-utils.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_SESSION_UTILS_H +#define E_MAIL_SESSION_UTILS_H + +/* High-level operations with Evolution-specific policies. */ + +#include <mail/e-mail-session.h> + +G_BEGIN_DECLS + +gboolean e_mail_session_handle_draft_headers_sync + (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error); +void e_mail_session_handle_draft_headers + (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_mail_session_handle_draft_headers_finish + (EMailSession *session, + GAsyncResult *result, + GError **error); +gboolean e_mail_session_handle_source_headers_sync + (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error); +void e_mail_session_handle_source_headers + (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_mail_session_handle_source_headers_finish + (EMailSession *session, + GAsyncResult *result, + GError **error); +void e_mail_session_send_to (EMailSession *session, + CamelFolder *outbox_folder, + const gchar *message_uid, + const gchar *destination, + gint io_priority, + GCancellable *cancellable, + CamelFilterGetFolderFunc get_folder_func, + gpointer get_folder_data, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_mail_session_send_to_finish (EMailSession *session, + GAsyncResult *result, + GError **error); + +G_END_DECLS + +#endif /* E_MAIL_SESSION_UTILS_H */ diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c index 11143124e0..316046eecd 100644 --- a/mail/em-composer-utils.c +++ b/mail/em-composer-utils.c @@ -44,8 +44,10 @@ #include "shell/e-shell.h" +#include "e-mail-folder-utils.h" #include "e-mail-local.h" #include "e-mail-session.h" +#include "e-mail-session-utils.h" #include "em-utils.h" #include "em-composer-utils.h" #include "composer/e-msg-composer.h" @@ -69,97 +71,32 @@ #define GCONF_KEY_TEMPLATE_PLACEHOLDERS "/apps/evolution/mail/template_placeholders" -static void em_utils_composer_send_cb (EMsgComposer *composer); -static void em_utils_composer_save_draft_cb (EMsgComposer *composer); +typedef struct _AsyncContext AsyncContext; -struct emcs_t { - guint ref_count; - - CamelFolder *drafts_folder; - gchar *drafts_uid; - - CamelFolder *folder; - guint32 flags, set; - gchar *uid; +struct _AsyncContext { + CamelMimeMessage *message; + EMsgComposer *composer; + EActivity *activity; + gchar *folder_uri; + gchar *message_uid; }; -static struct emcs_t * -emcs_new (void) -{ - struct emcs_t *emcs; - - emcs = g_new0 (struct emcs_t, 1); - emcs->ref_count = 1; - - return emcs; -} - -static void -emcs_set_drafts_info (struct emcs_t *emcs, - CamelFolder *drafts_folder, - const gchar *drafts_uid) -{ - g_return_if_fail (emcs != NULL); - g_return_if_fail (drafts_folder != NULL); - g_return_if_fail (drafts_uid != NULL); - - if (emcs->drafts_folder != NULL) - g_object_unref (emcs->drafts_folder); - g_free (emcs->drafts_uid); - - g_object_ref (drafts_folder); - emcs->drafts_folder = drafts_folder; - emcs->drafts_uid = g_strdup (drafts_uid); -} - -static void -emcs_set_folder_info (struct emcs_t *emcs, - CamelFolder *folder, - const gchar *uid, - guint32 flags, - guint32 set) -{ - g_return_if_fail (emcs != NULL); - g_return_if_fail (folder != NULL); - g_return_if_fail (uid != NULL); - - if (emcs->folder != NULL) - g_object_unref (emcs->folder); - g_free (emcs->uid); - - g_object_ref (folder); - emcs->folder = folder; - emcs->uid = g_strdup (uid); - emcs->flags = flags; - emcs->set = set; -} - static void -free_emcs (struct emcs_t *emcs) +async_context_free (AsyncContext *context) { - if (emcs->drafts_folder != NULL) - g_object_unref (emcs->drafts_folder); - g_free (emcs->drafts_uid); + if (context->message != NULL) + g_object_unref (context->message); - if (emcs->folder != NULL) - g_object_unref (emcs->folder); - g_free (emcs->uid); + if (context->composer != NULL) + g_object_unref (context->composer); - g_free (emcs); -} + if (context->activity != NULL) + g_object_unref (context->activity); -static void -emcs_ref (struct emcs_t *emcs) -{ - emcs->ref_count++; -} + g_free (context->folder_uri); + g_free (context->message_uid); -static void -emcs_unref (struct emcs_t *emcs) -{ - emcs->ref_count--; - if (emcs->ref_count == 0) - free_emcs (emcs); + g_slice_free (AsyncContext, context); } static gboolean @@ -211,69 +148,6 @@ ask_confirm_for_only_bcc (EMsgComposer *composer, gboolean hidden_list_case) hidden_list_case?"mail:ask-send-only-bcc-contact":"mail:ask-send-only-bcc", NULL); } -struct _send_data { - struct emcs_t *emcs; - EMsgComposer *composer; - gboolean send; -}; - -static void -composer_send_queued_cb (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, - gint queued, const gchar *appended_uid, gpointer data) -{ - CamelSession *session; - struct emcs_t *emcs; - struct _send_data *send = data; - - emcs = send->emcs; - - session = e_msg_composer_get_session (send->composer); - - if (queued) { - if (emcs && emcs->drafts_folder) { - /* delete the old draft message */ - camel_folder_set_message_flags ( - emcs->drafts_folder, emcs->drafts_uid, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); - g_object_unref (emcs->drafts_folder); - emcs->drafts_folder = NULL; - g_free (emcs->drafts_uid); - emcs->drafts_uid = NULL; - } - - if (emcs && emcs->folder) { - /* set any replied flags etc */ - camel_folder_set_message_flags ( - emcs->folder, emcs->uid, - emcs->flags, emcs->set); - camel_folder_set_message_user_flag ( - emcs->folder, emcs->uid, - "receipt-handled", TRUE); - g_object_unref (emcs->folder); - emcs->folder = NULL; - g_free (emcs->uid); - emcs->uid = NULL; - } - - gtk_widget_destroy (GTK_WIDGET (send->composer)); - - if (send->send && camel_session_get_online (session)) { - /* queue a message send */ - mail_send (E_MAIL_SESSION (session)); - } - } else - gtk_widget_show (GTK_WIDGET (send->composer)); - - camel_message_info_free (info); - - if (send->emcs) - emcs_unref (send->emcs); - - g_object_unref (send->composer); - g_free (send); -} - static gboolean is_group_definition (const gchar *str) { @@ -286,106 +160,101 @@ is_group_definition (const gchar *str) return colon > str && strchr (str, ';') > colon; } -static CamelMimeMessage * -composer_get_message (EMsgComposer *composer, gboolean save_html_object_data) +static gboolean +composer_presend_check_recipients (EMsgComposer *composer) { - CamelMimeMessage *message = NULL; - EDestination **recipients, **recipients_bcc; - gboolean html_mode, send_html, confirm_html; + EDestination **recipients; + EDestination **recipients_bcc; CamelInternetAddress *cia; - gint hidden = 0, shown = 0; - gint num = 0, num_bcc = 0, num_post = 0; - const gchar *subject; - GConfClient *gconf; - EAccount *account; - gint i; - EMEvent *eme; - EMEventTargetComposer *target; EComposerHeaderTable *table; EComposerHeader *post_to_header; GString *invalid_addrs = NULL; - GError *error = NULL; - - gconf = mail_config_get_gconf_client (); - table = e_msg_composer_get_header_table (composer); + gboolean check_passed = FALSE; + gint hidden = 0; + gint shown = 0; + gint num = 0; + gint num_bcc = 0; + gint num_post = 0; + gint ii; - /* We should do all of the validity checks based on the composer, and not on - the created message, as extra interaction may occur when we get the message - (e.g. to get a passphrase to sign a message) */ + /* We should do all of the validity checks based on the composer, + * and not on the created message, as extra interaction may occur + * when we get the message (e.g. passphrase to sign a message). */ - /* get the message recipients */ + table = e_msg_composer_get_header_table (composer); recipients = e_composer_header_table_get_destinations (table); cia = camel_internet_address_new (); - /* see which ones are visible/present, etc */ - if (recipients) { - for (i = 0; recipients[i] != NULL; i++) { - const gchar *addr = e_destination_get_address (recipients[i]); - - if (addr && addr[0]) { - gint len, j; - - camel_address_decode ((CamelAddress *) cia, addr); - len = camel_address_length ((CamelAddress *) cia); - - if (len > 0) { - if (!e_destination_is_evolution_list (recipients[i])) { - for (j = 0; j < len; j++) { - const gchar *name = NULL, *eml = NULL; - - if (!camel_internet_address_get (cia, j, &name, &eml) || - !eml || - strchr (eml, '@') <= eml) { - if (!invalid_addrs) - invalid_addrs = g_string_new (""); - else - g_string_append (invalid_addrs, ", "); - - if (name) - g_string_append (invalid_addrs, name); - if (eml) { - g_string_append (invalid_addrs, name ? " <" : ""); - g_string_append (invalid_addrs, eml); - g_string_append (invalid_addrs, name ? ">" : ""); - } - } - } - } + /* See which ones are visible, present, etc. */ + for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) { + const gchar *addr; + gint len, j; - camel_address_remove ((CamelAddress *) cia, -1); - num++; - if (e_destination_is_evolution_list (recipients[i]) - && !e_destination_list_show_addresses (recipients[i])) { - hidden++; - } else { - shown++; + addr = e_destination_get_address (recipients[ii]); + if (addr == NULL || *addr == '\0') + continue; + + camel_address_decode (CAMEL_ADDRESS (cia), addr); + len = camel_address_length (CAMEL_ADDRESS (cia)); + + if (len > 0) { + if (!e_destination_is_evolution_list (recipients[ii])) { + for (j = 0; j < len; j++) { + const gchar *name = NULL, *eml = NULL; + + if (!camel_internet_address_get (cia, j, &name, &eml) || + !eml || + strchr (eml, '@') <= eml) { + if (!invalid_addrs) + invalid_addrs = g_string_new (""); + else + g_string_append (invalid_addrs, ", "); + + if (name) + g_string_append (invalid_addrs, name); + if (eml) { + g_string_append (invalid_addrs, name ? " <" : ""); + g_string_append (invalid_addrs, eml); + g_string_append (invalid_addrs, name ? ">" : ""); + } } - } else if (is_group_definition (addr)) { - /* like an address, it will not claim on only-bcc */ - shown++; - num++; - } else if (!invalid_addrs) { - invalid_addrs = g_string_new (addr); - } else { - g_string_append (invalid_addrs, ", "); - g_string_append (invalid_addrs, addr); } } + + camel_address_remove (CAMEL_ADDRESS (cia), -1); + num++; + if (e_destination_is_evolution_list (recipients[ii]) + && !e_destination_list_show_addresses (recipients[ii])) { + hidden++; + } else { + shown++; + } + } else if (is_group_definition (addr)) { + /* like an address, it will not claim on only-bcc */ + shown++; + num++; + } else if (!invalid_addrs) { + invalid_addrs = g_string_new (addr); + } else { + g_string_append (invalid_addrs, ", "); + g_string_append (invalid_addrs, addr); } } recipients_bcc = e_composer_header_table_get_destinations_bcc (table); if (recipients_bcc) { - for (i = 0; recipients_bcc[i] != NULL; i++) { - const gchar *addr = e_destination_get_address (recipients_bcc[i]); - - if (addr && addr[0]) { - camel_address_decode ((CamelAddress *) cia, addr); - if (camel_address_length ((CamelAddress *) cia) > 0) { - camel_address_remove ((CamelAddress *) cia, -1); - num_bcc++; - } + for (ii = 0; recipients_bcc[ii] != NULL; ii++) { + const gchar *addr; + + addr = e_destination_get_address (recipients_bcc[ii]); + if (addr == NULL || *addr == '\0') + continue; + + camel_address_decode (CAMEL_ADDRESS (cia), addr); + if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) { + camel_address_remove (CAMEL_ADDRESS (cia), -1); + num_bcc++; } } @@ -394,24 +263,32 @@ composer_get_message (EMsgComposer *composer, gboolean save_html_object_data) g_object_unref (cia); - post_to_header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_POST_TO); + post_to_header = e_composer_header_table_get_header ( + table, E_COMPOSER_HEADER_POST_TO); if (e_composer_header_get_visible (post_to_header)) { GList *postlist; postlist = e_composer_header_table_get_post_to (table); num_post = g_list_length (postlist); - g_list_foreach (postlist, (GFunc)g_free, NULL); + g_list_foreach (postlist, (GFunc) g_free, NULL); g_list_free (postlist); } /* I'm sensing a lack of love, er, I mean recipients. */ if (num == 0 && num_post == 0) { - e_alert_run_dialog_for_args ((GtkWindow *)composer, "mail:send-no-recipients", NULL); + e_alert_submit ( + GTK_WIDGET (composer), + "mail:send-no-recipients", NULL); goto finished; } if (invalid_addrs) { - if (e_alert_run_dialog_for_args ((GtkWindow *)composer, strstr (invalid_addrs->str, ", ") ? "mail:ask-send-invalid-recip-multi" : "mail:ask-send-invalid-recip-one", invalid_addrs->str, NULL) == GTK_RESPONSE_CANCEL) { + if (e_alert_run_dialog_for_args ( + GTK_WINDOW (composer), + strstr (invalid_addrs->str, ", ") ? + "mail:ask-send-invalid-recip-multi" : + "mail:ask-send-invalid-recip-one", + invalid_addrs->str, NULL) == GTK_RESPONSE_CANCEL) { g_string_free (invalid_addrs, TRUE); goto finished; } @@ -425,154 +302,277 @@ composer_get_message (EMsgComposer *composer, gboolean save_html_object_data) goto finished; } - html_mode = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer)); - send_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/composer/send_html", NULL); - confirm_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", NULL); + check_passed = TRUE; - /* Only show this warning if our default is to send html. If it isn't, we've - manually switched into html mode in the composer and (presumably) had a good - reason for doing this. */ - if (html_mode && send_html && confirm_html) { +finished: + if (recipients != NULL) + e_destination_freev (recipients); - gboolean html_problem = FALSE; + return check_passed; +} - if (recipients) { - for (i = 0; recipients[i] != NULL && !html_problem; i++) { - if (!e_destination_get_html_mail_pref (recipients[i])) - html_problem = TRUE; - } - } +static gboolean +composer_presend_check_account (EMsgComposer *composer) +{ + EComposerHeaderTable *table; + EAccount *account; + gboolean check_passed; - if (html_problem) { - html_problem = !ask_confirm_for_unwanted_html_mail (composer, recipients); - if (html_problem) - goto finished; - } - } + table = e_msg_composer_get_header_table (composer); + account = e_composer_header_table_get_account (table); + check_passed = (account != NULL && account->enabled); - /* Check for no subject */ - subject = e_composer_header_table_get_subject (table); - if (subject == NULL || subject[0] == '\0') { - if (!ask_confirm_for_empty_subject (composer)) - goto finished; + if (!check_passed) + e_alert_submit ( + GTK_WIDGET (composer), + "mail:send-no-account-enabled", NULL); + + return check_passed; +} + +static gboolean +composer_presend_check_downloads (EMsgComposer *composer) +{ + EAttachmentView *view; + EAttachmentStore *store; + gboolean check_passed = TRUE; + + view = e_msg_composer_get_attachment_view (composer); + store = e_attachment_view_get_store (view); + + if (e_attachment_store_get_num_loading (store) > 0) { + if (!em_utils_prompt_user (GTK_WINDOW (composer), NULL, + "mail-composer:ask-send-message-pending-download", NULL)) + check_passed = FALSE; } + return check_passed; +} + +static gboolean +composer_presend_check_plugins (EMsgComposer *composer) +{ + EMEvent *eme; + EMEventTargetComposer *target; + gpointer data; + /** @Event: composer.presendchecks * @Title: Composer PreSend Checks * @Target: EMEventTargetMessage * - * composer.presendchecks is emitted during pre-checks for the message just before sending. - * Since the e-plugin framework doesn't provide a way to return a value from the plugin, - * use 'presend_check_status' to set whether the check passed / failed. + * composer.presendchecks is emitted during pre-checks for the + * message just before sending. Since the e-plugin framework + * doesn't provide a way to return a value from the plugin, + * use 'presend_check_status' to set whether the check passed. */ eme = em_event_peek (); target = em_event_target_new_composer (eme, composer, 0); - g_object_set_data (G_OBJECT (composer), "presend_check_status", GINT_TO_POINTER(0)); - e_event_emit((EEvent *)eme, "composer.presendchecks", (EEventTarget *)target); + e_event_emit ( + (EEvent *) eme, "composer.presendchecks", + (EEventTarget *) target); - if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (composer), "presend_check_status"))) - goto finished; + /* A non-NULL value for this key means the check failed. */ + data = g_object_get_data (G_OBJECT (composer), "presend_check_status"); - /* actually get the message now, this will sign/encrypt etc */ - message = e_msg_composer_get_message ( - composer, save_html_object_data, NULL, &error); + return (data == NULL); +} + +static gboolean +composer_presend_check_subject (EMsgComposer *composer) +{ + EComposerHeaderTable *table; + const gchar *subject; + gboolean check_passed = TRUE; + + table = e_msg_composer_get_header_table (composer); + subject = e_composer_header_table_get_subject (table); + + if (subject == NULL || subject[0] == '\0') { + if (!ask_confirm_for_empty_subject (composer)) + check_passed = FALSE; + } + + return check_passed; +} + +static gboolean +composer_presend_check_unwanted_html (EMsgComposer *composer) +{ + EDestination **recipients; + EComposerHeaderTable *table; + GConfClient *client; + gboolean check_passed = TRUE; + gboolean html_mode; + gboolean send_html; + gboolean confirm_html; + gint ii; + + client = gconf_client_get_default (); + + table = e_msg_composer_get_header_table (composer); + recipients = e_composer_header_table_get_destinations (table); + html_mode = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer)); + + send_html = gconf_client_get_bool ( + client, "/apps/evolution/mail/composer/send_html", NULL); + confirm_html = gconf_client_get_bool ( + client, "/apps/evolution/mail/prompts/unwanted_html", NULL); + + /* Only show this warning if our default is to send html. If it + * isn't, we've manually switched into html mode in the composer + * and (presumably) had a good reason for doing this. */ + if (html_mode && send_html && confirm_html && recipients != NULL) { + gboolean html_problem = FALSE; + + for (ii = 0; recipients[ii] != NULL; ii++) { + if (!e_destination_get_html_mail_pref (recipients[ii])) + html_problem = TRUE; + break; + } + + if (html_problem) { + if (!ask_confirm_for_unwanted_html_mail ( + composer, recipients)) + check_passed = FALSE; + } + } + + if (recipients != NULL) + e_destination_freev (recipients); + + g_object_unref (client); + + return check_passed; +} + +static void +composer_send_completed (EMailSession *session, + GAsyncResult *result, + AsyncContext *context) +{ + GError *error = NULL; + + e_mail_session_send_to_finish (session, result, &error); /* Ignore cancellations. */ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - g_warn_if_fail (message == NULL); g_error_free (error); - goto finished; + goto exit; } if (error != NULL) { - g_warn_if_fail (message == NULL); - e_alert_run_dialog_for_args ( - GTK_WINDOW (composer), - "mail-composer:no-build-message", + e_alert_submit ( + GTK_WIDGET (context->composer), + "mail-composer:send-error", error->message, NULL); g_error_free (error); - goto finished; - } - - g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - - /* Add info about the sending account */ - account = e_composer_header_table_get_account (table); - - if (account) { - /* FIXME: Why isn't this crap just in e_msg_composer_get_message? */ - camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Account", account->uid); - camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Transport", account->transport->url); - camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Fcc", account->sent_folder_uri); - if (account->id->organization && *account->id->organization) { - gchar *org; - - org = camel_header_encode_string ((const guchar *)account->id->organization); - camel_medium_set_header (CAMEL_MEDIUM (message), "Organization", org); - g_free (org); - } + goto exit; } - finished: + e_activity_complete (context->activity); - if (recipients) - e_destination_freev (recipients); + /* Wait for the EActivity's completion message to + * time out and then destroy the composer window. */ + g_object_weak_ref ( + G_OBJECT (context->activity), (GWeakNotify) + gtk_widget_destroy, context->composer); - return message; +exit: + async_context_free (context); } static void -em_utils_composer_send_cb (EMsgComposer *composer) +composer_send_appended (CamelFolder *outbox_folder, + GAsyncResult *result, + AsyncContext *context) { - EComposerHeaderTable *table; - CamelMimeMessage *message; - CamelMessageInfo *info; - struct _send_data *send; - CamelFolder *folder; - EAccount *account; + CamelSession *session; + GCancellable *cancellable; + gchar *message_uid = NULL; + GError *error = NULL; - table = e_msg_composer_get_header_table (composer); - account = e_composer_header_table_get_account (table); - if (!account || !account->enabled) { - e_alert_run_dialog_for_args ( - GTK_WINDOW (composer), - "mail:send-no-account-enabled", NULL); + e_mail_folder_append_message_finish ( + outbox_folder, result, &message_uid, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (message_uid == NULL); + async_context_free (context); + g_error_free (error); return; } - if ((message = composer_get_message (composer, FALSE)) == NULL) + if (error != NULL) { + g_warn_if_fail (message_uid == NULL); + e_alert_submit ( + GTK_WIDGET (context->composer), + "mail-composer:append-to-outbox-error", + error->message, NULL); + g_warning ("%s", error->message); + async_context_free (context); + g_error_free (error); return; + } - folder = e_mail_local_get_folder (E_MAIL_FOLDER_OUTBOX); - g_object_ref (folder); + session = e_msg_composer_get_session (context->composer); + cancellable = e_activity_get_cancellable (context->activity); - /* mail the message */ - e_msg_composer_set_mail_sent (composer, TRUE); - info = camel_message_info_new (NULL); + /* If we're online, go ahead and send the message now. */ + if (camel_session_get_online (session)) + e_mail_session_send_to ( + E_MAIL_SESSION (session), + outbox_folder, message_uid, NULL, + G_PRIORITY_DEFAULT, cancellable, NULL, NULL, + (GAsyncReadyCallback) composer_send_completed, + context); - camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0); + /* If we're offline, writing the message to the Outbox + * folder is as much as we can do. Tell the user. */ + else { + g_object_unref (context->activity); + context->activity = NULL; - send = g_malloc (sizeof (*send)); - send->emcs = g_object_get_data (G_OBJECT (composer), "emcs"); - if (send->emcs) - emcs_ref (send->emcs); - send->send = TRUE; - send->composer = g_object_ref (composer); - gtk_widget_hide (GTK_WIDGET (composer)); + e_alert_run_dialog_for_args ( + GTK_WINDOW (context->composer), + "mail-composer:saved-to-outbox", NULL); - mail_append_mail ( - folder, message, info, composer_send_queued_cb, send); + gtk_widget_destroy (GTK_WIDGET (context->composer)); + async_context_free (context); + } - g_object_unref (folder); - g_object_unref (message); + g_free (message_uid); } -struct _save_draft_info { - struct emcs_t *emcs; - EMsgComposer *composer; +static void +em_utils_composer_send_cb (EMsgComposer *composer, + CamelMimeMessage *message, + EActivity *activity) +{ + AsyncContext *context; + CamelFolder *outbox_folder; CamelMessageInfo *info; -}; + GCancellable *cancellable; + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + context->composer = g_object_ref (composer); + context->activity = g_object_ref (activity); + + cancellable = e_activity_get_cancellable (activity); + outbox_folder = e_mail_local_get_folder (E_MAIL_FOLDER_OUTBOX); + + info = camel_message_info_new (NULL); + camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0); + + e_mail_folder_append_message ( + outbox_folder, message, info, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) composer_send_appended, + context); + + camel_message_info_free (info); +} static void composer_set_no_change (EMsgComposer *composer) @@ -588,172 +588,211 @@ composer_set_no_change (EMsgComposer *composer) } static void -save_draft_done (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, gint ok, - const gchar *appended_uid, gpointer user_data) +composer_save_draft_complete (EMailSession *session, + GAsyncResult *result, + AsyncContext *context) { - struct _save_draft_info *sdi = user_data; - struct emcs_t *emcs; + GError *error = NULL; - if (!ok) - goto done; + /* We don't really care if this failed. If something other than + * cancellation happened, emit a runtime warning so the error is + * not completely lost. */ - if ((emcs = sdi->emcs) == NULL) - emcs = emcs_new (); + e_mail_session_handle_draft_headers_finish (session, result, &error); - if (emcs->drafts_folder) { - /* delete the original draft message */ - camel_folder_set_message_flags ( - emcs->drafts_folder, emcs->drafts_uid, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); - g_object_unref (emcs->drafts_folder); - emcs->drafts_folder = NULL; - g_free (emcs->drafts_uid); - emcs->drafts_uid = NULL; - } + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_error_free (error); - if (emcs->folder) { - /* set the replied flags etc */ - camel_folder_set_message_flags ( - emcs->folder, emcs->uid, - emcs->flags, emcs->set); - g_object_unref (emcs->folder); - emcs->folder = NULL; - g_free (emcs->uid); - emcs->uid = NULL; + else if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); } - if (appended_uid) { - g_object_ref (folder); - emcs->drafts_folder = folder; - emcs->drafts_uid = g_strdup (appended_uid); - } + /* Encode the draft message we just saved into the EMsgComposer + * as X-Evolution-Draft headers. The message will be marked for + * deletion if the user saves a newer draft message or sends the + * composed message. */ + e_msg_composer_set_draft_headers ( + context->composer, context->folder_uri, + context->message_uid); - if (e_msg_composer_is_exiting (sdi->composer)) - gtk_widget_destroy (GTK_WIDGET (sdi->composer)); + e_activity_complete (context->activity); - done: - g_object_unref (sdi->composer); - if (sdi->emcs) - emcs_unref (sdi->emcs); - camel_message_info_free (info); - g_free (sdi); + async_context_free (context); } static void -save_draft_folder (gchar *uri, CamelFolder *folder, gpointer data) +composer_save_draft_cleanup (CamelFolder *drafts_folder, + GAsyncResult *result, + AsyncContext *context) { - CamelFolder **save = data; + CamelSession *session; + GCancellable *cancellable; + GError *error = NULL; + + e_mail_folder_append_message_finish ( + drafts_folder, result, &context->message_uid, &error); + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warn_if_fail (context->message_uid == NULL); + async_context_free (context); + g_error_free (error); + return; + } - if (folder) { - *save = folder; - g_object_ref (folder); + if (error != NULL) { + g_warn_if_fail (context->message_uid == NULL); + e_alert_submit ( + GTK_WIDGET (context->composer), + "mail-composer:save-draft-error", + error->message, NULL); + async_context_free (context); + g_error_free (error); + return; } + + session = e_msg_composer_get_session (context->composer); + cancellable = e_activity_get_cancellable (context->activity); + + /* Mark the previously saved draft message for deletion. + * Note: This is just a nice-to-have; ignore failures. */ + e_mail_session_handle_draft_headers ( + E_MAIL_SESSION (session), context->message, + G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback) + composer_save_draft_complete, context); } static void -em_utils_composer_save_draft_cb (EMsgComposer *composer) +composer_save_draft_append_mail (AsyncContext *context, + CamelFolder *drafts_folder) { CamelFolder *local_drafts_folder; - EComposerHeaderTable *table; - struct _save_draft_info *sdi; - const gchar *local_drafts_folder_uri; - CamelFolder *folder = NULL; - CamelMimeMessage *msg; + GCancellable *cancellable; CamelMessageInfo *info; - CamelSession *session; - EAccount *account; - GError *error = NULL; - - /* need to get stuff from the composer here, since it could - * get destroyed while we're in mail_msg_wait() a little lower - * down, waiting for the folder to open */ - - session = e_msg_composer_get_session (composer); local_drafts_folder = e_mail_local_get_folder (E_MAIL_FOLDER_DRAFTS); - local_drafts_folder_uri = - e_mail_local_get_folder_uri (E_MAIL_FOLDER_DRAFTS); - msg = e_msg_composer_get_message_draft (composer, NULL, &error); + if (drafts_folder == NULL) + drafts_folder = g_object_ref (local_drafts_folder); + + cancellable = e_activity_get_cancellable (context->activity); + + info = camel_message_info_new (NULL); + + camel_message_info_set_flags ( + info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN, ~0); + + e_mail_folder_append_message ( + drafts_folder, context->message, + info, G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) composer_save_draft_cleanup, + context); + + camel_message_info_free (info); + + g_object_unref (drafts_folder); +} + +static void +composer_save_draft_got_folder (EMailSession *session, + GAsyncResult *result, + AsyncContext *context) +{ + CamelFolder *drafts_folder; + GError *error = NULL; + + drafts_folder = e_mail_session_uri_to_folder_finish ( + session, result, &error); /* Ignore cancellations. */ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - g_warn_if_fail (msg == NULL); + g_warn_if_fail (drafts_folder == NULL); + async_context_free (context); g_error_free (error); return; } if (error != NULL) { - g_warn_if_fail (msg == NULL); - e_alert_run_dialog_for_args ( - GTK_WINDOW (composer), - "mail-composer:no-build-message", - error->message, NULL); + gint response; + + g_warn_if_fail (drafts_folder == NULL); + + /* XXX Not showing the error message in the dialog? */ g_error_free (error); - return; + + /* If we can't retrieve the Drafts folder for the + * selected account, ask the user if he wants to + * save to the local Drafts folder instead. */ + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (context->composer), + "mail:ask-default-drafts", NULL); + if (response != GTK_RESPONSE_YES) { + async_context_free (context); + return; + } } - g_return_if_fail (CAMEL_IS_MIME_MESSAGE (msg)); + composer_save_draft_append_mail (context, drafts_folder); +} + +static void +em_utils_composer_save_draft_cb (EMsgComposer *composer, + CamelMimeMessage *message, + EActivity *activity) +{ + AsyncContext *context; + EComposerHeaderTable *table; + const gchar *drafts_folder_uri = NULL; + const gchar *local_drafts_folder_uri; + CamelSession *session; + EAccount *account; + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + context->composer = g_object_ref (composer); + context->activity = g_object_ref (activity); + + session = e_msg_composer_get_session (composer); + + local_drafts_folder_uri = + e_mail_local_get_folder_uri (E_MAIL_FOLDER_DRAFTS); table = e_msg_composer_get_header_table (composer); account = e_composer_header_table_get_account (table); - sdi = g_malloc (sizeof (struct _save_draft_info)); - sdi->composer = g_object_ref (composer); - sdi->emcs = g_object_get_data (G_OBJECT (composer), "emcs"); - if (sdi->emcs) - emcs_ref (sdi->emcs); + if (account != NULL && account->enabled) + drafts_folder_uri = account->drafts_folder_uri; - if (account && account->drafts_folder_uri && - strcmp (account->drafts_folder_uri, local_drafts_folder_uri) != 0) { - gint id; + if (g_strcmp0 (drafts_folder_uri, local_drafts_folder_uri) == 0) + drafts_folder_uri = NULL; - id = mail_get_folder ( - E_MAIL_SESSION (session), - account->drafts_folder_uri, 0, - save_draft_folder, &folder, - mail_msg_unordered_push); - mail_msg_wait (id); - - if (!folder || !account->enabled) { - if (e_alert_run_dialog_for_args ((GtkWindow *)composer, "mail:ask-default-drafts", NULL) != GTK_RESPONSE_YES) { - g_object_unref (composer); - g_object_unref (msg); - if (sdi->emcs) - emcs_unref (sdi->emcs); - g_free (sdi); - return; - } - - folder = local_drafts_folder; - g_object_ref (local_drafts_folder); - } + if (drafts_folder_uri == NULL) { + composer_save_draft_append_mail (context, NULL); + context->folder_uri = g_strdup (local_drafts_folder_uri); } else { - folder = local_drafts_folder; - g_object_ref (folder); - } - - info = camel_message_info_new (NULL); + GCancellable *cancellable; - camel_message_info_set_flags ( - info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN, ~0); + cancellable = e_activity_get_cancellable (activity); + context->folder_uri = g_strdup (drafts_folder_uri); - mail_append_mail (folder, msg, info, save_draft_done, sdi); - g_object_unref (folder); - g_object_unref (msg); + e_mail_session_uri_to_folder ( + E_MAIL_SESSION (session), + drafts_folder_uri, 0, G_PRIORITY_DEFAULT, + cancellable, (GAsyncReadyCallback) + composer_save_draft_got_folder, context); + } } static void em_utils_composer_print_cb (EMsgComposer *composer, - GtkPrintOperationAction action) + GtkPrintOperationAction action, + CamelMimeMessage *message, + EActivity *activity) { - CamelMimeMessage *message; EMFormatHTMLPrint *efhp; - message = e_msg_composer_get_message_print (composer, 1, NULL); - efhp = em_format_html_print_new (NULL, action); em_format_html_print_raw_message (efhp, message); g_object_unref (efhp); @@ -998,14 +1037,14 @@ traverse_parts (GSList *clues, CamelMimeMessage *message, CamelDataWrapper *cont static GtkWidget * edit_message (EShell *shell, + CamelFolder *folder, CamelMimeMessage *message, - CamelFolder *drafts, - const gchar *uid) + const gchar *message_uid) { EMsgComposer *composer; /* Template specific code follows. */ - if (em_utils_folder_is_templates (drafts, NULL) == TRUE) { + if (em_utils_folder_is_templates (folder, NULL)) { GConfClient *gconf; GSList *clue_list = NULL; @@ -1022,11 +1061,14 @@ edit_message (EShell *shell, composer = e_msg_composer_new_with_message (shell, message, NULL); - if (em_utils_folder_is_drafts (drafts, NULL)) { - struct emcs_t *emcs; + if (message_uid != NULL) { + const gchar *folder_uri; + + folder_uri = camel_folder_get_uri (folder); - emcs = g_object_get_data (G_OBJECT (composer), "emcs"); - emcs_set_drafts_info (emcs, drafts, uid); + if (em_utils_folder_is_drafts (folder, folder_uri)) + e_msg_composer_set_draft_headers ( + composer, folder_uri, message_uid); } composer_set_no_change (composer); @@ -1039,21 +1081,22 @@ edit_message (EShell *shell, /** * em_utils_edit_message: * @shell: an #EShell - * @message: message to edit - * @folder: used to recognize the templates folder + * @folder: a #CamelFolder + * @message: a #CamelMimeMessage * * Opens a composer filled in with the headers/mime-parts/etc of * @message. **/ GtkWidget * em_utils_edit_message (EShell *shell, - CamelMimeMessage *message, - CamelFolder *folder) + CamelFolder *folder, + CamelMimeMessage *message) { g_return_val_if_fail (E_IS_SHELL (shell), NULL); + g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - return edit_message (shell, message, folder, NULL); + return edit_message (shell, folder, message, NULL); } static void @@ -1071,7 +1114,7 @@ edit_messages_replace (CamelFolder *folder, for (ii = 0; ii < msgs->len; ii++) { camel_medium_remove_header ( CAMEL_MEDIUM (msgs->pdata[ii]), "X-Mailer"); - edit_message (shell, msgs->pdata[ii], folder, uids->pdata[ii]); + edit_message (shell, folder, msgs->pdata[ii], uids->pdata[ii]); } g_object_unref (shell); @@ -1092,7 +1135,7 @@ edit_messages_no_replace (CamelFolder *folder, for (ii = 0; ii < msgs->len; ii++) { camel_medium_remove_header ( CAMEL_MEDIUM (msgs->pdata[ii]), "X-Mailer"); - edit_message (shell, msgs->pdata[ii], NULL, NULL); + edit_message (shell, NULL, msgs->pdata[ii], NULL); } g_object_unref (shell); @@ -1300,6 +1343,7 @@ forward_non_attached (EShell *shell, { CamelMimeMessage *message; EMsgComposer *composer = NULL; + const gchar *folder_uri; gchar *subject, *text; gint i; guint32 flags; @@ -1307,6 +1351,8 @@ forward_non_attached (EShell *shell, if (messages->len == 0) return NULL; + folder_uri = camel_folder_get_uri (folder); + flags = EM_FORMAT_QUOTE_HEADERS | EM_FORMAT_QUOTE_KEEP_SIG; if (style == MAIL_CONFIG_FORWARD_QUOTED) flags |= EM_FORMAT_QUOTE_CITE; @@ -1329,12 +1375,11 @@ forward_non_attached (EShell *shell, e_msg_composer_set_body_text (composer, text, len); - if (uids && uids->pdata[i]) { - struct emcs_t *emcs; - - emcs = g_object_get_data (G_OBJECT (composer), "emcs"); - emcs_set_folder_info (emcs, folder, uids->pdata[i], CAMEL_MESSAGE_FORWARDED, CAMEL_MESSAGE_FORWARDED); - } + if (uids && uids->pdata[i]) + e_msg_composer_set_source_headers ( + composer, folder_uri, + uids->pdata[i], + CAMEL_MESSAGE_FORWARDED); emu_update_composers_security (composer, validity_found); composer_set_no_change (composer); @@ -2533,7 +2578,6 @@ em_utils_reply_to_message (EShell *shell, EMsgComposer *composer; EAccount *account; guint32 flags; - struct emcs_t *emcs; g_return_val_if_fail (E_IS_SHELL (shell), NULL); @@ -2599,8 +2643,15 @@ em_utils_reply_to_message (EShell *shell, composer_set_body (composer, message, source); g_object_unref (message); - emcs = g_object_get_data (G_OBJECT (composer), "emcs"); - emcs_set_folder_info (emcs, folder, uid, flags, flags); + + if (folder != NULL) { + const gchar *folder_uri; + + folder_uri = camel_folder_get_uri (folder); + + e_msg_composer_set_source_headers ( + composer, folder_uri, uid, flags); + } composer_set_no_change (composer); @@ -2679,7 +2730,6 @@ em_configure_new_composer (EMsgComposer *composer) EComposerHeaderTable *table; EComposerHeaderType header_type; EComposerHeader *header; - struct emcs_t *emcs; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); @@ -2687,11 +2737,29 @@ em_configure_new_composer (EMsgComposer *composer) table = e_msg_composer_get_header_table (composer); header = e_composer_header_table_get_header (table, header_type); - emcs = emcs_new (); + g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_recipients), NULL); - g_object_set_data_full ( - G_OBJECT (composer), "emcs", emcs, - (GDestroyNotify) emcs_unref); + g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_account), NULL); + + g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_downloads), NULL); + + g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_plugins), NULL); + + g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_subject), NULL); + + g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_unwanted_html), NULL); g_signal_connect ( composer, "send", diff --git a/mail/em-composer-utils.h b/mail/em-composer-utils.h index fa991863f6..81e8cf1eb0 100644 --- a/mail/em-composer-utils.h +++ b/mail/em-composer-utils.h @@ -37,8 +37,8 @@ EMsgComposer * em_utils_compose_new_message_with_mailto const gchar *url, const gchar *from_uri); GtkWidget * em_utils_edit_message (EShell *shell, - CamelMimeMessage *message, - CamelFolder *folder); + CamelFolder *folder, + CamelMimeMessage *message); void em_utils_edit_messages (EShell *shell, CamelFolder *folder, GPtrArray *uids, diff --git a/modules/composer-autosave/e-autosave-utils.c b/modules/composer-autosave/e-autosave-utils.c index 8dc9285884..70a609e833 100644 --- a/modules/composer-autosave/e-autosave-utils.c +++ b/modules/composer-autosave/e-autosave-utils.c @@ -37,6 +37,7 @@ struct _LoadContext { struct _SaveContext { GCancellable *cancellable; + GOutputStream *output_stream; }; static void @@ -54,6 +55,9 @@ save_context_free (SaveContext *context) if (context->cancellable != NULL) g_object_unref (context->cancellable); + if (context->output_stream != NULL) + g_object_unref (context->output_stream); + g_slice_free (SaveContext, context); } @@ -194,15 +198,12 @@ save_snapshot_splice_cb (GOutputStream *output_stream, } static void -save_snapshot_replace_cb (GFile *snapshot_file, - GAsyncResult *result, - GSimpleAsyncResult *simple) +save_snapshot_get_message_cb (EMsgComposer *composer, + GAsyncResult *result, + GSimpleAsyncResult *simple) { - GObject *object; - EMsgComposer *composer; SaveContext *context; CamelMimeMessage *message; - GFileOutputStream *output_stream; GInputStream *input_stream; CamelStream *camel_stream; GByteArray *buffer; @@ -210,34 +211,13 @@ save_snapshot_replace_cb (GFile *snapshot_file, context = g_simple_async_result_get_op_res_gpointer (simple); - output_stream = g_file_replace_finish (snapshot_file, result, &error); - - if (error != NULL) { - g_warn_if_fail (output_stream == NULL); - g_simple_async_result_set_from_error (simple, error); - g_simple_async_result_complete (simple); - g_object_unref (simple); - g_error_free (error); - return; - } - - g_return_if_fail (G_IS_OUTPUT_STREAM (output_stream)); - - /* g_async_result_get_source_object() returns a new reference. */ - object = g_async_result_get_source_object (G_ASYNC_RESULT (simple)); - - /* Extract a MIME message from the composer. */ - composer = E_MSG_COMPOSER (object); - message = e_msg_composer_get_message_draft ( - composer, context->cancellable, &error); - - g_object_unref (object); + message = e_msg_composer_get_message_draft_finish ( + composer, result, &error); if (error != NULL) { g_warn_if_fail (message == NULL); g_simple_async_result_set_from_error (simple, error); g_simple_async_result_complete (simple); - g_object_unref (output_stream); g_object_unref (simple); g_error_free (error); return; @@ -255,6 +235,7 @@ save_snapshot_replace_cb (GFile *snapshot_file, camel_data_wrapper_decode_to_stream_sync ( CAMEL_DATA_WRAPPER (message), camel_stream, NULL, NULL); g_object_unref (camel_stream); + g_object_unref (message); /* Load the buffer into a GMemoryInputStream. */ @@ -268,7 +249,7 @@ save_snapshot_replace_cb (GFile *snapshot_file, /* Splice the input and output streams. */ g_output_stream_splice_async ( - G_OUTPUT_STREAM (output_stream), input_stream, + context->output_stream, input_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, context->cancellable, @@ -276,7 +257,45 @@ save_snapshot_replace_cb (GFile *snapshot_file, simple); g_object_unref (input_stream); - g_object_unref (output_stream); +} + +static void +save_snapshot_replace_cb (GFile *snapshot_file, + GAsyncResult *result, + GSimpleAsyncResult *simple) +{ + GObject *object; + SaveContext *context; + GFileOutputStream *output_stream; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_replace_finish (snapshot_file, result, &error); + context->output_stream = (GOutputStream *) output_stream; + + if (error != NULL) { + g_warn_if_fail (output_stream == NULL); + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + g_error_free (error); + return; + } + + g_return_if_fail (G_IS_OUTPUT_STREAM (output_stream)); + + /* g_async_result_get_source_object() returns a new reference. */ + object = g_async_result_get_source_object (G_ASYNC_RESULT (simple)); + + /* Extract a MIME message from the composer. */ + e_msg_composer_get_message_draft ( + E_MSG_COMPOSER (object), G_PRIORITY_DEFAULT, + context->cancellable, (GAsyncReadyCallback) + save_snapshot_get_message_cb, simple); + + g_object_unref (object); } static EMsgComposer * diff --git a/plugins/email-custom-header/email-custom-header.c b/plugins/email-custom-header/email-custom-header.c index 94cc793d25..f202935c3e 100644 --- a/plugins/email-custom-header/email-custom-header.c +++ b/plugins/email-custom-header/email-custom-header.c @@ -471,7 +471,7 @@ epech_append_to_custom_header (CustomHeaderOptionsDialog *dialog, gint state, gp temp_header_value_ptr = &g_array_index (temp_header_ptr->sub_header_type_value, CustomSubHeader,sub_type_index); if (sub_type_index == g_array_index (priv->header_index_type, gint, index_subtype)) { - e_msg_composer_modify_header (composer, (temp_header_ptr->header_type_value)->str, + e_msg_composer_set_header (composer, (temp_header_ptr->header_type_value)->str, (temp_header_value_ptr->sub_header_string_value)->str); } } diff --git a/plugins/face/face.c b/plugins/face/face.c index 2013f7dac6..57e80832e6 100644 --- a/plugins/face/face.c +++ b/plugins/face/face.c @@ -466,7 +466,7 @@ face_handle_send (EPlugin *ep, EMEventTargetComposer *target) gchar *face = get_face_base64 (); if (face) - e_msg_composer_modify_header (target->composer, "Face", face); + e_msg_composer_set_header (target->composer, "Face", face); g_free (face); } diff --git a/plugins/templates/templates.c b/plugins/templates/templates.c index 901f902ae9..a2bac3dec9 100644 --- a/plugins/templates/templates.c +++ b/plugins/templates/templates.c @@ -519,7 +519,7 @@ create_new_message (CamelFolder *folder, const gchar *uid, CamelMimeMessage *mes camel_mime_message_get_recipients (template, CAMEL_RECIPIENT_TYPE_BCC)); /* Create the composer */ - em_utils_edit_message (shell, new, folder); + em_utils_edit_message (shell, folder, new); g_object_unref (new); } @@ -549,7 +549,7 @@ build_template_menus_recurse (GtkUIManager *ui_manager, guint *action_count, guint merge_id, CamelFolderInfo *folder_info, - CamelFolder *message_folder, + CamelFolder *message_folder, const gchar *message_uid) { CamelStore *store; @@ -669,28 +669,26 @@ build_template_menus_recurse (GtkUIManager *ui_manager, } static void -action_template_cb (GtkAction *action, - EMsgComposer *composer) +got_message_draft_cb (EMsgComposer *composer, + GAsyncResult *result) { + CamelMimeMessage *message; CamelMessageInfo *info; - CamelMimeMessage *msg; CamelFolder *folder; GError *error = NULL; - /* Get the templates folder and all UIDs of the messages there. */ - folder = e_mail_local_get_folder (E_MAIL_FOLDER_TEMPLATES); - - msg = e_msg_composer_get_message_draft (composer, NULL, &error); + message = e_msg_composer_get_message_draft_finish ( + composer, result, &error); /* Ignore cancellations. */ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - g_warn_if_fail (msg == NULL); + g_warn_if_fail (message == NULL); g_error_free (error); return; } if (error != NULL) { - g_warn_if_fail (msg == NULL); + g_warn_if_fail (message == NULL); e_alert_run_dialog_for_args ( GTK_WINDOW (composer), "mail-composer:no-build-message", @@ -699,7 +697,10 @@ action_template_cb (GtkAction *action, return; } - g_return_if_fail (CAMEL_IS_MIME_MESSAGE (msg)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + /* Get the templates folder and all UIDs of the messages there. */ + folder = e_mail_local_get_folder (E_MAIL_FOLDER_TEMPLATES); info = camel_message_info_new (NULL); @@ -709,7 +710,19 @@ action_template_cb (GtkAction *action, camel_message_info_set_flags ( info, CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DRAFT, ~0); - mail_append_mail (folder, msg, info, NULL, composer); + mail_append_mail (folder, message, info, NULL, composer); + + g_object_unref (message); +} + +static void +action_template_cb (GtkAction *action, + EMsgComposer *composer) +{ + /* XXX Pass a GCancellable */ + e_msg_composer_get_message_draft ( + composer, G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) got_message_draft_cb, NULL); } static GtkActionEntry composer_entries[] = { diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index 21faf33517..4ee47766b4 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -9,7 +9,9 @@ widgetsinclude_HEADERS = \ e-account-manager.h \ e-account-tree-view.h \ e-action-combo-box.h \ + e-activity-bar.h \ e-activity-proxy.h \ + e-alert-bar.h \ e-attachment.h \ e-attachment-button.h \ e-attachment-dialog.h \ @@ -85,7 +87,9 @@ libemiscwidgets_la_SOURCES = \ e-account-manager.c \ e-account-tree-view.c \ e-action-combo-box.c \ + e-activity-bar.c \ e-activity-proxy.c \ + e-alert-bar.c \ e-attachment.c \ e-attachment-button.c \ e-attachment-dialog.c \ diff --git a/widgets/misc/e-activity-bar.c b/widgets/misc/e-activity-bar.c new file mode 100644 index 0000000000..0337822adc --- /dev/null +++ b/widgets/misc/e-activity-bar.c @@ -0,0 +1,351 @@ +/* + * e-activity-bar.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-activity-bar.h" + +#define E_ACTIVITY_BAR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_ACTIVITY_BAR, EActivityBarPrivate)) + +#define FEEDBACK_PERIOD 1 /* seconds */ + +struct _EActivityBarPrivate { + EActivity *activity; /* weak reference */ + GtkWidget *image; /* not referenced */ + GtkWidget *label; /* not referenced */ + GtkWidget *cancel; /* not referenced */ + + /* If the user clicks the Cancel button, keep the cancelled + * EActivity object alive for a short duration so the user + * gets some visual feedback that cancellation worked. */ + guint timeout_id; +}; + +enum { + PROP_0, + PROP_ACTIVITY +}; + +G_DEFINE_TYPE ( + EActivityBar, + e_activity_bar, + GTK_TYPE_INFO_BAR) + +static void +activity_bar_update (EActivityBar *bar) +{ + EActivity *activity; + GCancellable *cancellable; + const gchar *icon_name; + gboolean cancelled; + gboolean completed; + gboolean sensitive; + gboolean visible; + gchar *description; + + activity = e_activity_bar_get_activity (bar); + + if (activity == NULL) { + gtk_widget_hide (GTK_WIDGET (bar)); + return; + } + + cancellable = e_activity_get_cancellable (activity); + cancelled = g_cancellable_is_cancelled (cancellable); + completed = e_activity_is_completed (activity); + icon_name = e_activity_get_icon_name (activity); + + description = e_activity_describe (activity); + gtk_label_set_text (GTK_LABEL (bar->priv->label), description); + + if (cancelled) { + PangoAttribute *attr; + PangoAttrList *attr_list; + + attr_list = pango_attr_list_new (); + + attr = pango_attr_strikethrough_new (TRUE); + pango_attr_list_insert (attr_list, attr); + + gtk_label_set_attributes ( + GTK_LABEL (bar->priv->label), attr_list); + + pango_attr_list_unref (attr_list); + } else + gtk_label_set_attributes ( + GTK_LABEL (bar->priv->label), NULL); + + if (cancelled) + gtk_image_set_from_stock ( + GTK_IMAGE (bar->priv->image), + GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON); + else { + if (completed) + icon_name = "emblem-default"; + gtk_image_set_from_icon_name ( + GTK_IMAGE (bar->priv->image), + icon_name, GTK_ICON_SIZE_BUTTON); + } + + visible = (icon_name != NULL); + gtk_widget_set_visible (bar->priv->image, visible); + + visible = (cancellable != NULL); + gtk_widget_set_visible (bar->priv->cancel, visible); + + sensitive = !cancelled && !completed; + gtk_widget_set_sensitive (bar->priv->cancel, sensitive); + + visible = (description != NULL && *description != '\0'); + gtk_widget_set_visible (GTK_WIDGET (bar), visible); + + g_free (description); +} + +static void +activity_bar_cancel (EActivityBar *bar) +{ + EActivity *activity; + GCancellable *cancellable; + + activity = e_activity_bar_get_activity (bar); + g_return_if_fail (E_IS_ACTIVITY (activity)); + + cancellable = e_activity_get_cancellable (activity); + g_cancellable_cancel (cancellable); +} + +static void +activity_bar_feedback (EActivityBar *bar) +{ + EActivity *activity; + + activity = e_activity_bar_get_activity (bar); + g_return_if_fail (E_IS_ACTIVITY (activity)); + + if (bar->priv->timeout_id > 0) + g_source_remove (bar->priv->timeout_id); + + /* Hold a reference on the EActivity for a short + * period so the activity bar stays visible. */ + bar->priv->timeout_id = g_timeout_add_seconds_full ( + G_PRIORITY_LOW, FEEDBACK_PERIOD, (GSourceFunc) gtk_false, + g_object_ref (activity), (GDestroyNotify) g_object_unref); +} + +static void +activity_bar_weak_notify_cb (EActivityBar *bar, + GObject *where_the_object_was) +{ + bar->priv->activity = NULL; + e_activity_bar_set_activity (bar, NULL); +} + +static void +activity_bar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ACTIVITY: + e_activity_bar_set_activity ( + E_ACTIVITY_BAR (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +activity_bar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ACTIVITY: + g_value_set_object ( + value, e_activity_bar_get_activity ( + E_ACTIVITY_BAR (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +activity_bar_dispose (GObject *object) +{ + EActivityBarPrivate *priv; + + priv = E_ACTIVITY_BAR_GET_PRIVATE (object); + + if (priv->timeout_id > 0) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + if (priv->activity != NULL) { + g_signal_handlers_disconnect_matched ( + priv->activity, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, object); + g_object_weak_unref ( + G_OBJECT (priv->activity), (GWeakNotify) + activity_bar_weak_notify_cb, object); + priv->activity = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_activity_bar_parent_class)->dispose (object); +} + +static void +e_activity_bar_class_init (EActivityBarClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (EActivityBarPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = activity_bar_set_property; + object_class->get_property = activity_bar_get_property; + object_class->dispose = activity_bar_dispose; + + g_object_class_install_property ( + object_class, + PROP_ACTIVITY, + g_param_spec_object ( + "activity", + NULL, + NULL, + E_TYPE_ACTIVITY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +e_activity_bar_init (EActivityBar *bar) +{ + GtkWidget *container; + GtkWidget *widget; + + bar->priv = E_ACTIVITY_BAR_GET_PRIVATE (bar); + + container = gtk_info_bar_get_content_area (GTK_INFO_BAR (bar)); + + widget = gtk_hbox_new (FALSE, 12); + gtk_container_add (GTK_CONTAINER (container), widget); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + bar->priv->image = widget; + gtk_widget_show (widget); + + widget = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + bar->priv->label = widget; + gtk_widget_show (widget); + + /* This is only shown if the EActivity has a GCancellable. */ + widget = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_info_bar_add_action_widget ( + GTK_INFO_BAR (bar), widget, GTK_RESPONSE_CANCEL); + bar->priv->cancel = widget; + gtk_widget_hide (widget); + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (activity_bar_cancel), bar); +} + +GtkWidget * +e_activity_bar_new (void) +{ + return g_object_new (E_TYPE_ACTIVITY_BAR, NULL); +} + +EActivity * +e_activity_bar_get_activity (EActivityBar *bar) +{ + g_return_val_if_fail (E_IS_ACTIVITY_BAR (bar), NULL); + + return bar->priv->activity; +} + +void +e_activity_bar_set_activity (EActivityBar *bar, + EActivity *activity) +{ + g_return_if_fail (E_IS_ACTIVITY_BAR (bar)); + + if (activity != NULL) { + g_return_if_fail (E_IS_ACTIVITY (activity)); + g_object_weak_ref ( + G_OBJECT (activity), (GWeakNotify) + activity_bar_weak_notify_cb, bar); + } + + if (bar->priv->timeout_id > 0) { + g_source_remove (bar->priv->timeout_id); + bar->priv->timeout_id = 0; + } + + if (bar->priv->activity != NULL) { + g_signal_handlers_disconnect_matched ( + bar->priv->activity, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, bar); + g_object_weak_unref ( + G_OBJECT (bar->priv->activity), + (GWeakNotify) activity_bar_weak_notify_cb, bar); + } + + bar->priv->activity = activity; + + if (activity != NULL) { + g_signal_connect_swapped ( + activity, "cancelled", + G_CALLBACK (activity_bar_feedback), bar); + + g_signal_connect_swapped ( + activity, "completed", + G_CALLBACK (activity_bar_feedback), bar); + + g_signal_connect_swapped ( + activity, "cancelled", + G_CALLBACK (activity_bar_update), bar); + + g_signal_connect_swapped ( + activity, "completed", + G_CALLBACK (activity_bar_update), bar); + + g_signal_connect_swapped ( + activity, "notify", + G_CALLBACK (activity_bar_update), bar); + } + + activity_bar_update (bar); + + g_object_notify (G_OBJECT (bar), "activity"); +} diff --git a/widgets/misc/e-activity-bar.h b/widgets/misc/e-activity-bar.h new file mode 100644 index 0000000000..24f56eca53 --- /dev/null +++ b/widgets/misc/e-activity-bar.h @@ -0,0 +1,67 @@ +/* + * e-activity-bar.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_ACTIVITY_BAR_H +#define E_ACTIVITY_BAR_H + +#include <gtk/gtk.h> +#include <e-util/e-activity.h> + +/* Standard GObject macros */ +#define E_TYPE_ACTIVITY_BAR \ + (e_activity_bar_get_type ()) +#define E_ACTIVITY_BAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_ACTIVITY_BAR, EActivityBar)) +#define E_ACTIVITY_BAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_ACTIVITY_BAR, EActivityBarClass)) +#define E_IS_ACTIVITY_BAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_ACTIVITY_BAR)) +#define E_IS_ACTIVITY_BAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_ACTIVITY_BAR)) +#define E_ACTIVITY_BAR_GET_CLASS(obj) \ + (G_TYPE_CHECK_INSTANCE_GET_TYPE \ + ((obj), E_TYPE_ACTIVITY_BAR, EActivityBarClass)) + +G_BEGIN_DECLS + +typedef struct _EActivityBar EActivityBar; +typedef struct _EActivityBarClass EActivityBarClass; +typedef struct _EActivityBarPrivate EActivityBarPrivate; + +struct _EActivityBar { + GtkInfoBar parent; + EActivityBarPrivate *priv; +}; + +struct _EActivityBarClass { + GtkInfoBarClass parent_class; +}; + +GType e_activity_bar_get_type (void); +GtkWidget * e_activity_bar_new (void); +EActivity * e_activity_bar_get_activity (EActivityBar *bar); +void e_activity_bar_set_activity (EActivityBar *bar, + EActivity *activity); + +G_END_DECLS + +#endif /* E_ACTIVITY_BAR_H */ diff --git a/widgets/misc/e-activity-proxy.c b/widgets/misc/e-activity-proxy.c index e96f18ed63..26d5d30ba0 100644 --- a/widgets/misc/e-activity-proxy.c +++ b/widgets/misc/e-activity-proxy.c @@ -69,6 +69,7 @@ activity_proxy_update (EActivityProxy *proxy) gboolean clickable; gboolean completed; gboolean sensitive; + gboolean visible; gchar *description; activity = proxy->priv->activity; @@ -108,10 +109,8 @@ activity_proxy_update (EActivityProxy *proxy) gtk_widget_hide (proxy->priv->image); } - if (cancellable != NULL) - gtk_widget_show (proxy->priv->cancel); - else - gtk_widget_hide (proxy->priv->cancel); + visible = (cancellable != NULL); + gtk_widget_set_visible (proxy->priv->cancel, visible); sensitive = !(cancelled || completed); gtk_widget_set_sensitive (proxy->priv->cancel, sensitive); diff --git a/widgets/misc/e-alert-bar.c b/widgets/misc/e-alert-bar.c new file mode 100644 index 0000000000..7796482689 --- /dev/null +++ b/widgets/misc/e-alert-bar.c @@ -0,0 +1,235 @@ +/* + * e-alert-bar.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-alert-bar.h" + +#include <config.h> +#include <glib/gi18n-lib.h> + +#define E_ALERT_BAR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_ALERT_BAR, EAlertBarPrivate)) + +#define ICON_SIZE GTK_ICON_SIZE_DIALOG + +struct _EAlertBarPrivate { + GQueue alerts; + GtkWidget *image; /* not referenced */ + GtkWidget *primary_label; /* not referenced */ + GtkWidget *secondary_label; /* not referenced */ +}; + +G_DEFINE_TYPE ( + EAlertBar, + e_alert_bar, + GTK_TYPE_INFO_BAR) + +static void +alert_bar_show_alert (EAlertBar *alert_bar) +{ + GtkImage *image; + GtkLabel *label; + GtkInfoBar *info_bar; + GtkWidget *action_area; + EAlertButton *buttons; + EAlert *alert; + GList *children; + GtkMessageType message_type; + const gchar *stock_id; + const gchar *text; + gint response_id; + + info_bar = GTK_INFO_BAR (alert_bar); + action_area = gtk_info_bar_get_action_area (info_bar); + + alert = g_queue_peek_head (&alert_bar->priv->alerts); + g_return_if_fail (E_IS_ALERT (alert)); + + /* Remove all buttons from the previous alert. */ + children = gtk_container_get_children (GTK_CONTAINER (action_area)); + while (children != NULL) { + GtkWidget *child = GTK_WIDGET (children->data); + gtk_container_remove (GTK_CONTAINER (action_area), child); + children = g_list_delete_link (children, children); + } + + /* Add new buttons. */ + buttons = e_alert_peek_buttons (alert); + if (buttons == NULL) { + gtk_info_bar_add_button ( + info_bar, _("_Dismiss"), GTK_RESPONSE_CLOSE); + } else while (buttons != NULL) { + const gchar *button_text; + + if (buttons->stock != NULL) + button_text = buttons->stock; + else + button_text = buttons->label; + + gtk_info_bar_add_button ( + info_bar, button_text, buttons->response); + + buttons = buttons->next; + } + + response_id = e_alert_get_default_response (alert); + gtk_info_bar_set_default_response (info_bar, response_id); + + message_type = e_alert_get_message_type (alert); + gtk_info_bar_set_message_type (info_bar, message_type); + + text = e_alert_get_primary_text (alert); + label = GTK_LABEL (alert_bar->priv->primary_label); + gtk_label_set_text (label, text); + + text = e_alert_get_secondary_text (alert); + label = GTK_LABEL (alert_bar->priv->secondary_label); + gtk_label_set_text (label, text); + + stock_id = e_alert_get_stock_id (alert); + image = GTK_IMAGE (alert_bar->priv->image); + gtk_image_set_from_stock (image, stock_id, ICON_SIZE); + + gtk_widget_show (GTK_WIDGET (alert_bar)); +} + +static void +alert_bar_dispose (GObject *object) +{ + EAlertBarPrivate *priv; + + priv = E_ALERT_BAR_GET_PRIVATE (object); + + while (!g_queue_is_empty (&priv->alerts)) + g_object_unref (g_queue_pop_head (&priv->alerts)); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_alert_bar_parent_class)->dispose (object); +} + +static void +alert_bar_response (GtkInfoBar *info_bar, + gint response_id) +{ + EAlertBar *alert_bar; + EAlert *alert; + + alert_bar = E_ALERT_BAR (info_bar); + + alert = g_queue_pop_head (&alert_bar->priv->alerts); + e_alert_response (alert, response_id); + g_object_unref (alert); + + if (!g_queue_is_empty (&alert_bar->priv->alerts)) + alert_bar_show_alert (alert_bar); + else + gtk_widget_hide (GTK_WIDGET (alert_bar)); +} + +static void +e_alert_bar_class_init (EAlertBarClass *class) +{ + GObjectClass *object_class; + GtkInfoBarClass *info_bar_class; + + g_type_class_add_private (class, sizeof (EAlertBarPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = alert_bar_dispose; + + info_bar_class = GTK_INFO_BAR_CLASS (class); + info_bar_class->response = alert_bar_response; +} + +static void +e_alert_bar_init (EAlertBar *alert_bar) +{ + GtkWidget *container; + GtkWidget *widget; + PangoAttribute *attr; + PangoAttrList *attr_list; + + alert_bar->priv = E_ALERT_BAR_GET_PRIVATE (alert_bar); + + g_queue_init (&alert_bar->priv->alerts); + + container = gtk_info_bar_get_content_area (GTK_INFO_BAR (alert_bar)); + + widget = gtk_hbox_new (FALSE, 12); + gtk_container_add (GTK_CONTAINER (container), widget); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_image_new (); + gtk_misc_set_alignment (GTK_MISC (widget), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + alert_bar->priv->image = widget; + gtk_widget_show (widget); + + widget = gtk_vbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + attr_list = pango_attr_list_new (); + attr = pango_attr_scale_new (PANGO_SCALE_LARGE); + pango_attr_list_insert (attr_list, attr); + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + pango_attr_list_insert (attr_list, attr); + + widget = gtk_label_new (NULL); + gtk_label_set_attributes (GTK_LABEL (widget), attr_list); + gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + alert_bar->priv->primary_label = widget; + gtk_widget_show (widget); + + widget = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + alert_bar->priv->secondary_label = widget; + gtk_widget_show (widget); + + pango_attr_list_unref (attr_list); +} + +GtkWidget * +e_alert_bar_new (void) +{ + return g_object_new (E_TYPE_ALERT_BAR, NULL); +} + +void +e_alert_bar_add_alert (EAlertBar *alert_bar, + EAlert *alert) +{ + gboolean show_it_now; + + g_return_if_fail (E_IS_ALERT_BAR (alert_bar)); + g_return_if_fail (E_IS_ALERT (alert)); + + show_it_now = g_queue_is_empty (&alert_bar->priv->alerts); + g_queue_push_tail (&alert_bar->priv->alerts, g_object_ref (alert)); + + if (show_it_now) + alert_bar_show_alert (alert_bar); +} diff --git a/widgets/misc/e-alert-bar.h b/widgets/misc/e-alert-bar.h new file mode 100644 index 0000000000..fc23dec8bf --- /dev/null +++ b/widgets/misc/e-alert-bar.h @@ -0,0 +1,66 @@ +/* + * e-alert-bar.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_ALERT_BAR_H +#define E_ALERT_BAR_H + +#include <gtk/gtk.h> +#include <e-util/e-alert.h> + +/* Standard GObject macros */ +#define E_TYPE_ALERT_BAR \ + (e_alert_bar_get_type ()) +#define E_ALERT_BAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_ALERT_BAR, EAlertBar)) +#define E_ALERT_BAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_ALERT_BAR, EAlertBarClass)) +#define E_IS_ALERT_BAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_ALERT_BAR)) +#define E_IS_ALERT_BAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_ALERT_BAR)) +#define E_ALERT_BAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_ALERT_BAR, EAlertBarClass)) + +G_BEGIN_DECLS + +typedef struct _EAlertBar EAlertBar; +typedef struct _EAlertBarClass EAlertBarClass; +typedef struct _EAlertBarPrivate EAlertBarPrivate; + +struct _EAlertBar { + GtkInfoBar parent; + EAlertBarPrivate *priv; +}; + +struct _EAlertBarClass { + GtkInfoBarClass parent_class; +}; + +GType e_alert_bar_get_type (void); +GtkWidget * e_alert_bar_new (void); +void e_alert_bar_add_alert (EAlertBar *alert_bar, + EAlert *alert); + +G_END_DECLS + +#endif /* E_ALERT_BAR_H */ |