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