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/>.
22 # include "claws-features.h"
26 #include <glib/gi18n.h>
28 #ifdef NOTIFICATION_BANNER
32 #include "gtk/gtkutils.h"
34 #include "prefs_common.h"
35 #include "mainwindow.h"
38 #include "messageview.h"
41 #include "notification_core.h"
42 #include "notification_prefs.h"
43 #include "notification_banner.h"
47 } NotificationBannerEntry
;
51 GtkWidget
*scrolled_win
;
54 NotificationBannerEntry
*entries
;
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
*,
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
)
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
);
108 notification_banner_destroy();
112 void notification_banner_destroy(void)
116 g_free(banner
.entries
);
117 banner
.entries
= NULL
;
119 gtk_widget_destroy(banner
.window
);
120 banner
.window
= NULL
;
123 sdata
.banner_width
= 0;
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
;
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);
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
);
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
));
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
);
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
);
184 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 5);
185 gtk_container_add(GTK_CONTAINER(viewport
),hbox
);
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
);
194 gtk_widget_get_preferred_size(hbox
, &requisition
, NULL
);
196 if (notify_config
.banner_width
> 0)
197 banner_width
= notify_config
.banner_width
;
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
,
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
);
216 sdata
.banner_width
= requisition_after
.width
- requisition
.width
;
217 sdata
.adj
= gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW
218 (banner
.scrolled_win
));
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
,
229 banner
.scrolling
= FALSE
;
230 if(banner
.timeout_id
) {
231 g_source_remove(banner
.timeout_id
);
232 banner
.timeout_id
= 0;
235 sdata
.banner_width
= 0;
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
),
246 MENUITEM_ADDUI_MANAGER(banner_ui_manager
, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR
)
247 MENUITEM_ADDUI_MANAGER(banner_ui_manager
, "/Menus", "BannerPopup", "BannerPopup",
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
),
258 static gboolean
notification_banner_configure(GtkWidget
*widget
,
259 GdkEventConfigure
*event
,
262 gtk_window_get_position(GTK_WINDOW(gtk_widget_get_toplevel(widget
)),
263 &(notify_config
.banner_root_x
),
264 &(notify_config
.banner_root_y
));
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();
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);
282 gtk_adjustment_set_value(sdata
.adj
, 0);
285 while(gtk_events_pending())
286 gtk_main_iteration();
288 return banner
.scrolling
;
291 static GtkWidget
* create_entrybox(GSList
*msg_list
)
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);
314 banner
.entries
= g_new(NotificationBannerEntry
, list_length
);
315 for (walk
= msg_list
; walk
!= NULL
; walk
= g_slist_next(walk
)) {
323 CollectedMsg
*cmsg
= walk
->data
;
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
),
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);
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
);
390 /* We have an empty list -- create an empty dummy element */
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
,
414 static gboolean
notification_banner_swap_colors(GtkWidget
*widget
,
415 GdkEventCrossing
*event
,
422 children
= gtk_container_get_children(GTK_CONTAINER(data
));
424 old_bg
= gdk_color_copy(&(gtk_widget_get_style(widget
)->bg
[GTK_STATE_NORMAL
]));
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
);
437 static gboolean
notification_banner_button_press(GtkWidget
*widget
,
438 GdkEventButton
*button
,
443 if (button
->type
== GDK_BUTTON_PRESS
) {
444 CollectedMsg
*cmsg
= (CollectedMsg
*) data
;
445 if (button
->button
== 1) {
446 /* jump to that message */
449 path
= procmsg_get_message_file_path(cmsg
->msginfo
);
450 mainwindow_jump_to(path
, 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
,
458 } else if (button
->button
== 3) {
459 current_msginfo
= cmsg
->msginfo
;
460 notification_banner_show_popup(widget
, button
);
461 banner_popup_open
= TRUE
;
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
,
477 current_msginfo
= NULL
;
478 banner_popup_open
= FALSE
;
481 static void banner_menu_reply_cb(GtkAction
*action
, gpointer data
)
484 MessageView
*messageview
;
485 GSList
*msginfo_list
= NULL
;
487 if(!(mainwin
= mainwindow_get_mainwindow()))
490 if(!(messageview
= (MessageView
*)mainwin
->messageview
))
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 */