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", NULL, NULL, NULL },
95 	{"BannerPopup/Reply",	NULL, N_("_Reply"), NULL, NULL, G_CALLBACK(banner_menu_reply_cb) },
96 };
97 
98 
notification_banner_show(GSList * msg_list)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 
notification_banner_destroy(void)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 
notification_banner_create(GSList * msg_list)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 
notification_banner_configure(GtkWidget * widget,GdkEventConfigure * event,gpointer data)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 
scroller(gpointer data)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(gtk_adjustment_get_value(sdata.adj) != sdata.banner_width)
276       gtk_adjustment_set_value(sdata.adj,
277 					gtk_adjustment_get_value(sdata.adj) + 1);
278     else
279       gtk_adjustment_set_value(sdata.adj, 0);
280 		gtk_adjustment_value_changed(sdata.adj);
281   }
282   G_UNLOCK(sdata);
283 	while(gtk_events_pending())
284 		gtk_main_iteration();
285   return banner.scrolling;
286 }
287 
create_entrybox(GSList * msg_list)288 static GtkWidget* create_entrybox(GSList *msg_list)
289 {
290   GtkWidget *entrybox;
291   GdkColor fg;
292 	GdkColor bg;
293   gint list_length;
294 
295   list_length = g_slist_length(msg_list);
296 
297   if(notify_config.banner_enable_colors) {
298     gtkut_convert_int_to_gdk_color(notify_config.banner_color_bg,&bg);
299 		gtkut_convert_int_to_gdk_color(notify_config.banner_color_fg,&fg);
300   }
301 
302   if(banner.entries) {
303     g_free(banner.entries);
304     banner.entries = NULL;
305   }
306 
307   entrybox = gtk_hbox_new(FALSE, 5);
308   if(list_length) {
309     GSList *walk;
310     gint ii = 0;
311     banner.entries = g_new(NotificationBannerEntry, list_length);
312     for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
313       GtkWidget *label1;
314       GtkWidget *label2;
315       GtkWidget *label3;
316       GtkWidget *label4;
317       GtkWidget *label5;
318       GtkWidget *label6;
319 			GtkWidget *ebox;
320       CollectedMsg *cmsg = walk->data;
321 
322       if(ii > 0) {
323 				GtkWidget *separator;
324 				separator = gtk_vseparator_new();
325 				gtk_box_pack_start(GTK_BOX(entrybox), separator, FALSE, FALSE, 0);
326       }
327 
328 			ebox = gtk_event_box_new();
329 			gtk_box_pack_start(GTK_BOX(entrybox), ebox, FALSE, FALSE, 0);
330 			gtk_widget_set_events(ebox,
331 														GDK_POINTER_MOTION_MASK |
332 														GDK_BUTTON_PRESS_MASK);
333 
334 			if(notify_config.banner_enable_colors)
335 				gtk_widget_modify_bg(ebox,GTK_STATE_NORMAL,&bg);
336 
337       banner.entries[ii].table = gtk_table_new(3, 2, FALSE);
338 			gtk_container_add(GTK_CONTAINER(ebox),banner.entries[ii].table);
339 			g_signal_connect(ebox, "enter-notify-event",
340 											 G_CALLBACK(notification_banner_swap_colors),
341 											 banner.entries[ii].table);
342 			g_signal_connect(ebox, "leave-notify-event",
343 											 G_CALLBACK(notification_banner_swap_colors),
344 											 banner.entries[ii].table);
345 			g_signal_connect(ebox, "button-press-event",
346 											 G_CALLBACK(notification_banner_button_press),
347 											 cmsg);
348 
349       label1 = gtk_label_new(prefs_common_translated_header_name("From:"));
350       gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
351       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
352 				label1, 0, 1, 0, 1);
353       label2 = gtk_label_new(prefs_common_translated_header_name("Subject:"));
354       gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
355       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
356 				label2, 0, 1, 1, 2);
357       label3 = gtk_label_new(_("Folder:"));
358       gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5);
359       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
360 				label3, 0, 1, 2, 3);
361 
362       label4 = gtk_label_new(cmsg->from);
363       gtk_misc_set_alignment(GTK_MISC(label4), 0, 0.5);
364       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
365 				label4, 1, 2, 0, 1);
366       label5 = gtk_label_new(cmsg->subject);
367       gtk_misc_set_alignment(GTK_MISC(label5), 0, 0.5);
368       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
369 				label5, 1, 2, 1, 2);
370       label6 = gtk_label_new(cmsg->folderitem_name);
371       gtk_misc_set_alignment(GTK_MISC(label6), 0, 0.5);
372       gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
373 				label6, 1, 2, 2, 3);
374       gtk_table_set_col_spacings(GTK_TABLE(banner.entries[ii].table), 5);
375 			ii++;
376       /* Color */
377       if(notify_config.banner_enable_colors) {
378 				gtk_widget_modify_fg(label1,GTK_STATE_NORMAL,&fg);
379 				gtk_widget_modify_fg(label2,GTK_STATE_NORMAL,&fg);
380 				gtk_widget_modify_fg(label3,GTK_STATE_NORMAL,&fg);
381 				gtk_widget_modify_fg(label4,GTK_STATE_NORMAL,&fg);
382 				gtk_widget_modify_fg(label5,GTK_STATE_NORMAL,&fg);
383 				gtk_widget_modify_fg(label6,GTK_STATE_NORMAL,&fg);
384       }
385     }
386   }
387   else {
388     /* We have an empty list -- create an empty dummy element */
389     GtkWidget *label;
390 
391     banner.entries = g_new(NotificationBannerEntry, 1);
392     banner.entries[0].table = gtk_table_new(3, 1, FALSE);
393     label = gtk_label_new("");
394     gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table),
395 			      label, 0, 1, 0, 1);
396     label = gtk_label_new("");
397     gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table),
398 			      label, 0, 1, 1, 2);
399     label = gtk_label_new("");
400     gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table),
401 			      label, 0, 1, 2, 3);
402     gtk_box_pack_start(GTK_BOX(entrybox), banner.entries[0].table,
403 		       FALSE, FALSE, 0);
404   }
405   return entrybox;
406 }
407 
notification_banner_swap_colors(GtkWidget * widget,GdkEventCrossing * event,gpointer data)408 static gboolean notification_banner_swap_colors(GtkWidget *widget,
409 																								GdkEventCrossing *event,
410 																								gpointer data)
411 {
412 	GList *children;
413 	GList *walk;
414 	GdkColor *old_bg;
415 
416 	children = gtk_container_get_children(GTK_CONTAINER(data));
417 
418 	old_bg = gdk_color_copy(&(gtk_widget_get_style(widget)->bg[GTK_STATE_NORMAL]));
419 	if(children)
420 		gtk_widget_modify_bg(widget,GTK_STATE_NORMAL,
421 												 &(gtk_widget_get_style(GTK_WIDGET(children->data))->fg[GTK_STATE_NORMAL]));
422 
423 	for(walk = children; walk; walk = walk->next)
424 		gtk_widget_modify_fg(GTK_WIDGET(walk->data),GTK_STATE_NORMAL,old_bg);
425 
426 	g_list_free(children);
427 	gdk_color_free(old_bg);
428 	return FALSE;
429 }
430 
notification_banner_button_press(GtkWidget * widget,GdkEventButton * button,gpointer data)431 static gboolean notification_banner_button_press(GtkWidget *widget,
432 																								 GdkEventButton *button,
433 																								 gpointer data)
434 {
435 	gboolean done;
436 	done = FALSE;
437 	if(button->type == GDK_BUTTON_PRESS) {
438 		CollectedMsg *cmsg = (CollectedMsg*) data;
439     if(button->button == 1) {
440 			/* jump to that message */
441 			if(cmsg->msginfo) {
442 				gchar *path;
443 				path = procmsg_get_message_file_path(cmsg->msginfo);
444 				mainwindow_jump_to(path, TRUE);
445 				g_free(path);
446 			}
447 			done = TRUE;
448 		}
449 		else if(button->button == 2) {
450       gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
451 																 button->button, button->x_root, button->y_root,
452 																 button->time);
453 		}
454 		else if(button->button == 3) {
455 			current_msginfo = cmsg->msginfo;
456 			notification_banner_show_popup(widget, button);
457 			banner_popup_open = TRUE;
458 			done = TRUE;
459 		}
460 	}
461 	return done;
462 }
463 
notification_banner_show_popup(GtkWidget * widget,GdkEventButton * event)464 static void notification_banner_show_popup(GtkWidget *widget,
465 																					 GdkEventButton *event)
466 {
467   int button, event_time;
468 
469   if(event) {
470 		button = event->button;
471 		event_time = event->time;
472 	}
473   else {
474 		button = 0;
475 		event_time = gtk_get_current_event_time();
476 	}
477 
478   gtk_menu_popup(GTK_MENU(banner_popup), NULL, NULL, NULL, NULL,
479 								 button, event_time);
480 }
481 
notification_banner_popup_done(GtkMenuShell * menushell,gpointer user_data)482 static void notification_banner_popup_done(GtkMenuShell *menushell,
483 																					 gpointer      user_data)
484 {
485 	current_msginfo = NULL;
486 	banner_popup_open = FALSE;
487 }
488 
banner_menu_reply_cb(GtkAction * action,gpointer data)489 static void banner_menu_reply_cb(GtkAction *action, gpointer data)
490 {
491 	MainWindow *mainwin;
492 	MessageView *messageview;
493 	GSList *msginfo_list = NULL;
494 
495 	if(!(mainwin = mainwindow_get_mainwindow()))
496 		return;
497 
498 	if(!(messageview = (MessageView*)mainwin->messageview))
499 		return;
500 
501 	g_return_if_fail(current_msginfo);
502 
503 	msginfo_list = g_slist_prepend(msginfo_list, current_msginfo);
504 	compose_reply_from_messageview(messageview, msginfo_list,
505 				       prefs_common_get_prefs()->reply_with_quote ?
506 				       COMPOSE_REPLY_WITH_QUOTE : COMPOSE_REPLY_WITHOUT_QUOTE);
507 	g_slist_free(msginfo_list);
508 }
509 
510 #endif /* NOTIFICATION_BANNER */
511