/*
 * e-mail-reader.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "e-mail-reader.h"

#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>

#ifdef HAVE_XFREE
#include <X11/XF86keysym.h>
#endif

#include "shell/e-shell-utils.h"

#include "libemail-engine/e-mail-enumtypes.h"
#include "libemail-engine/e-mail-utils.h"
#include "libemail-engine/mail-mt.h"
#include "libemail-engine/mail-ops.h"

#include "em-format/e-mail-formatter.h"
#include "em-format/e-mail-parser.h"
#include "em-format/e-mail-part-utils.h"

#include "e-mail-backend.h"
#include "e-mail-browser.h"
#include "e-mail-reader-utils.h"
#include "e-mail-ui-session.h"
#include "e-mail-view.h"
#include "em-composer-utils.h"
#include "em-event.h"
#include "em-folder-selector.h"
#include "em-folder-tree.h"
#include "em-utils.h"
#include "mail-autofilter.h"
#include "mail-vfolder-ui.h"
#include "message-list.h"

#define E_MAIL_READER_GET_PRIVATE(obj) \
	((EMailReaderPrivate *) g_object_get_qdata \
	(G_OBJECT (obj), quark_private))

#define d(x)

typedef struct _EMailReaderClosure EMailReaderClosure;
typedef struct _EMailReaderPrivate EMailReaderPrivate;

struct _EMailReaderClosure {
	EMailReader *reader;
	EActivity *activity;
	gchar *message_uid;
};

struct _EMailReaderPrivate {

	EMailForwardStyle forward_style;
	EMailReplyStyle reply_style;

	/* This timer runs when the user selects a single message. */
	guint message_selected_timeout_id;

	/* This allows message retrieval to be cancelled if another
	 * message is selected before the retrieval has completed. */
	GCancellable *retrieving_message;

	/* These flags work together to prevent message selection
	 * restoration after a folder switch from automatically
	 * marking the message as read.  We only want that to
	 * happen when the -user- selects a message. */
	guint folder_was_just_selected    : 1;
	guint restoring_message_selection : 1;
	guint avoid_next_mark_as_seen     : 1;

	guint group_by_threads : 1;
};

enum {
	CHANGED,
	COMPOSER_CREATED,
	FOLDER_LOADED,
	MESSAGE_LOADED,
	MESSAGE_SEEN,
	SHOW_SEARCH_BAR,
	UPDATE_ACTIONS,
	LAST_SIGNAL
};

/* Remembers the previously selected folder when transferring messages. */
static gchar *default_xfer_messages_uri;

static GQuark quark_private;
static guint signals[LAST_SIGNAL];

G_DEFINE_INTERFACE (EMailReader, e_mail_reader, G_TYPE_INITIALLY_UNOWNED)

static void
mail_reader_set_display_formatter_for_message (EMailReader *reader,
                                               EMailDisplay *display,
                                               const gchar *message_uid,
                                               CamelMimeMessage *message,
                                               CamelFolder *folder);

static void
mail_reader_closure_free (EMailReaderClosure *closure)
{
	if (closure->reader != NULL)
		g_object_unref (closure->reader);

	if (closure->activity != NULL)
		g_object_unref (closure->activity);

	g_free (closure->message_uid);

	g_slice_free (EMailReaderClosure, closure);
}

static void
mail_reader_private_free (EMailReaderPrivate *priv)
{
	if (priv->message_selected_timeout_id > 0)
		g_source_remove (priv->message_selected_timeout_id);

	if (priv->retrieving_message != NULL) {
		g_cancellable_cancel (priv->retrieving_message);
		g_object_unref (priv->retrieving_message);
		priv->retrieving_message = 0;
	}

	g_slice_free (EMailReaderPrivate, priv);
}

static void
action_mail_add_sender_cb (GtkAction *action,
                           EMailReader *reader)
{
	EShell *shell;
	EMailBackend *backend;
	EMailSession *session;
	EShellBackend *shell_backend;
	CamelInternetAddress *cia;
	CamelMessageInfo *info = NULL;
	CamelFolder *folder;
	GPtrArray *uids;
	const gchar *address;
	const gchar *message_uid;

	folder = e_mail_reader_get_folder (reader);
	backend = e_mail_reader_get_backend (reader);
	session = e_mail_backend_get_session (backend);

	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL && uids->len == 1);
	message_uid = g_ptr_array_index (uids, 0);

	info = camel_folder_get_message_info (folder, message_uid);
	if (info == NULL)
		goto exit;

	address = camel_message_info_from (info);
	if (address == NULL || *address == '\0')
		goto exit;

	/* XXX EBookShellBackend should be listening for this
	 *     event.  Kind of kludgey, but works for now. */
	shell_backend = E_SHELL_BACKEND (backend);
	shell = e_shell_backend_get_shell (shell_backend);
	e_shell_event (shell, "contact-quick-add-email", (gpointer) address);

	/* Remove this address from the photo cache. */
	cia = camel_internet_address_new ();
	if (camel_address_decode (CAMEL_ADDRESS (cia), address) > 0) {
		EPhotoCache *photo_cache;
		const gchar *address_only = NULL;

		photo_cache = e_mail_ui_session_get_photo_cache (
			E_MAIL_UI_SESSION (session));
		camel_internet_address_get (cia, 0, NULL, &address_only);
		e_photo_cache_remove_photo (photo_cache, address_only);
	}
	g_object_unref (cia);

exit:
	if (info != NULL)
		camel_folder_free_message_info (folder, info);
	em_utils_uids_free (uids);
}

static void
action_add_to_address_book_cb (GtkAction *action,
                               EMailReader *reader)
{
	EShell *shell;
	EMailBackend *backend;
	EMailDisplay *display;
	EMailSession *session;
	EShellBackend *shell_backend;
	CamelInternetAddress *cia;
	EPhotoCache *photo_cache;
	EWebView *web_view;
	CamelURL *curl;
	const gchar *uri;
	const gchar *address_only = NULL;
	gchar *email;

	/* This action is defined in EMailDisplay. */

	backend = e_mail_reader_get_backend (reader);
	session = e_mail_backend_get_session (backend);

	display = e_mail_reader_get_mail_display (reader);
	if (display == NULL)
		return;

	web_view = E_WEB_VIEW (display);
	uri = e_web_view_get_selected_uri (web_view);
	g_return_if_fail (uri != NULL);

	curl = camel_url_new (uri, NULL);
	g_return_if_fail (curl != NULL);

	if (curl->path == NULL || *curl->path == '\0')
		goto exit;

	cia = camel_internet_address_new ();
	if (camel_address_decode (CAMEL_ADDRESS (cia), curl->path) < 0) {
		g_object_unref (cia);
		goto exit;
	}

	/* XXX EBookShellBackend should be listening for this
	 *     event.  Kind of kludgey, but works for now. */
	shell_backend = E_SHELL_BACKEND (backend);
	shell = e_shell_backend_get_shell (shell_backend);
	email = camel_address_format (CAMEL_ADDRESS (cia));
	e_shell_event (shell, "contact-quick-add-email", email);
	g_free (email);

	/* Remove this address from the photo cache. */
	photo_cache = e_mail_ui_session_get_photo_cache (
		E_MAIL_UI_SESSION (session));
	camel_internet_address_get (cia, 0, NULL, &address_only);
	e_photo_cache_remove_photo (photo_cache, address_only);

	g_object_unref (cia);

exit:
	camel_url_free (curl);
}

static void
attachment_load_finish (EAttachment *attachment,
                        GAsyncResult *result,
                        GFile *file)
{
	EShell *shell;
	GtkWindow *parent;

	e_attachment_load_finish (attachment, result, NULL);

	shell = e_shell_get_default ();
	parent = e_shell_get_active_window (shell);

	e_attachment_save_async (
		attachment, file, (GAsyncReadyCallback)
		e_attachment_save_handle_error, parent);

	g_object_unref (file);
}

static void
action_mail_image_save_cb (GtkAction *action,
                           EMailReader *reader)
{
	EShell *shell;
	EMailBackend *backend;
	EMailDisplay *display;
	EWebView *web_view;
	EMailPartList *parts;
	const gchar *image_src;
	CamelMimePart *part;
	CamelMimeMessage *message;
	EAttachment *attachment;
	GFile *file;

	backend = e_mail_reader_get_backend (reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

	display = e_mail_reader_get_mail_display (reader);
	if (display == NULL)
		return;

	web_view = E_WEB_VIEW (display);
	image_src = e_web_view_get_cursor_image_src (web_view);
	if (image_src == NULL)
		return;

	parts = e_mail_display_get_parts_list (display);
	g_return_if_fail (parts != NULL);

	message = e_mail_part_list_get_message (parts);
	g_return_if_fail (message != NULL);

	if (g_str_has_prefix (image_src, "cid:")) {
		part = camel_mime_message_get_part_by_content_id (
			message, image_src + 4);
		g_return_if_fail (part != NULL);

		g_object_ref (part);
	} else {
		CamelStream *image_stream;
		CamelDataWrapper *dw;
		CamelDataCache *cache;
		const gchar *filename;
		const gchar *user_cache_dir;

		/* Open cache and find the file there */
		user_cache_dir = e_get_user_cache_dir ();
		cache = camel_data_cache_new (user_cache_dir, NULL);
		image_stream = camel_data_cache_get (
			cache, "http", image_src, NULL);
		if (image_stream == NULL) {
			g_object_unref (cache);
			return;
		}

		filename = strrchr (image_src, '/');
		if (filename != NULL) {
			if (strchr (filename, '?') == NULL)
				filename++;
			else
				filename = NULL;
		}

		part = camel_mime_part_new ();
		if (filename != NULL)
			camel_mime_part_set_filename (part, filename);

		dw = camel_data_wrapper_new ();
		camel_data_wrapper_set_mime_type (
			dw, "application/octet-stream");
		camel_data_wrapper_construct_from_stream_sync (
			dw, image_stream, NULL, NULL);
		camel_medium_set_content (CAMEL_MEDIUM (part), dw);
		g_object_unref (dw);

		camel_mime_part_set_encoding (
			part, CAMEL_TRANSFER_ENCODING_BASE64);

		g_object_unref (image_stream);
		g_object_unref (cache);
	}

	file = e_shell_run_save_dialog (
		shell, _("Save Image"),
		camel_mime_part_get_filename (part),
		NULL, NULL, NULL);
	if (file == NULL) {
		g_object_unref (part);
		return;
	}

	attachment = e_attachment_new ();
	e_attachment_set_mime_part (attachment, part);

	e_attachment_load_async (
		attachment, (GAsyncReadyCallback)
		attachment_load_finish, file);

	g_object_unref (part);
}

static void
action_mail_charset_cb (GtkRadioAction *action,
                        GtkRadioAction *current,
                        EMailReader *reader)
{
	EMailDisplay *display;
	EMailFormatter *formatter;

	if (action != current)
		return;

	display = e_mail_reader_get_mail_display (reader);
	formatter = e_mail_display_get_formatter (display);

	if (formatter != NULL) {
		const gchar *charset;

		/* Charset for "Default" action will be NULL. */
		charset = g_object_get_data (G_OBJECT (action), "charset");
		e_mail_formatter_set_charset (formatter, charset);
	}
}

static void
action_mail_check_for_junk_cb (GtkAction *action,
                               EMailReader *reader)
{
	EMailBackend *backend;
	EMailSession *session;
	CamelFolder *folder;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	backend = e_mail_reader_get_backend (reader);
	uids = e_mail_reader_get_selected_uids (reader);

	session = e_mail_backend_get_session (backend);

	mail_filter_folder (
		session, folder, uids,
		E_FILTER_SOURCE_JUNKTEST, FALSE);
}

static void
action_mail_copy_cb (GtkAction *action,
                     EMailReader *reader)
{
	CamelFolder *folder;
	EMailBackend *backend;
	EMailSession *session;
	EMFolderSelector *selector;
	EMFolderTree *folder_tree;
	EMFolderTreeModel *model;
	GtkWidget *dialog;
	GtkWindow *window;
	GPtrArray *uids;
	const gchar *uri;

	backend = e_mail_reader_get_backend (reader);
	session = e_mail_backend_get_session (backend);

	folder = e_mail_reader_get_folder (reader);
	window = e_mail_reader_get_window (reader);
	uids = e_mail_reader_get_selected_uids (reader);

	model = em_folder_tree_model_get_default ();

	dialog = em_folder_selector_new (
		window, model,
		EM_FOLDER_SELECTOR_CAN_CREATE,
		_("Copy to Folder"), NULL, _("C_opy"));

	selector = EM_FOLDER_SELECTOR (dialog);
	folder_tree = em_folder_selector_get_folder_tree (selector);

	em_folder_tree_set_excluded (
		folder_tree,
		EMFT_EXCLUDE_NOSELECT |
		EMFT_EXCLUDE_VIRTUAL |
		EMFT_EXCLUDE_VTRASH);

	if (default_xfer_messages_uri != NULL)
		em_folder_tree_set_selected (
			folder_tree, default_xfer_messages_uri, FALSE);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
		goto exit;

	uri = em_folder_selector_get_selected_uri (selector);

	g_free (default_xfer_messages_uri);
	default_xfer_messages_uri = g_strdup (uri);

	if (uri != NULL) {
		mail_transfer_messages (
			session, folder, uids,
			FALSE, uri, 0, NULL, NULL);
		uids = NULL;
	}

exit:
	if (uids != NULL)
		em_utils_uids_free (uids);

	gtk_widget_destroy (dialog);
}

static void
action_mail_delete_cb (GtkAction *action,
                       EMailReader *reader)
{
	guint32 mask = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED;
	guint32 set  = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED;

	if (!e_mail_reader_confirm_delete (reader))
		return;

	/* FIXME Verify all selected messages are deletable.
	 *       But handle it by disabling this action. */

	if (e_mail_reader_mark_selected (reader, mask, set) == 1)
		e_mail_reader_select_next_message (reader, FALSE);
}

static void
action_mail_filter_on_mailing_list_cb (GtkAction *action,
                                       EMailReader *reader)
{
	e_mail_reader_create_filter_from_selected (reader, AUTO_MLIST);
}

static void
action_mail_filter_on_recipients_cb (GtkAction *action,
                                     EMailReader *reader)
{
	e_mail_reader_create_filter_from_selected (reader, AUTO_TO);
}

static void
action_mail_filter_on_sender_cb (GtkAction *action,
                                 EMailReader *reader)
{
	e_mail_reader_create_filter_from_selected (reader, AUTO_FROM);
}

static void
action_mail_filter_on_subject_cb (GtkAction *action,
                                  EMailReader *reader)
{
	e_mail_reader_create_filter_from_selected (reader, AUTO_SUBJECT);
}

static void
action_mail_filters_apply_cb (GtkAction *action,
                              EMailReader *reader)
{
	EMailBackend *backend;
	EMailSession *session;
	CamelFolder *folder;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	backend = e_mail_reader_get_backend (reader);
	uids = e_mail_reader_get_selected_uids (reader);

	session = e_mail_backend_get_session (backend);

	mail_filter_folder (
		session, folder, uids,
		E_FILTER_SOURCE_DEMAND, FALSE);
}

static void
action_mail_remove_attachments_cb (GtkAction *action,
                                   EMailReader *reader)
{
	e_mail_reader_remove_attachments (reader);
}

static void
action_mail_remove_duplicates_cb (GtkAction *action,
                                  EMailReader *reader)
{
	e_mail_reader_remove_duplicates (reader);
}

static void
action_mail_find_cb (GtkAction *action,
                     EMailReader *reader)
{
	e_mail_reader_show_search_bar (reader);
}

static void
action_mail_flag_clear_cb (GtkAction *action,
                           EMailReader *reader)
{
	EMailDisplay *display;
	CamelFolder *folder;
	GtkWindow *window;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	display = e_mail_reader_get_mail_display (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	window = e_mail_reader_get_window (reader);

	em_utils_flag_for_followup_clear (window, folder, uids);

	e_mail_display_reload (display);
}

static void
action_mail_flag_completed_cb (GtkAction *action,
                               EMailReader *reader)
{
	EMailDisplay *display;
	CamelFolder *folder;
	GtkWindow *window;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	display = e_mail_reader_get_mail_display (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	window = e_mail_reader_get_window (reader);

	em_utils_flag_for_followup_completed (window, folder, uids);

	e_mail_display_reload (display);
}

static void
action_mail_flag_for_followup_cb (GtkAction *action,
                                  EMailReader *reader)
{
	CamelFolder *folder;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	uids = e_mail_reader_get_selected_uids (reader);

	em_utils_flag_for_followup (reader, folder, uids);
}

static void
action_mail_forward_cb (GtkAction *action,
                        EMailReader *reader)
{
	CamelFolder *folder;
	GtkWindow *window;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	window = e_mail_reader_get_window (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL);

	/* XXX Either e_mail_reader_get_selected_uids()
	 *     or MessageList should do this itself. */
	g_ptr_array_set_free_func (uids, (GDestroyNotify) g_free);

	if (em_utils_ask_open_many (window, uids->len))
		e_mail_reader_forward_messages (
			reader, folder, uids,
			e_mail_reader_get_forward_style (reader));

	g_ptr_array_unref (uids);
}

static void
action_mail_forward_attached_cb (GtkAction *action,
                                 EMailReader *reader)
{
	CamelFolder *folder;
	GtkWindow *window;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	window = e_mail_reader_get_window (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL);

	/* XXX Either e_mail_reader_get_selected_uids()
	 *     or MessageList should do this itself. */
	g_ptr_array_set_free_func (uids, (GDestroyNotify) g_free);

	if (em_utils_ask_open_many (window, uids->len))
		e_mail_reader_forward_messages (
			reader, folder, uids,
			E_MAIL_FORWARD_STYLE_ATTACHED);

	g_ptr_array_unref (uids);
}

static void
action_mail_forward_inline_cb (GtkAction *action,
                               EMailReader *reader)
{
	CamelFolder *folder;
	GtkWindow *window;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	window = e_mail_reader_get_window (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL);

	/* XXX Either e_mail_reader_get_selected_uids()
	 *     or MessageList should do this itself. */
	g_ptr_array_set_free_func (uids, (GDestroyNotify) g_free);

	if (em_utils_ask_open_many (window, uids->len))
		e_mail_reader_forward_messages (
			reader, folder, uids,
			E_MAIL_FORWARD_STYLE_INLINE);

	g_ptr_array_unref (uids);
}

static void
action_mail_forward_quoted_cb (GtkAction *action,
                               EMailReader *reader)
{
	CamelFolder *folder;
	GtkWindow *window;
	GPtrArray *uids;

	folder = e_mail_reader_get_folder (reader);
	window = e_mail_reader_get_window (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL);

	/* XXX Either e_mail_reader_get_selected_uids()
	 *     or MessageList should do this itself. */
	g_ptr_array_set_free_func (uids, (GDestroyNotify) g_free);

	if (em_utils_ask_open_many (window, uids->len))
		e_mail_reader_forward_messages (
			reader, folder, uids,
			E_MAIL_FORWARD_STYLE_QUOTED);

	g_ptr_array_unref (uids);
}

static void
action_mail_load_images_cb (GtkAction *action,
                            EMailReader *reader)
{
	EMailDisplay *display;

	display = e_mail_reader_get_mail_display (reader);

	e_mail_display_load_images (display);
}

static void
action_mail_mark_important_cb (GtkAction *action,
                               EMailReader *reader)
{
	guint32 mask = CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_DELETED;
	guint32 set  = CAMEL_MESSAGE_FLAGGED;

	e_mail_reader_mark_selected (reader, mask, set);
}

static gboolean
is_junk_folder_selected (EMailReader *reader)
{
	CamelFolder *folder;

	folder = e_mail_reader_get_folder (reader);

	if (folder == NULL)
		return FALSE;

	return (folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
}

static void
action_mail_mark_junk_cb (GtkAction *action,
                          EMailReader *reader)
{
	guint32 mask =
		CAMEL_MESSAGE_SEEN |
		CAMEL_MESSAGE_JUNK |
		CAMEL_MESSAGE_NOTJUNK |
		CAMEL_MESSAGE_JUNK_LEARN;
	guint32 set =
		CAMEL_MESSAGE_SEEN |
		CAMEL_MESSAGE_JUNK |
		CAMEL_MESSAGE_JUNK_LEARN;

	if (e_mail_reader_mark_selected (reader, mask, set) == 1 &&
	    !is_junk_folder_selected (reader))
		e_mail_reader_select_next_message (reader, TRUE);
}

static void
action_mail_mark_notjunk_cb (GtkAction *action,
                             EMailReader *reader)
{
	guint32 mask =
		CAMEL_MESSAGE_JUNK |
		CAMEL_MESSAGE_NOTJUNK |
		CAMEL_MESSAGE_JUNK_LEARN;
	guint32 set  =
		CAMEL_MESSAGE_NOTJUNK |
		CAMEL_MESSAGE_JUNK_LEARN;

	if (e_mail_reader_mark_selected (reader, mask, set) == 1 &&
	    is_junk_folder_selected (reader))
		e_mail_reader_select_next_message (reader, TRUE);
}

static void
action_mail_mark_read_cb (GtkAction *action,
                          EMailReader *reader)
{
	guint32 mask = CAMEL_MESSAGE_SEEN;
	guint32 set  = CAMEL_MESSAGE_SEEN;

	e_mail_reader_mark_selected (reader, mask, set);
}

static void
action_mail_mark_unimportant_cb (GtkAction *action,
                                 EMailReader *reader)
{
	guint32 mask = CAMEL_MESSAGE_FLAGGED;
	guint32 set  = 0;

	e_mail_reader_mark_selected (reader, mask, set);
}

static void
action_mail_mark_unread_cb (GtkAction *action,
                            EMailReader *reader)
{
	GtkWidget *message_list;
	EMFolderTreeModel *model;
	CamelFolder *folder;
	guint32 mask = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED;
	guint32 set  = 0;
	guint n_marked;

	message_list = e_mail_reader_get_message_list (reader);

	n_marked = e_mail_reader_mark_selected (reader, mask, set);

	if (MESSAGE_LIST (message_list)->seen_id != 0) {
		g_source_remove (MESSAGE_LIST (message_list)->seen_id);
		MESSAGE_LIST (message_list)->seen_id = 0;
	}

	/* Notify the tree model that the user has marked messages as
	 * unread so it doesn't mistake the event as new mail arriving. */
	model = em_folder_tree_model_get_default ();
	folder = e_mail_reader_get_folder (reader);
	em_folder_tree_model_user_marked_unread (model, folder, n_marked);
}

static void
action_mail_message_edit_cb (GtkAction *action,
                             EMailReader *reader)
{
	EShell *shell;
	EMailBackend *backend;
	ESourceRegistry *registry;
	CamelFolder *folder;
	GPtrArray *uids;
	gboolean replace;

	folder = e_mail_reader_get_folder (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL);

	backend = e_mail_reader_get_backend (reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
	registry = e_shell_get_registry (shell);

	/* XXX Either e_mail_reader_get_selected_uids()
	 *     or MessageList should do this itself. */
	g_ptr_array_set_free_func (uids, (GDestroyNotify) g_free);

	replace = em_utils_folder_is_drafts (registry, folder);
	e_mail_reader_edit_messages (reader, folder, uids, replace, replace);

	g_ptr_array_unref (uids);
}

static void
action_mail_message_new_cb (GtkAction *action,
                            EMailReader *reader)
{
	EShell *shell;
	EMailBackend *backend;
	EShellBackend *shell_backend;
	CamelFolder *folder;
	EMsgComposer *composer;

	folder = e_mail_reader_get_folder (reader);
	backend = e_mail_reader_get_backend (reader);

	shell_backend = E_SHELL_BACKEND (backend);
	shell = e_shell_backend_get_shell (shell_backend);

	composer = em_utils_compose_new_message (shell, folder);

	e_mail_reader_composer_created (reader, composer, NULL);
}

static void
action_mail_message_open_cb (GtkAction *action,
                             EMailReader *reader)
{
	e_mail_reader_open_selected_mail (reader);
}

static void
action_mail_move_cb (GtkAction *action,
                     EMailReader *reader)
{
	CamelFolder *folder;
	EMailBackend *backend;
	EMailSession *session;
	EMFolderSelector *selector;
	EMFolderTree *folder_tree;
	EMFolderTreeModel *model;
	GtkWidget *dialog;
	GtkWindow *window;
	GPtrArray *uids;
	const gchar *uri;

	backend = e_mail_reader_get_backend (reader);
	session = e_mail_backend_get_session (backend);

	folder = e_mail_reader_get_folder (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	window = e_mail_reader_get_window (reader);

	model = em_folder_tree_model_get_default ();

	dialog = em_folder_selector_new (
		window, model,
		EM_FOLDER_SELECTOR_CAN_CREATE,
		_("Move to Folder"), NULL, _("_Move"));

	selector = EM_FOLDER_SELECTOR (dialog);
	folder_tree = em_folder_selector_get_folder_tree (selector);

	em_folder_tree_set_excluded (
		folder_tree,
		EMFT_EXCLUDE_NOSELECT |
		EMFT_EXCLUDE_VIRTUAL |
		EMFT_EXCLUDE_VTRASH);

	if (default_xfer_messages_uri != NULL)
		em_folder_tree_set_selected (
			folder_tree, default_xfer_messages_uri, FALSE);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
		goto exit;

	uri = em_folder_selector_get_selected_uri (selector);

	g_free (default_xfer_messages_uri);
	default_xfer_messages_uri = g_strdup (uri);

	if (uri != NULL) {
		mail_transfer_messages (
			session, folder, uids,
			TRUE, uri, 0, NULL, NULL);
		uids = NULL;
	}

exit:
	if (uids != NULL)
		em_utils_uids_free (uids);

	gtk_widget_destroy (dialog);
}

static void
action_mail_next_cb (GtkAction *action,
                     EMailReader *reader)
{
	GtkWidget *message_list;
	MessageListSelectDirection direction;
	guint32 flags, mask;

	direction = MESSAGE_LIST_SELECT_NEXT;
	flags = 0;
	mask  = 0;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select (
		MESSAGE_LIST (message_list), direction, flags, mask);
}

static void
action_mail_next_important_cb (GtkAction *action,
                               EMailReader *reader)
{
	GtkWidget *message_list;
	MessageListSelectDirection direction;
	guint32 flags, mask;

	direction = MESSAGE_LIST_SELECT_NEXT | MESSAGE_LIST_SELECT_WRAP;
	flags = CAMEL_MESSAGE_FLAGGED;
	mask  = CAMEL_MESSAGE_FLAGGED;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select (
		MESSAGE_LIST (message_list), direction, flags, mask);
}

static void
action_mail_next_thread_cb (GtkAction *action,
                            EMailReader *reader)
{
	GtkWidget *message_list;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select_next_thread (MESSAGE_LIST (message_list));
}

static void
action_mail_next_unread_cb (GtkAction *action,
                            EMailReader *reader)
{
	GtkWidget *message_list;
	MessageListSelectDirection direction;
	guint32 flags, mask;

	direction = MESSAGE_LIST_SELECT_NEXT | MESSAGE_LIST_SELECT_WRAP;
	flags = 0;
	mask  = CAMEL_MESSAGE_SEEN;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select (
		MESSAGE_LIST (message_list), direction, flags, mask);
}

static void
action_mail_previous_cb (GtkAction *action,
                         EMailReader *reader)
{
	GtkWidget *message_list;
	MessageListSelectDirection direction;
	guint32 flags, mask;

	direction = MESSAGE_LIST_SELECT_PREVIOUS;
	flags = 0;
	mask  = 0;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select (
		MESSAGE_LIST (message_list), direction, flags, mask);
}

static void
action_mail_previous_important_cb (GtkAction *action,
                                   EMailReader *reader)
{
	GtkWidget *message_list;
	MessageListSelectDirection direction;
	guint32 flags, mask;

	direction = MESSAGE_LIST_SELECT_PREVIOUS | MESSAGE_LIST_SELECT_WRAP;
	flags = CAMEL_MESSAGE_FLAGGED;
	mask  = CAMEL_MESSAGE_FLAGGED;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select (
		MESSAGE_LIST (message_list), direction, flags, mask);
}

static void
action_mail_previous_thread_cb (GtkAction *action,
                                EMailReader *reader)
{
	GtkWidget *message_list;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select_prev_thread (MESSAGE_LIST (message_list));
}

static void
action_mail_previous_unread_cb (GtkAction *action,
                                EMailReader *reader)
{
	GtkWidget *message_list;
	MessageListSelectDirection direction;
	guint32 flags, mask;

	direction = MESSAGE_LIST_SELECT_PREVIOUS | MESSAGE_LIST_SELECT_WRAP;
	flags = 0;
	mask  = CAMEL_MESSAGE_SEEN;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select (
		MESSAGE_LIST (message_list), direction, flags, mask);
}

static void
action_mail_print_cb (GtkAction *action,
                      EMailReader *reader)
{
	GtkPrintOperationAction print_action;

	print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
	e_mail_reader_print (reader, print_action);
}

static void
action_mail_print_preview_cb (GtkAction *action,
                              EMailReader *reader)
{
	GtkPrintOperationAction print_action;

	print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW;
	e_mail_reader_print (reader, print_action);
}

static void
mail_reader_redirect_cb (CamelFolder *folder,
                         GAsyncResult *result,
                         EMailReaderClosure *closure)
{
	EShell *shell;
	EMailBackend *backend;
	EAlertSink *alert_sink;
	CamelMimeMessage *message;
	EMsgComposer *composer;
	GError *error = NULL;

	alert_sink = e_activity_get_alert_sink (closure->activity);

	message = camel_folder_get_message_finish (folder, result, &error);

	if (e_activity_handle_cancellation (closure->activity, error)) {
		g_warn_if_fail (message == NULL);
		mail_reader_closure_free (closure);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (message == NULL);
		e_alert_submit (
			alert_sink, "mail:no-retrieve-message",
			error->message, NULL);
		mail_reader_closure_free (closure);
		g_error_free (error);
		return;
	}

	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	backend = e_mail_reader_get_backend (closure->reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

	composer = em_utils_redirect_message (shell, message);

	e_mail_reader_composer_created (closure->reader, composer, message);

	g_object_unref (message);

	mail_reader_closure_free (closure);
}

static void
action_mail_redirect_cb (GtkAction *action,
                         EMailReader *reader)
{
	EActivity *activity;
	GCancellable *cancellable;
	EMailReaderClosure *closure;
	GtkWidget *message_list;
	CamelFolder *folder;
	const gchar *message_uid;

	folder = e_mail_reader_get_folder (reader);
	message_list = e_mail_reader_get_message_list (reader);

	message_uid = MESSAGE_LIST (message_list)->cursor_uid;
	g_return_if_fail (message_uid != NULL);

	/* Open the message asynchronously. */

	activity = e_mail_reader_new_activity (reader);
	cancellable = e_activity_get_cancellable (activity);

	closure = g_slice_new0 (EMailReaderClosure);
	closure->activity = activity;
	closure->reader = g_object_ref (reader);

	camel_folder_get_message (
		folder, message_uid, G_PRIORITY_DEFAULT,
		cancellable, (GAsyncReadyCallback)
		mail_reader_redirect_cb, closure);
}

static void
action_mail_reply_all_check (CamelFolder *folder,
                             GAsyncResult *result,
                             EMailReaderClosure *closure)
{
	EAlertSink *alert_sink;
	CamelMimeMessage *message;
	CamelInternetAddress *to, *cc;
	gint recip_count = 0;
	EMailReplyType type = E_MAIL_REPLY_TO_ALL;
	GError *error = NULL;

	alert_sink = e_activity_get_alert_sink (closure->activity);

	message = camel_folder_get_message_finish (folder, result, &error);

	if (e_activity_handle_cancellation (closure->activity, error)) {
		g_warn_if_fail (message == NULL);
		mail_reader_closure_free (closure);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (message == NULL);
		e_alert_submit (
			alert_sink, "mail:no-retrieve-message",
			error->message, NULL);
		mail_reader_closure_free (closure);
		g_error_free (error);
		return;
	}

	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	to = camel_mime_message_get_recipients (
		message, CAMEL_RECIPIENT_TYPE_TO);
	cc = camel_mime_message_get_recipients (
		message, CAMEL_RECIPIENT_TYPE_CC);

	recip_count = camel_address_length (CAMEL_ADDRESS (to));
	recip_count += camel_address_length (CAMEL_ADDRESS (cc));

	if (recip_count >= 15) {
		GtkWidget *dialog;
		GtkWidget *check;
		GtkWidget *container;
		gint response;

		dialog = e_alert_dialog_new_for_args (
			e_mail_reader_get_window (closure->reader),
			"mail:ask-reply-many-recips", NULL);

		container = e_alert_dialog_get_content_area (
			E_ALERT_DIALOG (dialog));

		/* Check buttons */
		check = gtk_check_button_new_with_mnemonic (
			_("_Do not ask me again."));
		gtk_box_pack_start (
			GTK_BOX (container), check, FALSE, FALSE, 0);
		gtk_widget_show (check);

		response = gtk_dialog_run (GTK_DIALOG (dialog));

		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check))) {
			GSettings *settings;
			const gchar *key;

			settings = g_settings_new ("org.gnome.evolution.mail");

			key = "prompt-on-reply-many-recips";
			g_settings_set_boolean (settings, key, FALSE);

			g_object_unref (settings);
		}

		gtk_widget_destroy (dialog);

		switch (response) {
			case GTK_RESPONSE_NO:
				type = E_MAIL_REPLY_TO_SENDER;
				break;
			case GTK_RESPONSE_CANCEL:
			case GTK_RESPONSE_DELETE_EVENT:
				goto exit;
			default:
				break;
		}
	}

	e_mail_reader_reply_to_message (closure->reader, message, type);

exit:
	g_object_unref (message);

	mail_reader_closure_free (closure);
}

static void
action_mail_reply_all_cb (GtkAction *action,
                          EMailReader *reader)
{
	GSettings *settings;
	const gchar *key;
	guint32 state;
	gboolean ask;

	state = e_mail_reader_check_state (reader);

	settings = g_settings_new ("org.gnome.evolution.mail");

	key = "prompt-on-reply-many-recips";
	ask = g_settings_get_boolean (settings, key);

	g_object_unref (settings);

	if (ask && !(state & E_MAIL_READER_SELECTION_IS_MAILING_LIST)) {
		EActivity *activity;
		GCancellable *cancellable;
		EMailReaderClosure *closure;
		CamelFolder *folder;
		GtkWidget *message_list;
		const gchar *message_uid;

		folder = e_mail_reader_get_folder (reader);
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

		message_list = e_mail_reader_get_message_list (reader);
		message_uid = MESSAGE_LIST (message_list)->cursor_uid;
		g_return_if_fail (message_uid != NULL);

		activity = e_mail_reader_new_activity (reader);
		cancellable = e_activity_get_cancellable (activity);

		closure = g_slice_new0 (EMailReaderClosure);
		closure->activity = activity;
		closure->reader = g_object_ref (reader);

		camel_folder_get_message (
			folder, message_uid, G_PRIORITY_DEFAULT,
			cancellable, (GAsyncReadyCallback)
			action_mail_reply_all_check, closure);

		return;
	}

	e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_ALL);
}

static void
action_mail_reply_group_cb (GtkAction *action,
                            EMailReader *reader)
{
	GSettings *settings;
	gboolean reply_list;
	guint32 state;
	const gchar *key;

	state = e_mail_reader_check_state (reader);

	settings = g_settings_new ("org.gnome.evolution.mail");

	key = "composer-group-reply-to-list";
	reply_list = g_settings_get_boolean (settings, key);

	g_object_unref (settings);

	if (reply_list && (state & E_MAIL_READER_SELECTION_IS_MAILING_LIST)) {
		e_mail_reader_reply_to_message (
			reader, NULL, E_MAIL_REPLY_TO_LIST);
	} else
		action_mail_reply_all_cb (action, reader);
}

static void
action_mail_reply_list_cb (GtkAction *action,
                           EMailReader *reader)
{
	e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_LIST);
}

static gboolean
message_is_list_administrative (CamelMimeMessage *message)
{
	const gchar *header;

	header = camel_medium_get_header (
		CAMEL_MEDIUM (message), "X-List-Administrivia");
	if (header == NULL)
		return FALSE;

	while (*header == ' ' || *header == '\t')
		header++;

	return g_ascii_strncasecmp (header, "yes", 3) == 0;
}

static void
action_mail_reply_sender_check (CamelFolder *folder,
                                GAsyncResult *result,
                                EMailReaderClosure *closure)
{
	EAlertSink *alert_sink;
	CamelMimeMessage *message;
	EMailReplyType type = E_MAIL_REPLY_TO_SENDER;
	GSettings *settings;
	gboolean ask_ignore_list_reply_to;
	gboolean ask_list_reply_to;
	gboolean munged_list_message;
	gboolean active;
	const gchar *key;
	GError *error = NULL;

	alert_sink = e_activity_get_alert_sink (closure->activity);

	message = camel_folder_get_message_finish (folder, result, &error);

	if (e_activity_handle_cancellation (closure->activity, error)) {
		g_warn_if_fail (message == NULL);
		mail_reader_closure_free (closure);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (message == NULL);
		e_alert_submit (
			alert_sink, "mail:no-retrieve-message",
			error->message, NULL);
		mail_reader_closure_free (closure);
		g_error_free (error);
		return;
	}

	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	settings = g_settings_new ("org.gnome.evolution.mail");

	key = "composer-ignore-list-reply-to";
	ask_ignore_list_reply_to = g_settings_get_boolean (settings, key);

	key = "prompt-on-list-reply-to";
	ask_list_reply_to = g_settings_get_boolean (settings, key);

	munged_list_message = em_utils_is_munged_list_message (message);

	if (message_is_list_administrative (message)) {
		/* Do not ask for messages which are list administrative,
		 * like list confirmation messages. */
	} else if (ask_ignore_list_reply_to || !munged_list_message) {
		/* Don't do the "Are you sure you want to reply in private?"
		 * pop-up if it's a Reply-To: munged list message... unless
		 * we're ignoring munging. */
		GtkWidget *dialog;
		GtkWidget *check;
		GtkWidget *container;
		gint response;

		dialog = e_alert_dialog_new_for_args (
			e_mail_reader_get_window (closure->reader),
			"mail:ask-list-private-reply", NULL);

		container = e_alert_dialog_get_content_area (
			E_ALERT_DIALOG (dialog));

		/* Check buttons */
		check = gtk_check_button_new_with_mnemonic (
			_("_Do not ask me again."));
		gtk_box_pack_start (
			GTK_BOX (container), check, FALSE, FALSE, 0);
		gtk_widget_show (check);

		response = gtk_dialog_run (GTK_DIALOG (dialog));

		active = gtk_toggle_button_get_active (
			GTK_TOGGLE_BUTTON (check));
		if (active) {
			key = "prompt-on-private-list-reply";
			g_settings_set_boolean (settings, key, FALSE);
		}

		gtk_widget_destroy (dialog);

		if (response == GTK_RESPONSE_YES)
			type = E_MAIL_REPLY_TO_ALL;
		else if (response == GTK_RESPONSE_OK)
			type = E_MAIL_REPLY_TO_LIST;
		else if (response == GTK_RESPONSE_CANCEL ||
			response == GTK_RESPONSE_DELETE_EVENT) {
			goto exit;
		}

	} else if (ask_list_reply_to) {
		GtkWidget *dialog;
		GtkWidget *container;
		GtkWidget *check_again;
		GtkWidget *check_always_ignore;
		gint response;

		dialog = e_alert_dialog_new_for_args (
			e_mail_reader_get_window (closure->reader),
			"mail:ask-list-honour-reply-to", NULL);

		container = e_alert_dialog_get_content_area (
			E_ALERT_DIALOG (dialog));

		check_again = gtk_check_button_new_with_mnemonic (
			_("_Do not ask me again."));
		gtk_box_pack_start (
			GTK_BOX (container), check_again, FALSE, FALSE, 0);
		gtk_widget_show (check_again);

		check_always_ignore = gtk_check_button_new_with_mnemonic (
			_("_Always ignore Reply-To: for mailing lists."));
		gtk_box_pack_start (
			GTK_BOX (container), check_always_ignore,
			FALSE, FALSE, 0);
		gtk_widget_show (check_always_ignore);

		response = gtk_dialog_run (GTK_DIALOG (dialog));

		active = gtk_toggle_button_get_active (
			GTK_TOGGLE_BUTTON (check_again));
		if (active) {
			key = "prompt-on-list-reply-to";
			g_settings_set_boolean (settings, key, FALSE);
		}

		key = "composer-ignore-list-reply-to";
		active = gtk_toggle_button_get_active (
			GTK_TOGGLE_BUTTON (check_always_ignore));
		g_settings_set_boolean (settings, key, active);

		gtk_widget_destroy (dialog);

		switch (response) {
			case GTK_RESPONSE_NO:
				type = E_MAIL_REPLY_TO_FROM;
				break;
			case GTK_RESPONSE_OK:
				type = E_MAIL_REPLY_TO_LIST;
				break;
			case GTK_RESPONSE_CANCEL:
			case GTK_RESPONSE_DELETE_EVENT:
				goto exit;
			default:
				break;
		}
	}

	e_mail_reader_reply_to_message (closure->reader, message, type);

exit:
	g_object_unref (settings);
	g_object_unref (message);

	mail_reader_closure_free (closure);
}

static void
action_mail_reply_sender_cb (GtkAction *action,
                             EMailReader *reader)
{
	GSettings *settings;
	gboolean ask_list_reply_to;
	gboolean ask_private_list_reply;
	gboolean ask;
	guint32 state;
	const gchar *key;

	state = e_mail_reader_check_state (reader);

	settings = g_settings_new ("org.gnome.evolution.mail");

	key = "prompt-on-list-reply-to";
	ask_list_reply_to = g_settings_get_boolean (settings, key);

	key = "prompt-on-private-list-reply";
	ask_private_list_reply = g_settings_get_boolean (settings, key);

	g_object_unref (settings);

	ask = (ask_private_list_reply || ask_list_reply_to);

	if (ask && (state & E_MAIL_READER_SELECTION_IS_MAILING_LIST)) {
		EActivity *activity;
		GCancellable *cancellable;
		EMailReaderClosure *closure;
		CamelFolder *folder;
		GtkWidget *message_list;
		const gchar *message_uid;

		folder = e_mail_reader_get_folder (reader);
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

		message_list = e_mail_reader_get_message_list (reader);
		message_uid = MESSAGE_LIST (message_list)->cursor_uid;
		g_return_if_fail (message_uid != NULL);

		activity = e_mail_reader_new_activity (reader);
		cancellable = e_activity_get_cancellable (activity);

		closure = g_slice_new0 (EMailReaderClosure);
		closure->activity = activity;
		closure->reader = g_object_ref (reader);

		camel_folder_get_message (
			folder, message_uid, G_PRIORITY_DEFAULT,
			cancellable, (GAsyncReadyCallback)
			action_mail_reply_sender_check, closure);

		return;
	}

	e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_SENDER);
}

static void
action_mail_reply_recipient_cb (GtkAction *action,
                                EMailReader *reader)
{
	e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_RECIPIENT);
}

static void
action_mail_save_as_cb (GtkAction *action,
                        EMailReader *reader)
{
	e_mail_reader_save_messages (reader);
}

static void
action_mail_search_folder_from_mailing_list_cb (GtkAction *action,
                                                EMailReader *reader)
{
	e_mail_reader_create_vfolder_from_selected (reader, AUTO_MLIST);
}

static void
action_mail_search_folder_from_recipients_cb (GtkAction *action,
                                              EMailReader *reader)
{
	e_mail_reader_create_vfolder_from_selected (reader, AUTO_TO);
}

static void
action_mail_search_folder_from_sender_cb (GtkAction *action,
                                          EMailReader *reader)
{
	e_mail_reader_create_vfolder_from_selected (reader, AUTO_FROM);
}

static void
action_mail_search_folder_from_subject_cb (GtkAction *action,
                                           EMailReader *reader)
{
	e_mail_reader_create_vfolder_from_selected (reader, AUTO_SUBJECT);
}

static void
action_mail_show_all_headers_cb (GtkToggleAction *action,
                                 EMailReader *reader)
{
	EMailDisplay *display;
	EMailFormatterMode mode;

	display = e_mail_reader_get_mail_display (reader);

	/* Ignore action when viewing message source. */
	mode = e_mail_display_get_mode (display);
	if (mode == E_MAIL_FORMATTER_MODE_SOURCE)
		return;
	if (mode == E_MAIL_FORMATTER_MODE_RAW)
		return;

	if (gtk_toggle_action_get_active (action))
		mode = E_MAIL_FORMATTER_MODE_ALL_HEADERS;
	else
		mode = E_MAIL_FORMATTER_MODE_NORMAL;

	e_mail_display_set_mode (display, mode);
}

static void
mail_source_retrieved (GObject *source_object,
                       GAsyncResult *result,
                       gpointer user_data)
{
	EMailReaderClosure *closure;
	CamelMimeMessage *message;
	EMailDisplay *display;
	GError *error = NULL;

	closure = (EMailReaderClosure *) user_data;
	display = e_mail_reader_get_mail_display (closure->reader);

	message = camel_folder_get_message_finish (
		CAMEL_FOLDER (source_object), result, &error);

	/* Sanity check. */
	g_return_if_fail (
		((message != NULL) && (error == NULL)) ||
		((message == NULL) && (error != NULL)));

	if (message != NULL) {
		mail_reader_set_display_formatter_for_message (
			closure->reader, display,
			closure->message_uid, message,
			CAMEL_FOLDER (source_object));
	} else {
		gchar *status;

		status = g_strdup_printf (
			"%s<br>%s",
			_("Failed to retrieve message:"),
			error->message);
		e_mail_display_set_status (display, status);
		g_free (status);

		g_error_free (error);
	}

	e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED);

	mail_reader_closure_free (closure);
}

static void
action_mail_show_source_cb (GtkAction *action,
                            EMailReader *reader)
{
	EMailDisplay *display;
	EMailBackend *backend;
	GtkWidget *browser;
	CamelFolder *folder;
	GPtrArray *uids;
	const gchar *message_uid;
	gchar *string;
	EActivity *activity;
	GCancellable *cancellable;
	EMailReaderClosure *closure;

	backend = e_mail_reader_get_backend (reader);
	folder = e_mail_reader_get_folder (reader);
	uids = e_mail_reader_get_selected_uids (reader);
	g_return_if_fail (uids != NULL && uids->len == 1);
	message_uid = g_ptr_array_index (uids, 0);

	browser = e_mail_browser_new (
		backend, NULL, NULL, E_MAIL_FORMATTER_MODE_SOURCE);
	e_mail_reader_set_folder (E_MAIL_READER (browser), folder);
	e_mail_reader_set_message (E_MAIL_READER (browser), message_uid);
	display = e_mail_reader_get_mail_display (E_MAIL_READER (browser));

	string = g_strdup_printf (_("Retrieving message '%s'"), message_uid);
	e_mail_display_set_parts_list (display, NULL);
	e_mail_display_set_status (display, string);
	gtk_widget_show (browser);

	activity = e_mail_reader_new_activity (reader);
	e_activity_set_text (activity, string);
	cancellable = e_activity_get_cancellable (activity);
	g_free (string);

	closure = g_slice_new0 (EMailReaderClosure);
	closure->reader = g_object_ref (browser);
	closure->activity = g_object_ref (activity);
	closure->message_uid = g_strdup (message_uid);

	camel_folder_get_message (
		folder, message_uid, G_PRIORITY_DEFAULT,
		cancellable, mail_source_retrieved, closure);

	g_object_unref (activity);

	em_utils_uids_free (uids);
}

static void
action_mail_toggle_important_cb (GtkAction *action,
                                 EMailReader *reader)
{
	CamelFolder *folder;
	GPtrArray *uids;
	guint ii;

	folder = e_mail_reader_get_folder (reader);
	uids = e_mail_reader_get_selected_uids (reader);

	camel_folder_freeze (folder);

	for (ii = 0; ii < uids->len; ii++) {
		guint32 flags;

		flags = camel_folder_get_message_flags (
			folder, uids->pdata[ii]);
		flags ^= CAMEL_MESSAGE_FLAGGED;
		if (flags & CAMEL_MESSAGE_FLAGGED)
			flags &= ~CAMEL_MESSAGE_DELETED;

		camel_folder_set_message_flags (
			folder, uids->pdata[ii], CAMEL_MESSAGE_FLAGGED |
			CAMEL_MESSAGE_DELETED, flags);
	}

	camel_folder_thaw (folder);

	em_utils_uids_free (uids);
}

static void
action_mail_undelete_cb (GtkAction *action,
                         EMailReader *reader)
{
	guint32 mask = CAMEL_MESSAGE_DELETED;
	guint32 set  = 0;

	e_mail_reader_mark_selected (reader, mask, set);
}

static void
action_mail_zoom_100_cb (GtkAction *action,
                         EMailReader *reader)
{
	EMailDisplay *display;

	display = e_mail_reader_get_mail_display (reader);

	webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (display), 1.0);
}

static void
action_mail_zoom_in_cb (GtkAction *action,
                        EMailReader *reader)
{
	EMailDisplay *display;

	display = e_mail_reader_get_mail_display (reader);

	webkit_web_view_zoom_in (WEBKIT_WEB_VIEW (display));
}

static void
action_mail_zoom_out_cb (GtkAction *action,
                         EMailReader *reader)
{
	EMailDisplay *display;

	display = e_mail_reader_get_mail_display (reader);

	webkit_web_view_zoom_out (WEBKIT_WEB_VIEW (display));
}

static void
action_search_folder_recipient_cb (GtkAction *action,
                                   EMailReader *reader)
{
	EMailBackend *backend;
	EMailSession *session;
	EWebView *web_view;
	CamelFolder *folder;
	CamelURL *curl;
	const gchar *uri;

	/* This action is defined in EMailDisplay. */

	folder = e_mail_reader_get_folder (reader);
	web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader));

	uri = e_web_view_get_selected_uri (web_view);
	g_return_if_fail (uri != NULL);

	curl = camel_url_new (uri, NULL);
	g_return_if_fail (curl != NULL);

	backend = e_mail_reader_get_backend (reader);
	session = e_mail_backend_get_session (backend);

	if (curl->path != NULL && *curl->path != '\0') {
		CamelInternetAddress *inet_addr;

		inet_addr = camel_internet_address_new ();
		camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
		vfolder_gui_add_from_address (
			session, inet_addr, AUTO_TO, folder);
		g_object_unref (inet_addr);
	}

	camel_url_free (curl);
}

static void
action_search_folder_sender_cb (GtkAction *action,
                                EMailReader *reader)
{
	EMailBackend *backend;
	EMailSession *session;
	EWebView *web_view;
	CamelFolder *folder;
	CamelURL *curl;
	const gchar *uri;

	/* This action is defined in EMailDisplay. */

	folder = e_mail_reader_get_folder (reader);
	web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader));

	uri = e_web_view_get_selected_uri (web_view);
	g_return_if_fail (uri != NULL);

	curl = camel_url_new (uri, NULL);
	g_return_if_fail (curl != NULL);

	backend = e_mail_reader_get_backend (reader);
	session = e_mail_backend_get_session (backend);

	if (curl->path != NULL && *curl->path != '\0') {
		CamelInternetAddress *inet_addr;

		inet_addr = camel_internet_address_new ();
		camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
		vfolder_gui_add_from_address (
			session, inet_addr, AUTO_FROM, folder);
		g_object_unref (inet_addr);
	}

	camel_url_free (curl);
}

static GtkActionEntry mail_reader_entries[] = {

	{ "mail-add-sender",
	  NULL,
	  N_("A_dd Sender to Address Book"),
	  NULL,
	  N_("Add sender to address book"),
	  G_CALLBACK (action_mail_add_sender_cb) },

	{ "mail-check-for-junk",
	  "mail-mark-junk",
	  N_("Check for _Junk"),
	  NULL,
	  N_("Filter the selected messages for junk status"),
	  G_CALLBACK (action_mail_check_for_junk_cb) },

	{ "mail-copy",
	  "mail-copy",
	  N_("_Copy to Folder..."),
	  "<Shift><Control>y",
	  N_("Copy selected messages to another folder"),
	  G_CALLBACK (action_mail_copy_cb) },

	{ "mail-delete",
	  "user-trash",
	  N_("_Delete Message"),
	  "<Control>d",
	  N_("Mark the selected messages for deletion"),
	  G_CALLBACK (action_mail_delete_cb) },

	{ "mail-filter-rule-for-mailing-list",
	  NULL,
	  N_("Create a Filter Rule for Mailing _List..."),
	  NULL,
	  N_("Create a rule to filter messages to this mailing list"),
	  G_CALLBACK (action_mail_filter_on_mailing_list_cb) },

	{ "mail-filter-rule-for-recipients",
	  NULL,
	  N_("Create a Filter Rule for _Recipients..."),
	  NULL,
	  N_("Create a rule to filter messages to these recipients"),
	  G_CALLBACK (action_mail_filter_on_recipients_cb) },

	{ "mail-filter-rule-for-sender",
	  NULL,
	  N_("Create a Filter Rule for Se_nder..."),
	  NULL,
	  N_("Create a rule to filter messages from this sender"),
	  G_CALLBACK (action_mail_filter_on_sender_cb) },

	{ "mail-filter-rule-for-subject",
	  NULL,
	  N_("Create a Filter Rule for _Subject..."),
	  NULL,
	  N_("Create a rule to filter messages with this subject"),
	  G_CALLBACK (action_mail_filter_on_subject_cb) },

	{ "mail-filters-apply",
	  "stock_mail-filters-apply",
	  N_("A_pply Filters"),
	  "<Control>y",
	  N_("Apply filter rules to the selected messages"),
	  G_CALLBACK (action_mail_filters_apply_cb) },

	{ "mail-find",
	  GTK_STOCK_FIND,
	  N_("_Find in Message..."),
	  "<Shift><Control>f",
	  N_("Search for text in the body of the displayed message"),
	  G_CALLBACK (action_mail_find_cb) },

	{ "mail-flag-clear",
	  NULL,
	  N_("_Clear Flag"),
	  NULL,
	  N_("Remove the follow-up flag from the selected messages"),
	  G_CALLBACK (action_mail_flag_clear_cb) },

	{ "mail-flag-completed",
	  NULL,
	  N_("_Flag Completed"),
	  NULL,
	  N_("Set the follow-up flag to completed on the selected messages"),
	  G_CALLBACK (action_mail_flag_completed_cb) },

	{ "mail-flag-for-followup",
	  "stock_mail-flag-for-followup",
	  N_("Follow _Up..."),
	  "<Shift><Control>g",
	  N_("Flag the selected messages for follow-up"),
	  G_CALLBACK (action_mail_flag_for_followup_cb) },

	{ "mail-forward-attached",
	  NULL,
	  N_("_Attached"),
	  NULL,
	  N_("Forward the selected message to someone as an attachment"),
	  G_CALLBACK (action_mail_forward_attached_cb) },

	{ "mail-forward-attached-full",
	  NULL,
	  N_("Forward As _Attached"),
	  NULL,
	  N_("Forward the selected message to someone as an attachment"),
	  G_CALLBACK (action_mail_forward_attached_cb) },

	{ "mail-forward-inline",
	  NULL,
	  N_("_Inline"),
	  NULL,
	  N_("Forward the selected message in the body of a new message"),
	  G_CALLBACK (action_mail_forward_inline_cb) },

	{ "mail-forward-inline-full",
	  NULL,
	  N_("Forward As _Inline"),
	  NULL,
	  N_("Forward the selected message in the body of a new message"),
	  G_CALLBACK (action_mail_forward_inline_cb) },

	{ "mail-forward-quoted",
	  NULL,
	  N_("_Quoted"),
	  NULL,
	  N_("Forward the selected message quoted like a reply"),
	  G_CALLBACK (action_mail_forward_quoted_cb) },

	{ "mail-forward-quoted-full",
	  NULL,
	  N_("Forward As _Quoted"),
	  NULL,
	  N_("Forward the selected message quoted like a reply"),
	  G_CALLBACK (action_mail_forward_quoted_cb) },

	{ "mail-load-images",
	  "image-x-generic",
	  N_("_Load Images"),
	  "<Control>i",
	  N_("Force images in HTML mail to be loaded"),
	  G_CALLBACK (action_mail_load_images_cb) },

	{ "mail-mark-important",
	  "mail-mark-important",
	  N_("_Important"),
	  NULL,
	  N_("Mark the selected messages as important"),
	  G_CALLBACK (action_mail_mark_important_cb) },

	{ "mail-mark-junk",
	  "mail-mark-junk",
	  N_("_Junk"),
	  "<Control>j",
	  N_("Mark the selected messages as junk"),
	  G_CALLBACK (action_mail_mark_junk_cb) },

	{ "mail-mark-notjunk",
	  "mail-mark-notjunk",
	  N_("_Not Junk"),
	  "<Shift><Control>j",
	  N_("Mark the selected messages as not being junk"),
	  G_CALLBACK (action_mail_mark_notjunk_cb) },

	{ "mail-mark-read",
	  "mail-mark-read",
	  N_("_Read"),
	  "<Control>k",
	  N_("Mark the selected messages as having been read"),
	  G_CALLBACK (action_mail_mark_read_cb) },

	{ "mail-mark-unimportant",
	  NULL,
	  N_("Uni_mportant"),
	  NULL,
	  N_("Mark the selected messages as unimportant"),
	  G_CALLBACK (action_mail_mark_unimportant_cb) },

	{ "mail-mark-unread",
	  "mail-mark-unread",
	  N_("_Unread"),
	  "<Shift><Control>k",
	  N_("Mark the selected messages as not having been read"),
	  G_CALLBACK (action_mail_mark_unread_cb) },

	{ "mail-message-edit",
	  NULL,
	  N_("_Edit as New Message..."),
	  NULL,
	  N_("Open the selected messages in the composer for editing"),
	  G_CALLBACK (action_mail_message_edit_cb) },

	{ "mail-message-new",
	  "mail-message-new",
	  N_("Compose _New Message"),
	  "<Shift><Control>m",
	  N_("Open a window for composing a mail message"),
	  G_CALLBACK (action_mail_message_new_cb) },

	{ "mail-message-open",
	  NULL,
	  N_("_Open in New Window"),
	  "<Control>o",
	  N_("Open the selected messages in a new window"),
	  G_CALLBACK (action_mail_message_open_cb) },

	{ "mail-move",
	  "mail-move",
	  N_("_Move to Folder..."),
	  "<Shift><Control>v",
	  N_("Move selected messages to another folder"),
	  G_CALLBACK (action_mail_move_cb) },

	{ "mail-next",
	  GTK_STOCK_GO_FORWARD,
	  N_("_Next Message"),
	  "<Control>Page_Down",
	  N_("Display the next message"),
	  G_CALLBACK (action_mail_next_cb) },

	{ "mail-next-important",
	  NULL,
	  N_("Next _Important Message"),
	  NULL,
	  N_("Display the next important message"),
	  G_CALLBACK (action_mail_next_important_cb) },

	{ "mail-next-thread",
	  NULL,
	  N_("Next _Thread"),
	  NULL,
	  N_("Display the next thread"),
	  G_CALLBACK (action_mail_next_thread_cb) },

	{ "mail-next-unread",
	  NULL,
	  N_("Next _Unread Message"),
	  "<Control>bracketright",
	  N_("Display the next unread message"),
	  G_CALLBACK (action_mail_next_unread_cb) },

	{ "mail-previous",
	  GTK_STOCK_GO_BACK,
	  N_("_Previous Message"),
	  "<Control>Page_Up",
	  N_("Display the previous message"),
	  G_CALLBACK (action_mail_previous_cb) },

	{ "mail-previous-important",
	  NULL,
	  N_("Pr_evious Important Message"),
	  NULL,
	  N_("Display the previous important message"),
	  G_CALLBACK (action_mail_previous_important_cb) },

	{ "mail-previous-thread",
	  NULL,
	  N_("Previous T_hread"),
	  NULL,
	  N_("Display the previous thread"),
	  G_CALLBACK (action_mail_previous_thread_cb) },

	{ "mail-previous-unread",
	  NULL,
	  N_("P_revious Unread Message"),
	  "<Control>bracketleft",
	  N_("Display the previous unread message"),
	  G_CALLBACK (action_mail_previous_unread_cb) },

	{ "mail-print",
	  GTK_STOCK_PRINT,
	  NULL,
	  "<Control>p",
	  N_("Print this message"),
	  G_CALLBACK (action_mail_print_cb) },

	{ "mail-print-preview",
	  GTK_STOCK_PRINT_PREVIEW,
	  NULL,
	  NULL,
	  N_("Preview the message to be printed"),
	  G_CALLBACK (action_mail_print_preview_cb) },

	{ "mail-redirect",
	  NULL,
	  N_("Re_direct"),
	  NULL,
	  N_("Redirect (bounce) the selected message to someone"),
	  G_CALLBACK (action_mail_redirect_cb) },

	{ "mail-remove-attachments",
	  GTK_STOCK_DELETE,
	  N_("Remo_ve Attachments"),
	  NULL,
	  N_("Remove attachments"),
	  G_CALLBACK (action_mail_remove_attachments_cb) },

	{ "mail-remove-duplicates",
	   NULL,
	   N_("Remove Du_plicate Messages"),
	   NULL,
	   N_("Checks selected messages for duplicates"),
	   G_CALLBACK (action_mail_remove_duplicates_cb) },

	{ "mail-reply-all",
	  NULL,
	  N_("Reply to _All"),
	  "<Shift><Control>r",
	  N_("Compose a reply to all the recipients of the selected message"),
	  G_CALLBACK (action_mail_reply_all_cb) },

	{ "mail-reply-list",
	  NULL,
	  N_("Reply to _List"),
	  "<Control>l",
	  N_("Compose a reply to the mailing list of the selected message"),
	  G_CALLBACK (action_mail_reply_list_cb) },

	{ "mail-reply-sender",
	  "mail-reply-sender",
	  N_("_Reply to Sender"),
	  "<Control>r",
	  N_("Compose a reply to the sender of the selected message"),
	  G_CALLBACK (action_mail_reply_sender_cb) },

	{ "mail-save-as",
	  GTK_STOCK_SAVE_AS,
	  N_("_Save as mbox..."),
	  "<Control>s",
	  N_("Save selected messages as an mbox file"),
	  G_CALLBACK (action_mail_save_as_cb) },

	{ "mail-show-source",
	  NULL,
	  N_("_Message Source"),
	  "<Control>u",
	  N_("Show the raw email source of the message"),
	  G_CALLBACK (action_mail_show_source_cb) },

	{ "mail-toggle-important",
	  NULL,
	  NULL,  /* No menu item; key press only */
	  NULL,
	  NULL,
	  G_CALLBACK (action_mail_toggle_important_cb) },

	{ "mail-undelete",
	  NULL,
	  N_("_Undelete Message"),
	  "<Shift><Control>d",
	  N_("Undelete the selected messages"),
	  G_CALLBACK (action_mail_undelete_cb) },

	{ "mail-zoom-100",
	  GTK_STOCK_ZOOM_100,
	  N_("_Normal Size"),
	  "<Control>0",
	  N_("Reset the text to its original size"),
	  G_CALLBACK (action_mail_zoom_100_cb) },

	{ "mail-zoom-in",
	  GTK_STOCK_ZOOM_IN,
	  N_("_Zoom In"),
	  "<Control>plus",
	  N_("Increase the text size"),
	  G_CALLBACK (action_mail_zoom_in_cb) },

	{ "mail-zoom-out",
	  GTK_STOCK_ZOOM_OUT,
	  N_("Zoom _Out"),
	  "<Control>minus",
	  N_("Decrease the text size"),
	  G_CALLBACK (action_mail_zoom_out_cb) },

	/*** Menus ***/

	{ "mail-create-menu",
	  NULL,
	  N_("Cre_ate"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-encoding-menu",
	  NULL,
	  N_("Ch_aracter Encoding"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-forward-as-menu",
	  NULL,
	  N_("F_orward As"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-reply-group-menu",
	  NULL,
	  N_("_Group Reply"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-goto-menu",
	  GTK_STOCK_JUMP_TO,
	  N_("_Go To"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-mark-as-menu",
	  NULL,
	  N_("Mar_k As"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-message-menu",
	  NULL,
	  N_("_Message"),
	  NULL,
	  NULL,
	  NULL },

	{ "mail-zoom-menu",
	  NULL,
	  N_("_Zoom"),
	  NULL,
	  NULL,
	  NULL }
};

static GtkActionEntry mail_reader_search_folder_entries[] = {

	{ "mail-search-folder-from-mailing-list",
	  NULL,
	  N_("Create a Search Folder from Mailing _List..."),
	  NULL,
	  N_("Create a search folder for this mailing list"),
	  G_CALLBACK (action_mail_search_folder_from_mailing_list_cb) },

	{ "mail-search-folder-from-recipients",
	  NULL,
	  N_("Create a Search Folder from Recipien_ts..."),
	  NULL,
	  N_("Create a search folder for these recipients"),
	  G_CALLBACK (action_mail_search_folder_from_recipients_cb) },

	{ "mail-search-folder-from-sender",
	  NULL,
	  N_("Create a Search Folder from Sen_der..."),
	  NULL,
	  N_("Create a search folder for this sender"),
	  G_CALLBACK (action_mail_search_folder_from_sender_cb) },

	{ "mail-search-folder-from-subject",
	  NULL,
	  N_("Create a Search Folder from S_ubject..."),
	  NULL,
	  N_("Create a search folder for this subject"),
	  G_CALLBACK (action_mail_search_folder_from_subject_cb) },
};

static EPopupActionEntry mail_reader_popup_entries[] = {

	{ "mail-popup-copy",
	  NULL,
	  "mail-copy" },

	{ "mail-popup-delete",
	  NULL,
	  "mail-delete" },

	{ "mail-popup-flag-clear",
	  NULL,
	  "mail-flag-clear" },

	{ "mail-popup-flag-completed",
	  NULL,
	  "mail-flag-completed" },

	{ "mail-popup-flag-for-followup",
	  N_("Mark for Follo_w Up..."),
	  "mail-flag-for-followup" },

	{ "mail-popup-forward",
	  NULL,
	  "mail-forward" },

	{ "mail-popup-mark-important",
	  N_("Mark as _Important"),
	  "mail-mark-important" },

	{ "mail-popup-mark-junk",
	  N_("Mark as _Junk"),
	  "mail-mark-junk" },

	{ "mail-popup-mark-notjunk",
	  N_("Mark as _Not Junk"),
	  "mail-mark-notjunk" },

	{ "mail-popup-mark-read",
	  N_("Mar_k as Read"),
	  "mail-mark-read" },

	{ "mail-popup-mark-unimportant",
	  N_("Mark as Uni_mportant"),
	  "mail-mark-unimportant" },

	{ "mail-popup-mark-unread",
	  N_("Mark as _Unread"),
	  "mail-mark-unread" },

	{ "mail-popup-message-edit",
	  NULL,
	  "mail-message-edit" },

	{ "mail-popup-move",
	  NULL,
	  "mail-move" },

	{ "mail-popup-print",
	  NULL,
	  "mail-print" },

	{ "mail-popup-remove-attachments",
	  NULL,
	  "mail-remove-attachments" },

	{ "mail-popup-remove-duplicates",
	  NULL,
	  "mail-remove-duplicates" },

	{ "mail-popup-reply-all",
	  NULL,
	  "mail-reply-all" },

	{ "mail-popup-reply-sender",
	  NULL,
	  "mail-reply-sender" },

	{ "mail-popup-save-as",
	  NULL,
	  "mail-save-as" },

	{ "mail-popup-undelete",
	  NULL,
	  "mail-undelete" }
};

static GtkToggleActionEntry mail_reader_toggle_entries[] = {

	{ "mail-caret-mode",
	  NULL,
	  N_("_Caret Mode"),
	  "F7",
	  N_("Show a blinking cursor in the body of displayed messages"),
	  NULL,  /* No callback required */
	  FALSE },

	{ "mail-show-all-headers",
	  NULL,
	  N_("All Message _Headers"),
	  NULL,
	  N_("Show messages with all email headers"),
	  G_CALLBACK (action_mail_show_all_headers_cb),
	  FALSE }
};

static void
mail_reader_double_click_cb (EMailReader *reader,
                             gint row,
                             ETreePath path,
                             gint col,
                             GdkEvent *event)
{
	GtkAction *action;

	/* Ignore double clicks on columns that handle their own state. */
	if (MESSAGE_LIST_COLUMN_IS_ACTIVE (col))
		return;

	action = e_mail_reader_get_action (reader, "mail-message-open");
	gtk_action_activate (action);
}

static gboolean
mail_reader_key_press_event_cb (EMailReader *reader,
                                GdkEventKey *event)
{
	GtkAction *action;
	const gchar *action_name;

	if (!gtk_widget_has_focus (GTK_WIDGET (reader))) {
		WebKitWebFrame *frame;
		WebKitDOMDocument *dom;
		WebKitDOMElement *element;
		EMailDisplay *display;
		gchar *name = NULL;

		display = e_mail_reader_get_mail_display (reader);
		frame = webkit_web_view_get_focused_frame (WEBKIT_WEB_VIEW (display));

		if (frame != NULL) {
			dom = webkit_web_frame_get_dom_document (frame);
			/* intentionally used "static_cast" */
			element = webkit_dom_html_document_get_active_element ((WebKitDOMHTMLDocument *) dom);

			if (element != NULL)
				name = webkit_dom_node_get_node_name (WEBKIT_DOM_NODE (element));

			/* If INPUT or TEXTAREA has focus,
			 * then any key press should go there. */
			if (name != NULL &&
			    (g_ascii_strcasecmp (name, "INPUT") == 0 ||
			     g_ascii_strcasecmp (name, "TEXTAREA") == 0)) {
				g_free (name);
				return FALSE;
			}
			g_free (name);
		}
	}

	if ((event->state & GDK_CONTROL_MASK) != 0)
		goto ctrl;

	/* <keyval> alone */
	switch (event->keyval) {
		case GDK_KEY_Delete:
		case GDK_KEY_KP_Delete:
			action_name = "mail-delete";
			break;

		case GDK_KEY_Return:
		case GDK_KEY_KP_Enter:
		case GDK_KEY_ISO_Enter:
			action_name = "mail-message-open";
			break;

		case GDK_KEY_period:
		case GDK_KEY_bracketright:
			action_name = "mail-next-unread";
			break;

		case GDK_KEY_comma:
		case GDK_KEY_bracketleft:
			action_name = "mail-previous-unread";
			break;

#ifdef HAVE_XFREE
		case XF86XK_Reply:
			action_name = "mail-reply-all";
			break;

		case XF86XK_MailForward:
			action_name = "mail-forward";
			break;
#endif

		case GDK_KEY_exclam:
			action_name = "mail-toggle-important";
			break;

		default:
			return FALSE;
	}

	goto exit;

ctrl:

	/* Ctrl + <keyval> */
	switch (event->keyval) {
		case GDK_KEY_period:
			action_name = "mail-next-unread";
			break;

		case GDK_KEY_comma:
			action_name = "mail-previous-unread";
			break;

		default:
			return FALSE;
	}

exit:
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_activate (action);

	return TRUE;
}

static gint
mail_reader_key_press_cb (EMailReader *reader,
                          gint row,
                          ETreePath path,
                          gint col,
                          GdkEvent *event)
{
	return mail_reader_key_press_event_cb (reader, &event->key);
}

static gboolean
mail_reader_message_seen_cb (EMailReaderClosure *closure)
{
	EMailReader *reader;
	GtkWidget *message_list;
	EMailPartList *parts;
	EMailDisplay *display;
	CamelMimeMessage *message;
	const gchar *current_uid;
	const gchar *message_uid;
	gboolean uid_is_current = TRUE;

	reader = closure->reader;
	message_uid = closure->message_uid;

	display = e_mail_reader_get_mail_display (reader);
	parts = e_mail_display_get_parts_list (display);
	message_list = e_mail_reader_get_message_list (reader);

	if (e_tree_is_dragging (E_TREE (message_list)))
		return FALSE;

	current_uid = MESSAGE_LIST (message_list)->cursor_uid;
	uid_is_current &= (g_strcmp0 (current_uid, message_uid) == 0);

	if (parts != NULL)
		message = e_mail_part_list_get_message (parts);
	else
		message = NULL;

	if (uid_is_current && message != NULL)
		g_signal_emit (
			reader, signals[MESSAGE_SEEN], 0,
			message_uid, message);

	return FALSE;
}

static gboolean
schedule_timeout_mark_seen (EMailReader *reader)
{
	MessageList *message_list;
	GSettings *settings;
	gboolean schedule_timeout;
	gint timeout_interval;
	const gchar *message_uid;

	message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));

	message_uid = message_list->cursor_uid;
	if (message_uid == NULL ||
	    e_tree_is_dragging (E_TREE (message_list)))
		return FALSE;

	settings = g_settings_new ("org.gnome.evolution.mail");

	/* FIXME These should be EMailReader properties. */
	schedule_timeout =
		(message_uid != NULL) &&
		g_settings_get_boolean (settings, "mark-seen");
	timeout_interval = g_settings_get_int (settings, "mark-seen-timeout");

	g_object_unref (settings);

	if (message_list->seen_id > 0) {
		g_source_remove (message_list->seen_id);
		message_list->seen_id = 0;
	}

	if (schedule_timeout) {
		EMailReaderClosure *timeout_closure;

		timeout_closure = g_slice_new0 (EMailReaderClosure);
		timeout_closure->reader = g_object_ref (reader);
		timeout_closure->message_uid = g_strdup (message_uid);

		MESSAGE_LIST (message_list)->seen_id = g_timeout_add_full (
			G_PRIORITY_DEFAULT, timeout_interval,
			(GSourceFunc) mail_reader_message_seen_cb,
			timeout_closure, (GDestroyNotify)
			mail_reader_closure_free);
	}

	return schedule_timeout;
}

static gboolean
discard_timeout_mark_seen_cb (EMailReader *reader)
{
	MessageList *message_list;

	g_return_val_if_fail (reader != NULL, FALSE);

	message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
	g_return_val_if_fail (message_list != NULL, FALSE);

	if (message_list->seen_id > 0) {
		g_source_remove (message_list->seen_id);
		message_list->seen_id = 0;
	}

	return FALSE;
}

static void
mail_reader_message_loaded_cb (CamelFolder *folder,
                               GAsyncResult *result,
                               EMailReaderClosure *closure)
{
	EMailReader *reader;
	EMailReaderPrivate *priv;
	CamelMimeMessage *message = NULL;
	GtkWidget *message_list;
	const gchar *message_uid;
	GError *error = NULL;

	reader = closure->reader;
	message_uid = closure->message_uid;

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	/* If the private struct is NULL, the EMailReader was destroyed
	 * while we were loading the message and we're likely holding the
	 * last reference.  Nothing to do but drop the reference.
	 * FIXME Use a GWeakRef instead of this hack. */
	if (priv == NULL) {
		mail_reader_closure_free (closure);
		return;
	}

	message = camel_folder_get_message_finish (folder, result, &error);

	/* If the user picked a different message in the time it took
	 * to fetch this message, then don't bother rendering it. */
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		g_clear_error (&error);
		goto exit;
	}

	message_list = e_mail_reader_get_message_list (reader);

	if (message_list == NULL) {
		/* For cases where message fetching took so long that
		 * user closed the message window before this was called. */
		goto exit;
	}

	if (message != NULL)
		g_signal_emit (
			reader, signals[MESSAGE_LOADED], 0,
			message_uid, message);

exit:
	priv->restoring_message_selection = FALSE;

	if (error != NULL) {
		EPreviewPane *preview_pane;
		EWebView *web_view;

		preview_pane = e_mail_reader_get_preview_pane (reader);
		web_view = e_preview_pane_get_web_view (preview_pane);

		e_alert_submit (
			E_ALERT_SINK (web_view),
			"mail:no-retrieve-message",
			error->message, NULL);
	}

	g_clear_error (&error);

	mail_reader_closure_free (closure);

	g_clear_object (&message);
}

static gboolean
mail_reader_message_selected_timeout_cb (EMailReader *reader)
{
	EMailReaderPrivate *priv;
	EMailDisplay *display;
	GtkWidget *message_list;
	CamelFolder *folder;
	const gchar *cursor_uid;
	const gchar *format_uid;
	EMailPartList *parts;

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	folder = e_mail_reader_get_folder (reader);

	message_list = e_mail_reader_get_message_list (reader);
	display = e_mail_reader_get_mail_display (reader);
	parts = e_mail_display_get_parts_list (display);

	cursor_uid = MESSAGE_LIST (message_list)->cursor_uid;
	if (parts != NULL)
		format_uid = e_mail_part_list_get_message_uid (parts);
	else
		format_uid = NULL;

	if (MESSAGE_LIST (message_list)->last_sel_single) {
		GtkWidget *widget;
		gboolean display_visible;
		gboolean selected_uid_changed;

		/* Decide whether to download the full message now. */
		widget = GTK_WIDGET (display);
		display_visible = gtk_widget_get_mapped (widget);

		selected_uid_changed = (g_strcmp0 (cursor_uid, format_uid) != 0);

		if (display_visible && selected_uid_changed) {
			EMailReaderClosure *closure;
			GCancellable *cancellable;
			EActivity *activity;
			gchar *string;

			string = g_strdup_printf (
				_("Retrieving message '%s'"), cursor_uid);
			e_mail_display_set_parts_list (display, NULL);
			e_mail_display_set_status (display, string);
			g_free (string);

			activity = e_mail_reader_new_activity (reader);
			e_activity_set_text (activity, _("Retrieving message"));
			cancellable = e_activity_get_cancellable (activity);

			closure = g_slice_new0 (EMailReaderClosure);
			closure->activity = activity;
			closure->reader = g_object_ref (reader);
			closure->message_uid = g_strdup (cursor_uid);

			camel_folder_get_message (
				folder, cursor_uid, G_PRIORITY_DEFAULT,
				cancellable, (GAsyncReadyCallback)
				mail_reader_message_loaded_cb, closure);

			if (priv->retrieving_message != NULL)
				g_object_unref (priv->retrieving_message);
			priv->retrieving_message = g_object_ref (cancellable);
		}
	} else {
		e_mail_display_set_parts_list (display, NULL);
		priv->restoring_message_selection = FALSE;
	}

	priv->message_selected_timeout_id = 0;

	return FALSE;
}

static void
mail_reader_message_selected_cb (EMailReader *reader,
                                 const gchar *message_uid)
{
	EMailReaderPrivate *priv;
	MessageList *message_list;

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	/* Cancel the previous message retrieval activity. */
	g_cancellable_cancel (priv->retrieving_message);

	/* Cancel the seen timer. */
	message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
	if (message_list != NULL && message_list->seen_id) {
		g_source_remove (message_list->seen_id);
		message_list->seen_id = 0;
	}

	/* Cancel the message selected timer. */
	if (priv->message_selected_timeout_id > 0) {
		g_source_remove (priv->message_selected_timeout_id);
		priv->message_selected_timeout_id = 0;
	}

	/* If a folder was just selected then we are now automatically
	 * restoring the previous message selection.  We behave slightly
	 * differently than if the user had selected the message. */
	priv->restoring_message_selection = priv->folder_was_just_selected;
	priv->folder_was_just_selected = FALSE;

	if (message_list_selected_count (message_list) != 1) {
		EMailDisplay *display;

		display = e_mail_reader_get_mail_display (reader);
		e_mail_display_set_parts_list (display, NULL);
		e_web_view_clear (E_WEB_VIEW (display));
	} else if (priv->restoring_message_selection) {
		/* Skip the timeout if we're restoring the previous message
		 * selection.  The timeout is there for when we're scrolling
		 * rapidly through the message list. */
		mail_reader_message_selected_timeout_cb (reader);
	} else {
		priv->message_selected_timeout_id = g_timeout_add (
			100, (GSourceFunc)
			mail_reader_message_selected_timeout_cb, reader);
	}

	e_mail_reader_changed (reader);
}

static void
mail_reader_message_cursor_change_cb (EMailReader *reader)
{
	MessageList *message_list;
	EMailReaderPrivate *priv;

	g_return_if_fail (reader != NULL);

	priv = E_MAIL_READER_GET_PRIVATE (reader);
	g_return_if_fail (priv != NULL);

	message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
	g_return_if_fail (message_list != NULL);

	if (message_list->seen_id == 0 &&
	    E_IS_MAIL_VIEW (reader) &&
	    e_mail_view_get_preview_visible (E_MAIL_VIEW (reader)) &&
	    !priv->avoid_next_mark_as_seen)
		schedule_timeout_mark_seen (reader);
}

static void
mail_reader_emit_folder_loaded (EMailReader *reader)
{
	EMailReaderPrivate *priv;
	MessageList *message_list;

	priv = E_MAIL_READER_GET_PRIVATE (reader);
	message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));

	if (priv && (message_list_count (message_list) <= 0 ||
	    message_list_selected_count (message_list) <= 0))
		priv->avoid_next_mark_as_seen = FALSE;

	g_signal_emit (reader, signals[FOLDER_LOADED], 0);
}

static EAlertSink *
mail_reader_get_alert_sink (EMailReader *reader)
{
	EPreviewPane *preview_pane;

	preview_pane = e_mail_reader_get_preview_pane (reader);

	return E_ALERT_SINK (preview_pane);
}

static GPtrArray *
mail_reader_get_selected_uids (EMailReader *reader)
{
	GtkWidget *message_list;

	message_list = e_mail_reader_get_message_list (reader);

	return message_list_get_selected (MESSAGE_LIST (message_list));
}

static CamelFolder *
mail_reader_get_folder (EMailReader *reader)
{
	GtkWidget *message_list;

	message_list = e_mail_reader_get_message_list (reader);

	return MESSAGE_LIST (message_list)->folder;
}

static void
mail_reader_set_folder (EMailReader *reader,
                        CamelFolder *folder)
{
	EMailReaderPrivate *priv;
	EMailDisplay *display;
	CamelFolder *previous_folder;
	ESourceRegistry *registry;
	GtkWidget *message_list;
	EMailBackend *backend;
	EShell *shell;
	gboolean outgoing;
	gboolean sync_folder;

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	backend = e_mail_reader_get_backend (reader);
	display = e_mail_reader_get_mail_display (reader);
	message_list = e_mail_reader_get_message_list (reader);

	previous_folder = e_mail_reader_get_folder (reader);

	backend = e_mail_reader_get_backend (reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
	registry = e_shell_get_registry (shell);

	/* Only synchronize the real folder if we're online. */
	sync_folder =
		(previous_folder != NULL) &&
		(CAMEL_IS_VEE_FOLDER (previous_folder) ||
		e_shell_get_online (shell));
	if (sync_folder)
		mail_sync_folder (previous_folder, TRUE, NULL, NULL);

	/* Skip the rest if we're already viewing the folder. */
	if (folder == previous_folder)
		return;

	outgoing = folder != NULL && (
		em_utils_folder_is_drafts (registry, folder) ||
		em_utils_folder_is_outbox (registry, folder) ||
		em_utils_folder_is_sent (registry, folder));

	e_web_view_clear (E_WEB_VIEW (display));

	priv->folder_was_just_selected = (folder != NULL);

	/* This is to make sure any post-poned changes in Search
	 * Folders will be propagated on folder selection. */
	if (CAMEL_IS_VEE_FOLDER (folder))
		mail_sync_folder (folder, FALSE, NULL, NULL);

	message_list_set_folder (
		MESSAGE_LIST (message_list), folder, outgoing);

	mail_reader_emit_folder_loaded (reader);
}

static void
mail_reader_set_message (EMailReader *reader,
                         const gchar *message_uid)
{
	GtkWidget *message_list;

	message_list = e_mail_reader_get_message_list (reader);

	message_list_select_uid (
		MESSAGE_LIST (message_list), message_uid, FALSE);
}

static void
mail_reader_folder_loaded (EMailReader *reader)
{
	guint32 state;

	state = e_mail_reader_check_state (reader);
	e_mail_reader_update_actions (reader, state);
}

static void
set_mail_display_part_list (GObject *object,
                            GAsyncResult *result,
                            gpointer user_data)
{
	EMailPartList *part_list;
	EMailReader *reader;
	EMailDisplay *display;

	reader = E_MAIL_READER (object);
	display = e_mail_reader_get_mail_display (reader);

	part_list = e_mail_reader_parse_message_finish (reader, result);

	e_mail_display_set_parts_list (display, part_list);
	e_mail_display_load (display, NULL);

	/* Remove the reference added when parts list was
	 * created, so that only owners are EMailDisplays. */
	g_object_unref (part_list);
}

static void
mail_reader_set_display_formatter_for_message (EMailReader *reader,
                                               EMailDisplay *display,
                                               const gchar *message_uid,
                                               CamelMimeMessage *message,
                                               CamelFolder *folder)
{
	CamelObjectBag *registry;
	EMailPartList *parts;
	EMailReaderPrivate *priv;
	gchar *mail_uri;

	priv = E_MAIL_READER_GET_PRIVATE (reader);
	mail_uri = e_mail_part_build_uri (folder, message_uid, NULL, NULL);
	registry = e_mail_part_list_get_registry ();
	parts = camel_object_bag_peek (registry, mail_uri);
	g_free (mail_uri);

	if (parts == NULL) {
		e_mail_reader_parse_message (
			reader, folder, message_uid, message,
			priv->retrieving_message,
			set_mail_display_part_list, NULL);
	} else {
		e_mail_display_set_parts_list (display, parts);
		e_mail_display_load (display, NULL);
		g_object_unref (parts);
	}
}

static void
mail_reader_message_loaded (EMailReader *reader,
                            const gchar *message_uid,
                            CamelMimeMessage *message)
{
	EMailReaderPrivate *priv;
	GtkWidget *message_list;
	EMailBackend *backend;
	CamelFolder *folder;
	EMailDisplay *display;
	EShellBackend *shell_backend;
	EShell *shell;
	EMEvent *event;
	EMEventTargetMessage *target;
	GError *error = NULL;

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	folder = e_mail_reader_get_folder (reader);
	backend = e_mail_reader_get_backend (reader);
	display = e_mail_reader_get_mail_display (reader);
	message_list = e_mail_reader_get_message_list (reader);

	shell_backend = E_SHELL_BACKEND (backend);
	shell = e_shell_backend_get_shell (shell_backend);

	/** @Event: message.reading
	 * @Title: Viewing a message
	 * @Target: EMEventTargetMessage
	 *
	 * message.reading is emitted whenever a user views a message.
	 */
	event = em_event_peek ();
	target = em_event_target_new_message (
		event, folder, message, message_uid, 0, NULL);
	e_event_emit (
		(EEvent *) event, "message.reading",
		(EEventTarget *) target);

	mail_reader_set_display_formatter_for_message (
		reader, display, message_uid, message, folder);

	/* Reset the shell view icon. */
	e_shell_event (shell, "mail-icon", (gpointer) "evolution-mail");

	if (MESSAGE_LIST (message_list)->seen_id > 0) {
		g_source_remove (MESSAGE_LIST (message_list)->seen_id);
		MESSAGE_LIST (message_list)->seen_id = 0;
	}

	/* Determine whether to mark the message as read. */
	if (message != NULL &&
	    !priv->restoring_message_selection &&
	    !priv->avoid_next_mark_as_seen &&
	    schedule_timeout_mark_seen (reader)) {
		g_clear_error (&error);
	} else if (error != NULL) {
		e_alert_submit (
			E_ALERT_SINK (display),
			"mail:no-retrieve-message",
			error->message, NULL);
		g_error_free (error);
	}

	priv->avoid_next_mark_as_seen = FALSE;
}

static void
mail_reader_message_seen (EMailReader *reader,
                          const gchar *message_uid,
                          CamelMimeMessage *message)
{
	CamelFolder *folder;
	guint32 mask, set;

	mask = CAMEL_MESSAGE_SEEN;
	set  = CAMEL_MESSAGE_SEEN;

	folder = e_mail_reader_get_folder (reader);
	camel_folder_set_message_flags (folder, message_uid, mask, set);
}

static void
mail_reader_show_search_bar (EMailReader *reader)
{
	EPreviewPane *preview_pane;

	preview_pane = e_mail_reader_get_preview_pane (reader);
	e_preview_pane_show_search_bar (preview_pane);
}

static void
mail_reader_update_actions (EMailReader *reader,
                            guint32 state)
{
	GtkAction *action;
	const gchar *action_name;
	gboolean sensitive;

	/* Be descriptive. */
	gboolean any_messages_selected;
	gboolean enable_flag_clear;
	gboolean enable_flag_completed;
	gboolean enable_flag_for_followup;
	gboolean have_enabled_account;
	gboolean multiple_messages_selected;
	gboolean selection_has_attachment_messages;
	gboolean selection_has_deleted_messages;
	gboolean selection_has_important_messages;
	gboolean selection_has_junk_messages;
	gboolean selection_has_not_junk_messages;
	gboolean selection_has_read_messages;
	gboolean selection_has_undeleted_messages;
	gboolean selection_has_unimportant_messages;
	gboolean selection_has_unread_messages;
	gboolean selection_is_mailing_list;
	gboolean single_message_selected;
	gboolean first_message_selected = FALSE;
	gboolean last_message_selected = FALSE;

	have_enabled_account =
		(state & E_MAIL_READER_HAVE_ENABLED_ACCOUNT);
	single_message_selected =
		(state & E_MAIL_READER_SELECTION_SINGLE);
	multiple_messages_selected =
		(state & E_MAIL_READER_SELECTION_MULTIPLE);
	/* FIXME Missing CAN_ADD_SENDER */
	enable_flag_clear =
		(state & E_MAIL_READER_SELECTION_FLAG_CLEAR);
	enable_flag_completed =
		(state & E_MAIL_READER_SELECTION_FLAG_COMPLETED);
	enable_flag_for_followup =
		(state & E_MAIL_READER_SELECTION_FLAG_FOLLOWUP);
	selection_has_attachment_messages =
		(state & E_MAIL_READER_SELECTION_HAS_ATTACHMENTS);
	selection_has_deleted_messages =
		(state & E_MAIL_READER_SELECTION_HAS_DELETED);
	selection_has_important_messages =
		(state & E_MAIL_READER_SELECTION_HAS_IMPORTANT);
	selection_has_junk_messages =
		(state & E_MAIL_READER_SELECTION_HAS_JUNK);
	selection_has_not_junk_messages =
		(state & E_MAIL_READER_SELECTION_HAS_NOT_JUNK);
	selection_has_read_messages =
		(state & E_MAIL_READER_SELECTION_HAS_READ);
	selection_has_undeleted_messages =
		(state & E_MAIL_READER_SELECTION_HAS_UNDELETED);
	selection_has_unimportant_messages =
		(state & E_MAIL_READER_SELECTION_HAS_UNIMPORTANT);
	selection_has_unread_messages =
		(state & E_MAIL_READER_SELECTION_HAS_UNREAD);
	selection_is_mailing_list =
		(state & E_MAIL_READER_SELECTION_IS_MAILING_LIST);

	any_messages_selected =
		(single_message_selected || multiple_messages_selected);

	if (any_messages_selected) {
		MessageList *message_list;
		gint row = -1, count = -1;
		ETreeTableAdapter *etta;
		ETreePath node = NULL;

		message_list = MESSAGE_LIST (
			e_mail_reader_get_message_list (reader));
		etta = e_tree_get_table_adapter (E_TREE (message_list));

		if (message_list->cursor_uid != NULL)
			node = g_hash_table_lookup (
				message_list->uid_nodemap,
				message_list->cursor_uid);

		if (node != NULL) {
			row = e_tree_table_adapter_row_of_node (etta, node);
			count = e_table_model_row_count (E_TABLE_MODEL (etta));
		}

		first_message_selected = row <= 0;
		last_message_selected = row < 0 || row + 1 >= count;
	}

	action_name = "mail-add-sender";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-check-for-junk";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-copy";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-create-menu";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	/* If a single message is selected, let the user hit delete to
	 * advance the cursor even if the message is already deleted. */
	action_name = "mail-delete";
	sensitive =
		single_message_selected ||
		selection_has_undeleted_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-filters-apply";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-find";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-flag-clear";
	sensitive = enable_flag_clear;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-flag-completed";
	sensitive = enable_flag_completed;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-flag-for-followup";
	sensitive = enable_flag_for_followup;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward";
	sensitive = have_enabled_account && any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-attached";
	sensitive = have_enabled_account && any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-attached-full";
	sensitive = have_enabled_account && any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-as-menu";
	sensitive = have_enabled_account && any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-inline";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-inline-full";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-quoted";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-forward-quoted-full";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-goto-menu";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-load-images";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-as-menu";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-important";
	sensitive = selection_has_unimportant_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-junk";
	sensitive =
		selection_has_not_junk_messages &&
		!(state & E_MAIL_READER_FOLDER_IS_JUNK);
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-notjunk";
	sensitive = selection_has_junk_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-read";
	sensitive = selection_has_unread_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-unimportant";
	sensitive = selection_has_important_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-mark-unread";
	sensitive = selection_has_read_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-message-edit";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-message-new";
	sensitive = have_enabled_account;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-message-open";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-move";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-next";
	sensitive = any_messages_selected && !last_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-next-important";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-next-thread";
	sensitive = single_message_selected && !last_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-next-unread";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-previous";
	sensitive = any_messages_selected && !first_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-previous-important";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-previous-unread";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-previous-thread";
	sensitive = any_messages_selected && !first_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-print";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-print-preview";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-redirect";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-remove-attachments";
	sensitive = any_messages_selected && selection_has_attachment_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-remove-duplicates";
	sensitive = multiple_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-reply-all";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-reply-group";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-reply-group-menu";
	sensitive = have_enabled_account && any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-reply-list";
	sensitive = have_enabled_account && single_message_selected &&
		selection_is_mailing_list;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-reply-sender";
	sensitive = have_enabled_account && single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-save-as";
	sensitive = any_messages_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-show-source";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-undelete";
	sensitive = selection_has_deleted_messages;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-zoom-100";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-zoom-in";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);

	action_name = "mail-zoom-out";
	sensitive = single_message_selected;
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_sensitive (action, sensitive);
}

static void
mail_reader_init_charset_actions (EMailReader *reader,
                                  GtkActionGroup *action_group)
{
	GtkRadioAction *default_action;
	GSList *radio_group;

	radio_group = e_charset_add_radio_actions (
		action_group, "mail-charset-", NULL,
		G_CALLBACK (action_mail_charset_cb), reader);

	/* XXX Add a tooltip! */
	default_action = gtk_radio_action_new (
		"mail-charset-default", _("Default"), NULL, NULL, -1);

	gtk_radio_action_set_group (default_action, radio_group);

	g_signal_connect (
		default_action, "changed",
		G_CALLBACK (action_mail_charset_cb), reader);

	gtk_action_group_add_action (
		action_group, GTK_ACTION (default_action));

	gtk_radio_action_set_current_value (default_action, -1);
}

static void
e_mail_reader_default_init (EMailReaderInterface *interface)
{
	quark_private = g_quark_from_static_string ("e-mail-reader-private");

	interface->get_alert_sink = mail_reader_get_alert_sink;
	interface->get_selected_uids = mail_reader_get_selected_uids;
	interface->get_folder = mail_reader_get_folder;
	interface->set_folder = mail_reader_set_folder;
	interface->set_message = mail_reader_set_message;
	interface->open_selected_mail = e_mail_reader_open_selected;
	interface->folder_loaded = mail_reader_folder_loaded;
	interface->message_loaded = mail_reader_message_loaded;
	interface->message_seen = mail_reader_message_seen;
	interface->show_search_bar = mail_reader_show_search_bar;
	interface->update_actions = mail_reader_update_actions;

	g_object_interface_install_property (
		interface,
		g_param_spec_enum (
			"forward-style",
			"Forward Style",
			"How to forward messages",
			E_TYPE_MAIL_FORWARD_STYLE,
			E_MAIL_FORWARD_STYLE_ATTACHED,
			G_PARAM_READWRITE));

	g_object_interface_install_property (
		interface,
		g_param_spec_boolean (
			"group-by-threads",
			"Group by Threads",
			"Whether to group messages by threads",
			FALSE,
			G_PARAM_READWRITE));

	g_object_interface_install_property (
		interface,
		g_param_spec_enum (
			"reply-style",
			"Reply Style",
			"How to reply to messages",
			E_TYPE_MAIL_REPLY_STYLE,
			E_MAIL_REPLY_STYLE_QUOTED,
			G_PARAM_READWRITE));

	signals[CHANGED] = g_signal_new (
		"changed",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_FIRST,
		0, NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	signals[COMPOSER_CREATED] = g_signal_new (
		"composer-created",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_FIRST,
		G_STRUCT_OFFSET (EMailReaderInterface, composer_created),
		NULL, NULL, NULL,
		G_TYPE_NONE, 2,
		E_TYPE_MSG_COMPOSER,
		CAMEL_TYPE_MIME_MESSAGE);

	signals[FOLDER_LOADED] = g_signal_new (
		"folder-loaded",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_FIRST,
		G_STRUCT_OFFSET (EMailReaderInterface, folder_loaded),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	signals[MESSAGE_LOADED] = g_signal_new (
		"message-loaded",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EMailReaderInterface, message_loaded),
		NULL, NULL,
		e_marshal_VOID__STRING_OBJECT,
		G_TYPE_NONE, 2,
		G_TYPE_STRING,
		CAMEL_TYPE_MIME_MESSAGE);

	signals[MESSAGE_SEEN] = g_signal_new (
		"message-seen",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EMailReaderInterface, message_seen),
		NULL, NULL,
		e_marshal_VOID__STRING_OBJECT,
		G_TYPE_NONE, 2,
		G_TYPE_STRING,
		CAMEL_TYPE_MIME_MESSAGE);

	signals[SHOW_SEARCH_BAR] = g_signal_new (
		"show-search-bar",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
		G_STRUCT_OFFSET (EMailReaderInterface, show_search_bar),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	signals[UPDATE_ACTIONS] = g_signal_new (
		"update-actions",
		G_OBJECT_CLASS_TYPE (interface),
		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
		G_STRUCT_OFFSET (EMailReaderInterface, update_actions),
		NULL, NULL,
		g_cclosure_marshal_VOID__UINT,
		G_TYPE_NONE, 1,
		G_TYPE_UINT);
}

void
e_mail_reader_init (EMailReader *reader,
                    gboolean init_actions,
                    gboolean connect_signals)
{
	EMenuToolAction *menu_tool_action;
	GtkActionGroup *action_group;
	GtkWidget *message_list;
	GtkAction *action;
	const gchar *action_name;
	EMailDisplay *display;
	GSettings *settings;

	g_return_if_fail (E_IS_MAIL_READER (reader));

	message_list = e_mail_reader_get_message_list (reader);
	display = e_mail_reader_get_mail_display (reader);

	/* Initialize a private struct. */
	g_object_set_qdata_full (
		G_OBJECT (reader), quark_private,
		g_slice_new0 (EMailReaderPrivate),
		(GDestroyNotify) mail_reader_private_free);

	if (!init_actions)
		goto connect_signals;

	/* Add the "standard" EMailReader actions. */

	action_group = e_mail_reader_get_action_group (
		reader, E_MAIL_READER_ACTION_GROUP_STANDARD);

	/* The "mail-forward" action is special: it uses a GtkMenuToolButton
	 * for its toolbar item type.  So we have to create it separately. */

	menu_tool_action = e_menu_tool_action_new (
		"mail-forward", _("_Forward"),
		_("Forward the selected message to someone"), NULL);

	gtk_action_set_icon_name (
		GTK_ACTION (menu_tool_action), "mail-forward");

	g_signal_connect (
		menu_tool_action, "activate",
		G_CALLBACK (action_mail_forward_cb), reader);

	gtk_action_group_add_action_with_accel (
		action_group, GTK_ACTION (menu_tool_action), "<Control>f");

	/* Likewise the "mail-reply-group" action. */

	/* For Translators: "Group Reply" will reply either to a mailing list
	 * (if possible and if that configuration option is enabled), or else
	 * it will reply to all. The word "Group" was chosen because it covers
	 * either of those, without too strongly implying one or the other. */
	menu_tool_action = e_menu_tool_action_new (
		"mail-reply-group", _("Group Reply"),
		_("Reply to the mailing list, or to all recipients"), NULL);

	gtk_action_set_icon_name (
		GTK_ACTION (menu_tool_action), "mail-reply-all");

	g_signal_connect (
		menu_tool_action, "activate",
		G_CALLBACK (action_mail_reply_group_cb), reader);

	gtk_action_group_add_action_with_accel (
		action_group, GTK_ACTION (menu_tool_action), "<Control>g");

	/* Add the other actions the normal way. */
	gtk_action_group_add_actions (
		action_group, mail_reader_entries,
		G_N_ELEMENTS (mail_reader_entries), reader);
	e_action_group_add_popup_actions (
		action_group, mail_reader_popup_entries,
		G_N_ELEMENTS (mail_reader_popup_entries));
	gtk_action_group_add_toggle_actions (
		action_group, mail_reader_toggle_entries,
		G_N_ELEMENTS (mail_reader_toggle_entries), reader);

	mail_reader_init_charset_actions (reader, action_group);

	/* Add EMailReader actions for Search Folders.  The action group
	 * should be made invisible if Search Folders are disabled. */

	action_group = e_mail_reader_get_action_group (
		reader, E_MAIL_READER_ACTION_GROUP_SEARCH_FOLDERS);

	gtk_action_group_add_actions (
		action_group, mail_reader_search_folder_entries,
		G_N_ELEMENTS (mail_reader_search_folder_entries), reader);

	display = e_mail_reader_get_mail_display (reader);

	/* Bind GObject properties to GSettings keys. */

	settings = g_settings_new ("org.gnome.evolution.mail");

	action_name = "mail-caret-mode";
	action = e_mail_reader_get_action (reader, action_name);
	g_settings_bind (
		settings, "caret-mode",
		action, "active", G_SETTINGS_BIND_DEFAULT);

	action_name = "mail-show-all-headers";
	action = e_mail_reader_get_action (reader, action_name);
	g_settings_bind (
		settings, "show-all-headers",
		action, "active", G_SETTINGS_BIND_DEFAULT);

	/* Mode change when viewing message source is ignored. */
	if (e_mail_display_get_mode (display) == E_MAIL_FORMATTER_MODE_SOURCE ||
	    e_mail_display_get_mode (display) == E_MAIL_FORMATTER_MODE_RAW) {
		gtk_action_set_sensitive (action, FALSE);
		gtk_action_set_visible (action, FALSE);
	}

	g_object_unref (settings);

	/* Fine tuning. */

	action_name = "mail-delete";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_short_label (action, _("Delete"));

	action_name = "mail-forward";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_is_important (action, TRUE);

	action_name = "mail-reply-group";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_is_important (action, TRUE);

	action_name = "mail-next";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_short_label (action, _("Next"));

	action_name = "mail-previous";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_short_label (action, _("Previous"));

	action_name = "mail-reply-all";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_is_important (action, TRUE);

	action_name = "mail-reply-sender";
	action = e_mail_reader_get_action (reader, action_name);
	gtk_action_set_is_important (action, TRUE);
	gtk_action_set_short_label (action, _("Reply"));

	action_name = "add-to-address-book";
	action = e_mail_display_get_action (display, action_name);
	g_signal_connect (
		action, "activate",
		G_CALLBACK (action_add_to_address_book_cb), reader);

	action_name = "send-reply";
	action = e_mail_display_get_action (display, action_name);
	g_signal_connect (
		action, "activate",
		G_CALLBACK (action_mail_reply_recipient_cb), reader);

	action_name = "search-folder-recipient";
	action = e_mail_display_get_action (display, action_name);
	g_signal_connect (
		action, "activate",
		G_CALLBACK (action_search_folder_recipient_cb), reader);

	action_name = "search-folder-sender";
	action = e_mail_display_get_action (display, action_name);
	g_signal_connect (
		action, "activate",
		G_CALLBACK (action_search_folder_sender_cb), reader);

	action_name = "image-save";
	action = e_mail_display_get_action (display, action_name);
	g_signal_connect (
		action, "activate",
		G_CALLBACK (action_mail_image_save_cb), reader);

#ifndef G_OS_WIN32
	/* Lockdown integration. */

	settings = g_settings_new ("org.gnome.desktop.lockdown");

	action_name = "mail-print";
	action = e_mail_reader_get_action (reader, action_name);
	g_settings_bind (
		settings, "disable-printing",
		action, "visible",
		G_SETTINGS_BIND_GET |
		G_SETTINGS_BIND_NO_SENSITIVITY |
		G_SETTINGS_BIND_INVERT_BOOLEAN);

	action_name = "mail-print-preview";
	action = e_mail_reader_get_action (reader, action_name);
	g_settings_bind (
		settings, "disable-printing",
		action, "visible",
		G_SETTINGS_BIND_GET |
		G_SETTINGS_BIND_NO_SENSITIVITY |
		G_SETTINGS_BIND_INVERT_BOOLEAN);

	action_name = "mail-save-as";
	action = e_mail_reader_get_action (reader, action_name);
	g_settings_bind (
		settings, "disable-save-to-disk",
		action, "visible",
		G_SETTINGS_BIND_GET |
		G_SETTINGS_BIND_NO_SENSITIVITY |
		G_SETTINGS_BIND_INVERT_BOOLEAN);

	g_object_unref (settings);
#endif

	/* Bind properties. */

	action_name = "mail-caret-mode";
	action = e_mail_reader_get_action (reader, action_name);

	g_object_bind_property (
		action, "active",
		display, "caret-mode",
		G_BINDING_BIDIRECTIONAL |
		G_BINDING_SYNC_CREATE);

connect_signals:

	if (!connect_signals)
		return;

	/* Connect signals. */
	g_signal_connect_swapped (
		display, "key-press-event",
		G_CALLBACK (mail_reader_key_press_event_cb), reader);

	g_signal_connect_swapped (
		message_list, "message-selected",
		G_CALLBACK (mail_reader_message_selected_cb), reader);

	/* re-schedule mark-as-seen,... */
	g_signal_connect_swapped (
		message_list, "cursor-change",
		G_CALLBACK (mail_reader_message_cursor_change_cb), reader);

	/* but do not mark-as-seen if... */
	g_signal_connect_swapped (
		message_list, "tree-drag-begin",
		G_CALLBACK (discard_timeout_mark_seen_cb), reader);

	g_signal_connect_swapped (
		message_list, "tree-drag-end",
		G_CALLBACK (discard_timeout_mark_seen_cb), reader);

	g_signal_connect_swapped (
		message_list, "right-click",
		G_CALLBACK (discard_timeout_mark_seen_cb), reader);

	g_signal_connect_swapped (
		message_list, "message-list-built",
		G_CALLBACK (mail_reader_emit_folder_loaded), reader);

	g_signal_connect_swapped (
		message_list, "double-click",
		G_CALLBACK (mail_reader_double_click_cb), reader);

	g_signal_connect_swapped (
		message_list, "key-press",
		G_CALLBACK (mail_reader_key_press_cb), reader);

	g_signal_connect_swapped (
		message_list, "selection-change",
		G_CALLBACK (e_mail_reader_changed), reader);
}

void
e_mail_reader_changed (EMailReader *reader)
{
	g_return_if_fail (E_IS_MAIL_READER (reader));

	g_signal_emit (reader, signals[CHANGED], 0);
}

guint32
e_mail_reader_check_state (EMailReader *reader)
{
	EShell *shell;
	GPtrArray *uids;
	CamelFolder *folder;
	CamelStore *store = NULL;
	EMailBackend *backend;
	ESourceRegistry *registry;
	EMailSession *mail_session;
	EMailAccountStore *account_store;
	const gchar *tag;
	gboolean can_clear_flags = FALSE;
	gboolean can_flag_completed = FALSE;
	gboolean can_flag_for_followup = FALSE;
	gboolean has_attachments = FALSE;
	gboolean has_deleted = FALSE;
	gboolean has_important = FALSE;
	gboolean has_junk = FALSE;
	gboolean has_not_junk = FALSE;
	gboolean has_read = FALSE;
	gboolean has_undeleted = FALSE;
	gboolean has_unimportant = FALSE;
	gboolean has_unread = FALSE;
	gboolean have_enabled_account = FALSE;
	gboolean drafts_or_outbox = FALSE;
	gboolean store_supports_vjunk = FALSE;
	gboolean is_mailing_list;
	gboolean is_junk_folder = FALSE;
	guint32 state = 0;
	guint ii;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);

	backend = e_mail_reader_get_backend (reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
	registry = e_shell_get_registry (shell);
	mail_session = e_mail_backend_get_session (backend);
	account_store = e_mail_ui_session_get_account_store (
		E_MAIL_UI_SESSION (mail_session));

	folder = e_mail_reader_get_folder (reader);
	uids = e_mail_reader_get_selected_uids (reader);

	if (folder != NULL) {
		store = camel_folder_get_parent_store (folder);
		store_supports_vjunk = (store->flags & CAMEL_STORE_VJUNK);
		is_junk_folder =
			(folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
		drafts_or_outbox =
			em_utils_folder_is_drafts (registry, folder) ||
			em_utils_folder_is_outbox (registry, folder);
	}

	/* Initialize this flag based on whether there are any
	 * messages selected.  We will update it in the loop. */
	is_mailing_list = (uids->len > 0);

	for (ii = 0; ii < uids->len; ii++) {
		CamelMessageInfo *info;
		const gchar *string;
		guint32 flags;

		info = camel_folder_get_message_info (
			folder, uids->pdata[ii]);
		if (info == NULL)
			continue;

		flags = camel_message_info_flags (info);

		if (flags & CAMEL_MESSAGE_SEEN)
			has_read = TRUE;
		else
			has_unread = TRUE;

		if (flags & CAMEL_MESSAGE_ATTACHMENTS)
			has_attachments = TRUE;

		if (drafts_or_outbox) {
			has_junk = FALSE;
			has_not_junk = FALSE;
		} else if (store_supports_vjunk) {
			guint32 bitmask;

			/* XXX Strictly speaking, this logic is correct.
			 *     Problem is there's nothing in the message
			 *     list that indicates whether a message is
			 *     already marked "Not Junk".  So the user may
			 *     think the "Not Junk" button is enabling and
			 *     disabling itself randomly as he reads mail. */

			if (flags & CAMEL_MESSAGE_JUNK)
				has_junk = TRUE;
			if (flags & CAMEL_MESSAGE_NOTJUNK)
				has_not_junk = TRUE;

			bitmask = CAMEL_MESSAGE_JUNK | CAMEL_MESSAGE_NOTJUNK;

			/* If neither junk flag is set, the
			 * message can be marked either way. */
			if ((flags & bitmask) == 0) {
				has_junk = TRUE;
				has_not_junk = TRUE;
			}

		} else {
			has_junk = TRUE;
			has_not_junk = TRUE;
		}

		if (flags & CAMEL_MESSAGE_DELETED)
			has_deleted = TRUE;
		else
			has_undeleted = TRUE;

		if (flags & CAMEL_MESSAGE_FLAGGED)
			has_important = TRUE;
		else
			has_unimportant = TRUE;

		tag = camel_message_info_user_tag (info, "follow-up");
		if (tag != NULL && *tag != '\0') {
			can_clear_flags = TRUE;
			tag = camel_message_info_user_tag (
				info, "completed-on");
			if (tag == NULL || *tag == '\0')
				can_flag_completed = TRUE;
		} else
			can_flag_for_followup = TRUE;

		string = camel_message_info_mlist (info);
		is_mailing_list &= (string != NULL && *string != '\0');

		camel_folder_free_message_info (folder, info);
	}

	have_enabled_account =
		e_mail_account_store_have_enabled_service (
		account_store, CAMEL_TYPE_STORE);

	if (have_enabled_account)
		state |= E_MAIL_READER_HAVE_ENABLED_ACCOUNT;
	if (uids->len == 1)
		state |= E_MAIL_READER_SELECTION_SINGLE;
	if (uids->len > 1)
		state |= E_MAIL_READER_SELECTION_MULTIPLE;
	if (!drafts_or_outbox && uids->len == 1)
		state |= E_MAIL_READER_SELECTION_CAN_ADD_SENDER;
	if (can_clear_flags)
		state |= E_MAIL_READER_SELECTION_FLAG_CLEAR;
	if (can_flag_completed)
		state |= E_MAIL_READER_SELECTION_FLAG_COMPLETED;
	if (can_flag_for_followup)
		state |= E_MAIL_READER_SELECTION_FLAG_FOLLOWUP;
	if (has_attachments)
		state |= E_MAIL_READER_SELECTION_HAS_ATTACHMENTS;
	if (has_deleted)
		state |= E_MAIL_READER_SELECTION_HAS_DELETED;
	if (has_important)
		state |= E_MAIL_READER_SELECTION_HAS_IMPORTANT;
	if (has_junk)
		state |= E_MAIL_READER_SELECTION_HAS_JUNK;
	if (has_not_junk)
		state |= E_MAIL_READER_SELECTION_HAS_NOT_JUNK;
	if (has_read)
		state |= E_MAIL_READER_SELECTION_HAS_READ;
	if (has_undeleted)
		state |= E_MAIL_READER_SELECTION_HAS_UNDELETED;
	if (has_unimportant)
		state |= E_MAIL_READER_SELECTION_HAS_UNIMPORTANT;
	if (has_unread)
		state |= E_MAIL_READER_SELECTION_HAS_UNREAD;
	if (is_mailing_list)
		state |= E_MAIL_READER_SELECTION_IS_MAILING_LIST;
	if (is_junk_folder)
		state |= E_MAIL_READER_FOLDER_IS_JUNK;

	em_utils_uids_free (uids);

	return state;
}

EActivity *
e_mail_reader_new_activity (EMailReader *reader)
{
	EActivity *activity;
	EMailBackend *backend;
	EAlertSink *alert_sink;
	GCancellable *cancellable;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	activity = e_activity_new ();

	alert_sink = e_mail_reader_get_alert_sink (reader);
	e_activity_set_alert_sink (activity, alert_sink);

	cancellable = camel_operation_new ();
	e_activity_set_cancellable (activity, cancellable);
	g_object_unref (cancellable);

	backend = e_mail_reader_get_backend (reader);
	e_shell_backend_add_activity (E_SHELL_BACKEND (backend), activity);

	return activity;
}

void
e_mail_reader_update_actions (EMailReader *reader,
                              guint32 state)
{
	g_return_if_fail (E_IS_MAIL_READER (reader));

	g_signal_emit (reader, signals[UPDATE_ACTIONS], 0, state);
}

GtkAction *
e_mail_reader_get_action (EMailReader *reader,
                          const gchar *action_name)
{
	GtkAction *action = NULL;
	gint ii;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
	g_return_val_if_fail (action_name != NULL, NULL);

	for (ii = 0; ii < E_MAIL_READER_NUM_ACTION_GROUPS; ii++) {
		GtkActionGroup *group;

		group = e_mail_reader_get_action_group (reader, ii);
		action = gtk_action_group_get_action (group, action_name);

		if (action != NULL)
			break;
	}

	if (action == NULL)
		g_critical (
			"%s: action '%s' not found", G_STRFUNC, action_name);

	return action;
}

GtkActionGroup *
e_mail_reader_get_action_group (EMailReader *reader,
                                EMailReaderActionGroup group)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_action_group != NULL, NULL);

	return interface->get_action_group (reader, group);
}

EAlertSink *
e_mail_reader_get_alert_sink (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_alert_sink != NULL, NULL);

	return interface->get_alert_sink (reader);
}

EMailBackend *
e_mail_reader_get_backend (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_backend != NULL, NULL);

	return interface->get_backend (reader);
}

EMailDisplay *
e_mail_reader_get_mail_display (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_mail_display != NULL, NULL);

	return interface->get_mail_display (reader);
}

gboolean
e_mail_reader_get_hide_deleted (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_hide_deleted != NULL, FALSE);

	return interface->get_hide_deleted (reader);
}

GtkWidget *
e_mail_reader_get_message_list (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_message_list != NULL, NULL);

	return interface->get_message_list (reader);
}

GtkMenu *
e_mail_reader_get_popup_menu (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_popup_menu != NULL, NULL);

	return interface->get_popup_menu (reader);
}

EPreviewPane *
e_mail_reader_get_preview_pane (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_preview_pane != NULL, NULL);

	return interface->get_preview_pane (reader);
}

GPtrArray *
e_mail_reader_get_selected_uids (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_selected_uids != NULL, NULL);

	return interface->get_selected_uids (reader);
}

GtkWindow *
e_mail_reader_get_window (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_window != NULL, NULL);

	return interface->get_window (reader);
}

CamelFolder *
e_mail_reader_get_folder (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->get_folder != NULL, NULL);

	return interface->get_folder (reader);
}

void
e_mail_reader_set_folder (EMailReader *reader,
                          CamelFolder *folder)
{
	EMailReaderInterface *interface;

	g_return_if_fail (E_IS_MAIL_READER (reader));

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_if_fail (interface->set_folder != NULL);

	interface->set_folder (reader, folder);
}

void
e_mail_reader_set_message (EMailReader *reader,
                           const gchar *message_uid)
{
	EMailReaderInterface *interface;

	g_return_if_fail (E_IS_MAIL_READER (reader));

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_if_fail (interface->set_message != NULL);

	interface->set_message (reader, message_uid);
}

guint
e_mail_reader_open_selected_mail (EMailReader *reader)
{
	EMailReaderInterface *interface;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);

	interface = E_MAIL_READER_GET_INTERFACE (reader);
	g_return_val_if_fail (interface->open_selected_mail != NULL, 0);

	return interface->open_selected_mail (reader);
}

EMailForwardStyle
e_mail_reader_get_forward_style (EMailReader *reader)
{
	EMailReaderPrivate *priv;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	return priv->forward_style;
}

void
e_mail_reader_set_forward_style (EMailReader *reader,
                                 EMailForwardStyle style)
{
	EMailReaderPrivate *priv;

	g_return_if_fail (E_IS_MAIL_READER (reader));

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	if (priv->forward_style == style)
		return;

	priv->forward_style = style;

	g_object_notify (G_OBJECT (reader), "forward-style");
}

gboolean
e_mail_reader_get_group_by_threads (EMailReader *reader)
{
	EMailReaderPrivate *priv;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	return priv->group_by_threads;
}

void
e_mail_reader_set_group_by_threads (EMailReader *reader,
                                    gboolean group_by_threads)
{
	EMailReaderPrivate *priv;
	GtkWidget *message_list;

	g_return_if_fail (E_IS_MAIL_READER (reader));

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	if (priv->group_by_threads == group_by_threads)
		return;

	priv->group_by_threads = group_by_threads;

	/* XXX MessageList should define a property for this. */
	message_list = e_mail_reader_get_message_list (reader);
	message_list_set_threaded (
		MESSAGE_LIST (message_list), group_by_threads);

	g_object_notify (G_OBJECT (reader), "group-by-threads");
}

EMailReplyStyle
e_mail_reader_get_reply_style (EMailReader *reader)
{
	EMailReaderPrivate *priv;

	g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	return priv->reply_style;
}

void
e_mail_reader_set_reply_style (EMailReader *reader,
                               EMailReplyStyle style)
{
	EMailReaderPrivate *priv;

	g_return_if_fail (E_IS_MAIL_READER (reader));

	priv = E_MAIL_READER_GET_PRIVATE (reader);

	if (priv->reply_style == style)
		return;

	priv->reply_style = style;

	g_object_notify (G_OBJECT (reader), "reply-style");
}

void
e_mail_reader_create_charset_menu (EMailReader *reader,
                                   GtkUIManager *ui_manager,
                                   guint merge_id)
{
	GtkAction *action;
	const gchar *action_name;
	const gchar *path;
	GSList *list;

	g_return_if_fail (E_IS_MAIL_READER (reader));
	g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));

	action_name = "mail-charset-default";
	action = e_mail_reader_get_action (reader, action_name);
	g_return_if_fail (action != NULL);

	list = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));
	list = g_slist_copy (list);
	list = g_slist_remove (list, action);
	list = g_slist_sort (list, (GCompareFunc) e_action_compare_by_label);

	path = "/main-menu/view-menu/mail-message-view-actions/mail-encoding-menu";

	while (list != NULL) {
		action = list->data;

		gtk_ui_manager_add_ui (
			ui_manager, merge_id, path,
			gtk_action_get_name (action),
			gtk_action_get_name (action),
			GTK_UI_MANAGER_AUTO, FALSE);

		list = g_slist_delete_link (list, list);
	}

	gtk_ui_manager_ensure_update (ui_manager);
}

void
e_mail_reader_show_search_bar (EMailReader *reader)
{
	g_return_if_fail (E_IS_MAIL_READER (reader));

	g_signal_emit (reader, signals[SHOW_SEARCH_BAR], 0);
}

void
e_mail_reader_avoid_next_mark_as_seen (EMailReader *reader)
{
	EMailReaderPrivate *priv;
	MessageList *message_list;

	g_return_if_fail (reader != NULL);

	priv = E_MAIL_READER_GET_PRIVATE (reader);
	g_return_if_fail (priv != NULL);

	message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
	g_return_if_fail (message_list != NULL);

	priv->avoid_next_mark_as_seen = TRUE;
}

/**
 * e_mail_reader_composer_created:
 * @reader: an #EMailReader
 * @composer: an #EMsgComposer
 * @message: the source #CamelMimeMessage, or %NULL
 *
 * Emits a #EMailReader::composer-created signal to indicate the @composer
 * window was created in response to a user action on @reader.  Examples of
 * such actions include replying, forwarding, and composing a new message.
 * If applicable, the source @message (i.e. the message being replied to or
 * forwarded) should be included.
 **/
void
e_mail_reader_composer_created (EMailReader *reader,
                                EMsgComposer *composer,
                                CamelMimeMessage *message)
{
	g_return_if_fail (E_IS_MAIL_READER (reader));
	g_return_if_fail (E_IS_MSG_COMPOSER (composer));

	if (message != NULL)
		g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	g_signal_emit (
		reader, signals[COMPOSER_CREATED], 0, composer, message);
}