diff options
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 */ |