Fix frequently ocurring typos :)
[claws.git] / src / plugins / notification / notification_banner.c
1 /* Notification plugin for Claws Mail
2  * Copyright (C) 2005-2008 Holger Berndt
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #  include "claws-features.h"
21 #endif
22
23 #include <glib.h>
24 #include <glib/gi18n.h>
25
26 #ifdef NOTIFICATION_BANNER
27
28 #include <gtk/gtk.h>
29
30 #include "gtk/gtkutils.h"
31
32 #include "prefs_common.h"
33 #include "mainwindow.h"
34 #include "menu.h"
35 #include "procmsg.h"
36 #include "messageview.h"
37 #include "compose.h"
38 #include "menu.h"
39
40 #include "notification_core.h"
41 #include "notification_prefs.h"
42 #include "notification_banner.h"
43
44 typedef struct {
45   GtkWidget *table;
46 } NotificationBannerEntry;
47
48 typedef struct {
49   GtkWidget *window;
50   GtkWidget *scrolled_win;
51         GtkWidget *viewport;
52
53   NotificationBannerEntry *entries;
54   guint timeout_id;
55   gboolean scrolling;
56 } NotificationBanner;
57
58 typedef struct {
59   gint banner_width;
60   GtkAdjustment *adj;
61 } ScrollingData;
62
63
64 static void       notification_banner_create(GSList*);
65 static gboolean   scroller(gpointer data);
66 static GtkWidget* create_entrybox(GSList*);
67 static gboolean   notification_banner_configure(GtkWidget*, GdkEventConfigure*,
68                                                 gpointer);
69 static gboolean notification_banner_swap_colors(GtkWidget*,GdkEventCrossing*,gpointer);
70 static gboolean notification_banner_button_press(GtkWidget*,GdkEventButton*,gpointer);
71 static void notification_banner_show_popup(GtkWidget*,GdkEventButton*);
72 static void notification_banner_popup_done(GtkMenuShell*,gpointer);
73
74 static void banner_menu_reply_cb(GtkAction *,gpointer);
75
76
77 static NotificationBanner banner;
78 static ScrollingData      sdata;
79
80 static GtkWidget *banner_popup;
81 static GtkUIManager *banner_ui_manager;
82 static GtkActionGroup *banner_action_group;
83
84 static gboolean banner_popup_open = FALSE;
85
86 static MsgInfo *current_msginfo = NULL;
87
88 /* Corresponding mutexes */
89 G_LOCK_DEFINE_STATIC(banner);
90 G_LOCK_DEFINE_STATIC(sdata);
91
92 static GtkActionEntry banner_popup_entries[] = 
93 {
94         {"BannerPopup",         NULL, "BannerPopup" },
95         {"BannerPopup/Reply",   NULL, N_("_Reply"), NULL, NULL, G_CALLBACK(banner_menu_reply_cb) },
96 };
97
98
99 void notification_banner_show(GSList *msg_list)
100 {
101   G_LOCK(banner);
102   if((notify_config.banner_show != NOTIFY_BANNER_SHOW_NEVER) &&
103      (g_slist_length(msg_list) ||
104       (notify_config.banner_show == NOTIFY_BANNER_SHOW_ALWAYS)))
105     notification_banner_create(msg_list);
106   else 
107     notification_banner_destroy();
108   G_UNLOCK(banner);
109 }
110
111 void notification_banner_destroy(void)
112 {
113   if(banner.window) {
114     if(banner.entries) {
115       g_free(banner.entries);
116       banner.entries = NULL;
117     }
118     gtk_widget_destroy(banner.window);
119     banner.window = NULL;
120     G_LOCK(sdata);
121     sdata.adj = NULL;
122     sdata.banner_width = 0;
123     G_UNLOCK(sdata);
124     if(banner.timeout_id) {
125       g_source_remove(banner.timeout_id);
126       banner.timeout_id = 0;
127     }
128   }
129 }
130
131 static void notification_banner_create(GSList *msg_list)
132 {
133   GtkRequisition requisition, requisition_after;
134         GtkWidget *viewport;
135         GtkWidget *hbox;
136         GtkWidget *entrybox;
137         GdkColor bg;
138         gint banner_width;
139
140   /* Window */
141   if(!banner.window) {
142
143     banner.window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_banner");
144     gtk_window_set_decorated(GTK_WINDOW(banner.window), FALSE);
145                 if(notify_config.banner_width > 0)
146                         gtk_widget_set_size_request(banner.window, notify_config.banner_width, -1);
147                 else
148                         gtk_widget_set_size_request(banner.window, gdk_screen_width(), -1);
149     gtk_window_set_keep_above(GTK_WINDOW(banner.window), TRUE);
150     gtk_window_set_accept_focus(GTK_WINDOW(banner.window), FALSE);
151     gtk_window_set_skip_taskbar_hint(GTK_WINDOW(banner.window), TRUE);
152     gtk_window_move(GTK_WINDOW(banner.window),
153                                                                                 notify_config.banner_root_x, notify_config.banner_root_y);
154     g_signal_connect(banner.window, "configure-event",
155                                                                                  G_CALLBACK(notification_banner_configure), NULL);
156   }
157   else {
158     if(banner.entries) {
159       g_free(banner.entries);
160       banner.entries = NULL;
161     }
162     gtk_widget_destroy(banner.scrolled_win);
163   }
164   if(notify_config.banner_sticky)
165     gtk_window_stick(GTK_WINDOW(banner.window));
166   else
167     gtk_window_unstick(GTK_WINDOW(banner.window));
168
169   /* Scrolled window */
170   banner.scrolled_win = gtk_scrolled_window_new(NULL, NULL);
171   gtk_container_add(GTK_CONTAINER(banner.window), banner.scrolled_win);
172   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(banner.scrolled_win),
173                                                                                                                                  GTK_POLICY_NEVER, GTK_POLICY_NEVER);
174
175         /* Viewport */
176         viewport = gtk_viewport_new(NULL,NULL);
177         banner.viewport = viewport;
178         gtk_container_add(GTK_CONTAINER(banner.scrolled_win),viewport);
179         if(notify_config.banner_enable_colors) {
180                 gtkut_convert_int_to_gdk_color(notify_config.banner_color_bg,&bg);
181                 gtk_widget_modify_bg(viewport,GTK_STATE_NORMAL,&bg);
182         }
183
184   /* Hbox */
185   hbox = gtk_hbox_new(FALSE, 5);
186         gtk_container_add(GTK_CONTAINER(viewport),hbox);
187
188   /* Entrybox */
189   entrybox = create_entrybox(msg_list);
190   gtk_box_pack_start(GTK_BOX(hbox), entrybox, FALSE, FALSE, 0);
191
192   gtk_widget_show_all(banner.window);
193
194   /* Scrolling */
195   gtk_widget_size_request(hbox, &requisition);
196   if(notify_config.banner_width > 0)
197                 banner_width = notify_config.banner_width;
198         else
199                 banner_width = gdk_screen_width();
200   if(requisition.width > banner_width) {
201     /* Line is too big for screen! */
202     /* Double the entrybox into hbox */
203     GtkWidget *separator, *second_entrybox;
204
205     separator = gtk_vseparator_new();
206     gtk_box_pack_start(GTK_BOX(hbox), separator,
207                        FALSE, FALSE, 0);
208     second_entrybox = create_entrybox(msg_list);
209     gtk_box_pack_start(GTK_BOX(hbox), second_entrybox, FALSE, FALSE, 0);
210
211     gtk_widget_show_all(banner.window);
212     gtk_widget_size_request(hbox, &requisition_after);
213
214     G_LOCK(sdata);
215     sdata.banner_width = requisition_after.width - requisition.width;
216     sdata.adj =
217       gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW
218                                           (banner.scrolled_win));
219     G_UNLOCK(sdata);
220     
221     banner.scrolling = TRUE;
222     if(banner.timeout_id) {
223       g_source_remove(banner.timeout_id);
224       banner.timeout_id = 0;
225     }
226     banner.timeout_id = 
227       g_timeout_add(notify_config.banner_speed, scroller, NULL);
228   }
229   else {
230     banner.scrolling = FALSE;
231     if(banner.timeout_id) {
232       g_source_remove(banner.timeout_id);
233       banner.timeout_id = 0;
234     }
235     G_LOCK(sdata);
236     sdata.banner_width = 0;
237     sdata.adj = NULL;
238     G_UNLOCK(sdata);
239   }
240
241         /* menu */
242   banner_ui_manager = gtk_ui_manager_new();
243   banner_action_group = cm_menu_create_action_group_full(banner_ui_manager,"BannerPopup", banner_popup_entries,
244                         G_N_ELEMENTS(banner_popup_entries), (gpointer)NULL);
245   MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR)
246   MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/Menus", "BannerPopup", "BannerPopup", GTK_UI_MANAGER_MENU)
247   MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/Menus/BannerPopup", "Reply", "BannerPopup/Reply", GTK_UI_MANAGER_MENUITEM)
248
249   banner_popup = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
250                                 gtk_ui_manager_get_widget(banner_ui_manager, "/Menus/BannerPopup")) );
251         g_signal_connect(banner_popup,"selection-done",
252                                                                          G_CALLBACK(notification_banner_popup_done), NULL);
253 }
254
255 static gboolean notification_banner_configure(GtkWidget *widget,
256                                               GdkEventConfigure *event,
257                                               gpointer data)
258 {
259   gtk_window_get_position(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
260                           &(notify_config.banner_root_x),
261                           &(notify_config.banner_root_y));
262   return TRUE;
263 }
264
265 static gboolean scroller(gpointer data)
266 {
267         // don't scroll during popup open
268         if(banner_popup_open)
269                 return banner.scrolling;
270
271         while(gtk_events_pending())
272                 gtk_main_iteration();
273   G_LOCK(sdata);
274   if(sdata.adj && GTK_IS_ADJUSTMENT(sdata.adj)) {
275     if(sdata.adj->value != sdata.banner_width)
276       gtk_adjustment_set_value(sdata.adj, sdata.adj->value + 1);
277     else
278       gtk_adjustment_set_value(sdata.adj, 0);
279                 gtk_adjustment_value_changed(sdata.adj);
280   }
281   G_UNLOCK(sdata);
282         while(gtk_events_pending())
283                 gtk_main_iteration();
284   return banner.scrolling;
285 }
286
287 static GtkWidget* create_entrybox(GSList *msg_list)
288 {
289   GtkWidget *entrybox;
290   GdkColor fg;
291         GdkColor bg;
292   gint list_length;
293
294   list_length = g_slist_length(msg_list);
295
296   if(notify_config.banner_enable_colors) {
297     gtkut_convert_int_to_gdk_color(notify_config.banner_color_bg,&bg);
298                 gtkut_convert_int_to_gdk_color(notify_config.banner_color_fg,&fg);
299   }
300
301   if(banner.entries) {
302     g_free(banner.entries);
303     banner.entries = NULL;
304   }
305
306   entrybox = gtk_hbox_new(FALSE, 5);
307   if(list_length) {
308     GSList *walk;
309     gint ii = 0;
310     banner.entries = g_new(NotificationBannerEntry, list_length);
311     for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
312       GtkWidget *label1;
313       GtkWidget *label2;
314       GtkWidget *label3;
315       GtkWidget *label4;
316       GtkWidget *label5;
317       GtkWidget *label6;
318                         GtkWidget *ebox;
319       CollectedMsg *cmsg = walk->data;
320       
321       if(ii > 0) {
322                                 GtkWidget *separator;
323                                 separator = gtk_vseparator_new();
324                                 gtk_box_pack_start(GTK_BOX(entrybox), separator, FALSE, FALSE, 0);
325       }
326
327                         ebox = gtk_event_box_new();
328                         gtk_box_pack_start(GTK_BOX(entrybox), ebox, FALSE, FALSE, 0);
329                         gtk_widget_set_events(ebox,
330                                                                                                                 GDK_POINTER_MOTION_MASK |
331                                                                                                                 GDK_BUTTON_PRESS_MASK);
332
333                         if(notify_config.banner_enable_colors)
334                                 gtk_widget_modify_bg(ebox,GTK_STATE_NORMAL,&bg);
335
336       banner.entries[ii].table = gtk_table_new(3, 2, FALSE);
337                         gtk_container_add(GTK_CONTAINER(ebox),banner.entries[ii].table);
338                         g_signal_connect(ebox, "enter-notify-event",
339                                                                                          G_CALLBACK(notification_banner_swap_colors),
340                                                                                          banner.entries[ii].table);
341                         g_signal_connect(ebox, "leave-notify-event",
342                                                                                          G_CALLBACK(notification_banner_swap_colors),
343                                                                                          banner.entries[ii].table);
344                         g_signal_connect(ebox, "button-press-event",
345                                                                                          G_CALLBACK(notification_banner_button_press),
346                                                                                          cmsg);
347
348       label1 = gtk_label_new(prefs_common_translated_header_name("From:"));
349       gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
350       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table), 
351                                 label1, 0, 1, 0, 1);
352       label2 = gtk_label_new(prefs_common_translated_header_name("Subject:"));
353       gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
354       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
355                                 label2, 0, 1, 1, 2);
356       label3 = gtk_label_new(_("Folder:"));
357       gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5);
358       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
359                                 label3, 0, 1, 2, 3);
360       
361       label4 = gtk_label_new(cmsg->from);
362       gtk_misc_set_alignment(GTK_MISC(label4), 0, 0.5);
363       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
364                                 label4, 1, 2, 0, 1);
365       label5 = gtk_label_new(cmsg->subject);
366       gtk_misc_set_alignment(GTK_MISC(label5), 0, 0.5);
367       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
368                                 label5, 1, 2, 1, 2);
369       label6 = gtk_label_new(cmsg->folderitem_name);
370       gtk_misc_set_alignment(GTK_MISC(label6), 0, 0.5);
371       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
372                                 label6, 1, 2, 2, 3);
373       gtk_table_set_col_spacings(GTK_TABLE(banner.entries[ii].table), 5);
374                         ii++;
375       /* Color */
376       if(notify_config.banner_enable_colors) {
377                                 gtk_widget_modify_fg(label1,GTK_STATE_NORMAL,&fg);
378                                 gtk_widget_modify_fg(label2,GTK_STATE_NORMAL,&fg);
379                                 gtk_widget_modify_fg(label3,GTK_STATE_NORMAL,&fg);
380                                 gtk_widget_modify_fg(label4,GTK_STATE_NORMAL,&fg);
381                                 gtk_widget_modify_fg(label5,GTK_STATE_NORMAL,&fg);
382                                 gtk_widget_modify_fg(label6,GTK_STATE_NORMAL,&fg);
383       }
384     }
385   }
386   else {
387     /* We have an empty list -- create an empty dummy element */
388     GtkWidget *label;
389
390     banner.entries = g_new(NotificationBannerEntry, 1);
391     banner.entries[0].table = gtk_table_new(3, 1, FALSE);
392     label = gtk_label_new("");
393     gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table), 
394                               label, 0, 1, 0, 1);
395     label = gtk_label_new("");
396     gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table), 
397                               label, 0, 1, 1, 2);
398     label = gtk_label_new("");
399     gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table), 
400                               label, 0, 1, 2, 3);
401     gtk_box_pack_start(GTK_BOX(entrybox), banner.entries[0].table,
402                        FALSE, FALSE, 0);
403   }
404   return entrybox;
405 }
406
407 static gboolean notification_banner_swap_colors(GtkWidget *widget,
408                                                                                                                                                                                                 GdkEventCrossing *event,
409                                                                                                                                                                                                 gpointer data)
410 {
411         GList *children;
412         GList *walk;
413         GdkColor *old_bg;
414         
415         children = gtk_container_get_children(GTK_CONTAINER(data));
416
417         old_bg = gdk_color_copy(&(gtk_widget_get_style(widget)->bg[GTK_STATE_NORMAL]));
418         if(children)
419                 gtk_widget_modify_bg(widget,GTK_STATE_NORMAL,
420                                                                                                  &(gtk_widget_get_style(GTK_WIDGET(children->data))->fg[GTK_STATE_NORMAL]));
421         
422         for(walk = children; walk; walk = walk->next)
423                 gtk_widget_modify_fg(GTK_WIDGET(walk->data),GTK_STATE_NORMAL,old_bg);
424         
425         g_list_free(children);
426         gdk_color_free(old_bg);
427         return FALSE;
428 }
429
430 static gboolean notification_banner_button_press(GtkWidget *widget,
431                                                                                                                                                                                                  GdkEventButton *button,
432                                                                                                                                                                                                  gpointer data)
433 {
434         gboolean done;
435         done = FALSE;
436         if(button->type == GDK_BUTTON_PRESS) {
437                 CollectedMsg *cmsg = (CollectedMsg*) data;
438     if(button->button == 1) {
439                         /* jump to that message */
440                         if(cmsg->msginfo) {
441                                 gchar *path;
442                                 path = procmsg_get_message_file_path(cmsg->msginfo);
443                                 mainwindow_jump_to(path, TRUE);
444                                 g_free(path);
445                         }
446                         done = TRUE;
447                 }
448                 else if(button->button == 2) {
449       gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)), 
450                                                                                                                                  button->button, button->x_root, button->y_root,
451                                                                                                                                  button->time);
452                 }
453                 else if(button->button == 3) {
454                         current_msginfo = cmsg->msginfo;
455                         notification_banner_show_popup(widget, button);
456                         banner_popup_open = TRUE;
457                         done = TRUE;
458                 }
459         }
460         return done;
461 }
462
463 static void notification_banner_show_popup(GtkWidget *widget,
464                                                                                                                                                                          GdkEventButton *event)
465 {
466   int button, event_time;
467
468   if(event) {
469                 button = event->button;
470                 event_time = event->time;
471         }
472   else {
473                 button = 0;
474                 event_time = gtk_get_current_event_time();
475         }
476
477   gtk_menu_popup(GTK_MENU(banner_popup), NULL, NULL, NULL, NULL,
478                                                                  button, event_time);
479 }
480
481 static void notification_banner_popup_done(GtkMenuShell *menushell,
482                                                                                                                                                                          gpointer      user_data)
483 {
484         current_msginfo = NULL;
485         banner_popup_open = FALSE;
486 }
487
488 static void banner_menu_reply_cb(GtkAction *action, gpointer data)
489 {
490         MainWindow *mainwin;
491         MessageView *messageview;
492         GSList *msginfo_list = NULL;
493
494         if(!(mainwin = mainwindow_get_mainwindow()))
495                 return;
496
497         if(!(messageview = (MessageView*)mainwin->messageview))
498                 return;
499
500         g_return_if_fail(current_msginfo);
501
502         msginfo_list = g_slist_prepend(msginfo_list, current_msginfo);
503         compose_reply_from_messageview(messageview, msginfo_list,
504                                        prefs_common_get_prefs()->reply_with_quote ?
505                                        COMPOSE_REPLY_WITH_QUOTE : COMPOSE_REPLY_WITHOUT_QUOTE);
506         g_slist_free(msginfo_list);
507 }
508
509 #endif /* NOTIFICATION_BANNER */