Blob Blame History Raw
/*
 * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com)
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * 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 General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "evolution-config.h"

#include <string.h>
#include <stdio.h>

#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>

#include <camel/camel.h>
#include <e-util/e-util.h>
#include <libemail-engine/libemail-engine.h>

#include "e-mail-notes.h"

#define E_TYPE_MAIL_NOTES_EDITOR \
	(e_mail_notes_editor_get_type ())
#define E_MAIL_NOTES_EDITOR(obj) \
	(G_TYPE_CHECK_INSTANCE_CAST \
	((obj), E_TYPE_MAIL_NOTES_EDITOR, EMailNotesEditor))
#define E_IS_MAIL_NOTES_EDITOR(obj) \
	(G_TYPE_CHECK_INSTANCE_TYPE \
	((obj), E_TYPE_MAIL_NOTES_EDITOR))

typedef struct _EMailNotesEditor EMailNotesEditor;
typedef struct _EMailNotesEditorClass EMailNotesEditorClass;

struct _EMailNotesEditor {
	GtkWindow parent;

	EHTMLEditor *editor; /* not referenced */
	EAttachmentPaned *attachment_paned; /* not referenced */
	EFocusTracker *focus_tracker;
	GtkActionGroup *action_group;

	gboolean had_message;
	CamelMimeMessage *message;
	CamelFolder *folder;
	gchar *uid;
};

struct _EMailNotesEditorClass {
	GtkWindowClass parent_class;
};

GType e_mail_notes_editor_get_type (void);

G_DEFINE_TYPE (EMailNotesEditor, e_mail_notes_editor, GTK_TYPE_WINDOW)

static gchar *
e_mail_notes_extract_text_content (CamelMimePart *part)
{
	CamelDataWrapper *content;
	CamelStream *stream;
	GByteArray *byte_array;
	gchar *text = NULL;

	g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL);

	content = camel_medium_get_content (CAMEL_MEDIUM (part));
	g_return_val_if_fail (content != NULL, NULL);

	stream = camel_stream_mem_new ();
	camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL);
	camel_stream_close (stream, NULL, NULL);

	byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));

	if (byte_array->data)
		text = g_strndup ((const gchar *) byte_array->data, byte_array->len);

	g_object_unref (stream);

	return text;
}

static void
e_mail_notes_extract_text_from_multipart_alternative (EContentEditor *cnt_editor,
						      CamelMultipart *in_multipart)
{
	guint ii, nparts;

	g_return_if_fail (E_IS_CONTENT_EDITOR (cnt_editor));
	g_return_if_fail (CAMEL_IS_MULTIPART (in_multipart));

	nparts = camel_multipart_get_number (in_multipart);

	for (ii = 0; ii < nparts; ii++) {
		CamelMimePart *part;
		CamelContentType *ct;

		/* Traverse from the end, where the best format available is stored */
		part = camel_multipart_get_part (in_multipart, nparts - ii - 1);
		if (!part)
			continue;

		ct = camel_mime_part_get_content_type (part);
		if (!ct)
			continue;

		if (camel_content_type_is (ct, "text", "html")) {
			gchar *text;

			text = e_mail_notes_extract_text_content (part);
			if (text) {
				e_content_editor_set_html_mode (cnt_editor, TRUE);
				e_content_editor_insert_content (
					cnt_editor,
					text,
					E_CONTENT_EDITOR_INSERT_TEXT_HTML |
					E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
				g_free (text);
				break;
			}
		} else if (camel_content_type_is (ct, "text", "plain")) {
			gchar *text;

			text = e_mail_notes_extract_text_content (part);
			if (text) {
				e_content_editor_insert_content (
					cnt_editor,
					text,
					E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
					E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
				g_free (text);
			}
			break;
		}
	}
}

static void
e_mail_notes_editor_extract_text_from_multipart_related (EMailNotesEditor *notes_editor,
							 CamelMultipart *multipart)
{
	EContentEditor *cnt_editor;
	guint ii, nparts;

	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));

	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
	nparts = camel_multipart_get_number (multipart);

	for (ii = 0; ii < nparts; ii++) {
		CamelMimePart *part;
		CamelContentType *ct;
		CamelDataWrapper *content;

		part = camel_multipart_get_part (multipart, ii);
		if (!part)
			continue;

		ct = camel_mime_part_get_content_type (part);
		if (!ct)
			continue;

		if (camel_content_type_is (ct, "image", "*")) {
			e_content_editor_insert_image_from_mime_part (cnt_editor, part);
		} else if (camel_content_type_is (ct, "multipart", "alternative")) {
			content = camel_medium_get_content (CAMEL_MEDIUM (part));
			if (CAMEL_IS_MULTIPART (content))
				e_mail_notes_extract_text_from_multipart_alternative (cnt_editor, CAMEL_MULTIPART (content));
		}
	}
}

static void
e_mail_notes_editor_extract_text_from_part (EMailNotesEditor *notes_editor,
					    CamelMimePart *part)
{
	CamelContentType *ct;
	CamelDataWrapper *content;
	EContentEditor *cnt_editor;

	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
	g_return_if_fail (CAMEL_IS_MIME_PART (part));

	content = camel_medium_get_content (CAMEL_MEDIUM (part));
	ct = camel_data_wrapper_get_mime_type_field (content);

	g_return_if_fail (content != NULL);
	g_return_if_fail (ct != NULL);

	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);

	if (camel_content_type_is (ct, "multipart", "related")) {
		g_return_if_fail (CAMEL_IS_MULTIPART (content));

		e_mail_notes_editor_extract_text_from_multipart_related (notes_editor, CAMEL_MULTIPART (content));
	} else if (camel_content_type_is (ct, "multipart", "alternative")) {
		if (CAMEL_IS_MULTIPART (content)) {
			e_mail_notes_extract_text_from_multipart_alternative (cnt_editor, CAMEL_MULTIPART (content));
		}
	} else if (camel_content_type_is (ct, "text", "plain")) {
		gchar *text;

		text = e_mail_notes_extract_text_content (part);
		if (text) {
			e_content_editor_insert_content (
				cnt_editor,
				text,
				E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
				E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
			g_free (text);
		}
	}
}

static void
e_mail_notes_editor_extract_text_from_message (EMailNotesEditor *notes_editor,
					       CamelMimeMessage *message)
{
	CamelContentType *ct;
	CamelDataWrapper *content;
	EContentEditor *cnt_editor;

	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	content = camel_medium_get_content (CAMEL_MEDIUM (message));
	ct = camel_data_wrapper_get_mime_type_field (content);

	g_return_if_fail (content != NULL);
	g_return_if_fail (ct != NULL);

	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);

	if (camel_content_type_is (ct, "multipart", "mixed")) {
		EAttachmentStore *attachment_store;
		CamelMultipart *multipart;
		guint ii, nparts;

		g_return_if_fail (CAMEL_IS_MULTIPART (content));

		attachment_store = e_attachment_view_get_store (E_ATTACHMENT_VIEW (notes_editor->attachment_paned));
		multipart = CAMEL_MULTIPART (content);
		nparts = camel_multipart_get_number (multipart);

		/* The first part is the note text, the rest are attachments */
		for (ii = 0; ii < nparts; ii++) {
			CamelMimePart *part;

			part = camel_multipart_get_part (multipart, ii);
			if (!part)
				continue;

			ct = camel_mime_part_get_content_type (part);
			if (!ct)
				continue;

			if (ii == 0) {
				e_mail_notes_editor_extract_text_from_part (notes_editor, part);
			} else {
				EAttachment *attachment;

				attachment = e_attachment_new ();

				e_attachment_set_mime_part (attachment, part);
				e_attachment_store_add_attachment (attachment_store, attachment);
				e_attachment_load_async (attachment, (GAsyncReadyCallback)
					e_attachment_load_handle_error, notes_editor);

				g_object_unref (attachment);
			}
		}
	} else {
		e_mail_notes_editor_extract_text_from_part (notes_editor, CAMEL_MIME_PART (message));
	}

	e_content_editor_set_changed (cnt_editor, FALSE);
}

static CamelMimeMessage *
e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor)
{
	EContentEditor *cnt_editor;
	EAttachmentStore *attachment_store;
	CamelMimeMessage *message = NULL;
	gchar *message_uid;
	const gchar *username;
	CamelInternetAddress *address;
	gboolean has_text = FALSE, has_attachments;

	g_return_val_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor), NULL);
	g_return_val_if_fail (notes_editor->editor, NULL);

	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
	g_return_val_if_fail (E_IS_CONTENT_EDITOR (cnt_editor), NULL);

	message = camel_mime_message_new ();
	username = g_get_user_name ();
	if (!username || !*username)
		username = g_get_real_name ();
	address = camel_internet_address_new ();
	camel_internet_address_add (address, NULL, username);

	message_uid = camel_header_msgid_generate (g_get_host_name ());

	camel_mime_message_set_from (message, address);
	camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
	camel_mime_message_set_subject (message, _("Message Note"));
	camel_mime_message_set_message_id (message, message_uid);

	g_object_unref (address);
	g_free (message_uid);

	attachment_store = e_attachment_view_get_store (E_ATTACHMENT_VIEW (notes_editor->attachment_paned));
	has_attachments = e_attachment_store_get_num_attachments (attachment_store) > 0;

	if (e_content_editor_get_html_mode (cnt_editor)) {
		CamelMultipart *multipart_alternative;
		CamelMultipart *multipart_body;
		CamelMimePart *part;
		GSList *inline_images_parts = NULL;
		gchar *text;

		multipart_alternative = camel_multipart_new ();
		camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart_alternative), "multipart/alternative");
		camel_multipart_set_boundary (multipart_alternative, NULL);

		text = e_content_editor_get_content (
			cnt_editor,
			E_CONTENT_EDITOR_GET_TEXT_PLAIN |
			E_CONTENT_EDITOR_GET_PROCESSED,
			NULL, NULL);

		if (text && *text) {
			if (!g_str_has_suffix (text, "\r\n")) {
				gchar *tmp = text;

				text = g_strconcat (tmp, "\r\n", NULL);
				g_free (tmp);
			}

			part = camel_mime_part_new ();
			camel_mime_part_set_content (part, text, strlen (text), "text/plain");
			camel_multipart_add_part (multipart_alternative, part);

			g_object_unref (part);

			has_text = TRUE;
		}

		g_free (text);

		text = e_content_editor_get_content (
			cnt_editor,
			E_CONTENT_EDITOR_GET_PROCESSED |
			E_CONTENT_EDITOR_GET_TEXT_HTML |
			E_CONTENT_EDITOR_GET_INLINE_IMAGES,
			g_get_host_name (),
			&inline_images_parts);

		if (has_attachments && !has_text && (!text || !*text)) {
			/* Text is required, thus if there are attachments,
			   but no text, then store at least a space. */
			g_free (text);
			text = g_strdup ("\r\n");
		}

		if (text && *text) {
			if (!g_str_has_suffix (text, "\r\n")) {
				gchar *tmp = text;

				text = g_strconcat (tmp, "\r\n", NULL);
				g_free (tmp);
			}

			part = camel_mime_part_new ();
			camel_mime_part_set_content (part, text, strlen (text), "text/html");
			camel_multipart_add_part (multipart_alternative, part);

			g_object_unref (part);

			has_text = TRUE;
		} else {
			g_slist_free_full (inline_images_parts, g_object_unref);
			inline_images_parts = NULL;
		}

		g_free (text);

		if (inline_images_parts) {
			GSList *link;

			multipart_body = camel_multipart_new ();
			camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart_body), "multipart/related");
			camel_multipart_set_boundary (multipart_body, NULL);

			part = camel_mime_part_new ();
			camel_medium_set_content (CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (multipart_alternative));
			camel_multipart_add_part (multipart_body, part);
			g_object_unref (part);

			for (link = inline_images_parts; link; link = g_slist_next (link)) {
				CamelMimePart *part = link->data;

				if (!part)
					continue;

				camel_multipart_add_part (multipart_body, part);
			}
		} else {
			multipart_body = multipart_alternative;
			multipart_alternative = NULL;
		}

		if (has_attachments) {
			CamelMultipart *multipart;

			multipart = camel_multipart_new ();
			camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart), "multipart/mixed");
			camel_multipart_set_boundary (multipart, NULL);

			part = camel_mime_part_new ();
			camel_medium_set_content (CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (multipart_body));
			camel_multipart_add_part (multipart, part);
			g_object_unref (part);

			e_attachment_store_add_to_multipart (attachment_store, multipart, "UTF-8");

			g_object_unref (multipart_body);
			multipart_body = multipart;
		}

		camel_medium_set_content (CAMEL_MEDIUM (message), CAMEL_DATA_WRAPPER (multipart_body));

		g_slist_free_full (inline_images_parts, g_object_unref);
		g_clear_object (&multipart_alternative);
		g_clear_object (&multipart_body);
	} else {
		gchar *text;

		text = e_content_editor_get_content (
			cnt_editor,
			E_CONTENT_EDITOR_GET_TEXT_PLAIN |
			E_CONTENT_EDITOR_GET_PROCESSED,
			NULL, NULL);

		if (has_attachments && !has_text && (!text || !*text)) {
			/* Text is required, thus if there are attachments,
			   but no text, then store at least a space. */
			g_free (text);
			text = g_strdup ("\r\n");
		}

		if (text && *text) {
			if (!g_str_has_suffix (text, "\r\n")) {
				gchar *tmp = text;

				text = g_strconcat (tmp, "\r\n", NULL);
				g_free (tmp);
			}

			if (has_attachments) {
				CamelMultipart *multipart;
				CamelMimePart *part;

				multipart = camel_multipart_new ();
				camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart), "multipart/mixed");
				camel_multipart_set_boundary (multipart, NULL);

				part = camel_mime_part_new ();
				camel_mime_part_set_content (part, text, strlen (text), "text/plain");
				camel_multipart_add_part (multipart, part);
				g_object_unref (part);

				e_attachment_store_add_to_multipart (attachment_store, multipart, "UTF-8");

				camel_medium_set_content (CAMEL_MEDIUM (message), CAMEL_DATA_WRAPPER (multipart));

				g_object_unref (multipart);
			} else {
				camel_mime_part_set_content (CAMEL_MIME_PART (message), text, strlen (text), "text/plain");
			}
			has_text = TRUE;
		}

		g_free (text);
	}

	if (has_text) {
		camel_mime_message_encode_8bit_parts (message);
	} else {
		g_clear_object (&message);
	}

	return message;
}

static void
e_mail_notes_retrieve_message_thread (EAlertSinkThreadJobData *job_data,
				      gpointer user_data,
				      GCancellable *cancellable,
				      GError **error)
{
	EMailNotesEditor *notes_editor = user_data;
	CamelMimeMessage *message;

	if (g_cancellable_set_error_if_cancelled (cancellable, error))
		return;

	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));

	message = camel_folder_get_message_sync (notes_editor->folder, notes_editor->uid, cancellable, error);
	if (!g_cancellable_is_cancelled (cancellable))
		notes_editor->message = message;
	else
		g_clear_object (&message);
}

static void
e_mail_notes_retrieve_message_done (gpointer ptr)
{
	EMailNotesEditor *notes_editor = ptr;

	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));

	if (notes_editor->message) {
		EActivityBar *activity_bar;
		CamelDataWrapper *content;
		CamelContentType *ct;

		content = camel_medium_get_content (CAMEL_MEDIUM (notes_editor->message));
		ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (notes_editor->message));

		if (ct && camel_content_type_is (ct, "multipart", "mixed") && CAMEL_IS_MULTIPART (content)) {
			CamelMultipart *multipart = CAMEL_MULTIPART (content);
			guint nparts, ii;

			nparts = camel_multipart_get_number (multipart);
			for (ii = 0; ii < nparts; ii++) {
				CamelMimePart *part;
				CamelContentType *ct;
				const gchar *x_evolution_note;

				part = camel_multipart_get_part (multipart, ii);
				if (!part)
					continue;

				ct = camel_mime_part_get_content_type (part);
				if (!ct || !camel_content_type_is (ct, "message", "rfc822"))
					continue;

				x_evolution_note = camel_medium_get_header (CAMEL_MEDIUM (part), E_MAIL_NOTES_HEADER);
				if (x_evolution_note) {
					content = camel_medium_get_content (CAMEL_MEDIUM (part));
					if (CAMEL_IS_MIME_MESSAGE (content)) {
						e_mail_notes_editor_extract_text_from_message (notes_editor,
							CAMEL_MIME_MESSAGE (content));
					}
					break;
				}
			}
		}

		g_clear_object (&notes_editor->message);
		notes_editor->had_message = TRUE;

		activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
		e_activity_bar_set_activity (activity_bar, NULL);
	} else {
		GtkAction *action;

		action = gtk_action_group_get_action (notes_editor->action_group, "save-and-close");
		gtk_action_set_sensitive (action, FALSE);
	}

	g_object_unref (notes_editor);
}

static gboolean
mail_notes_editor_delete_event_cb (EMailNotesEditor *notes_editor,
				   GdkEvent *event)
{
	GtkActionGroup *action_group;
	GtkAction *action;

	action_group = notes_editor->action_group;
	action = gtk_action_group_get_action (action_group, "close");
	gtk_action_activate (action);

	return TRUE;
}

static void
notes_editor_activity_notify_cb (EActivityBar *activity_bar,
				 GParamSpec *param,
				 EMailNotesEditor *notes_editor)
{
	EContentEditor *cnt_editor;
	GtkAction *action;
	gboolean can_edit;

	g_return_if_fail (E_IS_ACTIVITY_BAR (activity_bar));
	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));

	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
	can_edit = notes_editor->had_message && !e_activity_bar_get_activity (activity_bar);

	g_object_set (cnt_editor, "editable", can_edit, NULL);

	action = gtk_action_group_get_action (notes_editor->action_group, "save-and-close");
	gtk_action_set_sensitive (action, can_edit);
}

static gboolean
e_mail_notes_replace_message_in_folder_sync (CamelFolder *folder,
					     const gchar *uid,
					     CamelMimeMessage *message,
					     gboolean has_note,
					     GCancellable *cancellable,
					     GError **error)
{
	CamelMessageInfo *mi;
	gboolean success = FALSE;

	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
	g_return_val_if_fail (uid != NULL, FALSE);
	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);

	mi = camel_folder_get_message_info (folder, uid);
	if (mi) {
		CamelMessageInfo *clone;
		gchar *appended_uid = NULL;

		clone = camel_message_info_clone (mi, NULL);
		camel_message_info_set_abort_notifications (clone, TRUE);

		camel_message_info_set_user_flag (clone, E_MAIL_NOTES_USER_FLAG, has_note);

		success = camel_folder_append_message_sync (folder, message, clone,
			&appended_uid, cancellable, error);

		if (success)
			camel_message_info_set_flags (mi, CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_DELETED);

		g_clear_object (&clone);
		g_clear_object (&mi);
		g_free (appended_uid);
	} else {
		g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot find message in its folder summary"));
	}

	return success;
}

static gboolean
e_mail_notes_replace_note (CamelMimeMessage *message,
			   CamelMimeMessage *note)
{
	CamelMultipart *multipart;
	CamelMimePart *part;
	CamelDataWrapper *orig_content;
	CamelContentType *ct;

	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
	if (note)
		g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (note), FALSE);

	orig_content = camel_medium_get_content (CAMEL_MEDIUM (message));
	ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (message));
	if (ct && camel_content_type_is (ct, "multipart", "mixed") && CAMEL_IS_MULTIPART (orig_content)) {
		CamelMimePart *content_adept = NULL;
		CamelMultipart *multipart = CAMEL_MULTIPART (orig_content);
		guint nparts, ii;

		nparts = camel_multipart_get_number (multipart);
		for (ii = 0; ii < nparts; ii++) {
			CamelMimePart *part;
			CamelContentType *ct;
			const gchar *x_evolution_note;

			part = camel_multipart_get_part (multipart, ii);
			if (!part)
				continue;

			ct = camel_mime_part_get_content_type (part);
			if (!ct || !camel_content_type_is (ct, "message", "rfc822")) {
				if (content_adept) {
					content_adept = NULL;
					break;
				}
				content_adept = part;
				continue;
			}

			x_evolution_note = camel_medium_get_header (CAMEL_MEDIUM (part), E_MAIL_NOTES_HEADER);
			if (x_evolution_note)
				break;

			if (content_adept) {
				content_adept = NULL;
				break;
			}
			content_adept = part;
		}

		if (content_adept)
			orig_content = camel_medium_get_content (CAMEL_MEDIUM (content_adept));
	}

	if (!orig_content)
		return FALSE;

	g_object_ref (orig_content);

	if (note) {
		multipart = camel_multipart_new ();
		camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart), "multipart/mixed");
		camel_multipart_set_boundary (multipart, NULL);

		part = camel_mime_part_new ();
		camel_medium_set_content (CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (orig_content));
		camel_multipart_add_part (multipart, part);
		g_object_unref (part);

		part = camel_mime_part_new ();
		/* Value doesn't matter, it's checked for an existence only */
		camel_medium_add_header (CAMEL_MEDIUM (part), E_MAIL_NOTES_HEADER, "True");
		camel_mime_part_set_disposition (CAMEL_MIME_PART (part), "inline");
		camel_mime_part_set_description (CAMEL_MIME_PART (part), _("Message Note"));
		camel_medium_set_content (CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (note));

		camel_mime_part_set_content_type (part, "message/rfc822");

		camel_multipart_add_part (multipart, part);
		g_object_unref (part);

		camel_medium_set_content (CAMEL_MEDIUM (message), CAMEL_DATA_WRAPPER (multipart));
	} else {
		camel_medium_set_content (CAMEL_MEDIUM (message), CAMEL_DATA_WRAPPER (orig_content));
	}

	g_clear_object (&orig_content);

	return TRUE;
}

static void
action_close_cb (GtkAction *action,
		 EMailNotesEditor *notes_editor)
{
	EContentEditor *cnt_editor;
	gboolean something_changed = FALSE;

	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);

	something_changed = e_content_editor_get_changed (cnt_editor);

	if (something_changed) {
		gint response;

		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (notes_editor),
			"mail:ask-mail-note-changed", NULL);
		if (response == GTK_RESPONSE_YES) {
			GtkActionGroup *action_group;

			action_group = notes_editor->action_group;
			action = gtk_action_group_get_action (
				action_group, "save-and-close");
			gtk_action_activate (action);
			return;
		} else if (response == GTK_RESPONSE_CANCEL)
			return;
	}

	gtk_widget_destroy (GTK_WIDGET (notes_editor));
}

typedef struct {
	EMailNotesEditor *notes_editor;
	CamelMimeMessage *inner_message;
	gboolean success;
} SaveAndCloseData;

static void
save_and_close_data_free (gpointer ptr)
{
	SaveAndCloseData *scd = ptr;

	if (scd) {
		if (scd->success)
			gtk_widget_destroy (GTK_WIDGET (scd->notes_editor));
		else
			g_clear_object (&scd->notes_editor);
		g_clear_object (&scd->inner_message);
		g_free (scd);
	}
}

static void
e_mail_notes_store_changes_thread (EAlertSinkThreadJobData *job_data,
				   gpointer user_data,
				   GCancellable *cancellable,
				   GError **error)
{
	CamelMimeMessage *message;
	SaveAndCloseData *scd = user_data;

	g_return_if_fail (scd != NULL);

	if (g_cancellable_set_error_if_cancelled (cancellable, error))
		return;

	if (!scd->inner_message) {
		scd->success = e_mail_notes_remove_sync (scd->notes_editor->folder,
			scd->notes_editor->uid, cancellable, error);
		return;
	}

	message = camel_folder_get_message_sync (scd->notes_editor->folder, scd->notes_editor->uid, cancellable, error);
	if (!message)
		return;

	e_mail_notes_replace_note (message, scd->inner_message);

	scd->success = e_mail_notes_replace_message_in_folder_sync (scd->notes_editor->folder,
		scd->notes_editor->uid, message, TRUE, cancellable, error);

	g_clear_object (&message);
}

static void
action_save_and_close_cb (GtkAction *action,
			  EMailNotesEditor *notes_editor)
{
	SaveAndCloseData *scd;
	gchar *full_display_name;
	EActivityBar *activity_bar;
	EActivity *activity;

	g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));

	scd = g_new0 (SaveAndCloseData, 1);
	scd->notes_editor = g_object_ref (notes_editor);
	scd->inner_message = e_mail_notes_editor_encode_text_to_message (notes_editor);
	scd->success = FALSE;

	full_display_name = e_mail_folder_to_full_display_name (notes_editor->folder, NULL);

	activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
	activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (notes_editor->editor),
		_("Storing changes..."), "mail:failed-store-note",
		full_display_name ? full_display_name : camel_folder_get_display_name (notes_editor->folder),
		e_mail_notes_store_changes_thread,
		scd, save_and_close_data_free);
	e_activity_bar_set_activity (activity_bar, activity);
	g_clear_object (&activity);

	g_free (full_display_name);
}

static void
e_mail_notes_editor_dispose (GObject *object)
{
	EMailNotesEditor *notes_editor = E_MAIL_NOTES_EDITOR (object);

	if (notes_editor->editor) {
		EActivityBar *activity_bar;

		activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
		g_signal_handlers_disconnect_by_func (activity_bar,
			G_CALLBACK (notes_editor_activity_notify_cb), notes_editor);

		notes_editor->editor = NULL;
	}

	g_clear_object (&notes_editor->focus_tracker);
	g_clear_object (&notes_editor->action_group);

	/* Chain up to parent's method */
	G_OBJECT_CLASS (e_mail_notes_editor_parent_class)->dispose (object);
}

static void
e_mail_notes_editor_finalize (GObject *object)
{
	EMailNotesEditor *notes_editor = E_MAIL_NOTES_EDITOR (object);

	g_clear_object (&notes_editor->focus_tracker);
	g_clear_object (&notes_editor->folder);
	g_clear_object (&notes_editor->message);
	g_free (notes_editor->uid);

	/* Chain up to parent's method */
	G_OBJECT_CLASS (e_mail_notes_editor_parent_class)->finalize (object);
}

static void
e_mail_notes_editor_class_init (EMailNotesEditorClass *klass)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS (klass);
	object_class->dispose = e_mail_notes_editor_dispose;
	object_class->finalize = e_mail_notes_editor_finalize;
}

static void
e_mail_notes_editor_init (EMailNotesEditor *notes_editor)
{
}

static EMailNotesEditor *
e_mail_notes_editor_new_with_editor (EHTMLEditor *html_editor,
				     GtkWindow *parent,
				     CamelFolder *folder,
				     const gchar *uid)
{
	const gchar *ui =
		"<ui>\n"
		"  <menubar name='main-menu'>\n"
		"    <placeholder name='pre-edit-menu'>\n"
		"      <menu action='file-menu'>\n"
		"        <menuitem action='save-and-close'/>\n"
		"        <separator/>"
		"        <menuitem action='close'/>\n"
		"      </menu>\n"
		"    </placeholder>\n"
		"  </menubar>\n"
		"  <toolbar name='main-toolbar'>\n"
		"    <placeholder name='pre-main-toolbar'>\n"
		"      <toolitem action='save-and-close'/>\n"
		"    </placeholder>\n"
		"  </toolbar>\n"
		"</ui>";

	GtkActionEntry entries[] = {

		{ "close",
		  "window-close",
		  N_("_Close"),
		  "<Control>w",
		  N_("Close"),
		  G_CALLBACK (action_close_cb) },

		{ "save-and-close",
		  "document-save",
		  N_("_Save and Close"),
		  "<Control>Return",
		  N_("Save and Close"),
		  G_CALLBACK (action_save_and_close_cb) },

		{ "file-menu",
		  NULL,
		  N_("_File"),
		  NULL,
		  NULL,
		  NULL }
	};

	EMailNotesEditor *notes_editor;
	EContentEditor *cnt_editor;
	EFocusTracker *focus_tracker;
	EActivityBar *activity_bar;
	GtkUIManager *ui_manager;
	GtkWidget *widget, *content;
	GtkActionGroup *action_group;
	GtkAction *action;
	GSettings *settings;
	GError *local_error = NULL;

	notes_editor = g_object_new (E_TYPE_MAIL_NOTES_EDITOR, NULL);

	g_object_set (G_OBJECT (notes_editor),
		"transient-for", parent,
		"destroy-with-parent", TRUE,
		"window-position", GTK_WIN_POS_CENTER_ON_PARENT,
		"title", _("Edit Message Note"),
		NULL);

	gtk_window_set_default_size (GTK_WINDOW (notes_editor), 600, 440);

	widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
	gtk_container_add (GTK_CONTAINER (notes_editor), widget);
	gtk_widget_show (widget);

	content = widget;

	widget = GTK_WIDGET (html_editor);

	notes_editor->editor = html_editor;
	cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
	ui_manager = e_html_editor_get_ui_manager (notes_editor->editor);

	/* Because we are loading from a hard-coded string, there is
	 * no chance of I/O errors.  Failure here implies a malformed
	 * UI definition.  Full stop. */
	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &local_error);
	if (local_error != NULL)
		g_error ("%s: Failed to load built-in UI definition: %s", G_STRFUNC, local_error->message);

	action_group = gtk_action_group_new ("notes");
	gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
	gtk_action_group_add_actions (action_group, entries, G_N_ELEMENTS (entries), notes_editor);
	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
	notes_editor->action_group = g_object_ref (action_group);

	/* Hide page properties because it is not inherited in the mail. */
	action = e_html_editor_get_action (notes_editor->editor, "properties-page");
	gtk_action_set_visible (action, FALSE);

	action = e_html_editor_get_action (notes_editor->editor, "context-properties-page");
	gtk_action_set_visible (action, FALSE);

	gtk_ui_manager_ensure_update (ui_manager);

	/* Construct the window content. */

	widget = e_html_editor_get_managed_widget (notes_editor->editor, "/main-menu");
	gtk_box_pack_start (GTK_BOX (content), widget, FALSE, FALSE, 0);
	gtk_widget_show (widget);

	widget = e_html_editor_get_managed_widget (notes_editor->editor, "/main-toolbar");
	gtk_box_pack_start (GTK_BOX (content), widget, FALSE, FALSE, 0);
	gtk_widget_show (widget);

	widget = GTK_WIDGET (notes_editor->editor);
	g_object_set (G_OBJECT (widget),
		"halign", GTK_ALIGN_FILL,
		"hexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		"vexpand", TRUE,
		NULL);
	gtk_box_pack_start (GTK_BOX (content), widget, TRUE, TRUE, 0);
	gtk_widget_show (widget);

	widget = e_attachment_paned_new ();
	gtk_box_pack_start (GTK_BOX (content), widget, FALSE, FALSE, 0);
	notes_editor->attachment_paned = E_ATTACHMENT_PANED (widget);
	gtk_widget_show (widget);

	e_binding_bind_property (
		cnt_editor, "editable",
		widget, "sensitive",
		G_BINDING_SYNC_CREATE);

	/* Configure an EFocusTracker to manage selection actions. */
	focus_tracker = e_focus_tracker_new (GTK_WINDOW (notes_editor));

	action = e_html_editor_get_action (notes_editor->editor, "cut");
	e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);

	action = e_html_editor_get_action (notes_editor->editor, "copy");
	e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);

	action = e_html_editor_get_action (notes_editor->editor, "paste");
	e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);

	action = e_html_editor_get_action (notes_editor->editor, "select-all");
	e_focus_tracker_set_select_all_action (focus_tracker, action);

	notes_editor->focus_tracker = focus_tracker;

	gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	e_content_editor_set_html_mode (
		cnt_editor, g_settings_get_boolean (settings, "composer-send-html"));
	g_object_unref (settings);

	g_signal_connect (
		notes_editor, "delete-event",
		G_CALLBACK (mail_notes_editor_delete_event_cb), NULL);

	activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);

	g_signal_connect (activity_bar, "notify::activity",
		G_CALLBACK (notes_editor_activity_notify_cb), notes_editor);

	notes_editor->folder = g_object_ref (folder);
	notes_editor->uid = g_strdup (uid);
	notes_editor->had_message = FALSE;

	return notes_editor;
}

typedef struct _AsyncData {
	GtkWindow *parent;
	CamelFolder *folder;
	gchar *uid;
} AsyncData;

static void
async_data_free (gpointer ptr)
{
	AsyncData *ad = ptr;

	if (ad) {
		g_clear_object (&ad->parent);
		g_clear_object (&ad->folder);
		g_free (ad->uid);
		g_free (ad);
	}
}

static void
e_mail_notes_editor_ready_cb (GObject *source_object,
			      GAsyncResult *result,
			      gpointer user_data)
{
	AsyncData *ad = user_data;
	GtkWidget *html_editor;
	GError *error = NULL;

	g_return_if_fail (result != NULL);
	g_return_if_fail (ad != NULL);

	html_editor = e_html_editor_new_finish (result, &error);
	if (error) {
		g_warning ("%s: Failed to create HTML editor: %s", G_STRFUNC, error->message);
		g_clear_error (&error);
	} else {
		EMailNotesEditor *notes_editor;
		EActivityBar *activity_bar;
		EActivity *activity;

		notes_editor = e_mail_notes_editor_new_with_editor (E_HTML_EDITOR (html_editor),
			ad->parent, ad->folder, ad->uid);

		activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
		activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (notes_editor->editor),
			_("Retrieving message..."), "mail:no-retrieve-message", NULL,
			e_mail_notes_retrieve_message_thread,
			g_object_ref (notes_editor), e_mail_notes_retrieve_message_done);
		e_activity_bar_set_activity (activity_bar, activity);
		g_clear_object (&activity);

		gtk_widget_show (GTK_WIDGET (notes_editor));
	}

	async_data_free (ad);
}

void
e_mail_notes_edit (GtkWindow *parent,
		   CamelFolder *folder,
		   const gchar *uid)
{
	AsyncData *ad;

	g_return_if_fail (CAMEL_IS_FOLDER (folder));
	g_return_if_fail (uid != NULL);

	ad = g_new0 (AsyncData, 1);
	ad->parent = parent ? g_object_ref (parent) : NULL;
	ad->folder = g_object_ref (folder);
	ad->uid = g_strdup (uid);

	e_html_editor_new (e_mail_notes_editor_ready_cb, ad);
}

gboolean
e_mail_notes_remove_sync (CamelFolder *folder,
			  const gchar *uid,
			  GCancellable *cancellable,
			  GError **error)
{
	CamelMimeMessage *message;
	gboolean success;

	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
	g_return_val_if_fail (uid != NULL, FALSE);

	message = camel_folder_get_message_sync (folder, uid, cancellable, error);
	if (!message)
		return FALSE;

	success = e_mail_notes_replace_note (message, NULL);
	if (success) {
		success = e_mail_notes_replace_message_in_folder_sync (folder,
			uid, message, FALSE, cancellable, error);
	} else {
		/* There was no note found in the message, thus it was successfully removed */
		success = TRUE;
	}

	g_clear_object (&message);

	return success;
}