2007-08-29 [colin] 2.10.0cvs183
[claws.git] / src / plugins / trayicon / trayicon.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2007 the Claws Mail Team
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
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <stdio.h>
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29
30 #include "common/claws.h"
31 #include "common/version.h"
32 #include "plugin.h"
33 #include "utils.h"
34 #include "hooks.h"
35 #include "folder.h"
36 #include "mainwindow.h"
37 #include "gtkutils.h"
38 #include "menu.h"
39 #include "toolbar.h"
40 #include "prefs_common.h"
41 #include "main.h"
42 #include "alertpanel.h"
43 #include "account.h"
44 #include "gtk/manage_window.h"
45
46 #include "eggtrayicon.h"
47
48 #include "trayicon_prefs.h"
49
50 #include "newmarkedmail.xpm"
51 #include "unreadmarkedmail.xpm"
52 #include "newmail.xpm"
53 #include "unreadmail.xpm"
54 #include "nomail.xpm"
55 #include "newmarkedmail.offline.xpm"
56 #include "unreadmarkedmail.offline.xpm"
57 #include "newmail.offline.xpm"
58 #include "unreadmail.offline.xpm"
59 #include "nomail.offline.xpm"
60
61 #define PLUGIN_NAME (_("Trayicon"))
62
63 static guint item_hook_id;
64 static guint folder_hook_id;
65 static guint offline_hook_id;
66 static guint account_hook_id;
67 static guint close_hook_id;
68 static guint iconified_hook_id;
69
70 static GdkPixmap *newmail_pixmap[2];
71 static GdkPixmap *newmail_bitmap[2];
72 static GdkPixmap *unreadmail_pixmap[2];
73 static GdkPixmap *unreadmail_bitmap[2];
74 static GdkPixmap *newmarkedmail_pixmap[2];
75 static GdkPixmap *newmarkedmail_bitmap[2];
76 static GdkPixmap *unreadmarkedmail_pixmap[2];
77 static GdkPixmap *unreadmarkedmail_bitmap[2];
78 static GdkPixmap *nomail_pixmap[2];
79 static GdkPixmap *nomail_bitmap[2];
80
81 static EggTrayIcon *trayicon;
82 static GtkWidget *eventbox;
83 static GtkWidget *image;
84 static GtkTooltips *tooltips;
85 static GtkWidget *traymenu_popup;
86 static GtkItemFactory *traymenu_factory;
87 static gboolean updating_menu = FALSE;
88
89 guint destroy_signal_id;
90
91 typedef enum
92 {
93         TRAYICON_NEW,
94         TRAYICON_NEWMARKED,
95         TRAYICON_UNREAD,
96         TRAYICON_UNREADMARKED,
97         TRAYICON_NOTHING
98 } TrayIconType;
99
100 static void trayicon_get_all_cb     (gpointer data, guint action, GtkWidget *widget);
101 static void trayicon_compose_cb     (gpointer data, guint action, GtkWidget *widget);
102 static void trayicon_compose_acc_cb (GtkMenuItem *menuitem, gpointer data );
103 static void trayicon_addressbook_cb (gpointer data, guint action, GtkWidget *widget);
104 static void trayicon_exit_cb        (gpointer data, guint action, GtkWidget *widget);
105 static void trayicon_toggle_offline_cb  (gpointer data, guint action, GtkWidget *widget);
106 static void resize_cb               (GtkWidget *widget, GtkRequisition *req, gpointer user_data);
107
108 static GtkItemFactoryEntry trayicon_popup_menu_entries[] =
109 {
110         {N_("/_Get Mail"),              NULL, trayicon_get_all_cb,              0, NULL},
111         {"/---",                        NULL, NULL,                             0, "<Separator>"},
112         {N_("/_Email"),                 NULL, trayicon_compose_cb,              0, NULL},
113         {N_("/_Email from account"),    NULL, NULL,                             0, "<Branch>"},
114         {"/---",                        NULL, NULL,                             0, "<Separator>"},
115         {N_("/Open A_ddressbook"),      NULL, trayicon_addressbook_cb,          0, NULL},
116         {"/---",                        NULL, NULL,                             0, "<Separator>"},
117         {N_("/_Work Offline"),          NULL, trayicon_toggle_offline_cb,       0, "<CheckItem>"},
118         {"/---",                        NULL, NULL,                             0, "<Separator>"},
119         {N_("/E_xit Claws Mail"),       NULL, trayicon_exit_cb,                 0, NULL}
120 };
121
122 static gboolean trayicon_set_accounts_hook(gpointer source, gpointer data)
123 {
124         GList *cur_ac, *cur_item = NULL;
125         GtkWidget *menu;
126         GtkWidget *menuitem;
127         PrefsAccount *ac_prefs;
128
129         GList *account_list = account_get_list();
130
131         menu = gtk_item_factory_get_widget(traymenu_factory,
132                                            "/Email from account");
133
134         /* destroy all previous menu item */
135         cur_item = GTK_MENU_SHELL(menu)->children;
136         while (cur_item != NULL) {
137                 GList *next = cur_item->next;
138                 gtk_widget_destroy(GTK_WIDGET(cur_item->data));
139                 cur_item = next;
140         }
141
142         for (cur_ac = account_list; cur_ac != NULL; cur_ac = cur_ac->next) {
143                 ac_prefs = (PrefsAccount *)cur_ac->data;
144
145                 menuitem = gtk_menu_item_new_with_label
146                         (ac_prefs->account_name ? ac_prefs->account_name
147                          : _("Untitled"));
148                 gtk_widget_show(menuitem);
149                 gtk_menu_append(GTK_MENU(menu), menuitem);
150                 g_signal_connect(G_OBJECT(menuitem), "activate",
151                                  G_CALLBACK(trayicon_compose_acc_cb),
152                                  ac_prefs);
153         }
154         return FALSE;
155 }
156
157 static void set_trayicon_pixmap(TrayIconType icontype)
158 {
159         GdkPixmap *pixmap = NULL;
160         GdkBitmap *bitmap = NULL;
161         static GdkPixmap *last_pixmap = NULL;
162
163         switch(icontype) {
164         case TRAYICON_NEW:
165                 pixmap = newmail_pixmap[prefs_common.work_offline];
166                 bitmap = newmail_bitmap[prefs_common.work_offline];
167                 break;
168         case TRAYICON_NEWMARKED:
169                 pixmap = newmarkedmail_pixmap[prefs_common.work_offline];
170                 bitmap = newmarkedmail_bitmap[prefs_common.work_offline];
171                 break;
172         case TRAYICON_UNREAD:
173                 pixmap = unreadmail_pixmap[prefs_common.work_offline];
174                 bitmap = unreadmail_bitmap[prefs_common.work_offline];
175                 break;
176         case TRAYICON_UNREADMARKED:
177                 pixmap = unreadmarkedmail_pixmap[prefs_common.work_offline];
178                 bitmap = unreadmarkedmail_bitmap[prefs_common.work_offline];
179                 break;
180         default:
181                 pixmap = nomail_pixmap[prefs_common.work_offline];
182                 bitmap = nomail_bitmap[prefs_common.work_offline];
183                 break;
184         }
185
186         if (pixmap == last_pixmap) {
187                 return;
188         }
189
190         gtk_image_set_from_pixmap(GTK_IMAGE(image), pixmap, bitmap);
191
192         last_pixmap = pixmap;
193 }
194
195 static void update(FolderItem *removed_item)
196 {
197         guint new, unread, unreadmarked, marked, total;
198         gchar *buf;
199         TrayIconType icontype = TRAYICON_NOTHING;
200
201         folder_count_total_msgs(&new, &unread, &unreadmarked, &marked, &total);
202         if (removed_item) {
203                 total -= removed_item->total_msgs;
204                 new -= removed_item->new_msgs;
205                 unread -= removed_item->unread_msgs;
206         }
207
208         buf = g_strdup_printf(_("New %d, Unread: %d, Total: %d"), new, unread, total);
209
210         gtk_tooltips_set_tip(tooltips, eventbox, buf, "");
211         g_free(buf);
212
213         if (new > 0 && unreadmarked > 0) {
214                 icontype = TRAYICON_NEWMARKED;
215         } else if (new > 0) {
216                 icontype = TRAYICON_NEW;
217         } else if (unreadmarked > 0) {
218                 icontype = TRAYICON_UNREADMARKED;
219         } else if (unread > 0) {
220                 icontype = TRAYICON_UNREAD;
221         }
222
223         set_trayicon_pixmap(icontype);
224 }
225
226 static gboolean folder_item_update_hook(gpointer source, gpointer data)
227 {
228         update(NULL);
229
230         return FALSE;
231 }
232
233 static gboolean folder_update_hook(gpointer source, gpointer data)
234 {
235         FolderUpdateData *hookdata;
236         hookdata = source;
237         if (hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
238                 update(hookdata->item);
239         else
240                 update(NULL);
241
242         return FALSE;
243 }
244
245 static gboolean offline_update_hook(gpointer source, gpointer data)
246 {
247         update(NULL);
248         return FALSE;
249 }
250
251 static gboolean trayicon_close_hook(gpointer source, gpointer data)
252 {
253         if (source) {
254                 gboolean *close_allowed = (gboolean*)source;
255
256                 if (trayicon_prefs.close_to_tray) {
257                 MainWindow *mainwin = mainwindow_get_mainwindow();
258
259                         *close_allowed = FALSE;
260                 if (GTK_WIDGET_VISIBLE(GTK_WIDGET(mainwin->window)))
261                         main_window_hide(mainwin);
262         }
263         }
264         return FALSE;
265 }
266
267 static gboolean trayicon_got_iconified_hook(gpointer source, gpointer data)
268 {
269         MainWindow *mainwin = mainwindow_get_mainwindow();
270
271         if (trayicon_prefs.hide_when_iconified
272                         && GTK_WIDGET_VISIBLE(GTK_WIDGET(mainwin->window))
273                         && !gtk_window_get_skip_taskbar_hint(GTK_WINDOW(mainwin->window))) {
274                 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), TRUE);
275         }
276         return FALSE;
277 }
278
279 static void resize_cb(GtkWidget *widget, GtkRequisition *req,
280                       gpointer user_data)
281 {
282         update(NULL);
283 }
284
285 static gboolean click_cb(GtkWidget * widget,
286                          GdkEventButton * event, gpointer user_data)
287 {
288         MainWindow *mainwin;
289
290         if (event == NULL) {
291                 return TRUE;
292         }
293
294         mainwin = mainwindow_get_mainwindow();
295
296         switch (event->button) {
297         case 1:
298                 if (GTK_WIDGET_VISIBLE(GTK_WIDGET(mainwin->window))) {
299                         if ((gdk_window_get_state(GTK_WIDGET(mainwin->window)->window)&GDK_WINDOW_STATE_ICONIFIED)
300                                         || mainwindow_is_obscured()) {
301                                 gtk_window_deiconify(GTK_WINDOW(mainwin->window));
302                                 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
303                                 main_window_show(mainwin);
304                                 gtk_window_present(GTK_WINDOW(mainwin->window));
305                         } else {
306                                 main_window_hide(mainwin);
307                         }
308                 } else {
309                         gtk_window_deiconify(GTK_WINDOW(mainwin->window));
310                         gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
311                         main_window_show(mainwin);
312                         gtk_window_present(GTK_WINDOW(mainwin->window));
313         }
314                 break;
315         case 3:
316                 /* tell callbacks to skip any event */
317                 updating_menu = TRUE;
318                 /* initialize checkitem according to current offline state */
319                 gtk_check_menu_item_set_active(
320                         GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(traymenu_factory,
321                         "/Work Offline")), prefs_common.work_offline);
322                 gtk_widget_set_sensitive(
323                         GTK_WIDGET(gtk_item_factory_get_item(traymenu_factory,
324                         "/Get Mail")), mainwin->lock_count == 0);
325                 updating_menu = FALSE;
326
327                 gtk_menu_popup( GTK_MENU(traymenu_popup), NULL, NULL, NULL, NULL,
328                        event->button, event->time );
329                 break;
330         default:
331                 return TRUE;
332         }
333         return TRUE;
334 }
335
336 static void create_trayicon(void);
337
338 static void destroy_cb(GtkWidget *widget, gpointer *data)
339 {
340         debug_print("Widget destroyed\n");
341
342         create_trayicon();
343 }
344
345 static void create_trayicon()
346 {
347         gint n_entries = 0;
348
349         trayicon = egg_tray_icon_new("Claws Mail");
350         gtk_widget_realize(GTK_WIDGET(trayicon));
351         gtk_window_set_default_size(GTK_WINDOW(trayicon), 16, 16);
352         gtk_container_set_border_width(GTK_CONTAINER(trayicon), 0);
353
354         PIXMAP_CREATE(GTK_WIDGET(trayicon), nomail_pixmap[0], nomail_bitmap[0], nomail_xpm);
355         PIXMAP_CREATE(GTK_WIDGET(trayicon), unreadmail_pixmap[0], unreadmail_bitmap[0], unreadmail_xpm);
356         PIXMAP_CREATE(GTK_WIDGET(trayicon), newmail_pixmap[0], newmail_bitmap[0], newmail_xpm);
357         PIXMAP_CREATE(GTK_WIDGET(trayicon), unreadmarkedmail_pixmap[0], unreadmarkedmail_bitmap[0], unreadmarkedmail_xpm);
358         PIXMAP_CREATE(GTK_WIDGET(trayicon), newmarkedmail_pixmap[0], newmarkedmail_bitmap[0], newmarkedmail_xpm);
359
360         PIXMAP_CREATE(GTK_WIDGET(trayicon), nomail_pixmap[1], nomail_bitmap[1], nomail_offline_xpm);
361         PIXMAP_CREATE(GTK_WIDGET(trayicon), unreadmail_pixmap[1], unreadmail_bitmap[1], unreadmail_offline_xpm);
362         PIXMAP_CREATE(GTK_WIDGET(trayicon), newmail_pixmap[1], newmail_bitmap[1], newmail_offline_xpm);
363         PIXMAP_CREATE(GTK_WIDGET(trayicon), unreadmarkedmail_pixmap[1], unreadmarkedmail_bitmap[1], unreadmarkedmail_offline_xpm);
364         PIXMAP_CREATE(GTK_WIDGET(trayicon), newmarkedmail_pixmap[1], newmarkedmail_bitmap[1], newmarkedmail_offline_xpm);
365
366
367         eventbox = gtk_event_box_new();
368         gtk_container_set_border_width(GTK_CONTAINER(eventbox), 0);
369         gtk_container_add(GTK_CONTAINER(trayicon), GTK_WIDGET(eventbox));
370
371         image = gtk_image_new_from_pixmap(nomail_pixmap[0], nomail_bitmap[0]);
372         gtk_container_add(GTK_CONTAINER(eventbox), image);
373
374         destroy_signal_id =
375         g_signal_connect(G_OBJECT(trayicon), "destroy",
376                 G_CALLBACK(destroy_cb), NULL);
377         g_signal_connect(GTK_OBJECT(trayicon), "size-request",
378                 G_CALLBACK(resize_cb), NULL);
379         g_signal_connect(G_OBJECT(eventbox), "button-press-event",
380                 G_CALLBACK(click_cb), NULL);
381
382         tooltips = gtk_tooltips_new();
383         gtk_tooltips_enable(tooltips);
384
385         n_entries = sizeof(trayicon_popup_menu_entries) /
386         sizeof(trayicon_popup_menu_entries[0]);
387         traymenu_popup = menu_create_items(trayicon_popup_menu_entries,
388                                                 n_entries, "<TrayiconMenu>", &traymenu_factory,
389                                                 NULL);
390
391         gtk_widget_show_all(GTK_WIDGET(trayicon));
392
393         update(NULL);
394 }
395
396 int plugin_init(gchar **error)
397 {
398         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
399                                 VERSION_NUMERIC, PLUGIN_NAME, error))
400                 return -1;
401
402         item_hook_id = hooks_register_hook (FOLDER_ITEM_UPDATE_HOOKLIST, folder_item_update_hook, NULL);
403         if (item_hook_id == -1) {
404                 *error = g_strdup(_("Failed to register folder item update hook"));
405                 goto err_out_item;
406         }
407
408         folder_hook_id = hooks_register_hook (FOLDER_UPDATE_HOOKLIST, folder_update_hook, NULL);
409         if (folder_hook_id == -1) {
410                 *error = g_strdup(_("Failed to register folder update hook"));
411                 goto err_out_folder;
412         }
413
414         offline_hook_id = hooks_register_hook (OFFLINE_SWITCH_HOOKLIST, offline_update_hook, NULL);
415         if (offline_hook_id == -1) {
416                 *error = g_strdup(_("Failed to register offline switch hook"));
417                 goto err_out_offline;
418         }
419
420         account_hook_id = hooks_register_hook (ACCOUNT_LIST_CHANGED_HOOKLIST, trayicon_set_accounts_hook, NULL);
421         if (account_hook_id == -1) {
422                 *error = g_strdup(_("Failed to register account list changed hook"));
423                 goto err_out_account;
424         }
425
426         close_hook_id = hooks_register_hook (MAIN_WINDOW_CLOSE, trayicon_close_hook, NULL);
427         if (close_hook_id == -1) {
428                 *error = g_strdup(_("Failed to register close hook"));
429                 goto err_out_close;
430         }
431
432         iconified_hook_id = hooks_register_hook (MAIN_WINDOW_GOT_ICONIFIED, trayicon_got_iconified_hook, NULL);
433         if (iconified_hook_id == -1) {
434                 *error = g_strdup(_("Failed to register got iconified hook"));
435                 goto err_out_iconified;
436         }
437
438         create_trayicon();
439         trayicon_set_accounts_hook(NULL, NULL);
440
441         trayicon_prefs_init();
442
443         if (trayicon_prefs.hide_at_startup && claws_is_starting()) {
444                 MainWindow *mainwin = mainwindow_get_mainwindow();
445
446                 if (GTK_WIDGET_VISIBLE(GTK_WIDGET(mainwin->window)))
447                         main_window_hide(mainwin);
448                 main_set_show_at_startup(FALSE);
449         }
450
451         return 0;
452
453 err_out_iconified:
454         hooks_unregister_hook(MAIN_WINDOW_CLOSE, close_hook_id);
455 err_out_close:
456         hooks_unregister_hook(ACCOUNT_LIST_CHANGED_HOOKLIST, account_hook_id);
457 err_out_account:
458         hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, offline_hook_id);
459 err_out_offline:
460         hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, folder_hook_id);
461 err_out_folder:
462         hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, item_hook_id);
463 err_out_item:
464         return -1;
465 }
466
467 gboolean plugin_done(void)
468 {
469         trayicon_prefs_done();
470
471         hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, item_hook_id);
472         hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, folder_hook_id);
473         hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, offline_hook_id);
474         hooks_unregister_hook(ACCOUNT_LIST_CHANGED_HOOKLIST, account_hook_id);
475         hooks_unregister_hook(MAIN_WINDOW_CLOSE, close_hook_id);
476         hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, iconified_hook_id);
477
478         if (claws_is_exiting())
479                 return TRUE;
480
481         g_signal_handler_disconnect(G_OBJECT(trayicon), destroy_signal_id);
482         
483         gtk_widget_destroy(GTK_WIDGET(trayicon));
484
485         while (gtk_events_pending()) {
486                 gtk_main_iteration();
487         }
488         return TRUE;
489 }
490
491 const gchar *plugin_name(void)
492 {
493         return PLUGIN_NAME;
494 }
495
496 const gchar *plugin_desc(void)
497 {
498         return _("This plugin places a mailbox icon in the system tray that "
499                  "indicates if you have new or unread mail.\n"
500                  "\n"
501                  "The mailbox is empty if you have no unread mail, otherwise "
502                  "it contains a letter. A tooltip shows new, unread and total "
503                  "number of messages.");
504 }
505
506 const gchar *plugin_type(void)
507 {
508         return "GTK2";
509 }
510
511 const gchar *plugin_licence(void)
512 {
513         return "GPL3+";
514 }
515
516 const gchar *plugin_version(void)
517 {
518         return VERSION;
519 }
520
521
522 /* popup menu callbacks */
523 static void trayicon_get_all_cb( gpointer data, guint action, GtkWidget *widget )
524 {
525         MainWindow *mainwin = mainwindow_get_mainwindow();
526         inc_all_account_mail_cb(mainwin, 0, NULL);
527 }
528
529 static void trayicon_compose_cb( gpointer data, guint action, GtkWidget *widget )
530 {
531         MainWindow *mainwin = mainwindow_get_mainwindow();
532         compose_mail_cb(mainwin, 0, NULL);
533 }
534
535 static void trayicon_compose_acc_cb( GtkMenuItem *menuitem, gpointer data )
536 {
537         compose_new((PrefsAccount *)data, NULL, NULL);
538 }
539
540 static void trayicon_addressbook_cb( gpointer data, guint action, GtkWidget *widget )
541 {
542         addressbook_open(NULL);
543 }
544
545 static void trayicon_toggle_offline_cb( gpointer data, guint action, GtkWidget *widget )
546 {
547         /* toggle offline mode if menu checkitem has been clicked */
548         if (!updating_menu) {
549                 MainWindow *mainwin = mainwindow_get_mainwindow();
550                 main_window_toggle_work_offline(mainwin, !prefs_common.work_offline, TRUE);
551         }
552 }
553
554 static void app_exit_cb(MainWindow *mainwin, guint action, GtkWidget *widget)
555 {
556         if (prefs_common.confirm_on_exit) {
557                 if (alertpanel(_("Exit"), _("Exit Claws Mail?"),
558                                GTK_STOCK_CANCEL, GTK_STOCK_OK,
559                                NULL) != G_ALERTALTERNATE) {
560                         return;
561                 }
562                 manage_window_focus_in(mainwin->window, NULL, NULL);
563         }
564
565         app_will_exit(NULL, mainwin);
566 }
567
568 static void trayicon_exit_cb( gpointer data, guint action, GtkWidget *widget )
569 {
570         MainWindow *mainwin = mainwindow_get_mainwindow();
571
572         if (mainwin->lock_count == 0) {
573                 app_exit_cb(mainwin, 0, NULL);
574         }
575 }
576
577 struct PluginFeature *plugin_provides(void)
578 {
579         static struct PluginFeature features[] = 
580                 { {PLUGIN_NOTIFIER, N_("Trayicon")},
581                   {PLUGIN_NOTHING, NULL}};
582         return features;
583 }