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-shell.gob (25553B) - 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 #include <dbus/dbus-glib.h>
     23 #include "mn-mailboxes.h"
     24 #include "mn-mailbox-properties-dialog.h"
     25 #include "mn-mail-icon.h"
     26 #include "mn-popups.h"
     27 %}
     28 
     29 %privateheader{
     30 #include "mn-sound-player.h"
     31 %}
     32 
     33 %{
     34 #include <unistd.h>
     35 #include <fcntl.h>
     36 #include <signal.h>
     37 #include <glib/gi18n.h>
     38 #include <libxml/tree.h>
     39 #include "mn-properties-dialog.h"
     40 #include "mn-util.h"
     41 #include "mn-conf.h"
     42 #include "mn-mailboxes.h"
     43 #include "mn-about-dialog.h"
     44 #include "mn-standard-message-view.h"
     45 #include "mn-compact-message-view.h"
     46 #include "mn-message.h"
     47 #include "mn-stock.h"
     48 #include "mn-locked-callback.h"
     49 
     50 MNShell *mn_shell = NULL;
     51 
     52 typedef struct
     53 {
     54   int		num;
     55   const char	*name;
     56 } UnixSignalInfo;
     57 
     58 #define SIGNAL_INFO(name) { name, #name }
     59 
     60 static const UnixSignalInfo unix_quit_signals[] = {
     61   SIGNAL_INFO(SIGHUP),
     62   SIGNAL_INFO(SIGINT),
     63   SIGNAL_INFO(SIGTERM),
     64   SIGNAL_INFO(SIGUSR1),
     65   SIGNAL_INFO(SIGUSR2)
     66 };
     67 %}
     68 
     69 enum MN_SHELL_TOOLTIP_MAIL_SUMMARY
     70 {
     71   STANDARD,
     72   COMPACT,
     73   NONE
     74 } MN:Shell:Tooltip:Mail:Summary;
     75 
     76 class MN:Shell from G:Object
     77 {
     78   public MNMailboxes *mailboxes;
     79 
     80   public MNMailIcon *icon;
     81   public MNPopups *popups;
     82 
     83   public DBusGConnection *session_bus;
     84   property POINTER session_bus (flags = CONSTRUCT_ONLY, link, type = DBusGConnection *);
     85 
     86   public DBusGProxy *session_bus_proxy;
     87   property POINTER session_bus_proxy (flags = CONSTRUCT_ONLY, link, type = DBusGProxy *);
     88 
     89   private int unix_signal_pipe_write_end;
     90 
     91   private MNSoundPlayer *sound_player;
     92 
     93   private gboolean has_new;
     94 
     95   private GtkWidget *properties_dialog;
     96   private GSList *mailbox_properties_dialogs;
     97   private GtkWidget *about_dialog;
     98 
     99   constructor (self)
    100   {
    101     g_assert(mn_shell == NULL);
    102 
    103     mn_shell = self;
    104     g_object_weak_ref(G_OBJECT(self), self_weak_notify_cb, NULL);
    105 
    106     selfp->sound_player = mn_sound_player_new();
    107     mn_add_weak_pointer(&selfp->sound_player);
    108 
    109     /*
    110      * self->mailboxes is assigned in MNMailboxes itself, so that
    111      * mailboxes can access it even during construction of
    112      * MNMailboxes.
    113      */
    114     mn_mailboxes_new();
    115     mn_add_weak_pointer(&self->mailboxes);
    116 
    117     self_init_icon(self);
    118 
    119     mn_g_object_gconf_notifications_add_gdk_locked(self,
    120 						   MN_CONF_BLINK_ON_ERRORS, self_notify_icon_cb, self,
    121 						   MN_CONF_ALWAYS_DISPLAY_ICON, self_notify_icon_cb, self,
    122 						   MN_CONF_DISPLAY_MESSAGE_COUNT, self_notify_icon_cb, self,
    123 						   MN_CONF_GNOME_MAIL_READER_NAMESPACE, self_notify_mail_reader_cb, self,
    124 						   MN_CONF_TOOLTIP_MAIL_SUMMARY, self_notify_tooltip_cb, self,
    125 						   MN_CONF_TOOLTIP_MAIL_SUMMARY_LIMIT, self_notify_tooltip_cb, self,
    126 						   NULL);
    127 
    128     g_object_connect(self->mailboxes,
    129 		     "signal::messages-changed", self_messages_changed_h, self,
    130 		     "signal::mailbox-removed", self_mailbox_removed_h, self,
    131 		     "swapped-signal::notify::manually-checkable", self_update_sensitivity, self,
    132 		     "swapped-signal::list-changed", self_update_tooltip, self,
    133 		     "swapped-signal::list-changed", self_update_icon, self,
    134 		     "swapped-signal::error-changed", self_update_tooltip, self,
    135 		     "swapped-signal::error-changed", self_update_icon, self,
    136 		     NULL);
    137 
    138     self->popups = mn_popups_new();
    139     mn_add_weak_pointer(&self->popups);
    140 
    141     /*
    142      * Exit gracefully (unreferencing our components) when receiving a
    143      * UNIX signal.
    144      */
    145     if (! self_install_unix_quit_signal_handlers(self))
    146       /* unlikely and unimportant, not worth a translation */
    147       g_warning("unable to install UNIX quit signal handlers");
    148   }
    149 
    150   finalize (self)
    151   {
    152     /*
    153      * We explicitly unreference or destroy each component, even
    154      * though MN will exit after the shell is finalized. This is done
    155      * for the sake of respecting encapsulation: we should not know
    156      * whether a particular component has something to do on exit or
    157      * not (for instance, MNSoundPlayer must kill the play process).
    158      *
    159      * Of course, the order in which we get rid of the components
    160      * matters: for instance, when destroyed, MNPropertiesDialog might
    161      * remove the test mailbox and thus requires a valid MNMailboxes
    162      * object. We destroy the components in the inverse order of their
    163      * creation.
    164      *
    165      * Also note that we do not need to nullify the pointers after
    166      * destruction since we use mn_add_weak_pointer() at creation
    167      * time.
    168      */
    169 
    170     g_slist_foreach(selfp->mailbox_properties_dialogs, (GFunc) gtk_widget_destroy, NULL);
    171     /* the list is freed in mailbox_properties_dialog_weak_notify_cb() */
    172 
    173     if (selfp->properties_dialog)
    174       gtk_widget_destroy(selfp->properties_dialog);
    175 
    176     if (selfp->about_dialog)
    177       gtk_widget_destroy(selfp->about_dialog);
    178 
    179     mn_g_object_null_unref(self->popups);
    180 
    181     if (self->icon)
    182       {
    183 	/* do not recreate the icon after we destroy it */
    184 	g_signal_handlers_disconnect_by_func(self->icon, self_icon_destroy_h, self);
    185 	gtk_widget_destroy(GTK_WIDGET(self->icon));
    186       }
    187 
    188     mn_g_object_null_unref(self->mailboxes);
    189 
    190     mn_g_object_null_unref(selfp->sound_player);
    191   }
    192 
    193   private gboolean
    194     install_unix_quit_signal_handlers (self)
    195   {
    196     int sigpipe[2];
    197     int flags;
    198     GIOChannel *read_channel = NULL;
    199     int i;
    200 
    201     if (pipe(sigpipe) < 0)
    202       return FALSE;
    203 
    204     /*
    205      * Enable non-blocking mode for the write end, so that if another
    206      * signal occurs while the main thread is busy processing the
    207      * first signal, the signal handler will not block in write().
    208      */
    209 
    210     flags = fcntl(sigpipe[1], F_GETFL);
    211     if (flags < 0)
    212       goto error;
    213 
    214     if (fcntl(sigpipe[1], F_SETFL, flags | O_NONBLOCK) < 0)
    215       goto error;
    216 
    217     selfp->unix_signal_pipe_write_end = sigpipe[1];
    218 
    219     read_channel = g_io_channel_unix_new(sigpipe[0]);
    220 
    221     if (g_io_channel_set_encoding(read_channel, NULL, NULL) != G_IO_STATUS_NORMAL)
    222       goto error;
    223 
    224     for (i = 0; i < G_N_ELEMENTS(unix_quit_signals); i++)
    225       {
    226 	struct sigaction sa;
    227 
    228 	sa.sa_handler = self_unix_quit_signal_handler;
    229 	sa.sa_flags = 0;
    230 	sigemptyset(&sa.sa_mask);
    231 
    232 	if (sigaction(unix_quit_signals[i].num, &sa, NULL) < 0)
    233 	  goto error;
    234       }
    235 
    236     g_io_add_watch(read_channel, G_IO_IN | G_IO_PRI, self_unix_quit_signal_watch_cb, self);
    237 
    238     return TRUE;		/* ok */
    239 
    240   error:
    241     if (read_channel)
    242       {
    243 	g_io_channel_shutdown(read_channel, FALSE, NULL);
    244 	g_io_channel_unref(read_channel);
    245       }
    246     else
    247       close(sigpipe[0]);
    248 
    249     close(sigpipe[1]);
    250 
    251     return FALSE;
    252   }
    253 
    254   private void
    255     unix_quit_signal_handler (int signum)
    256   {
    257     write(mn_shell->_priv->unix_signal_pipe_write_end, &signum, sizeof(signum));
    258   }
    259 
    260   private const UnixSignalInfo *
    261     lookup_unix_quit_signal (int signum)
    262   {
    263     int i;
    264 
    265     for (i = 0; i < G_N_ELEMENTS(unix_quit_signals); i++)
    266       if (unix_quit_signals[i].num == signum)
    267 	return &unix_quit_signals[i];
    268 
    269     g_assert_not_reached();
    270     return NULL;
    271   }
    272 
    273   private gboolean
    274     unix_quit_signal_watch_cb (GIOChannel *source,
    275 			       GIOCondition condition,
    276 			       gpointer data)
    277   {
    278     Self *self = data;
    279     GIOStatus status;
    280     int signum;
    281     gsize bytes_read;
    282     const UnixSignalInfo *info;
    283 
    284     status = g_io_channel_read_chars(source, (char *) &signum, sizeof(signum), &bytes_read, NULL);
    285     g_assert(status == G_IO_STATUS_NORMAL);
    286     g_assert(bytes_read == sizeof(signum));
    287 
    288     info = self_lookup_unix_quit_signal(signum);
    289 
    290     g_message(_("received %s signal, exiting"), info->name);
    291 
    292     GDK_THREADS_ENTER();
    293     self_quit(self);
    294     GDK_THREADS_LEAVE();
    295 
    296     return FALSE;		/* remove source */
    297   }
    298 
    299   private void
    300     weak_notify_cb (gpointer data, GObject *former_object)
    301   {
    302     gtk_main_quit();
    303   }
    304 
    305   private void
    306     messages_changed_h (MNMailboxes *mailboxes,
    307 			gboolean has_new,
    308 			gpointer user_data)
    309   {
    310     Self *self = user_data;
    311 
    312     if (mn_conf_has_command(MN_CONF_COMMANDS_MAIL_CHANGED_NAMESPACE))
    313       mn_conf_execute_command(MN_CONF_COMMANDS_MAIL_CHANGED_COMMAND);
    314 
    315     if (has_new)
    316       {
    317 	self_play_new_mail_sound(self);
    318 
    319 	if (mn_conf_has_command(MN_CONF_COMMANDS_NEW_MAIL_NAMESPACE))
    320 	  mn_conf_execute_command(MN_CONF_COMMANDS_NEW_MAIL_COMMAND);
    321       }
    322 
    323     self_update_sensitivity(self);
    324     self_update_tooltip(self);
    325     self_update_icon(self);
    326   }
    327 
    328   private void
    329     play_new_mail_sound (self)
    330   {
    331     char *file;
    332 
    333     if (! mn_conf_get_bool(MN_CONF_SOUNDS_NEW_MAIL_ENABLED))
    334       return;
    335 
    336     file = mn_conf_get_string(MN_CONF_SOUNDS_NEW_MAIL_FILE);
    337 
    338     if (file && *file)
    339       mn_sound_player_play(selfp->sound_player, file, NULL);
    340 
    341     g_free(file);
    342   }
    343 
    344   private void
    345     mailbox_removed_h (MNMailboxes *mailboxes,
    346 		       MNMailbox *mailbox,
    347 		       gpointer user_data)
    348   {
    349     Self *self = user_data;
    350     MNMailboxPropertiesDialog *dialog;
    351 
    352     /* destroy the associated properties dialog, if any */
    353     dialog = self_get_mailbox_properties_dialog(self, mailbox);
    354     if (dialog)
    355       gtk_widget_destroy(GTK_WIDGET(dialog));
    356   }
    357 
    358   private void
    359     init_icon (self)
    360   {
    361     self->icon = MN_MAIL_ICON(mn_mail_icon_new());
    362     mn_add_weak_pointer(&self->icon);
    363 
    364     g_object_connect(self->icon,
    365 		     "signal::activate", self_icon_activate_h, self,
    366 		     "signal::activate-mail-reader", self_icon_activate_mail_reader_h, self,
    367 		     "signal::activate-open-latest-message", self_icon_activate_open_latest_message_h, self,
    368 		     "swapped-signal::activate-consider-new-mail-as-read", self_consider_new_mail_as_read, self,
    369 		     "swapped-signal::activate-update", self_update, self,
    370 		     "signal::activate-properties", self_icon_activate_properties_h, self,
    371 		     "signal::activate-help", self_icon_activate_help_h, self,
    372 		     "signal::activate-about", self_icon_activate_about_h, self,
    373 		     "swapped-signal::activate-remove", self_quit, self,
    374 		     "signal::destroy", self_icon_destroy_h, self,
    375 		     NULL);
    376 
    377     self_update_sensitivity(self);
    378     self_update_tooltip(self);
    379     self_update_icon(self);
    380   }
    381 
    382   private void
    383     notify_icon_cb (GConfClient *client,
    384 		    unsigned int cnxn_id,
    385 		    GConfEntry *entry,
    386 		    gpointer user_data)
    387   {
    388     Self *self = user_data;
    389 
    390     self_update_icon(self);
    391   }
    392 
    393   private void
    394     notify_tooltip_cb (GConfClient *client,
    395 		       unsigned int cnxn_id,
    396 		       GConfEntry *entry,
    397 		       gpointer user_data)
    398   {
    399     Self *self = user_data;
    400 
    401     self_update_tooltip(self);
    402   }
    403 
    404   private void
    405     notify_mail_reader_cb (GConfClient *client,
    406 			   unsigned int cnxn_id,
    407 			   GConfEntry *entry,
    408 			   gpointer user_data)
    409   {
    410     Self *self = user_data;
    411 
    412     self_update_sensitivity(self);
    413   }
    414 
    415   private void
    416     icon_activate_h (MNMailIcon *icon, gpointer user_data)
    417   {
    418     MNShell *self = user_data;
    419     MNAction action;
    420 
    421     action = mn_conf_get_enum_value(MN_TYPE_ACTION, MN_CONF_CLICK_ACTION);
    422 
    423     switch (action)
    424       {
    425       case MN_ACTION_LAUNCH_MAIL_READER:
    426 	if (mn_conf_has_command(MN_CONF_GNOME_MAIL_READER_NAMESPACE))
    427 	  mn_conf_execute_mail_reader();
    428 	else
    429 	  mn_show_error_dialog_with_markup(NULL,
    430 					   _("No mail reader is configured"),
    431 					   _("You can configure a mail reader by choosing <b>System → Preferences → Preferred Applications</b>."));
    432 	break;
    433 
    434       case MN_ACTION_OPEN_LATEST_MESSAGE:
    435 	if (self->mailboxes->messages->len != 0)
    436 	  {
    437 	    MNMessage *message = g_ptr_array_index(self->mailboxes->messages, 0);
    438 
    439 	    if (mn_message_can_perform_action(message, mn_message_get_action("open")))
    440 	      self_open_latest_message(self);
    441 	    else
    442 	      mn_show_error_dialog(NULL,
    443 				   _("Unable to open the latest message"),
    444 				   _("Messages of mailbox \"%s\" cannot be opened."),
    445 				   message->mailbox->runtime_name);
    446 	  }
    447 	else
    448 	  mn_show_error_dialog(NULL,
    449 			       _("Unable to open the latest message"),
    450 			       _("You have no new mail."));
    451 	break;
    452 
    453       case MN_ACTION_CONSIDER_NEW_MAIL_AS_READ:
    454 	self_consider_new_mail_as_read(self);
    455 	break;
    456 
    457       case MN_ACTION_UPDATE_MAIL_STATUS:
    458 	mn_mailboxes_check(self->mailboxes);
    459 	break;
    460 
    461       default:
    462 	g_assert_not_reached();
    463       }
    464   }
    465 
    466   private void
    467     icon_activate_mail_reader_h (MNMailIcon *icon, gpointer user_data)
    468   {
    469     mn_conf_execute_mail_reader();
    470   }
    471 
    472   private void
    473     icon_activate_open_latest_message_h (MNMailIcon *icon, gpointer user_data)
    474   {
    475     Self *self = user_data;
    476     self_open_latest_message(self);
    477   }
    478 
    479   private void
    480     icon_activate_properties_h (MNMailIcon *icon, gpointer user_data)
    481   {
    482     Self *self = user_data;
    483     self_show_properties_dialog(self, gtk_get_current_event_time());
    484   }
    485 
    486   private void
    487     icon_activate_help_h (MNMailIcon *icon, gpointer user_data)
    488   {
    489     mn_show_help(NULL, NULL);
    490   }
    491 
    492   private void
    493     icon_activate_about_h (MNMailIcon *icon, gpointer user_data)
    494   {
    495     Self *self = user_data;
    496     self_show_about_dialog(self, gtk_get_current_event_time());
    497   }
    498 
    499   private void
    500     icon_destroy_h (GtkObject *object, gpointer user_data)
    501   {
    502     Self *self = user_data;
    503 
    504     /* The Notification Area applet has been terminated. Recreate the icon. */
    505     mn_remove_weak_pointer(&self->icon);
    506     self_init_icon(self);
    507   }
    508 
    509   private void
    510     update_sensitivity (self)
    511   {
    512     gtk_widget_set_sensitive(self->icon->mail_reader_item,
    513 			     mn_conf_has_command(MN_CONF_GNOME_MAIL_READER_NAMESPACE));
    514     gtk_widget_set_sensitive(self->icon->open_latest_message_item,
    515 			     self->mailboxes->messages->len != 0
    516 			     && mn_message_can_perform_action(g_ptr_array_index(self->mailboxes->messages, 0),
    517 							      mn_message_get_action("open")));
    518     gtk_widget_set_sensitive(self->icon->consider_new_mail_as_read_item,
    519 			     self->mailboxes->messages->len != 0);
    520     gtk_widget_set_sensitive(self->icon->update_item,
    521 			     mn_mailboxes_get_manually_checkable(self->mailboxes));
    522   }
    523 
    524   private void
    525     update_icon (self)
    526   {
    527     GList *l;
    528     gboolean has_new = FALSE;
    529     gboolean blink = FALSE;
    530     gboolean always = mn_conf_get_bool(MN_CONF_ALWAYS_DISPLAY_ICON);
    531 
    532     MN_LIST_FOREACH(l, self->mailboxes->list)
    533       {
    534 	MNMailbox *mailbox = l->data;
    535 
    536 	if (g_hash_table_size(mailbox->messages) != 0)
    537 	  has_new = TRUE;
    538 	if (mailbox->error)
    539 	  blink = TRUE;
    540       }
    541 
    542     if (selfp->has_new && ! has_new && mn_conf_has_command(MN_CONF_COMMANDS_MAIL_READ_NAMESPACE))
    543       mn_conf_execute_command(MN_CONF_COMMANDS_MAIL_READ_COMMAND);
    544     selfp->has_new = has_new;
    545 
    546     if (blink && ! mn_conf_get_bool(MN_CONF_BLINK_ON_ERRORS))
    547       blink = FALSE;
    548 
    549     if (has_new || blink || always)
    550       {
    551 	int count;
    552 
    553 	if (mn_conf_get_bool(MN_CONF_DISPLAY_MESSAGE_COUNT))
    554 	  count = self->mailboxes->messages->len;
    555 	else
    556 	  count = 0;
    557 
    558 	mn_mail_icon_set_from_stock(self->icon, has_new ? MN_STOCK_MAIL : MN_STOCK_NO_MAIL);
    559 	mn_mail_icon_set_blinking(self->icon, blink);
    560 	mn_mail_icon_set_count(self->icon, count);
    561 	gtk_widget_show(GTK_WIDGET(self->icon));
    562       }
    563     else
    564       {
    565 	gtk_widget_hide(GTK_WIDGET(self->icon));
    566 	mn_mail_icon_set_blinking(self->icon, FALSE);
    567       }
    568   }
    569 
    570   private void
    571     update_tooltip (self)
    572   {
    573     GtkVBox *vbox = NULL;
    574 
    575     if (self->mailboxes->list)
    576       {
    577 	GList *la;
    578 	GSList *lb;
    579 	GSList *new_mailboxes = NULL;
    580 	GSList *error_mailboxes = NULL;
    581 
    582 	MN_LIST_FOREACH(la, self->mailboxes->list)
    583           {
    584 	    MNMailbox *mailbox = la->data;
    585 
    586 	    if (g_hash_table_size(mailbox->messages) != 0)
    587 	      new_mailboxes = g_slist_insert_sorted(new_mailboxes, mailbox, (GCompareFunc) self_new_mailboxes_compare_cb);
    588 
    589 	    if (mailbox->error)
    590 	      error_mailboxes = g_slist_insert_sorted(error_mailboxes, mailbox, mn_mailboxes_compare_by_name_func);
    591 	  }
    592 
    593 	if (new_mailboxes)
    594 	  {
    595 	    GString *string = g_string_new(NULL);
    596 
    597 	    MN_LIST_FOREACH(lb, new_mailboxes)
    598 	      {
    599 		MNMailbox *mailbox = lb->data;
    600 
    601 		g_assert(g_hash_table_size(mailbox->messages) != 0);
    602 
    603 		if (*string->str)
    604 		  g_string_append_c(string, '\n');
    605 
    606 		g_string_append_printf(string, _("%s (%i)"),
    607 				       mailbox->runtime_name,
    608 				       (int) g_hash_table_size(mailbox->messages));
    609 	      }
    610 	    g_slist_free(new_mailboxes);
    611 
    612 	    /* translators: header capitalization */
    613 	    self_tooltip_text_section_new(&vbox, _("Mailboxes Having New Mail"), string->str);
    614 	    g_string_free(string, TRUE);
    615 	  }
    616 
    617 	if (error_mailboxes)
    618 	  {
    619 	    GString *string = g_string_new(NULL);
    620 
    621 	    MN_LIST_FOREACH(lb, error_mailboxes)
    622 	      {
    623 		MNMailbox *mailbox = lb->data;
    624 
    625 		if (*string->str)
    626 		  g_string_append_c(string, '\n');
    627 		g_string_append_printf(string, _("%s: %s"), mailbox->runtime_name, mailbox->error);
    628 	      }
    629 	    g_slist_free(error_mailboxes);
    630 
    631 	    /* translators: header capitalization */
    632 	    self_tooltip_text_section_new(&vbox, _("Errors"), string->str);
    633 	    g_string_free(string, TRUE);
    634 	  }
    635       }
    636 
    637     if (self->mailboxes->messages->len != 0)
    638       {
    639 	MNShellTooltipMailSummary mail_summary;
    640 
    641 	mail_summary = mn_conf_get_enum_value(MN_TYPE_SHELL_TOOLTIP_MAIL_SUMMARY, MN_CONF_TOOLTIP_MAIL_SUMMARY);
    642 	if (mail_summary != MN_SHELL_TOOLTIP_MAIL_SUMMARY_NONE)
    643 	  {
    644 	    GtkWidget *alignment;
    645 	    GtkWidget *message_view;
    646 	    int limit;
    647 	    GSList *messages = NULL;
    648 	    int num_messages;
    649 	    int i;
    650 
    651 	    /* translators: header capitalization */
    652 	    alignment = self_tooltip_section_new(&vbox, _("Mail Summary"));
    653 
    654 	    switch (mail_summary)
    655 	      {
    656 	      case MN_SHELL_TOOLTIP_MAIL_SUMMARY_STANDARD:
    657 		message_view = mn_standard_message_view_new();
    658 		break;
    659 
    660 	      case MN_SHELL_TOOLTIP_MAIL_SUMMARY_COMPACT:
    661 		message_view = mn_compact_message_view_new();
    662 		break;
    663 
    664 	      default:
    665 		g_assert_not_reached();
    666 		break;
    667 	      }
    668 
    669 	    gtk_widget_set_name(message_view, "mn-message-view");
    670 
    671 	    limit = mn_conf_get_int(MN_CONF_TOOLTIP_MAIL_SUMMARY_LIMIT);
    672 
    673 	    num_messages = MIN(self->mailboxes->messages->len, limit);
    674 	    for (i = num_messages - 1; i >= 0; i--)
    675 	      messages = g_slist_prepend(messages, g_ptr_array_index(self->mailboxes->messages, i));
    676 	    mn_message_view_set_messages(MN_MESSAGE_VIEW(message_view), messages);
    677 	    g_slist_free(messages);
    678 
    679 	    if (self->mailboxes->messages->len <= limit)
    680 	      gtk_container_add(GTK_CONTAINER(alignment), message_view);
    681 	    else
    682 	      {
    683 		int remaining;
    684 		char *markup;
    685 		GtkWidget *label;
    686 		GtkWidget *summary_vbox;
    687 
    688 		remaining = self->mailboxes->messages->len - limit;
    689 
    690 		label = gtk_label_new(NULL);
    691 
    692 		markup = g_strdup_printf(ngettext("<span style=\"italic\">%i message is not displayed</span>",
    693 						  "<span style=\"italic\">%i messages are not displayed</span>",
    694 						  remaining),
    695 					 remaining);
    696 		gtk_label_set_markup(GTK_LABEL(label), markup);
    697 		g_free(markup);
    698 
    699 		gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    700 
    701 		summary_vbox = gtk_vbox_new(FALSE, 12);
    702 
    703 		gtk_box_pack_start(GTK_BOX(summary_vbox), message_view, TRUE, TRUE, 0);
    704 		gtk_box_pack_start(GTK_BOX(summary_vbox), label, FALSE, FALSE, 0);
    705 
    706 		gtk_container_add(GTK_CONTAINER(alignment), summary_vbox);
    707 	      }
    708 	  }
    709       }
    710 
    711     if (vbox)
    712       {
    713 	gtk_widget_show_all(GTK_WIDGET(vbox));
    714 	mn_mail_icon_set_tip_widget(self->icon, GTK_WIDGET(vbox));
    715       }
    716     else
    717       mn_mail_icon_set_tip(self->icon, _("You have no new mail."));
    718   }
    719 
    720   private int
    721     new_mailboxes_compare_cb (MNMailbox *a, MNMailbox *b)
    722   {
    723     int cmp;
    724 
    725     /* sort by timestamp (descending order) */
    726     cmp = b->timestamp - a->timestamp;
    727     if (cmp != 0)
    728       return cmp;
    729 
    730     /* sort by number of messages (descending order) */
    731     cmp = (int) g_hash_table_size(b->messages) - g_hash_table_size(a->messages);
    732     if (cmp != 0)
    733       return cmp;
    734 
    735     /* sort by name (ascending order) */
    736     return mn_mailboxes_compare_by_name_func(a, b);
    737   }
    738 
    739   private GtkWidget *
    740     tooltip_section_new (GtkVBox **vbox (check null),
    741 			 const char *title (check null))
    742   {
    743     GtkWidget *section;
    744     GtkWidget *label;
    745     GtkWidget *alignment;
    746 
    747     if (! *vbox)
    748       *vbox = GTK_VBOX(gtk_vbox_new(FALSE, 18));
    749 
    750     section = mn_hig_section_new(title, &label, &alignment);
    751 
    752     gtk_widget_set_name(label, "mn-tooltip-section-title");
    753 
    754     gtk_box_pack_start(GTK_BOX(*vbox), section, TRUE, TRUE, 0);
    755 
    756     return alignment;
    757   }
    758 
    759   private void
    760     tooltip_text_section_new (GtkVBox **vbox (check null),
    761 			      const char *title (check null),
    762 			      const char *text (check null))
    763   {
    764     GtkWidget *alignment;
    765     GtkWidget *label;
    766 
    767     alignment = self_tooltip_section_new(vbox, title);
    768 
    769     label = gtk_label_new(text);
    770     gtk_widget_set_name(label, "mn-tooltip-section-body");
    771     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    772 
    773     gtk_container_add(GTK_CONTAINER(alignment), label);
    774   }
    775 
    776   private void
    777     open_latest_message (self)
    778   {
    779     MNMessage *message;
    780     MNMessageAction *action;
    781 
    782     g_return_if_fail(self->mailboxes->messages->len != 0);
    783 
    784     message = g_ptr_array_index(self->mailboxes->messages, 0);
    785 
    786     action = mn_message_get_action("open");
    787     g_return_if_fail(action != NULL);
    788     g_return_if_fail(mn_message_can_perform_action(message, action));
    789 
    790     mn_message_perform_action(message, action, self_open_latest_message_done_cb, NULL);
    791   }
    792 
    793   private void
    794     open_latest_message_done_cb (MNMessageAction *action, GError *err, gpointer data)
    795   {
    796     if (err && ! g_error_matches(err, MN_MESSAGE_ACTION_ERROR, MN_MESSAGE_ACTION_ERROR_CANCELLED))
    797       mn_show_error_dialog(NULL, _("Unable to open the latest message"), "%s", err->message);
    798   }
    799 
    800   public MNShell *
    801     new (DBusGConnection *session_bus (check null),
    802 	 DBus:G:Proxy *session_bus_proxy (check null))
    803   {
    804     return GET_NEW_VARG(MN_SHELL_PROP_SESSION_BUS(session_bus),
    805 			MN_SHELL_PROP_SESSION_BUS_PROXY(session_bus_proxy),
    806 			NULL);
    807   }
    808 
    809   public void
    810     consider_new_mail_as_read (self)
    811   {
    812     GList *l;
    813 
    814     MN_LIST_FOREACH(l, self->mailboxes->list)
    815       {
    816 	MNMailbox *mailbox = l->data;
    817 	GList *list;
    818 
    819 	list = g_hash_table_get_values(mailbox->messages);
    820 	mn_message_consider_as_read_list(list);
    821 	g_list_free(list);
    822       }
    823   }
    824 
    825   public void
    826     update (self)
    827   {
    828     mn_mailboxes_check(self->mailboxes);
    829   }
    830 
    831   public void
    832     quit (self)
    833   {
    834     g_object_unref(self);
    835   }
    836 
    837   public void
    838     show_properties_dialog (self, guint32 timestamp)
    839   {
    840     self_show_window(self, MN_TYPE_PROPERTIES_DIALOG, &selfp->properties_dialog, timestamp);
    841   }
    842 
    843   public void
    844     show_about_dialog (self, guint32 timestamp)
    845   {
    846     self_show_window(self, MN_TYPE_ABOUT_DIALOG, &selfp->about_dialog, timestamp);
    847   }
    848 
    849   private void
    850     show_window (self,
    851 		 GType type (check != 0),
    852 		 GtkWidget **ptr (check null),
    853 		 guint32 timestamp)
    854   {
    855     if (*ptr)
    856       {
    857 	if (timestamp)
    858 	  gtk_window_present_with_time(GTK_WINDOW(*ptr), timestamp);
    859 	else
    860 	  gtk_window_present(GTK_WINDOW(*ptr));
    861 	return;
    862       }
    863 
    864     *ptr = g_object_new(type, NULL);
    865     mn_add_weak_pointer(ptr);
    866 
    867     gtk_widget_show(*ptr);
    868   }
    869 
    870   public void
    871     add_mailbox_properties_dialog (self, MN:Mailbox:Properties:Dialog *dialog (check null type))
    872   {
    873     selfp->mailbox_properties_dialogs = g_slist_append(selfp->mailbox_properties_dialogs, dialog);
    874     g_object_weak_ref(G_OBJECT(dialog), self_mailbox_properties_dialog_weak_notify_cb, self);
    875   }
    876 
    877   private void
    878     mailbox_properties_dialog_weak_notify_cb (gpointer data,
    879 					      GObject *former_object)
    880   {
    881     Self *self = data;
    882 
    883     selfp->mailbox_properties_dialogs = g_slist_remove(selfp->mailbox_properties_dialogs, former_object);
    884   }
    885 
    886   public MNMailboxPropertiesDialog *
    887     get_mailbox_properties_dialog (self, MN:Mailbox *mailbox (check null type))
    888   {
    889     GSList *l;
    890 
    891     MN_LIST_FOREACH(l, selfp->mailbox_properties_dialogs)
    892       {
    893 	MNMailboxPropertiesDialog *dialog = l->data;
    894 	MNMailbox *this_mailbox;
    895 	gboolean found;
    896 
    897 	this_mailbox = mn_mailbox_properties_dialog_get_mailbox(dialog);
    898 	found = this_mailbox == mailbox;
    899 	g_object_unref(this_mailbox);
    900 
    901 	if (found)
    902 	  return dialog;
    903       }
    904 
    905     return NULL;
    906   }
    907 
    908   public char *
    909     get_summary (self)
    910   {
    911     int indent;
    912     xmlDoc *doc;
    913     xmlNode *root;
    914     int i;
    915     xmlChar *summary;
    916 
    917     indent = xmlIndentTreeOutput;
    918     xmlIndentTreeOutput = 1;
    919 
    920     doc = xmlNewDoc("1.0");
    921     root = xmlNewNode(NULL, "messages");
    922     xmlDocSetRootElement(doc, root);
    923 
    924     MN_ARRAY_FOREACH(i, self->mailboxes->messages)
    925       {
    926 	MNMessage *message = g_ptr_array_index(self->mailboxes->messages, i);
    927 	xmlNode *node;
    928 
    929 	node = mn_message_xml_node_new(message);
    930 	xmlAddChild(root, node); /* owns node */
    931       }
    932 
    933     xmlDocDumpFormatMemory(doc, &summary, NULL, 1);
    934 
    935     xmlFreeDoc(doc);
    936     xmlIndentTreeOutput = indent;
    937 
    938     return summary;
    939   }
    940 }
    941 
    942 %h{
    943 extern MNShell *mn_shell;
    944 %}
    945 
    946 /*
    947  * These enumerations really belong to mn-enums.gob, but gob does not
    948  * allow a class-less input file.
    949  */
    950 
    951 enum MN_ACTION
    952 {
    953   LAUNCH_MAIL_READER,
    954   OPEN_LATEST_MESSAGE,
    955   CONSIDER_NEW_MAIL_AS_READ,
    956   UPDATE_MAIL_STATUS
    957 } MN:Action;
    958 
    959 enum MN_EXPIRATION_ENABLED
    960 {
    961   DEFAULT,
    962   FALSE,
    963   TRUE
    964 } MN:Expiration:Enabled;
    965 
    966 enum MN_POPUP_POSITION
    967 {
    968   ATTACHED,
    969   FREE
    970 } MN:Popup:Position;