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;