/*
 * Mail Notification
 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

%headertop{
#include <gtk/gtk.h>
#include <dbus/dbus-glib.h>
#include "mn-mailboxes.h"
#include "mn-mailbox-properties-dialog.h"
#include "mn-mail-icon.h"
#include "mn-popups.h"
%}

%privateheader{
#include "mn-sound-player.h"
%}

%{
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <glib/gi18n.h>
#include <libxml/tree.h>
#include "mn-properties-dialog.h"
#include "mn-util.h"
#include "mn-conf.h"
#include "mn-mailboxes.h"
#include "mn-about-dialog.h"
#include "mn-standard-message-view.h"
#include "mn-compact-message-view.h"
#include "mn-message.h"
#include "mn-stock.h"
#include "mn-locked-callback.h"

MNShell *mn_shell = NULL;

typedef struct
{
  int		num;
  const char	*name;
} UnixSignalInfo;

#define SIGNAL_INFO(name) { name, #name }

static const UnixSignalInfo unix_quit_signals[] = {
  SIGNAL_INFO(SIGHUP),
  SIGNAL_INFO(SIGINT),
  SIGNAL_INFO(SIGTERM),
  SIGNAL_INFO(SIGUSR1),
  SIGNAL_INFO(SIGUSR2)
};
%}

enum MN_SHELL_TOOLTIP_MAIL_SUMMARY
{
  STANDARD,
  COMPACT,
  NONE
} MN:Shell:Tooltip:Mail:Summary;

class MN:Shell from G:Object
{
  public MNMailboxes *mailboxes;

  public MNMailIcon *icon;
  public MNPopups *popups;

  public DBusGConnection *session_bus;
  property POINTER session_bus (flags = CONSTRUCT_ONLY, link, type = DBusGConnection *);

  public DBusGProxy *session_bus_proxy;
  property POINTER session_bus_proxy (flags = CONSTRUCT_ONLY, link, type = DBusGProxy *);

  private int unix_signal_pipe_write_end;

  private MNSoundPlayer *sound_player;

  private gboolean has_new;

  private GtkWidget *properties_dialog;
  private GSList *mailbox_properties_dialogs;
  private GtkWidget *about_dialog;

  constructor (self)
  {
    g_assert(mn_shell == NULL);

    mn_shell = self;
    g_object_weak_ref(G_OBJECT(self), self_weak_notify_cb, NULL);

    selfp->sound_player = mn_sound_player_new();
    mn_add_weak_pointer(&selfp->sound_player);

    /*
     * self->mailboxes is assigned in MNMailboxes itself, so that
     * mailboxes can access it even during construction of
     * MNMailboxes.
     */
    mn_mailboxes_new();
    mn_add_weak_pointer(&self->mailboxes);

    self_init_icon(self);

    mn_g_object_gconf_notifications_add_gdk_locked(self,
						   MN_CONF_BLINK_ON_ERRORS, self_notify_icon_cb, self,
						   MN_CONF_ALWAYS_DISPLAY_ICON, self_notify_icon_cb, self,
						   MN_CONF_DISPLAY_MESSAGE_COUNT, self_notify_icon_cb, self,
						   MN_CONF_GNOME_MAIL_READER_NAMESPACE, self_notify_mail_reader_cb, self,
						   MN_CONF_TOOLTIP_MAIL_SUMMARY, self_notify_tooltip_cb, self,
						   MN_CONF_TOOLTIP_MAIL_SUMMARY_LIMIT, self_notify_tooltip_cb, self,
						   NULL);

    g_object_connect(self->mailboxes,
		     "signal::messages-changed", self_messages_changed_h, self,
		     "signal::mailbox-removed", self_mailbox_removed_h, self,
		     "swapped-signal::notify::manually-checkable", self_update_sensitivity, self,
		     "swapped-signal::list-changed", self_update_tooltip, self,
		     "swapped-signal::list-changed", self_update_icon, self,
		     "swapped-signal::error-changed", self_update_tooltip, self,
		     "swapped-signal::error-changed", self_update_icon, self,
		     NULL);

    self->popups = mn_popups_new();
    mn_add_weak_pointer(&self->popups);

    /*
     * Exit gracefully (unreferencing our components) when receiving a
     * UNIX signal.
     */
    if (! self_install_unix_quit_signal_handlers(self))
      /* unlikely and unimportant, not worth a translation */
      g_warning("unable to install UNIX quit signal handlers");
  }

  finalize (self)
  {
    /*
     * We explicitly unreference or destroy each component, even
     * though MN will exit after the shell is finalized. This is done
     * for the sake of respecting encapsulation: we should not know
     * whether a particular component has something to do on exit or
     * not (for instance, MNSoundPlayer must kill the play process).
     *
     * Of course, the order in which we get rid of the components
     * matters: for instance, when destroyed, MNPropertiesDialog might
     * remove the test mailbox and thus requires a valid MNMailboxes
     * object. We destroy the components in the inverse order of their
     * creation.
     *
     * Also note that we do not need to nullify the pointers after
     * destruction since we use mn_add_weak_pointer() at creation
     * time.
     */

    g_slist_foreach(selfp->mailbox_properties_dialogs, (GFunc) gtk_widget_destroy, NULL);
    /* the list is freed in mailbox_properties_dialog_weak_notify_cb() */

    if (selfp->properties_dialog)
      gtk_widget_destroy(selfp->properties_dialog);

    if (selfp->about_dialog)
      gtk_widget_destroy(selfp->about_dialog);

    mn_g_object_null_unref(self->popups);

    if (self->icon)
      {
	/* do not recreate the icon after we destroy it */
	g_signal_handlers_disconnect_by_func(self->icon, self_icon_destroy_h, self);
	gtk_widget_destroy(GTK_WIDGET(self->icon));
      }

    mn_g_object_null_unref(self->mailboxes);

    mn_g_object_null_unref(selfp->sound_player);
  }

  private gboolean
    install_unix_quit_signal_handlers (self)
  {
    int sigpipe[2];
    int flags;
    GIOChannel *read_channel = NULL;
    int i;

    if (pipe(sigpipe) < 0)
      return FALSE;

    /*
     * Enable non-blocking mode for the write end, so that if another
     * signal occurs while the main thread is busy processing the
     * first signal, the signal handler will not block in write().
     */

    flags = fcntl(sigpipe[1], F_GETFL);
    if (flags < 0)
      goto error;

    if (fcntl(sigpipe[1], F_SETFL, flags | O_NONBLOCK) < 0)
      goto error;

    selfp->unix_signal_pipe_write_end = sigpipe[1];

    read_channel = g_io_channel_unix_new(sigpipe[0]);

    if (g_io_channel_set_encoding(read_channel, NULL, NULL) != G_IO_STATUS_NORMAL)
      goto error;

    for (i = 0; i < G_N_ELEMENTS(unix_quit_signals); i++)
      {
	struct sigaction sa;

	sa.sa_handler = self_unix_quit_signal_handler;
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);

	if (sigaction(unix_quit_signals[i].num, &sa, NULL) < 0)
	  goto error;
      }

    g_io_add_watch(read_channel, G_IO_IN | G_IO_PRI, self_unix_quit_signal_watch_cb, self);

    return TRUE;		/* ok */

  error:
    if (read_channel)
      {
	g_io_channel_shutdown(read_channel, FALSE, NULL);
	g_io_channel_unref(read_channel);
      }
    else
      close(sigpipe[0]);

    close(sigpipe[1]);

    return FALSE;
  }

  private void
    unix_quit_signal_handler (int signum)
  {
    write(mn_shell->_priv->unix_signal_pipe_write_end, &signum, sizeof(signum));
  }

  private const UnixSignalInfo *
    lookup_unix_quit_signal (int signum)
  {
    int i;

    for (i = 0; i < G_N_ELEMENTS(unix_quit_signals); i++)
      if (unix_quit_signals[i].num == signum)
	return &unix_quit_signals[i];

    g_assert_not_reached();
    return NULL;
  }

  private gboolean
    unix_quit_signal_watch_cb (GIOChannel *source,
			       GIOCondition condition,
			       gpointer data)
  {
    Self *self = data;
    GIOStatus status;
    int signum;
    gsize bytes_read;
    const UnixSignalInfo *info;

    status = g_io_channel_read_chars(source, (char *) &signum, sizeof(signum), &bytes_read, NULL);
    g_assert(status == G_IO_STATUS_NORMAL);
    g_assert(bytes_read == sizeof(signum));

    info = self_lookup_unix_quit_signal(signum);

    g_message(_("received %s signal, exiting"), info->name);

    GDK_THREADS_ENTER();
    self_quit(self);
    GDK_THREADS_LEAVE();

    return FALSE;		/* remove source */
  }

  private void
    weak_notify_cb (gpointer data, GObject *former_object)
  {
    gtk_main_quit();
  }

  private void
    messages_changed_h (MNMailboxes *mailboxes,
			gboolean has_new,
			gpointer user_data)
  {
    Self *self = user_data;

    if (mn_conf_has_command(MN_CONF_COMMANDS_MAIL_CHANGED_NAMESPACE))
      mn_conf_execute_command(MN_CONF_COMMANDS_MAIL_CHANGED_COMMAND);

    if (has_new)
      {
	self_play_new_mail_sound(self);

	if (mn_conf_has_command(MN_CONF_COMMANDS_NEW_MAIL_NAMESPACE))
	  mn_conf_execute_command(MN_CONF_COMMANDS_NEW_MAIL_COMMAND);
      }

    self_update_sensitivity(self);
    self_update_tooltip(self);
    self_update_icon(self);
  }

  private void
    play_new_mail_sound (self)
  {
    char *file;

    if (! mn_conf_get_bool(MN_CONF_SOUNDS_NEW_MAIL_ENABLED))
      return;

    file = mn_conf_get_string(MN_CONF_SOUNDS_NEW_MAIL_FILE);

    if (file && *file)
      mn_sound_player_play(selfp->sound_player, file, NULL);

    g_free(file);
  }

  private void
    mailbox_removed_h (MNMailboxes *mailboxes,
		       MNMailbox *mailbox,
		       gpointer user_data)
  {
    Self *self = user_data;
    MNMailboxPropertiesDialog *dialog;

    /* destroy the associated properties dialog, if any */
    dialog = self_get_mailbox_properties_dialog(self, mailbox);
    if (dialog)
      gtk_widget_destroy(GTK_WIDGET(dialog));
  }

  private void
    init_icon_base (self)
  {
    self->icon = MN_MAIL_ICON(mn_mail_icon_new());
    mn_add_weak_pointer(&self->icon);

    g_object_connect(self->icon,
		     "signal::activate", self_icon_activate_h, self,
		     "signal::activate-mail-reader", self_icon_activate_mail_reader_h, self,
		     "signal::activate-open-latest-message", self_icon_activate_open_latest_message_h, self,
		     "swapped-signal::activate-consider-new-mail-as-read", self_consider_new_mail_as_read, self,
		     "swapped-signal::activate-update", self_update, self,
		     "signal::activate-properties", self_icon_activate_properties_h, self,
		     "signal::activate-help", self_icon_activate_help_h, self,
		     "signal::activate-about", self_icon_activate_about_h, self,
		     "swapped-signal::activate-remove", self_quit, self,
		     "signal::destroy", self_icon_destroy_h, self,
		     NULL);

  }

  private void
    init_icon (self)
  {
    self_init_icon_base(self);
    self_update_sensitivity(self);
    self_update_tooltip(self);
    self_update_icon(self);
  }

  private void
    notify_icon_cb (GConfClient *client,
		    unsigned int cnxn_id,
		    GConfEntry *entry,
		    gpointer user_data)
  {
    Self *self = user_data;

    self_update_icon(self);
  }

  private void
    notify_tooltip_cb (GConfClient *client,
		       unsigned int cnxn_id,
		       GConfEntry *entry,
		       gpointer user_data)
  {
    Self *self = user_data;

    self_update_tooltip(self);
  }

  private void
    notify_mail_reader_cb (GConfClient *client,
			   unsigned int cnxn_id,
			   GConfEntry *entry,
			   gpointer user_data)
  {
    Self *self = user_data;

    self_update_sensitivity(self);
  }

  private void
    icon_activate_h (MNMailIcon *icon, gpointer user_data)
  {
    MNShell *self = user_data;
    MNAction action;

    action = mn_conf_get_enum_value(MN_TYPE_ACTION, MN_CONF_CLICK_ACTION);

    switch (action)
      {
      case MN_ACTION_LAUNCH_MAIL_READER:
	if (mn_conf_has_command(MN_CONF_GNOME_MAIL_READER_NAMESPACE))
	  mn_conf_execute_mail_reader();
	else
	  mn_show_error_dialog_with_markup(NULL,
					   _("No mail reader is configured"),
					   _("You can configure a mail reader by choosing <b>System → Preferences → Preferred Applications</b>."));
	break;

      case MN_ACTION_OPEN_LATEST_MESSAGE:
	if (self->mailboxes->messages->len != 0)
	  {
	    MNMessage *message = g_ptr_array_index(self->mailboxes->messages, 0);

	    if (mn_message_can_perform_action(message, mn_message_get_action("open")))
	      self_open_latest_message(self);
	    else
	      mn_show_error_dialog(NULL,
				   _("Unable to open the latest message"),
				   _("Messages of mailbox \"%s\" cannot be opened."),
				   message->mailbox->runtime_name);
	  }
	else
	  mn_show_error_dialog(NULL,
			       _("Unable to open the latest message"),
			       _("You have no new mail."));
	break;

      case MN_ACTION_CONSIDER_NEW_MAIL_AS_READ:
	self_consider_new_mail_as_read(self);
	break;

      case MN_ACTION_UPDATE_MAIL_STATUS:
	mn_mailboxes_check(self->mailboxes);
	break;

      default:
	g_assert_not_reached();
      }
  }

  private void
    icon_activate_mail_reader_h (MNMailIcon *icon, gpointer user_data)
  {
    mn_conf_execute_mail_reader();
  }

  private void
    icon_activate_open_latest_message_h (MNMailIcon *icon, gpointer user_data)
  {
    Self *self = user_data;
    self_open_latest_message(self);
  }

  private void
    icon_activate_properties_h (MNMailIcon *icon, gpointer user_data)
  {
    Self *self = user_data;
    self_show_properties_dialog(self, gtk_get_current_event_time());
  }

  private void
    icon_activate_help_h (MNMailIcon *icon, gpointer user_data)
  {
    mn_show_help(NULL, NULL);
  }

  private void
    icon_activate_about_h (MNMailIcon *icon, gpointer user_data)
  {
    Self *self = user_data;
    self_show_about_dialog(self, gtk_get_current_event_time());
  }

  private void
    icon_destroy_h (GtkWidget *object, gpointer user_data)
  {
    Self *self = user_data;

    /* The Notification Area applet has been terminated. Recreate the icon. */
    mn_remove_weak_pointer(&self->icon);
    self_init_icon(self);
  }

  private void
    update_sensitivity (self)
  {
    gtk_widget_set_sensitive(self->icon->mail_reader_item,
			     mn_conf_has_command(MN_CONF_GNOME_MAIL_READER_NAMESPACE));
    gtk_widget_set_sensitive(self->icon->open_latest_message_item,
			     self->mailboxes->messages->len != 0
			     && mn_message_can_perform_action(g_ptr_array_index(self->mailboxes->messages, 0),
							      mn_message_get_action("open")));
    gtk_widget_set_sensitive(self->icon->consider_new_mail_as_read_item,
			     self->mailboxes->messages->len != 0);
    gtk_widget_set_sensitive(self->icon->update_item,
			     mn_mailboxes_get_manually_checkable(self->mailboxes));
  }

  private void
    update_icon (self)
  {
    GList *l;
    gboolean has_new = FALSE;
    gboolean blink = FALSE;
    gboolean always = mn_conf_get_bool(MN_CONF_ALWAYS_DISPLAY_ICON);

    MN_LIST_FOREACH(l, self->mailboxes->list)
      {
	MNMailbox *mailbox = l->data;

	if (g_hash_table_size(mailbox->messages) != 0)
	  has_new = TRUE;
	if (mailbox->error)
	  blink = TRUE;
      }

    if (selfp->has_new && ! has_new && mn_conf_has_command(MN_CONF_COMMANDS_MAIL_READ_NAMESPACE))
      mn_conf_execute_command(MN_CONF_COMMANDS_MAIL_READ_COMMAND);
    selfp->has_new = has_new;

    if (blink && ! mn_conf_get_bool(MN_CONF_BLINK_ON_ERRORS))
      blink = FALSE;

    if (has_new || blink || always)
      {
	int count;

	if (mn_conf_get_bool(MN_CONF_DISPLAY_MESSAGE_COUNT))
	  count = self->mailboxes->messages->len;
	else
	  count = 0;

	mn_mail_icon_set_from_stock(self->icon, has_new ? MN_STOCK_MAIL : MN_STOCK_NO_MAIL);
	mn_mail_icon_set_blinking(self->icon, blink);
	mn_mail_icon_set_count(self->icon, count);
	gtk_widget_show(GTK_WIDGET(self->icon));
      }
    else
      {
	/* Re-create the icon as a regular gtk_widget_hide causes the
	 * icon to remain visible on non-GNOME environments. We can't
	 * use the callback self_icon_destroy_h here as it can cause an
	 * endless recursion */
	gtk_widget_unrealize(GTK_WIDGET(self->icon));
	g_signal_handlers_disconnect_by_func(self->icon, self_icon_destroy_h, self);
	gtk_widget_destroy(GTK_WIDGET(self->icon));
	self_init_icon_base(self);
	mn_mail_icon_set_blinking(self->icon, FALSE);
      }
  }

  private void
    update_tooltip (self)
  {
    GtkVBox *vbox = NULL;

    if (self->mailboxes->list)
      {
	GList *la;
	GSList *lb;
	GSList *new_mailboxes = NULL;
	GSList *error_mailboxes = NULL;

	MN_LIST_FOREACH(la, self->mailboxes->list)
          {
	    MNMailbox *mailbox = la->data;

	    if (g_hash_table_size(mailbox->messages) != 0)
	      new_mailboxes = g_slist_insert_sorted(new_mailboxes, mailbox, (GCompareFunc) self_new_mailboxes_compare_cb);

	    if (mailbox->error)
	      error_mailboxes = g_slist_insert_sorted(error_mailboxes, mailbox, mn_mailboxes_compare_by_name_func);
	  }

	if (new_mailboxes)
	  {
	    GString *string = g_string_new(NULL);

	    MN_LIST_FOREACH(lb, new_mailboxes)
	      {
		MNMailbox *mailbox = lb->data;

		g_assert(g_hash_table_size(mailbox->messages) != 0);

		if (*string->str)
		  g_string_append_c(string, '\n');

		g_string_append_printf(string, _("%s (%i)"),
				       mailbox->runtime_name,
				       (int) g_hash_table_size(mailbox->messages));
	      }
	    g_slist_free(new_mailboxes);

	    /* translators: header capitalization */
	    self_tooltip_text_section_new(&vbox, _("Mailboxes Having New Mail"), string->str);
	    g_string_free(string, TRUE);
	  }

	if (error_mailboxes)
	  {
	    GString *string = g_string_new(NULL);

	    MN_LIST_FOREACH(lb, error_mailboxes)
	      {
		MNMailbox *mailbox = lb->data;

		if (*string->str)
		  g_string_append_c(string, '\n');
		g_string_append_printf(string, _("%s: %s"), mailbox->runtime_name, mailbox->error);
	      }
	    g_slist_free(error_mailboxes);

	    /* translators: header capitalization */
	    self_tooltip_text_section_new(&vbox, _("Errors"), string->str);
	    g_string_free(string, TRUE);
	  }
      }

    if (self->mailboxes->messages->len != 0)
      {
	MNShellTooltipMailSummary mail_summary;

	mail_summary = mn_conf_get_enum_value(MN_TYPE_SHELL_TOOLTIP_MAIL_SUMMARY, MN_CONF_TOOLTIP_MAIL_SUMMARY);
	if (mail_summary != MN_SHELL_TOOLTIP_MAIL_SUMMARY_NONE)
	  {
	    GtkWidget *alignment;
	    GtkWidget *message_view;
	    int limit;
	    GSList *messages = NULL;
	    int num_messages;
	    int i;

	    /* translators: header capitalization */
	    alignment = self_tooltip_section_new(&vbox, _("Mail Summary"));

	    switch (mail_summary)
	      {
	      case MN_SHELL_TOOLTIP_MAIL_SUMMARY_STANDARD:
		message_view = mn_standard_message_view_new();
		break;

	      case MN_SHELL_TOOLTIP_MAIL_SUMMARY_COMPACT:
		message_view = mn_compact_message_view_new();
		break;

	      default:
		g_assert_not_reached();
		break;
	      }

	    gtk_widget_set_name(message_view, "mn-message-view");

	    limit = mn_conf_get_int(MN_CONF_TOOLTIP_MAIL_SUMMARY_LIMIT);

	    num_messages = MIN(self->mailboxes->messages->len, limit);
	    for (i = num_messages - 1; i >= 0; i--)
	      messages = g_slist_prepend(messages, g_ptr_array_index(self->mailboxes->messages, i));
	    mn_message_view_set_messages(MN_MESSAGE_VIEW(message_view), messages);
	    g_slist_free(messages);

	    if (self->mailboxes->messages->len <= limit)
	      gtk_container_add(GTK_CONTAINER(alignment), message_view);
	    else
	      {
		int remaining;
		char *markup;
		GtkWidget *label;
		GtkWidget *summary_vbox;

		remaining = self->mailboxes->messages->len - limit;

		label = gtk_label_new(NULL);

		markup = g_strdup_printf(ngettext("<span style=\"italic\">%i message is not displayed</span>",
						  "<span style=\"italic\">%i messages are not displayed</span>",
						  remaining),
					 remaining);
		gtk_label_set_markup(GTK_LABEL(label), markup);
		g_free(markup);

		gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

		summary_vbox = gtk_vbox_new(FALSE, 12);

		gtk_box_pack_start(GTK_BOX(summary_vbox), message_view, TRUE, TRUE, 0);
		gtk_box_pack_start(GTK_BOX(summary_vbox), label, FALSE, FALSE, 0);

		gtk_container_add(GTK_CONTAINER(alignment), summary_vbox);
	      }
	  }
      }

    if (vbox)
      {
	gtk_widget_show_all(GTK_WIDGET(vbox));
	mn_mail_icon_set_tip_widget(self->icon, GTK_WIDGET(vbox));
      }
    else
      mn_mail_icon_set_tip(self->icon, _("You have no new mail."));
  }

  private int
    new_mailboxes_compare_cb (MNMailbox *a, MNMailbox *b)
  {
    int cmp;

    /* sort by timestamp (descending order) */
    cmp = b->timestamp - a->timestamp;
    if (cmp != 0)
      return cmp;

    /* sort by number of messages (descending order) */
    cmp = (int) g_hash_table_size(b->messages) - g_hash_table_size(a->messages);
    if (cmp != 0)
      return cmp;

    /* sort by name (ascending order) */
    return mn_mailboxes_compare_by_name_func(a, b);
  }

  private GtkWidget *
    tooltip_section_new (GtkVBox **vbox (check null),
			 const char *title (check null))
  {
    GtkWidget *section;
    GtkWidget *label;
    GtkWidget *alignment;

    if (! *vbox)
      *vbox = GTK_VBOX(gtk_vbox_new(FALSE, 18));

    section = mn_hig_section_new(title, &label, &alignment);

    gtk_widget_set_name(label, "mn-tooltip-section-title");

    gtk_box_pack_start(GTK_BOX(*vbox), section, TRUE, TRUE, 0);

    return alignment;
  }

  private void
    tooltip_text_section_new (GtkVBox **vbox (check null),
			      const char *title (check null),
			      const char *text (check null))
  {
    GtkWidget *alignment;
    GtkWidget *label;

    alignment = self_tooltip_section_new(vbox, title);

    label = gtk_label_new(text);
    gtk_widget_set_name(label, "mn-tooltip-section-body");
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    gtk_container_add(GTK_CONTAINER(alignment), label);
  }

  private void
    open_latest_message (self)
  {
    MNMessage *message;
    MNMessageAction *action;

    g_return_if_fail(self->mailboxes->messages->len != 0);

    message = g_ptr_array_index(self->mailboxes->messages, 0);

    action = mn_message_get_action("open");
    g_return_if_fail(action != NULL);
    g_return_if_fail(mn_message_can_perform_action(message, action));

    mn_message_perform_action(message, action, self_open_latest_message_done_cb, NULL);
  }

  private void
    open_latest_message_done_cb (MNMessageAction *action, GError *err, gpointer data)
  {
    if (err && ! g_error_matches(err, MN_MESSAGE_ACTION_ERROR, MN_MESSAGE_ACTION_ERROR_CANCELLED))
      mn_show_error_dialog(NULL, _("Unable to open the latest message"), "%s", err->message);
  }

  public MNShell *
    new (DBusGConnection *session_bus (check null),
	 DBus:G:Proxy *session_bus_proxy (check null))
  {
    return GET_NEW_VARG(MN_SHELL_PROP_SESSION_BUS(session_bus),
			MN_SHELL_PROP_SESSION_BUS_PROXY(session_bus_proxy),
			NULL);
  }

  public void
    consider_new_mail_as_read (self)
  {
    GList *l;

    MN_LIST_FOREACH(l, self->mailboxes->list)
      {
	MNMailbox *mailbox = l->data;
	GList *list;

	list = g_hash_table_get_values(mailbox->messages);
	mn_message_consider_as_read_list(list);
	g_list_free(list);
      }
  }

  public void
    update (self)
  {
    mn_mailboxes_check(self->mailboxes);
  }

  public void
    quit (self)
  {
    g_object_unref(self);
  }

  public void
    show_properties_dialog (self, guint32 timestamp)
  {
    self_show_window(self, MN_TYPE_PROPERTIES_DIALOG, &selfp->properties_dialog, timestamp);
  }

  public void
    show_about_dialog (self, guint32 timestamp)
  {
    self_show_window(self, MN_TYPE_ABOUT_DIALOG, &selfp->about_dialog, timestamp);
  }

  private void
    show_window (self,
		 GType type (check != 0),
		 GtkWidget **ptr (check null),
		 guint32 timestamp)
  {
    if (*ptr)
      {
	if (timestamp)
	  gtk_window_present_with_time(GTK_WINDOW(*ptr), timestamp);
	else
	  gtk_window_present(GTK_WINDOW(*ptr));
	return;
      }

    *ptr = g_object_new(type, NULL);
    mn_add_weak_pointer(ptr);

    gtk_widget_show(*ptr);
  }

  public void
    add_mailbox_properties_dialog (self, MN:Mailbox:Properties:Dialog *dialog (check null type))
  {
    selfp->mailbox_properties_dialogs = g_slist_append(selfp->mailbox_properties_dialogs, dialog);
    g_object_weak_ref(G_OBJECT(dialog), self_mailbox_properties_dialog_weak_notify_cb, self);
  }

  private void
    mailbox_properties_dialog_weak_notify_cb (gpointer data,
					      GObject *former_object)
  {
    Self *self = data;

    selfp->mailbox_properties_dialogs = g_slist_remove(selfp->mailbox_properties_dialogs, former_object);
  }

  public MNMailboxPropertiesDialog *
    get_mailbox_properties_dialog (self, MN:Mailbox *mailbox (check null type))
  {
    GSList *l;

    MN_LIST_FOREACH(l, selfp->mailbox_properties_dialogs)
      {
	MNMailboxPropertiesDialog *dialog = l->data;
	MNMailbox *this_mailbox;
	gboolean found;

	this_mailbox = mn_mailbox_properties_dialog_get_mailbox(dialog);
	found = this_mailbox == mailbox;
	g_object_unref(this_mailbox);

	if (found)
	  return dialog;
      }

    return NULL;
  }

  public char *
    get_summary (self)
  {
    int indent;
    xmlDoc *doc;
    xmlNode *root;
    int i;
    xmlChar *summary;

    indent = xmlIndentTreeOutput;
    xmlIndentTreeOutput = 1;

    doc = xmlNewDoc("1.0");
    root = xmlNewNode(NULL, "messages");
    xmlDocSetRootElement(doc, root);

    MN_ARRAY_FOREACH(i, self->mailboxes->messages)
      {
	MNMessage *message = g_ptr_array_index(self->mailboxes->messages, i);
	xmlNode *node;

	node = mn_message_xml_node_new(message);
	xmlAddChild(root, node); /* owns node */
      }

    xmlDocDumpFormatMemory(doc, &summary, NULL, 1);

    xmlFreeDoc(doc);
    xmlIndentTreeOutput = indent;

    return summary;
  }
}

%h{
extern MNShell *mn_shell;
%}

/*
 * These enumerations really belong to mn-enums.gob, but gob does not
 * allow a class-less input file.
 */

enum MN_ACTION
{
  LAUNCH_MAIL_READER,
  OPEN_LATEST_MESSAGE,
  CONSIDER_NEW_MAIL_AS_READ,
  UPDATE_MAIL_STATUS
} MN:Action;

enum MN_EXPIRATION_ENABLED
{
  DEFAULT,
  FALSE,
  TRUE
} MN:Expiration:Enabled;

enum MN_POPUP_POSITION
{
  ATTACHED,
  FREE
} MN:Popup:Position;
