Fix error creation and warning
[claws.git] / src / plugins / notification / notification_banner.c
blobb055e48f5415341f2f63a6ad4e1bf9ba25c7e750
1 /*
2 * Notification plugin for Claws Mail
3 * Claws Mail -- A GTK based, lightweight, and fast e-mail client
4 * Copyright (C) 2005-2021 Holger Berndt and the Claws Mail team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 # include "claws-features.h"
23 #endif
25 #include <glib.h>
26 #include <glib/gi18n.h>
28 #ifdef NOTIFICATION_BANNER
30 #include <gtk/gtk.h>
32 #include "gtk/gtkutils.h"
34 #include "prefs_common.h"
35 #include "mainwindow.h"
36 #include "menu.h"
37 #include "procmsg.h"
38 #include "messageview.h"
39 #include "compose.h"
41 #include "notification_core.h"
42 #include "notification_prefs.h"
43 #include "notification_banner.h"
45 typedef struct {
46 GtkWidget *table;
47 } NotificationBannerEntry;
49 typedef struct {
50 GtkWidget *window;
51 GtkWidget *scrolled_win;
52 GtkWidget *viewport;
54 NotificationBannerEntry *entries;
55 guint timeout_id;
56 gboolean scrolling;
57 } NotificationBanner;
59 typedef struct {
60 gint banner_width;
61 GtkAdjustment *adj;
62 } ScrollingData;
65 static void notification_banner_create(GSList*);
66 static gboolean scroller(gpointer data);
67 static GtkWidget* create_entrybox(GSList*);
68 static gboolean notification_banner_configure(GtkWidget*, GdkEventConfigure*,
69 gpointer);
70 static gboolean notification_banner_swap_colors(GtkWidget*,GdkEventCrossing*,gpointer);
71 static gboolean notification_banner_button_press(GtkWidget*,GdkEventButton*,gpointer);
72 static void notification_banner_show_popup(GtkWidget*,GdkEventButton*);
73 static void notification_banner_popup_done(GtkMenuShell*,gpointer);
75 static void banner_menu_reply_cb(GtkAction *,gpointer);
78 static NotificationBanner banner;
79 static ScrollingData sdata;
81 static GtkWidget *banner_popup;
82 static GtkUIManager *banner_ui_manager;
83 static GtkActionGroup *banner_action_group;
85 static gboolean banner_popup_open = FALSE;
87 static MsgInfo *current_msginfo = NULL;
89 /* Corresponding mutexes */
90 G_LOCK_DEFINE_STATIC(banner);
91 G_LOCK_DEFINE_STATIC(sdata);
93 static GtkActionEntry banner_popup_entries[] =
95 {"BannerPopup", NULL, "BannerPopup", NULL, NULL, NULL },
96 {"BannerPopup/Reply", NULL, N_("_Reply"), NULL, NULL, G_CALLBACK(banner_menu_reply_cb) },
100 void notification_banner_show(GSList *msg_list)
102 G_LOCK(banner);
103 if((notify_config.banner_show != NOTIFY_BANNER_SHOW_NEVER) &&
104 (g_slist_length(msg_list) ||
105 (notify_config.banner_show == NOTIFY_BANNER_SHOW_ALWAYS)))
106 notification_banner_create(msg_list);
107 else
108 notification_banner_destroy();
109 G_UNLOCK(banner);
112 void notification_banner_destroy(void)
114 if(banner.window) {
115 if(banner.entries) {
116 g_free(banner.entries);
117 banner.entries = NULL;
119 gtk_widget_destroy(banner.window);
120 banner.window = NULL;
121 G_LOCK(sdata);
122 sdata.adj = NULL;
123 sdata.banner_width = 0;
124 G_UNLOCK(sdata);
125 if(banner.timeout_id) {
126 g_source_remove(banner.timeout_id);
127 banner.timeout_id = 0;
132 static void notification_banner_create(GSList *msg_list)
134 GtkRequisition requisition, requisition_after;
135 GtkWidget *viewport;
136 GtkWidget *hbox;
137 GtkWidget *entrybox;
138 GdkColor bg;
139 gint banner_width;
141 /* Window */
142 if(!banner.window) {
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 } else {
157 if(banner.entries) {
158 g_free(banner.entries);
159 banner.entries = NULL;
161 gtk_widget_destroy(banner.scrolled_win);
163 if(notify_config.banner_sticky)
164 gtk_window_stick(GTK_WINDOW(banner.window));
165 else
166 gtk_window_unstick(GTK_WINDOW(banner.window));
168 /* Scrolled window */
169 banner.scrolled_win = gtk_scrolled_window_new(NULL, NULL);
170 gtk_container_add(GTK_CONTAINER(banner.window), banner.scrolled_win);
171 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(banner.scrolled_win),
172 GTK_POLICY_NEVER, GTK_POLICY_NEVER);
174 /* Viewport */
175 viewport = gtk_viewport_new(NULL,NULL);
176 banner.viewport = viewport;
177 gtk_container_add(GTK_CONTAINER(banner.scrolled_win),viewport);
178 if(notify_config.banner_enable_colors) {
179 GTKUT_GDKRGBA_TO_GDKCOLOR(notify_config.banner_color_bg,bg);
180 gtk_widget_modify_bg(viewport,GTK_STATE_NORMAL,&bg);
183 /* Hbox */
184 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
185 gtk_container_add(GTK_CONTAINER(viewport),hbox);
187 /* Entrybox */
188 entrybox = create_entrybox(msg_list);
189 gtk_box_pack_start(GTK_BOX(hbox), entrybox, FALSE, FALSE, 0);
191 gtk_widget_show_all(banner.window);
193 /* Scrolling */
194 gtk_widget_get_preferred_size(hbox, &requisition, NULL);
196 if (notify_config.banner_width > 0)
197 banner_width = notify_config.banner_width;
198 else
199 banner_width = gdk_screen_width();
201 if(requisition.width > banner_width) {
202 /* Line is too big for screen! */
203 /* Double the entrybox into hbox */
204 GtkWidget *separator, *second_entrybox;
206 separator = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
207 gtk_box_pack_start(GTK_BOX(hbox), separator,
208 FALSE, FALSE, 0);
209 second_entrybox = create_entrybox(msg_list);
210 gtk_box_pack_start(GTK_BOX(hbox), second_entrybox, FALSE, FALSE, 0);
212 gtk_widget_show_all(banner.window);
213 gtk_widget_get_preferred_size(hbox, &requisition_after, NULL);
215 G_LOCK(sdata);
216 sdata.banner_width = requisition_after.width - requisition.width;
217 sdata.adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW
218 (banner.scrolled_win));
219 G_UNLOCK(sdata);
221 banner.scrolling = TRUE;
222 if (banner.timeout_id) {
223 g_source_remove(banner.timeout_id);
224 banner.timeout_id = 0;
226 banner.timeout_id = g_timeout_add(notify_config.banner_speed, scroller,
227 NULL);
228 } else {
229 banner.scrolling = FALSE;
230 if(banner.timeout_id) {
231 g_source_remove(banner.timeout_id);
232 banner.timeout_id = 0;
234 G_LOCK(sdata);
235 sdata.banner_width = 0;
236 sdata.adj = NULL;
237 G_UNLOCK(sdata);
240 /* menu */
241 banner_ui_manager = gtk_ui_manager_new();
242 banner_action_group = cm_menu_create_action_group_full(banner_ui_manager,"BannerPopup",
243 banner_popup_entries,
244 G_N_ELEMENTS(banner_popup_entries),
245 (gpointer)NULL);
246 MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR)
247 MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/Menus", "BannerPopup", "BannerPopup",
248 GTK_UI_MANAGER_MENU)
249 MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/Menus/BannerPopup", "Reply", "BannerPopup/Reply",
250 GTK_UI_MANAGER_MENUITEM)
252 banner_popup = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
253 gtk_ui_manager_get_widget(banner_ui_manager, "/Menus/BannerPopup")) );
254 g_signal_connect(banner_popup,"selection-done", G_CALLBACK(notification_banner_popup_done),
255 NULL);
258 static gboolean notification_banner_configure(GtkWidget *widget,
259 GdkEventConfigure *event,
260 gpointer data)
262 gtk_window_get_position(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
263 &(notify_config.banner_root_x),
264 &(notify_config.banner_root_y));
265 return TRUE;
268 static gboolean scroller(gpointer data)
270 // don't scroll during popup open
271 if(banner_popup_open)
272 return banner.scrolling;
274 while(gtk_events_pending())
275 gtk_main_iteration();
276 G_LOCK(sdata);
277 if (sdata.adj && GTK_IS_ADJUSTMENT(sdata.adj)) {
278 if (gtk_adjustment_get_value(sdata.adj) != sdata.banner_width)
279 gtk_adjustment_set_value(sdata.adj,
280 gtk_adjustment_get_value(sdata.adj) + 1);
281 else
282 gtk_adjustment_set_value(sdata.adj, 0);
284 G_UNLOCK(sdata);
285 while(gtk_events_pending())
286 gtk_main_iteration();
288 return banner.scrolling;
291 static GtkWidget* create_entrybox(GSList *msg_list)
293 GtkWidget *entrybox;
294 GdkColor fg;
295 GdkColor bg;
296 gint list_length;
298 list_length = g_slist_length(msg_list);
300 if (notify_config.banner_enable_colors) {
301 GTKUT_GDKRGBA_TO_GDKCOLOR(notify_config.banner_color_bg,bg);
302 GTKUT_GDKRGBA_TO_GDKCOLOR(notify_config.banner_color_fg,fg);
305 if (banner.entries) {
306 g_free(banner.entries);
307 banner.entries = NULL;
310 entrybox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
311 if (list_length) {
312 GSList *walk;
313 gint ii = 0;
314 banner.entries = g_new(NotificationBannerEntry, list_length);
315 for (walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
316 GtkWidget *label1;
317 GtkWidget *label2;
318 GtkWidget *label3;
319 GtkWidget *label4;
320 GtkWidget *label5;
321 GtkWidget *label6;
322 GtkWidget *ebox;
323 CollectedMsg *cmsg = walk->data;
325 if (ii > 0) {
326 GtkWidget *separator;
327 separator = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
328 gtk_box_pack_start(GTK_BOX(entrybox), separator, FALSE, FALSE, 0);
331 ebox = gtk_event_box_new();
332 gtk_box_pack_start(GTK_BOX(entrybox), ebox, FALSE, FALSE, 0);
333 gtk_widget_set_events(ebox,
334 GDK_POINTER_MOTION_MASK |
335 GDK_BUTTON_PRESS_MASK);
337 if (notify_config.banner_enable_colors)
338 gtk_widget_modify_bg(ebox,GTK_STATE_NORMAL,&bg);
340 banner.entries[ii].table = gtk_grid_new();
341 gtk_container_add(GTK_CONTAINER(ebox),banner.entries[ii].table);
342 g_signal_connect(ebox, "enter-notify-event",
343 G_CALLBACK(notification_banner_swap_colors),
344 banner.entries[ii].table);
345 g_signal_connect(ebox, "leave-notify-event",
346 G_CALLBACK(notification_banner_swap_colors),
347 banner.entries[ii].table);
348 g_signal_connect(ebox, "button-press-event",
349 G_CALLBACK(notification_banner_button_press),
350 cmsg);
352 label1 = gtk_label_new(prefs_common_translated_header_name("From:"));
353 gtk_label_set_xalign(GTK_LABEL(label1), 0.0);
354 gtk_grid_attach(GTK_GRID(banner.entries[ii].table), label1, 0, 0, 1, 1);
356 label2 = gtk_label_new(prefs_common_translated_header_name("Subject:"));
357 gtk_label_set_xalign(GTK_LABEL(label2), 0.0);
358 gtk_grid_attach(GTK_GRID(banner.entries[ii].table), label2, 0, 1, 1, 1);
360 label3 = gtk_label_new(_("Folder:"));
361 gtk_label_set_xalign(GTK_LABEL(label3), 0.0);
362 gtk_grid_attach(GTK_GRID(banner.entries[ii].table), label3, 0, 2, 1, 1);
364 label4 = gtk_label_new(cmsg->from);
365 gtk_label_set_xalign(GTK_LABEL(label4), 0.0);
366 gtk_grid_attach(GTK_GRID(banner.entries[ii].table), label4, 1, 0, 1, 1);
368 label5 = gtk_label_new(cmsg->subject);
369 gtk_label_set_xalign(GTK_LABEL(label5), 0.0);
370 gtk_grid_attach(GTK_GRID(banner.entries[ii].table), label5, 1, 1, 1, 1);
372 label6 = gtk_label_new(cmsg->folderitem_name);
373 gtk_label_set_xalign(GTK_LABEL(label6), 0.0);
374 gtk_grid_attach(GTK_GRID(banner.entries[ii].table), label6, 1, 2, 1, 1);
376 gtk_grid_set_column_spacing(GTK_GRID(banner.entries[ii].table), 5);
378 ii++;
379 /* Color */
380 if(notify_config.banner_enable_colors) {
381 gtk_widget_modify_fg(label1,GTK_STATE_NORMAL,&fg);
382 gtk_widget_modify_fg(label2,GTK_STATE_NORMAL,&fg);
383 gtk_widget_modify_fg(label3,GTK_STATE_NORMAL,&fg);
384 gtk_widget_modify_fg(label4,GTK_STATE_NORMAL,&fg);
385 gtk_widget_modify_fg(label5,GTK_STATE_NORMAL,&fg);
386 gtk_widget_modify_fg(label6,GTK_STATE_NORMAL,&fg);
389 } else {
390 /* We have an empty list -- create an empty dummy element */
391 GtkWidget *label;
393 banner.entries = g_new(NotificationBannerEntry, 1);
394 banner.entries[0].table = gtk_grid_new();
395 label = gtk_label_new("");
396 gtk_grid_attach(GTK_GRID(banner.entries[0].table), label, 0, 0, 1, 1);
397 gtk_widget_set_hexpand(label, TRUE);
398 gtk_widget_set_halign(label, GTK_ALIGN_FILL);
399 label = gtk_label_new("");
400 gtk_grid_attach(GTK_GRID(banner.entries[0].table), label, 0, 1, 1, 1);
401 gtk_widget_set_hexpand(label, TRUE);
402 gtk_widget_set_halign(label, GTK_ALIGN_FILL);
403 label = gtk_label_new("");
404 gtk_grid_attach(GTK_GRID(banner.entries[0].table), label, 0, 2, 1, 1);
405 gtk_widget_set_hexpand(label, TRUE);
406 gtk_widget_set_halign(label, GTK_ALIGN_FILL);
407 gtk_box_pack_start(GTK_BOX(entrybox), banner.entries[0].table,
408 FALSE, FALSE, 0);
411 return entrybox;
414 static gboolean notification_banner_swap_colors(GtkWidget *widget,
415 GdkEventCrossing *event,
416 gpointer data)
418 GList *children;
419 GList *walk;
420 GdkColor *old_bg;
422 children = gtk_container_get_children(GTK_CONTAINER(data));
424 old_bg = gdk_color_copy(&(gtk_widget_get_style(widget)->bg[GTK_STATE_NORMAL]));
425 if (children)
426 gtk_widget_modify_bg(widget,GTK_STATE_NORMAL,
427 &(gtk_widget_get_style(GTK_WIDGET(children->data))->fg[GTK_STATE_NORMAL]));
429 for (walk = children; walk; walk = walk->next)
430 gtk_widget_modify_fg(GTK_WIDGET(walk->data),GTK_STATE_NORMAL,old_bg);
432 g_list_free(children);
433 gdk_color_free(old_bg);
434 return FALSE;
437 static gboolean notification_banner_button_press(GtkWidget *widget,
438 GdkEventButton *button,
439 gpointer data)
441 gboolean done;
442 done = FALSE;
443 if (button->type == GDK_BUTTON_PRESS) {
444 CollectedMsg *cmsg = (CollectedMsg*) data;
445 if (button->button == 1) {
446 /* jump to that message */
447 if (cmsg->msginfo) {
448 gchar *path;
449 path = procmsg_get_message_file_path(cmsg->msginfo);
450 mainwindow_jump_to(path, TRUE);
451 g_free(path);
453 done = TRUE;
454 } else if (button->button == 2) {
455 gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
456 button->button, button->x_root, button->y_root,
457 button->time);
458 } else if (button->button == 3) {
459 current_msginfo = cmsg->msginfo;
460 notification_banner_show_popup(widget, button);
461 banner_popup_open = TRUE;
462 done = TRUE;
465 return done;
468 static void notification_banner_show_popup(GtkWidget *widget,
469 GdkEventButton *event)
471 gtk_menu_popup_at_pointer(GTK_MENU(banner_popup), NULL);
474 static void notification_banner_popup_done(GtkMenuShell *menushell,
475 gpointer user_data)
477 current_msginfo = NULL;
478 banner_popup_open = FALSE;
481 static void banner_menu_reply_cb(GtkAction *action, gpointer data)
483 MainWindow *mainwin;
484 MessageView *messageview;
485 GSList *msginfo_list = NULL;
487 if(!(mainwin = mainwindow_get_mainwindow()))
488 return;
490 if(!(messageview = (MessageView*)mainwin->messageview))
491 return;
493 g_return_if_fail(current_msginfo);
495 msginfo_list = g_slist_prepend(msginfo_list, current_msginfo);
496 compose_reply_from_messageview(messageview, msginfo_list,
497 prefs_common_get_prefs()->reply_with_quote ?
498 COMPOSE_REPLY_WITH_QUOTE : COMPOSE_REPLY_WITHOUT_QUOTE);
499 g_slist_free(msginfo_list);
502 #endif /* NOTIFICATION_BANNER */