mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

src/mn-mailbox-view.gob (25636B) - raw

      1 /*
      2  * Mail Notification
      3  * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License along
     16  * with this program; if not, write to the Free Software Foundation, Inc.,
     17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     18  */
     19 
     20 %headertop{
     21 #include <gtk/gtk.h>
     22 %}
     23 
     24 %{
     25 #include <string.h>
     26 #include <glib/gi18n.h>
     27 #include <gdk/gdkkeysyms.h>
     28 #include "mn-util.h"
     29 #include "mn-shell.h"
     30 #include "mn-stock.h"
     31 #include "mn-mailbox-properties-dialog.h"
     32 #include "mn-conf.h"
     33 #include "mn-mailbox.h"
     34 #include "nautilus-cell-renderer-pixbuf-emblem.h"
     35 #include "mn-test-mailbox.h"
     36 
     37 enum
     38 {
     39   COLUMN_MAILBOX,
     40   COLUMN_ICON,
     41   COLUMN_EMBLEM,
     42   COLUMN_PIXBUF,
     43   COLUMN_LABEL,
     44   COLUMN_RAW_LABEL,
     45   N_COLUMNS
     46 };
     47 
     48 enum
     49 {
     50   TARGET_MAILBOXES,
     51   TARGET_GNOME_COPIED_FILES,
     52   N_TARGETS
     53 };
     54 
     55 typedef struct
     56 {
     57   gboolean has_manually_checkable;
     58   gboolean has_selection;
     59 } SensitivityInfo;
     60 
     61 static struct
     62 {
     63   char		*name;
     64   GdkAtom	atom;
     65 } clipboard_info[N_TARGETS] = {
     66   { "x-special/mail-notification-mailboxes" },
     67   { "x-special/gnome-copied-files" }
     68 };
     69 
     70 static GtkClipboard *global_clipboard;
     71 %}
     72 
     73 class MN:Mailbox:View from Gtk:Tree:View
     74 {
     75   private GtkWidget *menu = {gtk_menu_new()} destroywith gtk_widget_destroy;
     76   private GtkWidget *properties_item;
     77   private GtkWidget *update_item;
     78   private GtkWidget *remove_item;
     79   private GtkWidget *cut_item;
     80   private GtkWidget *copy_item;
     81   private GtkWidget *paste_item;
     82   private GtkWidget *select_all_item;
     83 
     84   signal (ACTION) NONE (NONE)
     85     void activate_update (self)
     86   {
     87     GtkTreeSelection *selection;
     88 
     89     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
     90     gtk_tree_selection_selected_foreach(selection, self_activate_update_cb, self);
     91   }
     92 
     93   private void
     94     activate_update_cb (GtkTreeModel *model,
     95 			GtkTreePath *path,
     96 			GtkTreeIter *iter,
     97 			gpointer data)
     98   {
     99     MNMailbox *mailbox;
    100 
    101     gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    102     if (mn_mailbox_get_manually_checkable(mailbox))
    103       mn_mailbox_check(mailbox);
    104     g_object_unref(mailbox);
    105   }
    106 
    107   signal (ACTION) NONE (NONE)
    108     void activate_properties (self)
    109   {
    110     GtkTreeSelection *selection;
    111 
    112     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    113     gtk_tree_selection_selected_foreach(selection, self_activate_properties_cb, self);
    114   }
    115 
    116   private void
    117     activate_properties_cb (GtkTreeModel *model,
    118 			    GtkTreePath *path,
    119 			    GtkTreeIter *iter,
    120 			    gpointer data)
    121   {
    122     Self *self = data;
    123     MNMailbox *mailbox;
    124     GtkWidget *dialog;
    125 
    126     gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    127 
    128     dialog = (GtkWidget *) mn_shell_get_mailbox_properties_dialog(mn_shell, mailbox);
    129     if (dialog)
    130       mn_window_present_from_event(GTK_WINDOW(dialog));
    131     else
    132       {
    133 	dialog = mn_mailbox_properties_dialog_new(mn_widget_get_parent_window(GTK_WIDGET(self)), mailbox);
    134 
    135 	g_signal_connect(dialog,
    136 			 "response",
    137 			 G_CALLBACK(self_properties_edit_response_h),
    138 			 NULL);
    139 
    140 	mn_shell_add_mailbox_properties_dialog(mn_shell, MN_MAILBOX_PROPERTIES_DIALOG(dialog));
    141 	gtk_widget_show(dialog);
    142       }
    143 
    144     g_object_unref(mailbox);
    145   }
    146 
    147   signal (ACTION) NONE (NONE)
    148     void activate_add (self)
    149   {
    150     GtkWidget *dialog;
    151 
    152     dialog = mn_mailbox_properties_dialog_new(mn_widget_get_parent_window(GTK_WIDGET(self)), NULL);
    153 
    154     g_signal_connect(dialog,
    155 		     "response",
    156 		     G_CALLBACK(self_properties_add_response_h),
    157 		     NULL);
    158 
    159     gtk_widget_show(dialog);
    160   }
    161 
    162   signal (ACTION) NONE (NONE)
    163     void activate_remove (self)
    164   {
    165     self_remove_mailbox(self);
    166   }
    167 
    168   signal (ACTION) NONE (NONE)
    169     void activate_cut (self)
    170   {
    171     self_copy_mailbox(self);
    172     self_remove_mailbox(self);
    173   }
    174 
    175   signal (ACTION) NONE (NONE)
    176     void activate_copy (self)
    177   {
    178     self_copy_mailbox(self);
    179   }
    180 
    181   signal (ACTION) NONE (NONE)
    182     void activate_paste (self)
    183   {
    184     GtkSelectionData *data;
    185 
    186     data = gtk_clipboard_wait_for_contents(global_clipboard, clipboard_info[TARGET_MAILBOXES].atom);
    187     if (data)
    188       {
    189 	GSList *configurations;
    190 	GSList *l;
    191 
    192 	memcpy(&configurations, data->data, data->length);
    193 
    194 	MN_LIST_FOREACH(l, configurations)
    195 	  {
    196 	    MNMailbox *mailbox;
    197 
    198 	    mailbox = mn_mailbox_new_from_configuration(l->data);
    199 
    200 	    mn_mailbox_seal(mailbox);
    201 	    mn_mailboxes_queue_add(mn_shell->mailboxes, mailbox);
    202 
    203 	    g_object_unref(mailbox);
    204 	  }
    205 
    206 	gtk_selection_data_free(data);
    207 	return;
    208       }
    209 
    210     data = gtk_clipboard_wait_for_contents(global_clipboard, clipboard_info[TARGET_GNOME_COPIED_FILES].atom);
    211     if (data)
    212       {
    213 	if (data->format == 8 && data->length > 0)
    214 	  {
    215 	    char *gnome_copied_files;
    216 	    gboolean status;
    217 	    MNGnomeCopiedFilesType type;
    218 	    GSList *uri_list;
    219 
    220 	    gnome_copied_files = g_strndup(data->data, data->length);
    221 	    status = mn_parse_gnome_copied_files(gnome_copied_files, &type, &uri_list);
    222 	    g_free(gnome_copied_files);
    223 
    224 	    if (status)
    225 	      {
    226 		if (type == MN_GNOME_COPIED_FILES_COPY)
    227 		  {
    228 		    GSList *l;
    229 		    GSList *invalid_uri_list = NULL;
    230 
    231 		    MN_LIST_FOREACH(l, uri_list)
    232 		      {
    233 			const char *uri = l->data;
    234 			MNMailbox *mailbox;
    235 
    236 			mailbox = mn_mailbox_new_from_uri(uri);
    237 			if (mailbox)
    238 			  {
    239 			    mn_mailbox_seal(mailbox);
    240 			    mn_mailboxes_queue_add(mn_shell->mailboxes, mailbox);
    241 			    g_object_unref(mailbox);
    242 			  }
    243 			else
    244 			  invalid_uri_list = g_slist_append(invalid_uri_list, (gpointer) uri);
    245 		      }
    246 
    247 		    if (invalid_uri_list)
    248 		      {
    249 			mn_show_invalid_uri_list_dialog(mn_widget_get_parent_window(GTK_WIDGET(self)), _("A paste error has occurred"), invalid_uri_list);
    250 			g_slist_free(invalid_uri_list);
    251 		      }
    252 		  }
    253 
    254 		mn_g_slist_free_deep(uri_list);
    255 	      }
    256 	  }
    257 
    258 	gtk_selection_data_free(data);
    259       }
    260   }
    261 
    262   class_init (class)
    263   {
    264     GtkBindingSet *binding_set;
    265     int i;
    266 
    267     global_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
    268 
    269     for (i = 0; i < N_TARGETS; i++)
    270       clipboard_info[i].atom = gdk_atom_intern(clipboard_info[i].name, FALSE);
    271 
    272     binding_set = gtk_binding_set_by_class(class);
    273 
    274     /* Delete removes a row */
    275     gtk_binding_entry_add_signal(binding_set, GDK_Delete, 0, "activate-remove", 0);
    276     gtk_binding_entry_add_signal(binding_set, GDK_KP_Delete, 0, "activate-remove", 0);
    277 
    278     /* HIG 2.0 cut/copy/paste shortcuts */
    279     gtk_binding_entry_add_signal(binding_set, GDK_x, GDK_CONTROL_MASK, "activate-cut", 0);
    280     gtk_binding_entry_add_signal(binding_set, GDK_c, GDK_CONTROL_MASK, "activate-copy", 0);
    281     gtk_binding_entry_add_signal(binding_set, GDK_v, GDK_CONTROL_MASK, "activate-paste", 0);
    282 
    283     /* cut/copy/paste shortcuts taken from gtkentry.c */
    284     gtk_binding_entry_add_signal(binding_set, GDK_Delete, GDK_SHIFT_MASK, "activate-cut", 0);
    285     gtk_binding_entry_add_signal(binding_set, GDK_Insert, GDK_CONTROL_MASK, "activate-copy", 0);
    286     gtk_binding_entry_add_signal(binding_set, GDK_Insert, GDK_SHIFT_MASK, "activate-paste", 0);
    287 
    288     /* HIG 2.0 properties */
    289     gtk_binding_entry_add_signal(binding_set, GDK_Return, GDK_MOD1_MASK, "activate-properties", 0);
    290     gtk_binding_entry_add_signal(binding_set, GDK_ISO_Enter, GDK_MOD1_MASK, "activate-properties", 0);
    291     gtk_binding_entry_add_signal(binding_set, GDK_KP_Enter, GDK_MOD1_MASK, "activate-properties", 0);
    292   }
    293 
    294   init (self)
    295   {
    296     GtkMenuShell *shell;
    297     GtkWidget *add_item;
    298     GtkListStore *store;
    299     GtkTreeViewColumn *column;
    300     GtkCellRenderer *renderer;
    301     GtkTreeSelection *selection;
    302     GList *l;
    303 
    304     /* popup menu */
    305 
    306     shell = GTK_MENU_SHELL(selfp->menu);
    307 
    308     /* ordered according to chapter 4 of the HIG */
    309 
    310     selfp->properties_item = mn_menu_shell_append(shell, GTK_STOCK_PROPERTIES, NULL);
    311     /* translators: header capitalization */
    312     selfp->update_item = mn_menu_shell_append(shell, GTK_STOCK_REFRESH, _("_Update"));
    313     mn_menu_shell_append(shell, NULL, NULL);
    314     add_item = mn_menu_shell_append(shell, GTK_STOCK_ADD, NULL);
    315     selfp->remove_item = mn_menu_shell_append(shell, GTK_STOCK_REMOVE, NULL);
    316     mn_menu_shell_append(shell, NULL, NULL);
    317     selfp->cut_item = mn_menu_shell_append(shell, GTK_STOCK_CUT, NULL);
    318     selfp->copy_item = mn_menu_shell_append(shell, GTK_STOCK_COPY, NULL);
    319     selfp->paste_item = mn_menu_shell_append(shell, GTK_STOCK_PASTE, NULL);
    320     mn_menu_shell_append(shell, NULL, NULL);
    321     selfp->select_all_item = mn_menu_shell_append(shell, GTK_STOCK_SELECT_ALL, NULL);
    322 
    323     g_signal_connect_swapped(selfp->properties_item, "activate", G_CALLBACK(self_activate_properties), self);
    324     g_signal_connect_swapped(selfp->update_item, "activate", G_CALLBACK(self_activate_update), self);
    325     g_signal_connect_swapped(add_item, "activate", G_CALLBACK(self_activate_add), self);
    326     g_signal_connect_swapped(selfp->remove_item, "activate", G_CALLBACK(self_activate_remove), self);
    327     g_signal_connect_swapped(selfp->cut_item, "activate", G_CALLBACK(self_activate_cut), self);
    328     g_signal_connect_swapped(selfp->copy_item, "activate", G_CALLBACK(self_activate_copy), self);
    329     g_signal_connect_swapped(selfp->paste_item, "activate", G_CALLBACK(self_activate_paste), self);
    330     g_signal_connect_swapped(selfp->select_all_item, "activate", G_CALLBACK(self_select_all), self);
    331 
    332     /* store */
    333 
    334     store = gtk_list_store_new(N_COLUMNS,
    335 			       MN_TYPE_MAILBOX,
    336 			       G_TYPE_STRING,
    337 			       GDK_TYPE_PIXBUF,
    338 			       GDK_TYPE_PIXBUF,
    339 			       G_TYPE_STRING,
    340 			       G_TYPE_STRING);
    341 
    342     gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(store),
    343 					    self_sort_cb,
    344 					    NULL,
    345 					    NULL);
    346     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
    347 					 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
    348 					 GTK_SORT_ASCENDING);
    349 
    350     gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(store));
    351 
    352     column = gtk_tree_view_column_new();
    353 
    354     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    355 
    356     renderer = nautilus_cell_renderer_pixbuf_emblem_new();
    357     g_object_set(renderer, "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
    358     gtk_tree_view_column_pack_start(column, renderer, FALSE);
    359     gtk_tree_view_column_set_attributes(column, renderer,
    360 					"stock-id", COLUMN_ICON,
    361 					"pixbuf-emblem", COLUMN_EMBLEM,
    362 					"pixbuf", COLUMN_PIXBUF,
    363 					NULL);
    364 
    365     renderer = gtk_cell_renderer_text_new();
    366     gtk_tree_view_column_pack_start(column, renderer, TRUE);
    367     gtk_tree_view_column_add_attribute(column, renderer, "markup", COLUMN_LABEL);
    368 
    369     gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
    370 
    371     /* misc */
    372 
    373     gtk_tree_view_set_search_column(GTK_TREE_VIEW(self), COLUMN_RAW_LABEL);
    374     gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(self), self_search_equal_cb, NULL, NULL);
    375 
    376     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);
    377 
    378     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    379     gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
    380 
    381     MN_LIST_FOREACH(l, mn_shell->mailboxes->list)
    382       {
    383 	MNMailbox *mailbox = l->data;
    384 
    385 	if (! MN_IS_TEST_MAILBOX(mailbox))
    386 	  self_append(self, l->data);
    387       }
    388 
    389     self_update_sensitivity(self);
    390 
    391     g_object_connect(store,
    392 		     /* for the sensitivity of "Select All" */
    393 		     "swapped-signal::row-inserted", self_update_sensitivity, self,
    394 		     "swapped-signal::row-deleted", self_update_sensitivity, self,
    395 		     NULL);
    396 
    397     mn_g_object_connect(self,
    398 			mn_shell->mailboxes,
    399 			"signal::mailbox-added", self_mailbox_added_h, self,
    400 			"signal::mailbox-notify::stock-id", self_mailbox_notify_h, self,
    401 			"swapped-signal::mailbox-notify::manually-checkable", self_update_sensitivity, self,
    402 			"signal::mailbox-notify::format", self_mailbox_notify_h, self,
    403 			"signal::mailbox-notify::error", self_mailbox_notify_h, self,
    404 			"signal::mailbox-removed", self_mailbox_removed_h, self,
    405 			NULL);
    406 
    407     g_signal_connect_swapped(selection, "changed", G_CALLBACK(self_update_sensitivity), self);
    408 
    409     g_object_connect(self,
    410 		     "signal::popup-menu", self_popup_menu_h, NULL,
    411 		     "signal::button-press-event", self_button_press_event_h, NULL,
    412 		     "signal::row-activated", self_row_activated_h, NULL,
    413 		     NULL);
    414 
    415     g_object_unref(store);
    416   }
    417 
    418   private int
    419     sort_cb (GtkTreeModel *model,
    420 	     GtkTreeIter *a,
    421 	     GtkTreeIter *b,
    422 	     gpointer user_data)
    423   {
    424     MNMailbox *mailbox_a;
    425     MNMailbox *mailbox_b;
    426     int cmp;
    427 
    428     gtk_tree_model_get(model, a, COLUMN_MAILBOX, &mailbox_a, -1);
    429     gtk_tree_model_get(model, b, COLUMN_MAILBOX, &mailbox_b, -1);
    430 
    431     cmp = mn_mailboxes_compare_by_name_func(mailbox_a, mailbox_b);
    432 
    433     g_object_unref(mailbox_a);
    434     g_object_unref(mailbox_b);
    435 
    436     return cmp;
    437   }
    438 
    439   private gboolean
    440     search_equal_cb (GtkTreeModel *model,
    441 		     int column,
    442 		     const char *key,
    443 		     GtkTreeIter *iter,
    444 		     gpointer search_data)
    445   {
    446     gboolean status = TRUE;
    447     GValue value = { 0, };
    448     GValue transformed = { 0, };
    449     const char *str;
    450     char *normalized_string;
    451     char *normalized_key;
    452 
    453     /*
    454      * The stock gtk_tree_view_search_equal_func() only searches the
    455      * start of the string, using strncmp(). We use strstr(), to
    456      * search anywhere inside the string.
    457      */
    458 
    459     gtk_tree_model_get_value(model, iter, column, &value);
    460 
    461     g_value_init(&transformed, G_TYPE_STRING);
    462 
    463     if (! g_value_transform(&value, &transformed))
    464       {
    465 	g_value_unset(&value);
    466 	return TRUE;
    467       }
    468 
    469     g_value_unset(&value);
    470 
    471     str = g_value_get_string(&transformed);
    472     if (! str)
    473       {
    474 	g_value_unset(&transformed);
    475 	return TRUE;
    476       }
    477 
    478     normalized_string = g_utf8_normalize(str, -1, G_NORMALIZE_ALL);
    479     normalized_key = g_utf8_normalize(key, -1, G_NORMALIZE_ALL);
    480 
    481     if (normalized_string && normalized_key)
    482       {
    483 	char *case_normalized_string;
    484 	char *case_normalized_key;
    485 
    486 	case_normalized_string = g_utf8_casefold(normalized_string, -1);
    487 	case_normalized_key = g_utf8_casefold(normalized_key, -1);
    488 
    489 	if (strstr(case_normalized_string, case_normalized_key))
    490 	  status = FALSE;
    491 
    492 	g_free(case_normalized_string);
    493 	g_free(case_normalized_key);
    494       }
    495 
    496     g_value_unset (&transformed);
    497     g_free(normalized_key);
    498     g_free(normalized_string);
    499 
    500     return status;
    501   }
    502 
    503   private void
    504     append (self, MN:Mailbox *mailbox (check null type))
    505   {
    506     GtkTreeModel *model;
    507     GtkTreeIter iter;
    508 
    509     model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    510     gtk_list_store_append(GTK_LIST_STORE(model), &iter);
    511     self_update_iter(self, &iter, mailbox);
    512   }
    513 
    514   private void
    515     mailbox_added_h (MNMailboxes *mailboxes,
    516 		     MNMailbox *mailbox,
    517 		     gpointer user_data)
    518   {
    519     Self *self = user_data;
    520 
    521     if (! MN_IS_TEST_MAILBOX(mailbox))
    522       self_append(self, mailbox);
    523   }
    524 
    525   private void
    526     mailbox_notify_h (MNMailboxes *mailboxes,
    527 		      MNMailbox *mailbox,
    528 		      GParamSpec *pspec,
    529 		      gpointer user_data)
    530   {
    531     Self *self = user_data;
    532     GtkTreeIter iter;
    533 
    534     if (self_find_mailbox(self, mailbox, &iter))
    535       self_update_iter(self, &iter, mailbox);
    536   }
    537 
    538   private void
    539     mailbox_removed_h (MNMailboxes *mailboxes,
    540 		       MNMailbox *mailbox,
    541 		       gpointer user_data)
    542   {
    543     Self *self = user_data;
    544     GtkTreeIter iter;
    545 
    546     if (self_find_mailbox(self, mailbox, &iter))
    547       {
    548 	GtkTreeModel *model;
    549 
    550 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    551 	gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    552       }
    553   }
    554 
    555   private void
    556     update_iter (self,
    557 		 GtkTreeIter *iter (check null),
    558 		 MN:Mailbox *mailbox (check null type))
    559   {
    560     GtkTreeModel *model;
    561     char *status;
    562     const char *emblem_stock_id = NULL;
    563     GdkPixbuf *emblem = NULL;
    564     char *markup;
    565     char *text;
    566 
    567     if (mailbox->format)
    568       {
    569 	if (mailbox->error)
    570 	  {
    571 	    status = g_strdup_printf(_("unhandled %s mailbox (%s)"), mailbox->format, mailbox->error);
    572 	    emblem_stock_id = MN_STOCK_ERROR;
    573 	  }
    574 	else
    575 	  status = g_strdup_printf(_("%s mailbox"), mailbox->format);
    576       }
    577     else
    578       {
    579 	if (mailbox->error)
    580 	  {
    581 	    status = g_strdup(mailbox->error);
    582 	    emblem_stock_id = MN_STOCK_ERROR;
    583 	  }
    584 	else
    585 	  {
    586 	    status = g_strdup(_("detecting mailbox type..."));
    587 	    emblem_stock_id = GTK_STOCK_EXECUTE;
    588 	  }
    589       }
    590 
    591     if (emblem_stock_id)
    592       emblem = gtk_widget_render_icon(GTK_WIDGET(self),
    593 				      emblem_stock_id,
    594 				      GTK_ICON_SIZE_MENU,
    595 				      NULL);
    596 
    597     markup = g_markup_printf_escaped("<span weight=\"bold\">%s</span>\n%s",
    598 				     mailbox->runtime_name,
    599 				     status);
    600     text = g_strdup_printf("%s\n%s", mailbox->runtime_name, status);
    601     g_free(status);
    602 
    603     model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    604     gtk_list_store_set(GTK_LIST_STORE(model), iter,
    605 		       COLUMN_MAILBOX, mailbox,
    606 		       COLUMN_ICON, mailbox->stock_id,
    607 		       COLUMN_EMBLEM, emblem,
    608 		       COLUMN_PIXBUF, NULL,
    609 		       COLUMN_LABEL, markup,
    610 		       COLUMN_RAW_LABEL, text,
    611 		       -1);
    612 
    613     if (emblem)
    614       g_object_unref(emblem);
    615 
    616     g_free(markup);
    617     g_free(text);
    618   }
    619 
    620   private gboolean
    621     find_mailbox (self,
    622 		  MN:Mailbox *mailbox (check null type),
    623 		  GtkTreeIter *iter (check null))
    624   {
    625     GtkTreeModel *model;
    626     GtkTreeIter our_iter;
    627     gboolean valid;
    628 
    629     model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    630 
    631     MN_TREE_MODEL_FOREACH(valid, &our_iter, model)
    632       {
    633 	MNMailbox *this_mailbox;
    634 	gboolean is;
    635 
    636 	gtk_tree_model_get(model, &our_iter, COLUMN_MAILBOX, &this_mailbox, -1);
    637 	is = mailbox == this_mailbox;
    638 	g_object_unref(this_mailbox);
    639 
    640 	if (is)
    641 	  {
    642 	    *iter = our_iter;
    643 	    return TRUE;
    644 	  }
    645       }
    646 
    647     return FALSE;
    648   }
    649 
    650   private void
    651     update_sensitivity (self)
    652   {
    653     GtkTreeSelection *selection;
    654     SensitivityInfo info = { FALSE, FALSE };
    655     GtkTreeModel *model;
    656     GtkTreeIter iter;
    657     gboolean has_mailboxes;
    658 
    659     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    660     gtk_tree_selection_selected_foreach(selection, self_update_sensitivity_cb, &info);
    661 
    662     model = gtk_tree_view_get_model(GTK_TREE_VIEW(self));
    663     has_mailboxes = gtk_tree_model_get_iter_first(model, &iter);
    664 
    665     gtk_widget_set_sensitive(selfp->properties_item, info.has_selection);
    666     gtk_widget_set_sensitive(selfp->update_item, info.has_manually_checkable);
    667     gtk_widget_set_sensitive(selfp->remove_item, info.has_selection);
    668     gtk_widget_set_sensitive(selfp->cut_item, info.has_selection);
    669     gtk_widget_set_sensitive(selfp->copy_item, info.has_selection);
    670     gtk_widget_set_sensitive(selfp->select_all_item, has_mailboxes);
    671   }
    672 
    673   private void
    674     update_sensitivity_cb (GtkTreeModel *model,
    675 			   GtkTreePath *path,
    676 			   GtkTreeIter *iter,
    677 			   gpointer data)
    678   {
    679     SensitivityInfo *info = data;
    680 
    681     info->has_selection = TRUE;
    682     if (! info->has_manually_checkable)
    683       {
    684 	MNMailbox *mailbox;
    685 
    686 	gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    687 	if (mn_mailbox_get_manually_checkable(mailbox))
    688 	  info->has_manually_checkable = TRUE;
    689 	g_object_unref(mailbox);
    690       }
    691   }
    692 
    693   private void
    694     remove_mailbox (self)
    695   {
    696     GtkTreeSelection *selection;
    697 
    698     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    699     gtk_tree_selection_selected_foreach(selection, self_remove_mailbox_cb, NULL);
    700   }
    701 
    702   private void
    703     remove_mailbox_cb (GtkTreeModel *model,
    704 		       GtkTreePath *path,
    705 		       GtkTreeIter *iter,
    706 		       gpointer data)
    707   {
    708     MNMailbox *mailbox;
    709 
    710     gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    711     mn_mailboxes_queue_remove(mn_shell->mailboxes, mailbox);
    712     g_object_unref(mailbox);
    713   }
    714 
    715   private void
    716     copy_mailbox (self)
    717   {
    718     GSList *configurations = NULL;
    719     GtkTreeSelection *selection;
    720     const GtkTargetEntry targets[] = {
    721       { clipboard_info[TARGET_MAILBOXES].name, 0, TARGET_MAILBOXES }
    722     };
    723     gboolean status;
    724 
    725     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    726     gtk_tree_selection_selected_foreach(selection, self_copy_mailbox_cb, &configurations);
    727 
    728     status = gtk_clipboard_set_with_data(global_clipboard,
    729 					 targets,
    730 					 G_N_ELEMENTS(targets),
    731 					 self_get_clipboard_cb,
    732 					 self_clear_clipboard_cb,
    733 					 configurations);
    734     g_assert(status == TRUE);
    735   }
    736 
    737   private void
    738     copy_mailbox_cb (GtkTreeModel *model,
    739 		     GtkTreePath *path,
    740 		     GtkTreeIter *iter,
    741 		     gpointer data)
    742   {
    743     GSList **configurations = data;
    744     MNMailbox *mailbox;
    745 
    746     /*
    747      * Note that the MNMailboxConfiguration object returned by
    748      * mn_mailbox_get_configuration() does not include the keyring
    749      * password. This is not a problem unless the user copies a
    750      * mailbox, removes the password from the keyring and then pastes
    751      * the mailbox: the pasted mailbox will have no password.
    752      *
    753      * Including the password in MNMailboxConfiguration would require
    754      * to cope with the fact that gnome-keyring requests can block the
    755      * UI. It's really not worth the effort.
    756      */
    757 
    758     gtk_tree_model_get(model, iter, COLUMN_MAILBOX, &mailbox, -1);
    759     *configurations = g_slist_append(*configurations, mn_mailbox_get_configuration(mailbox));
    760     g_object_unref(mailbox);
    761   }
    762 
    763   private void
    764     get_clipboard_cb (GtkClipboard *clipboard,
    765 		      GtkSelectionData *selection_data,
    766 		      unsigned int info,
    767 		      gpointer user_data_or_owner)
    768   {
    769     GSList *configurations = user_data_or_owner;
    770 
    771     g_assert(info == TARGET_MAILBOXES);
    772 
    773     gtk_selection_data_set(selection_data,
    774 			   clipboard_info[info].atom,
    775 			   8,
    776 			   (unsigned char *) &configurations,
    777 			   sizeof(configurations));
    778   }
    779 
    780   private void
    781     clear_clipboard_cb (GtkClipboard *clipboard, gpointer user_data_or_owner)
    782   {
    783     GSList *configurations = user_data_or_owner;
    784 
    785     mn_g_slist_free_deep_custom(configurations, (GFunc) mn_mailbox_configuration_free, NULL);
    786   }
    787 
    788   private void
    789     select_all (self)
    790   {
    791     GtkTreeSelection *selection;
    792 
    793     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
    794     gtk_tree_selection_select_all(selection);
    795   }
    796 
    797   private void
    798     properties_add_response_h (GtkDialog *dialog,
    799 			       int response,
    800 			       gpointer user_data)
    801   {
    802     switch (response)
    803       {
    804       case GTK_RESPONSE_CANCEL:
    805 	gtk_widget_destroy(GTK_WIDGET(dialog));
    806 	break;
    807 
    808       case GTK_RESPONSE_ACCEPT:
    809 	{
    810 	  MNMailbox *mailbox;
    811 
    812 	  mailbox = mn_mailbox_properties_dialog_get_current_mailbox(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
    813 	  g_assert(MN_IS_MAILBOX(mailbox));
    814 
    815 	  mn_mailbox_seal(mailbox);
    816 	  mn_mailboxes_add(mn_shell->mailboxes, mailbox);
    817 	  g_object_unref(mailbox);
    818 
    819 	  gtk_widget_destroy(GTK_WIDGET(dialog));
    820 	}
    821 	break;
    822       }
    823   }
    824 
    825   private void
    826     properties_edit_response_h (GtkDialog *dialog,
    827 				int response,
    828 				gpointer user_data)
    829   {
    830     switch (response)
    831       {
    832       case GTK_RESPONSE_APPLY:
    833 	mn_mailbox_properties_dialog_apply(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
    834 	break;
    835 
    836       case GTK_RESPONSE_CANCEL:
    837 	mn_mailbox_properties_dialog_cancel(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
    838 	gtk_widget_destroy(GTK_WIDGET(dialog));
    839 	break;
    840 
    841       case GTK_RESPONSE_OK:
    842 	mn_mailbox_properties_dialog_apply(MN_MAILBOX_PROPERTIES_DIALOG(dialog));
    843 	gtk_widget_destroy(GTK_WIDGET(dialog));
    844 	break;
    845       }
    846   }
    847 
    848   private void
    849     popup_menu (self, unsigned int button, guint32 activate_time)
    850   {
    851     Self **self_box;
    852 
    853     gtk_widget_set_sensitive(selfp->paste_item, FALSE);
    854 
    855     /* do not crash if self is finalized before the request completes */
    856     self_box = g_new(Self *, 1);
    857     *self_box = self;
    858     mn_add_weak_pointer(self_box);
    859 
    860     gtk_clipboard_request_targets(global_clipboard, self_popup_menu_targets_received_cb, self_box);
    861 
    862     gtk_menu_popup(GTK_MENU(selfp->menu), NULL, NULL, NULL, NULL, button, activate_time);
    863   }
    864 
    865   private gboolean
    866     can_paste (GdkAtom *atoms, int n_atoms)
    867   {
    868     int i;
    869 
    870     for (i = 0; i < n_atoms; i++)
    871       if (atoms[i] == clipboard_info[TARGET_MAILBOXES].atom
    872 	  || atoms[i] == clipboard_info[TARGET_GNOME_COPIED_FILES].atom)
    873 	return TRUE;
    874 
    875     return FALSE;
    876   }
    877 
    878   private void
    879     popup_menu_targets_received_cb (GtkClipboard *clipboard,
    880 				    GdkAtom *atoms,
    881 				    int n_atoms,
    882 				    gpointer data)
    883   {
    884     Self **self_box = data;
    885     Self *self = *self_box;
    886 
    887     if (self)
    888       {
    889 	if (atoms)
    890 	  gtk_widget_set_sensitive(selfp->paste_item, self_can_paste(atoms, n_atoms));
    891 
    892 	mn_remove_weak_pointer(self_box);
    893       }
    894 
    895     g_free(self_box);
    896   }
    897 
    898   private gboolean
    899     popup_menu_h (GtkWidget *widget, gpointer user_data)
    900   {
    901     Self *self = SELF(widget);
    902 
    903     self_popup_menu(self, 0, gtk_get_current_event_time());
    904     gtk_menu_shell_select_first(GTK_MENU_SHELL(selfp->menu), FALSE);
    905 
    906     return TRUE;		/* a menu was activated */
    907   }
    908 
    909   private gboolean
    910     button_press_event_h (GtkWidget *widget,
    911 			  GdkEventButton *event,
    912 			  gpointer user_data)
    913   {
    914     if (event->button == 3)
    915       self_popup_menu(SELF(widget), event->button, event->time);
    916 
    917     return FALSE;			/* propagate event */
    918   }
    919 
    920   private void
    921     row_activated_h (GtkTreeView *treeview,
    922 		     GtkTreePath *path,
    923 		     GtkTreeViewColumn *column,
    924 		     gpointer user_data)
    925   {
    926     self_activate_properties(SELF(treeview));
    927   }
    928 
    929   public GtkWidget *
    930     new (void)
    931   {
    932     g_return_val_if_fail(mn_shell != NULL, NULL);
    933     return GTK_WIDGET(GET_NEW);
    934   }
    935 }