/*
 * 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 <string.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>
#include "mn-util.h"
#include "mn-shell.h"
#include "mn-stock.h"
#include "mn-mailbox-properties-dialog.h"
#include "mn-conf.h"
#include "mn-mailbox.h"
#include "nautilus-cell-renderer-pixbuf-emblem.h"
#include "mn-test-mailbox.h"

enum
{
  COLUMN_MAILBOX,
  COLUMN_ICON,
  COLUMN_EMBLEM,
  COLUMN_PIXBUF,
  COLUMN_LABEL,
  COLUMN_RAW_LABEL,
  N_COLUMNS
};

enum
{
  TARGET_MAILBOXES,
  TARGET_GNOME_COPIED_FILES,
  N_TARGETS
};

typedef struct
{
  gboolean has_manually_checkable;
  gboolean has_selection;
} SensitivityInfo;

static struct
{
  char		*name;
  GdkAtom	atom;
} clipboard_info[N_TARGETS] = {
  { "x-special/mail-notification-mailboxes" },
  { "x-special/gnome-copied-files" }
};

static GtkClipboard *global_clipboard;
%}

class MN:Mailbox:View from Gtk:Tree:View
{
  private GtkWidget *menu = {gtk_menu_new()} destroywith gtk_widget_destroy;
  private GtkWidget *properties_item;
  private GtkWidget *update_item;
  private GtkWidget *remove_item;
  private GtkWidget *cut_item;
  private GtkWidget *copy_item;
  private GtkWidget *paste_item;
  private GtkWidget *select_all_item;

  signal (ACTION) NONE (NONE)
    void activate_update (self)
  {
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_selected_foreach(selection, self_activate_update_cb, self);
  }

  private void
    activate_update_cb (GtkTreeModel *model,
			GtkTreePath *path,
			GtkTreeIter *iter,
			gpointer data)
  {
    MNMailbox *mailbox;

    gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    if (mn_mailbox_get_manually_checkable(mailbox))
      mn_mailbox_check(mailbox);
    g_object_unref(mailbox);
  }

  signal (ACTION) NONE (NONE)
    void activate_properties (self)
  {
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_selected_foreach(selection, self_activate_properties_cb, self);
  }

  private void
    activate_properties_cb (GtkTreeModel *model,
			    GtkTreePath *path,
			    GtkTreeIter *iter,
			    gpointer data)
  {
    Self *self = data;
    MNMailbox *mailbox;
    GtkWidget *dialog;

    gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);

    dialog = (GtkWidget *) mn_shell_get_mailbox_properties_dialog(mn_shell, mailbox);
    if (dialog)
      mn_window_present_from_event(GTK_WINDOW(dialog));
    else
      {
	dialog = mn_mailbox_properties_dialog_new(mn_widget_get_parent_window(GTK_WIDGET(self)), mailbox);

	g_signal_connect(dialog,
			 "response",
			 G_CALLBACK(self_properties_edit_response_h),
			 NULL);

	mn_shell_add_mailbox_properties_dialog(mn_shell, MN_MAILBOX_PROPERTIES_DIALOG(dialog));
	gtk_widget_show(dialog);
      }

    g_object_unref(mailbox);
  }

  signal (ACTION) NONE (NONE)
    void activate_add (self)
  {
    GtkWidget *dialog;

    dialog = mn_mailbox_properties_dialog_new(mn_widget_get_parent_window(GTK_WIDGET(self)), NULL);

    g_signal_connect(dialog,
		     "response",
		     G_CALLBACK(self_properties_add_response_h),
		     NULL);

    gtk_widget_show(dialog);
  }

  signal (ACTION) NONE (NONE)
    void activate_remove (self)
  {
    self_remove_mailbox(self);
  }

  signal (ACTION) NONE (NONE)
    void activate_cut (self)
  {
    self_copy_mailbox(self);
    self_remove_mailbox(self);
  }

  signal (ACTION) NONE (NONE)
    void activate_copy (self)
  {
    self_copy_mailbox(self);
  }

  signal (ACTION) NONE (NONE)
    void activate_paste (self)
  {
    GtkSelectionData *data;

    data = gtk_clipboard_wait_for_contents(global_clipboard, clipboard_info[TARGET_MAILBOXES].atom);
    if (data)
      {
	GSList *configurations;
	GSList *l;

	memcpy(&configurations, gtk_selection_data_get_data(data), gtk_selection_data_get_length(data));

	MN_LIST_FOREACH(l, configurations)
	  {
	    MNMailbox *mailbox;

	    mailbox = mn_mailbox_new_from_configuration(l->data);

	    mn_mailbox_seal(mailbox);
	    mn_mailboxes_queue_add(mn_shell->mailboxes, mailbox);

	    g_object_unref(mailbox);
	  }

	gtk_selection_data_free(data);
	return;
      }

    data = gtk_clipboard_wait_for_contents(global_clipboard, clipboard_info[TARGET_GNOME_COPIED_FILES].atom);
    if (data)
      {
	if (gtk_selection_data_get_format(data) == 8 && gtk_selection_data_get_length(data) > 0)
	  {
	    char *gnome_copied_files;
	    gboolean status;
	    MNGnomeCopiedFilesType type;
	    GSList *uri_list;

	    gnome_copied_files = g_strndup(gtk_selection_data_get_data(data), gtk_selection_data_get_length(data));
	    status = mn_parse_gnome_copied_files(gnome_copied_files, &type, &uri_list);
	    g_free(gnome_copied_files);

	    if (status)
	      {
		if (type == MN_GNOME_COPIED_FILES_COPY)
		  {
		    GSList *l;
		    GSList *invalid_uri_list = NULL;

		    MN_LIST_FOREACH(l, uri_list)
		      {
			const char *uri = l->data;
			MNMailbox *mailbox;

			mailbox = mn_mailbox_new_from_uri(uri);
			if (mailbox)
			  {
			    mn_mailbox_seal(mailbox);
			    mn_mailboxes_queue_add(mn_shell->mailboxes, mailbox);
			    g_object_unref(mailbox);
			  }
			else
			  invalid_uri_list = g_slist_append(invalid_uri_list, (gpointer) uri);
		      }

		    if (invalid_uri_list)
		      {
			mn_show_invalid_uri_list_dialog(mn_widget_get_parent_window(GTK_WIDGET(self)), _("A paste error has occurred"), invalid_uri_list);
			g_slist_free(invalid_uri_list);
		      }
		  }

		mn_g_slist_free_deep(uri_list);
	      }
	  }

	gtk_selection_data_free(data);
      }
  }

  class_init (class)
  {
    GtkBindingSet *binding_set;
    int i;

    global_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);

    for (i = 0; i < N_TARGETS; i++)
      clipboard_info[i].atom = gdk_atom_intern(clipboard_info[i].name, FALSE);

    binding_set = gtk_binding_set_by_class(class);

    /* Delete removes a row */
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_Delete, 0, "activate-remove", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Delete, 0, "activate-remove", 0);

    /* HIG 2.0 cut/copy/paste shortcuts */
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_x, GDK_CONTROL_MASK, "activate-cut", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_c, GDK_CONTROL_MASK, "activate-copy", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_v, GDK_CONTROL_MASK, "activate-paste", 0);

    /* cut/copy/paste shortcuts taken from gtkentry.c */
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_Delete, GDK_SHIFT_MASK, "activate-cut", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_Insert, GDK_CONTROL_MASK, "activate-copy", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_Insert, GDK_SHIFT_MASK, "activate-paste", 0);

    /* HIG 2.0 properties */
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_MOD1_MASK, "activate-properties", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_ISO_Enter, GDK_MOD1_MASK, "activate-properties", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Enter, GDK_MOD1_MASK, "activate-properties", 0);
  }

  init (self)
  {
    GtkMenuShell *shell;
    GtkWidget *add_item;
    GtkListStore *store;
    GtkTreeViewColumn *column;
    GtkCellRenderer *renderer;
    GtkTreeSelection *selection;
    GList *l;

    /* popup menu */

    shell = GTK_MENU_SHELL(selfp->menu);

    /* ordered according to chapter 4 of the HIG */

    selfp->properties_item = mn_menu_shell_append(shell, GTK_STOCK_PROPERTIES, NULL);
    /* translators: header capitalization */
    selfp->update_item = mn_menu_shell_append(shell, GTK_STOCK_REFRESH, _("_Update"));
    mn_menu_shell_append(shell, NULL, NULL);
    add_item = mn_menu_shell_append(shell, GTK_STOCK_ADD, NULL);
    selfp->remove_item = mn_menu_shell_append(shell, GTK_STOCK_REMOVE, NULL);
    mn_menu_shell_append(shell, NULL, NULL);
    selfp->cut_item = mn_menu_shell_append(shell, GTK_STOCK_CUT, NULL);
    selfp->copy_item = mn_menu_shell_append(shell, GTK_STOCK_COPY, NULL);
    selfp->paste_item = mn_menu_shell_append(shell, GTK_STOCK_PASTE, NULL);
    mn_menu_shell_append(shell, NULL, NULL);
    selfp->select_all_item = mn_menu_shell_append(shell, GTK_STOCK_SELECT_ALL, NULL);

    g_signal_connect_swapped(selfp->properties_item, "activate", G_CALLBACK(self_activate_properties), self);
    g_signal_connect_swapped(selfp->update_item, "activate", G_CALLBACK(self_activate_update), self);
    g_signal_connect_swapped(add_item, "activate", G_CALLBACK(self_activate_add), self);
    g_signal_connect_swapped(selfp->remove_item, "activate", G_CALLBACK(self_activate_remove), self);
    g_signal_connect_swapped(selfp->cut_item, "activate", G_CALLBACK(self_activate_cut), self);
    g_signal_connect_swapped(selfp->copy_item, "activate", G_CALLBACK(self_activate_copy), self);
    g_signal_connect_swapped(selfp->paste_item, "activate", G_CALLBACK(self_activate_paste), self);
    g_signal_connect_swapped(selfp->select_all_item, "activate", G_CALLBACK(self_select_all), self);

    /* store */

    store = gtk_list_store_new(N_COLUMNS,
			       MN_TYPE_MAILBOX,
			       G_TYPE_STRING,
			       GDK_TYPE_PIXBUF,
			       GDK_TYPE_PIXBUF,
			       G_TYPE_STRING,
			       G_TYPE_STRING);

    gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(store),
					    self_sort_cb,
					    NULL,
					    NULL);
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
					 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
					 GTK_SORT_ASCENDING);

    gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(store));

    column = gtk_tree_view_column_new();

    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);

    renderer = nautilus_cell_renderer_pixbuf_emblem_new();
    g_object_set(renderer, "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer,
					"stock-id", COLUMN_ICON,
					"pixbuf-emblem", COLUMN_EMBLEM,
					"pixbuf", COLUMN_PIXBUF,
					NULL);

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    gtk_tree_view_column_add_attribute(column, renderer, "markup", COLUMN_LABEL);

    gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);

    /* misc */

    gtk_tree_view_set_search_column(GTK_TREE_VIEW(self), COLUMN_RAW_LABEL);
    gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(self), self_search_equal_cb, NULL, NULL);

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);

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

	if (! MN_IS_TEST_MAILBOX(mailbox))
	  self_append(self, l->data);
      }

    self_update_sensitivity(self);

    g_object_connect(store,
		     /* for the sensitivity of "Select All" */
		     "swapped-signal::row-inserted", self_update_sensitivity, self,
		     "swapped-signal::row-deleted", self_update_sensitivity, self,
		     NULL);

    mn_g_object_connect(self,
			mn_shell->mailboxes,
			"signal::mailbox-added", self_mailbox_added_h, self,
			"signal::mailbox-notify::stock-id", self_mailbox_notify_h, self,
			"swapped-signal::mailbox-notify::manually-checkable", self_update_sensitivity, self,
			"signal::mailbox-notify::format", self_mailbox_notify_h, self,
			"signal::mailbox-notify::error", self_mailbox_notify_h, self,
			"signal::mailbox-removed", self_mailbox_removed_h, self,
			NULL);

    g_signal_connect_swapped(selection, "changed", G_CALLBACK(self_update_sensitivity), self);

    g_object_connect(self,
		     "signal::popup-menu", self_popup_menu_h, NULL,
		     "signal::button-press-event", self_button_press_event_h, NULL,
		     "signal::row-activated", self_row_activated_h, NULL,
		     NULL);

    g_object_unref(store);
  }

  private int
    sort_cb (GtkTreeModel *model,
	     GtkTreeIter *a,
	     GtkTreeIter *b,
	     gpointer user_data)
  {
    MNMailbox *mailbox_a;
    MNMailbox *mailbox_b;
    int cmp;

    gtk_tree_model_get(model, a, COLUMN_MAILBOX, &mailbox_a, -1);
    gtk_tree_model_get(model, b, COLUMN_MAILBOX, &mailbox_b, -1);

    cmp = mn_mailboxes_compare_by_name_func(mailbox_a, mailbox_b);

    g_object_unref(mailbox_a);
    g_object_unref(mailbox_b);

    return cmp;
  }

  private gboolean
    search_equal_cb (GtkTreeModel *model,
		     int column,
		     const char *key,
		     GtkTreeIter *iter,
		     gpointer search_data)
  {
    gboolean status = TRUE;
    GValue value = { 0, };
    GValue transformed = { 0, };
    const char *str;
    char *normalized_string;
    char *normalized_key;

    /*
     * The stock gtk_tree_view_search_equal_func() only searches the
     * start of the string, using strncmp(). We use strstr(), to
     * search anywhere inside the string.
     */

    gtk_tree_model_get_value(model, iter, column, &value);

    g_value_init(&transformed, G_TYPE_STRING);

    if (! g_value_transform(&value, &transformed))
      {
	g_value_unset(&value);
	return TRUE;
      }

    g_value_unset(&value);

    str = g_value_get_string(&transformed);
    if (! str)
      {
	g_value_unset(&transformed);
	return TRUE;
      }

    normalized_string = g_utf8_normalize(str, -1, G_NORMALIZE_ALL);
    normalized_key = g_utf8_normalize(key, -1, G_NORMALIZE_ALL);

    if (normalized_string && normalized_key)
      {
	char *case_normalized_string;
	char *case_normalized_key;

	case_normalized_string = g_utf8_casefold(normalized_string, -1);
	case_normalized_key = g_utf8_casefold(normalized_key, -1);

	if (strstr(case_normalized_string, case_normalized_key))
	  status = FALSE;

	g_free(case_normalized_string);
	g_free(case_normalized_key);
      }

    g_value_unset (&transformed);
    g_free(normalized_key);
    g_free(normalized_string);

    return status;
  }

  private void
    append (self, MN:Mailbox *mailbox (check null type))
  {
    GtkTreeModel *model;
    GtkTreeIter iter;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    gtk_list_store_append(GTK_LIST_STORE(model), &iter);
    self_update_iter(self, &iter, mailbox);
  }

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

    if (! MN_IS_TEST_MAILBOX(mailbox))
      self_append(self, mailbox);
  }

  private void
    mailbox_notify_h (MNMailboxes *mailboxes,
		      MNMailbox *mailbox,
		      GParamSpec *pspec,
		      gpointer user_data)
  {
    Self *self = user_data;
    GtkTreeIter iter;

    if (self_find_mailbox(self, mailbox, &iter))
      self_update_iter(self, &iter, mailbox);
  }

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

    if (self_find_mailbox(self, mailbox, &iter))
      {
	GtkTreeModel *model;

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
	gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
      }
  }

  private void
    update_iter (self,
		 GtkTreeIter *iter (check null),
		 MN:Mailbox *mailbox (check null type))
  {
    GtkTreeModel *model;
    char *status;
    const char *emblem_stock_id = NULL;
    GdkPixbuf *emblem = NULL;
    char *markup;
    char *text;

    if (mailbox->format)
      {
	if (mailbox->error)
	  {
	    status = g_strdup_printf(_("unhandled %s mailbox (%s)"), mailbox->format, mailbox->error);
	    emblem_stock_id = MN_STOCK_ERROR;
	  }
	else
	  status = g_strdup_printf(_("%s mailbox"), mailbox->format);
      }
    else
      {
	if (mailbox->error)
	  {
	    status = g_strdup(mailbox->error);
	    emblem_stock_id = MN_STOCK_ERROR;
	  }
	else
	  {
	    status = g_strdup(_("detecting mailbox type..."));
	    emblem_stock_id = GTK_STOCK_EXECUTE;
	  }
      }

    if (emblem_stock_id)
      emblem = gtk_widget_render_icon(GTK_WIDGET(self),
				      emblem_stock_id,
				      GTK_ICON_SIZE_MENU,
				      NULL);

    markup = g_markup_printf_escaped("<span weight=\"bold\">%s</span>\n%s",
				     mailbox->runtime_name,
				     status);
    text = g_strdup_printf("%s\n%s", mailbox->runtime_name, status);
    g_free(status);

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    gtk_list_store_set(GTK_LIST_STORE(model), iter,
		       COLUMN_MAILBOX, mailbox,
		       COLUMN_ICON, mailbox->stock_id,
		       COLUMN_EMBLEM, emblem,
		       COLUMN_PIXBUF, NULL,
		       COLUMN_LABEL, markup,
		       COLUMN_RAW_LABEL, text,
		       -1);

    if (emblem)
      g_object_unref(emblem);

    g_free(markup);
    g_free(text);
  }

  private gboolean
    find_mailbox (self,
		  MN:Mailbox *mailbox (check null type),
		  GtkTreeIter *iter (check null))
  {
    GtkTreeModel *model;
    GtkTreeIter our_iter;
    gboolean valid;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));

    MN_TREE_MODEL_FOREACH(valid, &our_iter, model)
      {
	MNMailbox *this_mailbox;
	gboolean is;

	gtk_tree_model_get(model, &our_iter, COLUMN_MAILBOX, &this_mailbox, -1);
	is = mailbox == this_mailbox;
	g_object_unref(this_mailbox);

	if (is)
	  {
	    *iter = our_iter;
	    return TRUE;
	  }
      }

    return FALSE;
  }

  private void
    update_sensitivity (self)
  {
    GtkTreeSelection *selection;
    SensitivityInfo info = { FALSE, FALSE };
    GtkTreeModel *model;
    GtkTreeIter iter;
    gboolean has_mailboxes;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_selected_foreach(selection, self_update_sensitivity_cb, &info);

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    has_mailboxes = gtk_tree_model_get_iter_first(model, &iter);

    gtk_widget_set_sensitive(selfp->properties_item, info.has_selection);
    gtk_widget_set_sensitive(selfp->update_item, info.has_manually_checkable);
    gtk_widget_set_sensitive(selfp->remove_item, info.has_selection);
    gtk_widget_set_sensitive(selfp->cut_item, info.has_selection);
    gtk_widget_set_sensitive(selfp->copy_item, info.has_selection);
    gtk_widget_set_sensitive(selfp->select_all_item, has_mailboxes);
  }

  private void
    update_sensitivity_cb (GtkTreeModel *model,
			   GtkTreePath *path,
			   GtkTreeIter *iter,
			   gpointer data)
  {
    SensitivityInfo *info = data;

    info->has_selection = TRUE;
    if (! info->has_manually_checkable)
      {
	MNMailbox *mailbox;

	gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
	if (mn_mailbox_get_manually_checkable(mailbox))
	  info->has_manually_checkable = TRUE;
	g_object_unref(mailbox);
      }
  }

  private void
    remove_mailbox (self)
  {
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_selected_foreach(selection, self_remove_mailbox_cb, NULL);
  }

  private void
    remove_mailbox_cb (GtkTreeModel *model,
		       GtkTreePath *path,
		       GtkTreeIter *iter,
		       gpointer data)
  {
    MNMailbox *mailbox;

    gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    mn_mailboxes_queue_remove(mn_shell->mailboxes, mailbox);
    g_object_unref(mailbox);
  }

  private void
    copy_mailbox (self)
  {
    GSList *configurations = NULL;
    GtkTreeSelection *selection;
    const GtkTargetEntry targets[] = {
      { clipboard_info[TARGET_MAILBOXES].name, 0, TARGET_MAILBOXES }
    };
    gboolean status;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_selected_foreach(selection, self_copy_mailbox_cb, &configurations);

    status = gtk_clipboard_set_with_data(global_clipboard,
					 targets,
					 G_N_ELEMENTS(targets),
					 self_get_clipboard_cb,
					 self_clear_clipboard_cb,
					 configurations);
    g_assert(status == TRUE);
  }

  private void
    copy_mailbox_cb (GtkTreeModel *model,
		     GtkTreePath *path,
		     GtkTreeIter *iter,
		     gpointer data)
  {
    GSList **configurations = data;
    MNMailbox *mailbox;

    /*
     * Note that the MNMailboxConfiguration object returned by
     * mn_mailbox_get_configuration() does not include the keyring
     * password. This is not a problem unless the user copies a
     * mailbox, removes the password from the keyring and then pastes
     * the mailbox: the pasted mailbox will have no password.
     *
     * Including the password in MNMailboxConfiguration would require
     * to cope with the fact that gnome-keyring requests can block the
     * UI. It's really not worth the effort.
     */

    gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    *configurations = g_slist_append(*configurations, mn_mailbox_get_configuration(mailbox));
    g_object_unref(mailbox);
  }

  private void
    get_clipboard_cb (GtkClipboard *clipboard,
		      GtkSelectionData *selection_data,
		      unsigned int info,
		      gpointer user_data_or_owner)
  {
    GSList *configurations = user_data_or_owner;

    g_assert(info == TARGET_MAILBOXES);

    gtk_selection_data_set(selection_data,
			   clipboard_info[info].atom,
			   8,
			   (unsigned char *) &configurations,
			   sizeof(configurations));
  }

  private void
    clear_clipboard_cb (GtkClipboard *clipboard, gpointer user_data_or_owner)
  {
    GSList *configurations = user_data_or_owner;

    mn_g_slist_free_deep_custom(configurations, (GFunc) mn_mailbox_configuration_free, NULL);
  }

  private void
    select_all (self)
  {
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    gtk_tree_selection_select_all(selection);
  }

  private void
    properties_add_response_h (GtkDialog *dialog,
			       int response,
			       gpointer user_data)
  {
    switch (response)
      {
      case GTK_RESPONSE_CANCEL:
	gtk_widget_destroy(GTK_WIDGET(dialog));
	break;

      case GTK_RESPONSE_ACCEPT:
	{
	  MNMailbox *mailbox;

	  mailbox = mn_mailbox_properties_dialog_get_current_mailbox(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
	  g_assert(MN_IS_MAILBOX(mailbox));

	  mn_mailbox_seal(mailbox);
	  mn_mailboxes_add(mn_shell->mailboxes, mailbox);
	  g_object_unref(mailbox);

	  gtk_widget_destroy(GTK_WIDGET(dialog));
	}
	break;
      }
  }

  private void
    properties_edit_response_h (GtkDialog *dialog,
				int response,
				gpointer user_data)
  {
    switch (response)
      {
      case GTK_RESPONSE_APPLY:
	mn_mailbox_properties_dialog_apply(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
	break;

      case GTK_RESPONSE_CANCEL:
	mn_mailbox_properties_dialog_cancel(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
	gtk_widget_destroy(GTK_WIDGET(dialog));
	break;

      case GTK_RESPONSE_OK:
	mn_mailbox_properties_dialog_apply(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
	gtk_widget_destroy(GTK_WIDGET(dialog));
	break;
      }
  }

  private void
    popup_menu (self, unsigned int button, guint32 activate_time)
  {
    Self **self_box;

    gtk_widget_set_sensitive(selfp->paste_item, FALSE);

    /* do not crash if self is finalized before the request completes */
    self_box = g_new(Self *, 1);
    *self_box = self;
    mn_add_weak_pointer(self_box);

    gtk_clipboard_request_targets(global_clipboard, self_popup_menu_targets_received_cb, self_box);

    gtk_menu_popup(GTK_MENU(selfp->menu), NULL, NULL, NULL, NULL, button, activate_time);
  }

  private gboolean
    can_paste (GdkAtom *atoms, int n_atoms)
  {
    int i;

    for (i = 0; i < n_atoms; i++)
      if (atoms[i] == clipboard_info[TARGET_MAILBOXES].atom
	  || atoms[i] == clipboard_info[TARGET_GNOME_COPIED_FILES].atom)
	return TRUE;

    return FALSE;
  }

  private void
    popup_menu_targets_received_cb (GtkClipboard *clipboard,
				    GdkAtom *atoms,
				    int n_atoms,
				    gpointer data)
  {
    Self **self_box = data;
    Self *self = *self_box;

    if (self)
      {
	if (atoms)
	  gtk_widget_set_sensitive(selfp->paste_item, self_can_paste(atoms, n_atoms));

	mn_remove_weak_pointer(self_box);
      }

    g_free(self_box);
  }

  private gboolean
    popup_menu_h (GtkWidget *widget, gpointer user_data)
  {
    Self *self = SELF(widget);

    self_popup_menu(self, 0, gtk_get_current_event_time());
    gtk_menu_shell_select_first(GTK_MENU_SHELL(selfp->menu), FALSE);

    return TRUE;		/* a menu was activated */
  }

  private gboolean
    button_press_event_h (GtkWidget *widget,
			  GdkEventButton *event,
			  gpointer user_data)
  {
    if (event->button == 3)
      self_popup_menu(SELF(widget), event->button, event->time);

    return FALSE;			/* propagate event */
  }

  private void
    row_activated_h (GtkTreeView *treeview,
		     GtkTreePath *path,
		     GtkTreeViewColumn *column,
		     gpointer user_data)
  {
    self_activate_properties(SELF(treeview));
  }

  public GtkWidget *
    new (void)
  {
    g_return_val_if_fail(mn_shell != NULL, NULL);
    return GTK_WIDGET(GET_NEW);
  }
}
