/*
 * 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-composer-header-table.h"

#include <glib/gi18n-lib.h>

#include "e-msg-composer.h"
#include "e-composer-private.h"
#include "e-composer-from-header.h"
#include "e-composer-name-header.h"
#include "e-composer-post-header.h"
#include "e-composer-spell-header.h"
#include "e-composer-text-header.h"

#define E_COMPOSER_HEADER_TABLE_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_COMPOSER_HEADER_TABLE, EComposerHeaderTablePrivate))

#define HEADER_TOOLTIP_TO \
	_("Enter the recipients of the message")
#define HEADER_TOOLTIP_CC \
	_("Enter the addresses that will receive a " \
	  "carbon copy of the message")
#define HEADER_TOOLTIP_BCC \
	_("Enter the addresses that will receive a " \
	  "carbon copy of the message without appearing " \
	  "in the recipient list of the message")

struct _EComposerHeaderTablePrivate {
	EComposerHeader *headers[E_COMPOSER_NUM_HEADERS];
	GtkWidget *signature_label;
	GtkWidget *signature_combo_box;
	ENameSelector *name_selector;
	EClientCache *client_cache;
};

enum {
	PROP_0,
	PROP_CLIENT_CACHE,
	PROP_DESTINATIONS_BCC,
	PROP_DESTINATIONS_CC,
	PROP_DESTINATIONS_TO,
	PROP_IDENTITY_UID,
	PROP_POST_TO,
	PROP_REPLY_TO,
	PROP_SIGNATURE_COMBO_BOX,
	PROP_SIGNATURE_UID,
	PROP_SUBJECT
};

G_DEFINE_TYPE (
	EComposerHeaderTable,
	e_composer_header_table,
	GTK_TYPE_TABLE)

static void
g_value_set_destinations (GValue *value,
                          EDestination **destinations)
{
	GPtrArray *array;
	gint ii;

	/* Preallocate some reasonable number. */
	array = g_ptr_array_new_full (64, g_object_unref);

	for (ii = 0; destinations[ii] != NULL; ii++) {
		g_ptr_array_add (array, e_destination_copy (destinations[ii]));
	}

	g_value_take_boxed (value, array);
}

static EDestination **
g_value_dup_destinations (const GValue *value)
{
	EDestination **destinations;
	const EDestination *dest;
	GPtrArray *array;
	guint ii;

	array = g_value_get_boxed (value);
	destinations = g_new0 (EDestination *, array->len + 1);

	for (ii = 0; ii < array->len; ii++) {
		dest = g_ptr_array_index (array, ii);
		destinations[ii] = e_destination_copy (dest);
	}

	return destinations;
}

static void
g_value_set_string_list (GValue *value,
                         GList *list)
{
	GPtrArray *array;

	array = g_ptr_array_new_full (g_list_length (list), g_free);

	while (list != NULL) {
		g_ptr_array_add (array, g_strdup (list->data));
		list = list->next;
	}

	g_value_take_boxed (value, array);
}

static GList *
g_value_dup_string_list (const GValue *value)
{
	GPtrArray *array;
	GList *list = NULL;
	gint ii;

	array = g_value_get_boxed (value);

	for (ii = 0; ii < array->len; ii++) {
		const gchar *element = g_ptr_array_index (array, ii);
		list = g_list_prepend (list, g_strdup (element));
	}

	return g_list_reverse (list);
}

static void
composer_header_table_notify_header (EComposerHeader *header,
                                     const gchar *property_name)
{
	GtkWidget *parent;

	parent = gtk_widget_get_parent (header->input_widget);
	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (parent));
	g_object_notify (G_OBJECT (parent), property_name);
}

static void
composer_header_table_notify_widget (GtkWidget *widget,
                                     const gchar *property_name)
{
	GtkWidget *parent;

	parent = gtk_widget_get_parent (widget);
	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (parent));
	g_object_notify (G_OBJECT (parent), property_name);
}

static void
composer_header_table_bind_header (const gchar *property_name,
                                   const gchar *signal_name,
                                   EComposerHeader *header)
{
	/* Propagate the signal as "notify::property_name". */

	g_signal_connect (
		header, signal_name,
		G_CALLBACK (composer_header_table_notify_header),
		(gpointer) property_name);
}

static void
composer_header_table_bind_widget (const gchar *property_name,
                                   const gchar *signal_name,
                                   GtkWidget *widget)
{
	/* Propagate the signal as "notify::property_name". */

	g_signal_connect (
		widget, signal_name,
		G_CALLBACK (composer_header_table_notify_widget),
		(gpointer) property_name);
}

static EDestination **
composer_header_table_update_destinations (EDestination **old_destinations,
                                           const gchar * const *auto_addresses)
{
	CamelAddress *address;
	CamelInternetAddress *inet_address;
	EDestination **new_destinations;
	EDestination *destination;
	GQueue queue = G_QUEUE_INIT;
	guint length;
	gint ii;

	/* Include automatic recipients for the selected account. */

	if (auto_addresses == NULL)
		goto skip_auto;

	inet_address = camel_internet_address_new ();
	address = CAMEL_ADDRESS (inet_address);

	/* XXX Calling camel_address_decode() multiple times on the same
	 *     CamelInternetAddress has a cumulative effect, which isn't
	 *     well documented. */
	for (ii = 0; auto_addresses[ii] != NULL; ii++)
		camel_address_decode (address, auto_addresses[ii]);

	for (ii = 0; ii < camel_address_length (address); ii++) {
		const gchar *name, *email;

		if (!camel_internet_address_get (
			inet_address, ii, &name, &email))
			continue;

		destination = e_destination_new ();
		e_destination_set_auto_recipient (destination, TRUE);

		if (name != NULL)
			e_destination_set_name (destination, name);

		if (email != NULL)
			e_destination_set_email (destination, email);

		g_queue_push_tail (&queue, destination);
	}

	g_object_unref (inet_address);

skip_auto:

	/* Include custom recipients for this message. */

	if (old_destinations == NULL)
		goto skip_custom;

	for (ii = 0; old_destinations[ii] != NULL; ii++) {
		if (e_destination_is_auto_recipient (old_destinations[ii]))
			continue;

		destination = e_destination_copy (old_destinations[ii]);
		g_queue_push_tail (&queue, destination);
	}

skip_custom:

	length = g_queue_get_length (&queue);
	new_destinations = g_new0 (EDestination *, length + 1);

	for (ii = 0; ii < length; ii++)
		new_destinations[ii] = g_queue_pop_head (&queue);

	/* Sanity check. */
	g_warn_if_fail (g_queue_is_empty (&queue));

	return new_destinations;
}

static void
composer_header_table_setup_mail_headers (EComposerHeaderTable *table)
{
	GSettings *settings;
	gint ii;

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

	for (ii = 0; ii < E_COMPOSER_NUM_HEADERS; ii++) {
		EComposerHeader *header;
		const gchar *key;
		gboolean sensitive;
		gboolean visible;

		header = e_composer_header_table_get_header (table, ii);

		switch (ii) {
			case E_COMPOSER_HEADER_BCC:
				key = "composer-show-bcc";
				break;

			case E_COMPOSER_HEADER_CC:
				key = "composer-show-cc";
				break;

			case E_COMPOSER_HEADER_REPLY_TO:
				key = "composer-show-reply-to";
				break;

			default:
				key = NULL;
				break;
		}

		if (key != NULL)
			g_settings_unbind (header, "visible");

		switch (ii) {
			case E_COMPOSER_HEADER_FROM:
				sensitive = TRUE;
				visible = TRUE;
				break;

			case E_COMPOSER_HEADER_BCC:
			case E_COMPOSER_HEADER_CC:
			case E_COMPOSER_HEADER_REPLY_TO:
			case E_COMPOSER_HEADER_SUBJECT:
			case E_COMPOSER_HEADER_TO:
				sensitive = TRUE;
				visible = TRUE;
				break;

			default:
				sensitive = FALSE;
				visible = FALSE;
				break;
		}

		e_composer_header_set_sensitive (header, sensitive);
		e_composer_header_set_visible (header, visible);

		if (key != NULL)
			g_settings_bind (
				settings, key,
				header, "visible",
				G_SETTINGS_BIND_DEFAULT);
	}

	g_object_unref (settings);
}

static void
composer_header_table_setup_post_headers (EComposerHeaderTable *table)
{
	GSettings *settings;
	gint ii;

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

	for (ii = 0; ii < E_COMPOSER_NUM_HEADERS; ii++) {
		EComposerHeader *header;
		const gchar *key;

		header = e_composer_header_table_get_header (table, ii);

		switch (ii) {
			case E_COMPOSER_HEADER_FROM:
				key = "composer-show-post-from";
				break;

			case E_COMPOSER_HEADER_REPLY_TO:
				key = "composer-show-post-reply-to";
				break;

			default:
				key = NULL;
				break;
		}

		if (key != NULL)
			g_settings_unbind (header, "visible");

		switch (ii) {
			case E_COMPOSER_HEADER_FROM:
			case E_COMPOSER_HEADER_POST_TO:
			case E_COMPOSER_HEADER_REPLY_TO:
			case E_COMPOSER_HEADER_SUBJECT:
				e_composer_header_set_sensitive (header, TRUE);
				e_composer_header_set_visible (header, TRUE);
				break;

			default:  /* this includes TO, CC and BCC */
				e_composer_header_set_sensitive (header, FALSE);
				e_composer_header_set_visible (header, FALSE);
				break;
		}

		if (key != NULL)
			g_settings_bind (
				settings, key,
				header, "visible",
				G_SETTINGS_BIND_DEFAULT);
	}

	g_object_unref (settings);
}

static gboolean
composer_header_table_show_post_headers (EComposerHeaderTable *table)
{
	EClientCache *client_cache;
	ESourceRegistry *registry;
	GList *list, *link;
	const gchar *extension_name;
	const gchar *target_uid;
	gboolean show_post_headers = FALSE;

	client_cache = e_composer_header_table_ref_client_cache (table);
	registry = e_client_cache_ref_registry (client_cache);

	target_uid = e_composer_header_table_get_identity_uid (table);

	extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
	list = e_source_registry_list_sources (registry, extension_name);

	/* Look for a mail account referencing this mail identity.
	 * If the mail account's backend name is "nntp", show the
	 * post headers.  Otherwise show the mail headers.
	 *
	 * XXX What if multiple accounts use this identity but only
	 *     one is "nntp"?  Maybe it should be indicated by the
	 *     transport somehow?
	 */
	for (link = list; link != NULL; link = link->next) {
		ESource *source = E_SOURCE (link->data);
		ESourceExtension *extension;
		const gchar *backend_name;
		const gchar *identity_uid;

		extension = e_source_get_extension (source, extension_name);

		backend_name = e_source_backend_get_backend_name (
			E_SOURCE_BACKEND (extension));
		identity_uid = e_source_mail_account_get_identity_uid (
			E_SOURCE_MAIL_ACCOUNT (extension));

		if (g_strcmp0 (identity_uid, target_uid) != 0)
			continue;

		if (g_strcmp0 (backend_name, "nntp") != 0)
			continue;

		show_post_headers = TRUE;
		break;
	}

	g_list_free_full (list, (GDestroyNotify) g_object_unref);

	g_object_unref (client_cache);
	g_object_unref (registry);

	return show_post_headers;
}

static void
composer_header_table_from_changed_cb (EComposerHeaderTable *table)
{
	ESource *source = NULL;
	ESource *mail_account = NULL;
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerPostHeader *post_header;
	EComposerTextHeader *text_header;
	EDestination **old_destinations;
	EDestination **new_destinations;
	const gchar *reply_to = NULL;
	const gchar * const *bcc = NULL;
	const gchar * const *cc = NULL;
	const gchar *uid;

	/* Keep "Post-To" and "Reply-To" synchronized with "From" */

	uid = e_composer_header_table_get_identity_uid (table);
	if (uid != NULL)
		source = e_composer_header_table_ref_source (table, uid);

	/* Make sure this is really a mail identity source. */
	if (source != NULL) {
		const gchar *extension_name;

		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
		if (!e_source_has_extension (source, extension_name)) {
			g_object_unref (source);
			source = NULL;
		}
	}

	if (source != NULL) {
		ESourceMailIdentity *mi;
		ESourceMailComposition *mc;
		const gchar *extension_name;

		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
		mi = e_source_get_extension (source, extension_name);

		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
		mc = e_source_get_extension (source, extension_name);

		reply_to = e_source_mail_identity_get_reply_to (mi);
		bcc = e_source_mail_composition_get_bcc (mc);
		cc = e_source_mail_composition_get_cc (mc);

		g_object_unref (source);
	}

	type = E_COMPOSER_HEADER_POST_TO;
	header = e_composer_header_table_get_header (table, type);
	post_header = E_COMPOSER_POST_HEADER (header);
	e_composer_post_header_set_mail_account (post_header, mail_account);

	type = E_COMPOSER_HEADER_REPLY_TO;
	header = e_composer_header_table_get_header (table, type);
	text_header = E_COMPOSER_TEXT_HEADER (header);
	e_composer_text_header_set_text (text_header, reply_to);

	/* Update automatic CC destinations. */
	old_destinations =
		e_composer_header_table_get_destinations_cc (table);
	new_destinations =
		composer_header_table_update_destinations (
		old_destinations, cc);
	e_composer_header_table_set_destinations_cc (table, new_destinations);
	e_destination_freev (old_destinations);
	e_destination_freev (new_destinations);

	/* Update automatic BCC destinations. */
	old_destinations =
		e_composer_header_table_get_destinations_bcc (table);
	new_destinations =
		composer_header_table_update_destinations (
		old_destinations, bcc);
	e_composer_header_table_set_destinations_bcc (table, new_destinations);
	e_destination_freev (old_destinations);
	e_destination_freev (new_destinations);

	if (composer_header_table_show_post_headers (table))
		composer_header_table_setup_post_headers (table);
	else
		composer_header_table_setup_mail_headers (table);
}

static void
composer_header_table_set_client_cache (EComposerHeaderTable *table,
                                        EClientCache *client_cache)
{
	g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
	g_return_if_fail (table->priv->client_cache == NULL);

	table->priv->client_cache = g_object_ref (client_cache);
}

static void
composer_header_table_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
	EDestination **destinations;
	GList *list;

	switch (property_id) {
		case PROP_CLIENT_CACHE:
			composer_header_table_set_client_cache (
				E_COMPOSER_HEADER_TABLE (object),
				g_value_get_object (value));
			return;

		case PROP_DESTINATIONS_BCC:
			destinations = g_value_dup_destinations (value);
			e_composer_header_table_set_destinations_bcc (
				E_COMPOSER_HEADER_TABLE (object),
				destinations);
			e_destination_freev (destinations);
			return;

		case PROP_DESTINATIONS_CC:
			destinations = g_value_dup_destinations (value);
			e_composer_header_table_set_destinations_cc (
				E_COMPOSER_HEADER_TABLE (object),
				destinations);
			e_destination_freev (destinations);
			return;

		case PROP_DESTINATIONS_TO:
			destinations = g_value_dup_destinations (value);
			e_composer_header_table_set_destinations_to (
				E_COMPOSER_HEADER_TABLE (object),
				destinations);
			e_destination_freev (destinations);
			return;

		case PROP_IDENTITY_UID:
			e_composer_header_table_set_identity_uid (
				E_COMPOSER_HEADER_TABLE (object),
				g_value_get_string (value));
			return;

		case PROP_POST_TO:
			list = g_value_dup_string_list (value);
			e_composer_header_table_set_post_to_list (
				E_COMPOSER_HEADER_TABLE (object), list);
			g_list_foreach (list, (GFunc) g_free, NULL);
			g_list_free (list);
			return;

		case PROP_REPLY_TO:
			e_composer_header_table_set_reply_to (
				E_COMPOSER_HEADER_TABLE (object),
				g_value_get_string (value));
			return;

		case PROP_SIGNATURE_UID:
			e_composer_header_table_set_signature_uid (
				E_COMPOSER_HEADER_TABLE (object),
				g_value_get_string (value));
			return;

		case PROP_SUBJECT:
			e_composer_header_table_set_subject (
				E_COMPOSER_HEADER_TABLE (object),
				g_value_get_string (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
composer_header_table_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
	EDestination **destinations;
	GList *list;

	switch (property_id) {
		case PROP_CLIENT_CACHE:
			g_value_take_object (
				value,
				e_composer_header_table_ref_client_cache (
				E_COMPOSER_HEADER_TABLE (object)));
			return;

		case PROP_DESTINATIONS_BCC:
			destinations =
				e_composer_header_table_get_destinations_bcc (
				E_COMPOSER_HEADER_TABLE (object));
			g_value_set_destinations (value, destinations);
			e_destination_freev (destinations);
			return;

		case PROP_DESTINATIONS_CC:
			destinations =
				e_composer_header_table_get_destinations_cc (
				E_COMPOSER_HEADER_TABLE (object));
			g_value_set_destinations (value, destinations);
			e_destination_freev (destinations);
			return;

		case PROP_DESTINATIONS_TO:
			destinations =
				e_composer_header_table_get_destinations_to (
				E_COMPOSER_HEADER_TABLE (object));
			g_value_set_destinations (value, destinations);
			e_destination_freev (destinations);
			return;

		case PROP_IDENTITY_UID:
			g_value_set_string (
				value,
				e_composer_header_table_get_identity_uid (
				E_COMPOSER_HEADER_TABLE (object)));
			return;

		case PROP_POST_TO:
			list = e_composer_header_table_get_post_to (
				E_COMPOSER_HEADER_TABLE (object));
			g_value_set_string_list (value, list);
			g_list_foreach (list, (GFunc) g_free, NULL);
			g_list_free (list);
			return;

		case PROP_REPLY_TO:
			g_value_set_string (
				value,
				e_composer_header_table_get_reply_to (
				E_COMPOSER_HEADER_TABLE (object)));
			return;

		case PROP_SIGNATURE_COMBO_BOX:
			g_value_set_object (
				value,
				e_composer_header_table_get_signature_combo_box (
				E_COMPOSER_HEADER_TABLE (object)));
			return;

		case PROP_SIGNATURE_UID:
			g_value_set_string (
				value,
				e_composer_header_table_get_signature_uid (
				E_COMPOSER_HEADER_TABLE (object)));
			return;

		case PROP_SUBJECT:
			g_value_set_string (
				value,
				e_composer_header_table_get_subject (
				E_COMPOSER_HEADER_TABLE (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
composer_header_table_dispose (GObject *object)
{
	EComposerHeaderTablePrivate *priv;
	gint ii;

	priv = E_COMPOSER_HEADER_TABLE_GET_PRIVATE (object);

	for (ii = 0; ii < G_N_ELEMENTS (priv->headers); ii++) {
		if (priv->headers[ii] != NULL) {
			g_object_unref (priv->headers[ii]);
			priv->headers[ii] = NULL;
		}
	}

	if (priv->signature_combo_box != NULL) {
		g_object_unref (priv->signature_combo_box);
		priv->signature_combo_box = NULL;
	}

	if (priv->name_selector != NULL) {
		e_name_selector_cancel_loading (priv->name_selector);
		g_object_unref (priv->name_selector);
		priv->name_selector = NULL;
	}

	if (priv->client_cache != NULL) {
		g_object_unref (priv->client_cache);
		priv->client_cache = NULL;
	}

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

static void
composer_header_table_constructed (GObject *object)
{
	EComposerHeaderTable *table;
	ENameSelector *name_selector;
	EClientCache *client_cache;
	ESourceRegistry *registry;
	EComposerHeader *header;
	GtkWidget *widget;
	guint ii;
	gint row_padding;

	/* Chain up to parent's constructed() method. */
	G_OBJECT_CLASS (e_composer_header_table_parent_class)->
		constructed (object);

	table = E_COMPOSER_HEADER_TABLE (object);

	client_cache = e_composer_header_table_ref_client_cache (table);
	registry = e_client_cache_ref_registry (client_cache);

	name_selector = e_name_selector_new (client_cache);
	table->priv->name_selector = name_selector;

	header = e_composer_from_header_new (registry, _("Fr_om:"));
	composer_header_table_bind_header ("identity-uid", "changed", header);
	g_signal_connect_swapped (
		header, "changed", G_CALLBACK (
		composer_header_table_from_changed_cb), table);
	table->priv->headers[E_COMPOSER_HEADER_FROM] = header;

	header = e_composer_text_header_new_label (registry, _("_Reply-To:"));
	composer_header_table_bind_header ("reply-to", "changed", header);
	table->priv->headers[E_COMPOSER_HEADER_REPLY_TO] = header;

	header = e_composer_name_header_new (
		registry, _("_To:"), name_selector);
	e_composer_header_set_input_tooltip (header, HEADER_TOOLTIP_TO);
	composer_header_table_bind_header ("destinations-to", "changed", header);
	table->priv->headers[E_COMPOSER_HEADER_TO] = header;

	header = e_composer_name_header_new (
		registry, _("_Cc:"), name_selector);
	e_composer_header_set_input_tooltip (header, HEADER_TOOLTIP_CC);
	composer_header_table_bind_header ("destinations-cc", "changed", header);
	table->priv->headers[E_COMPOSER_HEADER_CC] = header;

	header = e_composer_name_header_new (
		registry, _("_Bcc:"), name_selector);
	e_composer_header_set_input_tooltip (header, HEADER_TOOLTIP_BCC);
	composer_header_table_bind_header ("destinations-bcc", "changed", header);
	table->priv->headers[E_COMPOSER_HEADER_BCC] = header;

	header = e_composer_post_header_new (registry, _("_Post To:"));
	composer_header_table_bind_header ("post-to", "changed", header);
	table->priv->headers[E_COMPOSER_HEADER_POST_TO] = header;

	header = e_composer_spell_header_new_label (registry, _("S_ubject:"));
	composer_header_table_bind_header ("subject", "changed", header);
	table->priv->headers[E_COMPOSER_HEADER_SUBJECT] = header;

	widget = e_mail_signature_combo_box_new (registry);
	composer_header_table_bind_widget ("signature-uid", "changed", widget);
	table->priv->signature_combo_box = g_object_ref_sink (widget);

	widget = gtk_label_new_with_mnemonic (_("Si_gnature:"));
	gtk_label_set_mnemonic_widget (
		GTK_LABEL (widget), table->priv->signature_combo_box);
	table->priv->signature_label = g_object_ref_sink (widget);

	/* Use "ypadding" instead of "row-spacing" because some rows may
	 * be invisible and we don't want spacing around them. */

	row_padding = 3;

	for (ii = 0; ii < G_N_ELEMENTS (table->priv->headers); ii++) {
		gtk_table_attach (
			GTK_TABLE (object),
			table->priv->headers[ii]->title_widget, 0, 1,
			ii, ii + 1, GTK_FILL, GTK_FILL, 0, row_padding);
		gtk_table_attach (
			GTK_TABLE (object),
			table->priv->headers[ii]->input_widget, 1, 4,
			ii, ii + 1, GTK_FILL | GTK_EXPAND, 0, 0, row_padding);
	}

	ii = E_COMPOSER_HEADER_FROM;

	/* Leave room in the "From" row for signature stuff. */
	gtk_container_child_set (
		GTK_CONTAINER (object),
		table->priv->headers[ii]->input_widget,
		"right-attach", 2, NULL);

	g_object_bind_property (
		table->priv->headers[ii]->input_widget, "visible",
		table->priv->signature_label, "visible",
		G_BINDING_SYNC_CREATE);

	g_object_bind_property (
		table->priv->headers[ii]->input_widget, "visible",
		table->priv->signature_combo_box, "visible",
		G_BINDING_SYNC_CREATE);

	/* Now add the signature stuff. */
	gtk_table_attach (
		GTK_TABLE (object),
		table->priv->signature_label,
		2, 3, ii, ii + 1, 0, 0, 0, row_padding);
	gtk_table_attach (
		GTK_TABLE (object),
		table->priv->signature_combo_box,
		3, 4, ii, ii + 1, 0, 0, 0, row_padding);

	/* Initialize the headers. */
	composer_header_table_from_changed_cb (table);

	g_object_unref (client_cache);
	g_object_unref (registry);
}

static void
e_composer_header_table_class_init (EComposerHeaderTableClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (class, sizeof (EComposerHeaderTablePrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = composer_header_table_set_property;
	object_class->get_property = composer_header_table_get_property;
	object_class->dispose = composer_header_table_dispose;
	object_class->constructed = composer_header_table_constructed;

	/**
	 * EComposerHeaderTable:client-cache:
	 *
	 * Cache of shared #EClient instances.
	 **/
	g_object_class_install_property (
		object_class,
		PROP_CLIENT_CACHE,
		g_param_spec_object (
			"client-cache",
			"Client Cache",
			"Cache of shared EClient instances",
			E_TYPE_CLIENT_CACHE,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_DESTINATIONS_BCC,
		g_param_spec_boxed (
			"destinations-bcc",
			NULL,
			NULL,
			G_TYPE_PTR_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_DESTINATIONS_CC,
		g_param_spec_boxed (
			"destinations-cc",
			NULL,
			NULL,
			G_TYPE_PTR_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_DESTINATIONS_TO,
		g_param_spec_boxed (
			"destinations-to",
			NULL,
			NULL,
			G_TYPE_PTR_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_IDENTITY_UID,
		g_param_spec_string (
			"identity-uid",
			NULL,
			NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_POST_TO,
		g_param_spec_boxed (
			"post-to",
			NULL,
			NULL,
			G_TYPE_PTR_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_REPLY_TO,
		g_param_spec_string (
			"reply-to",
			NULL,
			NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SIGNATURE_COMBO_BOX,
		g_param_spec_string (
			"signature-combo-box",
			NULL,
			NULL,
			NULL,
			G_PARAM_READABLE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SIGNATURE_UID,
		g_param_spec_string (
			"signature-uid",
			NULL,
			NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SUBJECT,
		g_param_spec_string (
			"subject",
			NULL,
			NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));
}

static void
composer_header_table_realize_cb (EComposerHeaderTable *table)
{
	g_return_if_fail (table != NULL);
	g_return_if_fail (table->priv != NULL);

	g_signal_handlers_disconnect_by_func (
		table, composer_header_table_realize_cb, NULL);

	e_name_selector_load_books (table->priv->name_selector);
}

static void
e_composer_header_table_init (EComposerHeaderTable *table)
{
	gint rows;

	table->priv = E_COMPOSER_HEADER_TABLE_GET_PRIVATE (table);

	rows = G_N_ELEMENTS (table->priv->headers);
	gtk_table_resize (GTK_TABLE (table), rows, 4);
	gtk_table_set_row_spacings (GTK_TABLE (table), 0);
	gtk_table_set_col_spacings (GTK_TABLE (table), 6);

	/* postpone name_selector loading, do that only when really needed */
	g_signal_connect (
		table, "realize",
		G_CALLBACK (composer_header_table_realize_cb), NULL);
}

GtkWidget *
e_composer_header_table_new (EClientCache *client_cache)
{
	g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);

	return g_object_new (
		E_TYPE_COMPOSER_HEADER_TABLE,
		"client-cache", client_cache, NULL);
}

EClientCache *
e_composer_header_table_ref_client_cache (EComposerHeaderTable *table)
{
	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	return g_object_ref (table->priv->client_cache);
}

EComposerHeader *
e_composer_header_table_get_header (EComposerHeaderTable *table,
                                    EComposerHeaderType type)
{
	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);
	g_return_val_if_fail (type < E_COMPOSER_NUM_HEADERS, NULL);

	return table->priv->headers[type];
}

EMailSignatureComboBox *
e_composer_header_table_get_signature_combo_box (EComposerHeaderTable *table)
{
	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	return E_MAIL_SIGNATURE_COMBO_BOX (table->priv->signature_combo_box);
}

EDestination **
e_composer_header_table_get_destinations (EComposerHeaderTable *table)
{
	EDestination **destinations;
	EDestination **to, **cc, **bcc;
	gint total, n_to, n_cc, n_bcc;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	to = e_composer_header_table_get_destinations_to (table);
	for (n_to = 0; to != NULL && to[n_to] != NULL; n_to++);

	cc = e_composer_header_table_get_destinations_cc (table);
	for (n_cc = 0; cc != NULL && cc[n_cc] != NULL; n_cc++);

	bcc = e_composer_header_table_get_destinations_bcc (table);
	for (n_bcc = 0; bcc != NULL && bcc[n_bcc] != NULL; n_bcc++);

	total = n_to + n_cc + n_bcc;
	destinations = g_new0 (EDestination *, total + 1);

	while (n_bcc > 0 && total > 0)
		destinations[--total] = g_object_ref (bcc[--n_bcc]);

	while (n_cc > 0 && total > 0)
		destinations[--total] = g_object_ref (cc[--n_cc]);

	while (n_to > 0 && total > 0)
		destinations[--total] = g_object_ref (to[--n_to]);

	/* Counters should all be zero now. */
	g_assert (total == 0 && n_to == 0 && n_cc == 0 && n_bcc == 0);

	e_destination_freev (to);
	e_destination_freev (cc);
	e_destination_freev (bcc);

	return destinations;
}

EDestination **
e_composer_header_table_get_destinations_bcc (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_BCC;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	return e_composer_name_header_get_destinations (name_header);
}

void
e_composer_header_table_add_destinations_bcc (EComposerHeaderTable *table,
                                              EDestination **destinations)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_BCC;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	e_composer_name_header_add_destinations (name_header, destinations);

	if (destinations != NULL && *destinations != NULL)
		e_composer_header_set_visible (header, TRUE);
}

void
e_composer_header_table_set_destinations_bcc (EComposerHeaderTable *table,
                                              EDestination **destinations)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_BCC;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	e_composer_name_header_set_destinations (name_header, destinations);

	if (destinations != NULL && *destinations != NULL)
		e_composer_header_set_visible (header, TRUE);
}

EDestination **
e_composer_header_table_get_destinations_cc (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_CC;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	return e_composer_name_header_get_destinations (name_header);
}

void
e_composer_header_table_add_destinations_cc (EComposerHeaderTable *table,
                                             EDestination **destinations)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_CC;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	e_composer_name_header_add_destinations (name_header, destinations);

	if (destinations != NULL && *destinations != NULL)
		e_composer_header_set_visible (header, TRUE);
}

void
e_composer_header_table_set_destinations_cc (EComposerHeaderTable *table,
                                             EDestination **destinations)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_CC;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	e_composer_name_header_set_destinations (name_header, destinations);

	if (destinations != NULL && *destinations != NULL)
		e_composer_header_set_visible (header, TRUE);
}

EDestination **
e_composer_header_table_get_destinations_to (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_TO;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	return e_composer_name_header_get_destinations (name_header);
}

void
e_composer_header_table_add_destinations_to (EComposerHeaderTable *table,
                                             EDestination **destinations)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_TO;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	e_composer_name_header_add_destinations (name_header, destinations);
}

void
e_composer_header_table_set_destinations_to (EComposerHeaderTable *table,
                                             EDestination **destinations)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerNameHeader *name_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_TO;
	header = e_composer_header_table_get_header (table, type);
	name_header = E_COMPOSER_NAME_HEADER (header);

	e_composer_name_header_set_destinations (name_header, destinations);
}

const gchar *
e_composer_header_table_get_identity_uid (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerFromHeader *from_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_FROM;
	header = e_composer_header_table_get_header (table, type);
	from_header = E_COMPOSER_FROM_HEADER (header);

	return e_composer_from_header_get_active_id (from_header);
}

void
e_composer_header_table_set_identity_uid (EComposerHeaderTable *table,
                                          const gchar *identity_uid)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerFromHeader *from_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_FROM;
	header = e_composer_header_table_get_header (table, type);
	from_header = E_COMPOSER_FROM_HEADER (header);

	e_composer_from_header_set_active_id (from_header, identity_uid);
}

GList *
e_composer_header_table_get_post_to (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerPostHeader *post_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_POST_TO;
	header = e_composer_header_table_get_header (table, type);
	post_header = E_COMPOSER_POST_HEADER (header);

	return e_composer_post_header_get_folders (post_header);
}

void
e_composer_header_table_set_post_to_base (EComposerHeaderTable *table,
                                          const gchar *base_url,
                                          const gchar *folders)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerPostHeader *post_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_POST_TO;
	header = e_composer_header_table_get_header (table, type);
	post_header = E_COMPOSER_POST_HEADER (header);

	e_composer_post_header_set_folders_base (post_header, base_url, folders);
}

void
e_composer_header_table_set_post_to_list (EComposerHeaderTable *table,
                                          GList *folders)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerPostHeader *post_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_POST_TO;
	header = e_composer_header_table_get_header (table, type);
	post_header = E_COMPOSER_POST_HEADER (header);

	e_composer_post_header_set_folders (post_header, folders);
}

const gchar *
e_composer_header_table_get_reply_to (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerTextHeader *text_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_REPLY_TO;
	header = e_composer_header_table_get_header (table, type);
	text_header = E_COMPOSER_TEXT_HEADER (header);

	return e_composer_text_header_get_text (text_header);
}

void
e_composer_header_table_set_reply_to (EComposerHeaderTable *table,
                                      const gchar *reply_to)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerTextHeader *text_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_REPLY_TO;
	header = e_composer_header_table_get_header (table, type);
	text_header = E_COMPOSER_TEXT_HEADER (header);

	e_composer_text_header_set_text (text_header, reply_to);

	if (reply_to != NULL && *reply_to != '\0')
		e_composer_header_set_visible (header, TRUE);
}

const gchar *
e_composer_header_table_get_signature_uid (EComposerHeaderTable *table)
{
	EMailSignatureComboBox *combo_box;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	combo_box = e_composer_header_table_get_signature_combo_box (table);

	return gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
}

void
e_composer_header_table_set_signature_uid (EComposerHeaderTable *table,
                                           const gchar *signature_uid)
{
	EMailSignatureComboBox *combo_box;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	combo_box = e_composer_header_table_get_signature_combo_box (table);

	gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), signature_uid);
}

const gchar *
e_composer_header_table_get_subject (EComposerHeaderTable *table)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerTextHeader *text_header;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);

	type = E_COMPOSER_HEADER_SUBJECT;
	header = e_composer_header_table_get_header (table, type);
	text_header = E_COMPOSER_TEXT_HEADER (header);

	return e_composer_text_header_get_text (text_header);
}

void
e_composer_header_table_set_subject (EComposerHeaderTable *table,
                                     const gchar *subject)
{
	EComposerHeader *header;
	EComposerHeaderType type;
	EComposerTextHeader *text_header;

	g_return_if_fail (E_IS_COMPOSER_HEADER_TABLE (table));

	type = E_COMPOSER_HEADER_SUBJECT;
	header = e_composer_header_table_get_header (table, type);
	text_header = E_COMPOSER_TEXT_HEADER (header);

	e_composer_text_header_set_text (text_header, subject);
}

void
e_composer_header_table_set_header_visible (EComposerHeaderTable *table,
                                            EComposerHeaderType type,
                                            gboolean visible)
{
	EComposerHeader *header;

	header = e_composer_header_table_get_header (table, type);
	e_composer_header_set_visible (header, visible);

	/* Signature widgets track the "From" header. */
	if (type == E_COMPOSER_HEADER_FROM) {
		if (visible) {
			gtk_widget_show (table->priv->signature_label);
			gtk_widget_show (table->priv->signature_combo_box);
		} else {
			gtk_widget_hide (table->priv->signature_label);
			gtk_widget_hide (table->priv->signature_combo_box);
		}
	}
}

/**
 * e_composer_header_table_ref_source:
 * @table: an #EComposerHeaderTable
 * @uid: a unique identifier string
 *
 * Convenience function that works just like e_source_registry_ref_source(),
 * but spares the caller from digging out the #ESourceRegistry from @table.
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Returns: an #ESource, or %NULL if no match was found
 **/
ESource *
e_composer_header_table_ref_source (EComposerHeaderTable *table,
                                    const gchar *uid)
{
	EClientCache *client_cache;
	ESourceRegistry *registry;
	ESource *source;

	g_return_val_if_fail (E_IS_COMPOSER_HEADER_TABLE (table), NULL);
	g_return_val_if_fail (uid != NULL, NULL);

	client_cache = e_composer_header_table_ref_client_cache (table);
	registry = e_client_cache_ref_registry (client_cache);

	source = e_source_registry_ref_source (registry, uid);

	g_object_unref (client_cache);
	g_object_unref (registry);

	return source;
}