1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2017 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23 
24 #include <stddef.h>
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30 
31 #include "hooks.h"
32 #include "mainwindow.h"
33 #include "alertpanel.h"
34 #include "manage_window.h"
35 #include "utils.h"
36 #include "gtkutils.h"
37 #include "inc.h"
38 #include "logwindow.h"
39 #include "prefs_common.h"
40 
41 #define ALERT_PANEL_WIDTH	380
42 #define TITLE_HEIGHT		72
43 #define MESSAGE_HEIGHT		62
44 #define ALERT_PANEL_BUFSIZE	1024
45 
46 static AlertValue value;
47 static gboolean alertpanel_is_open = FALSE;
48 static GtkWidget *dialog;
49 
50 static void alertpanel_show		(void);
51 static void alertpanel_create		(const gchar	*title,
52 					 const gchar	*message,
53 					 const gchar	*button1_label,
54 					 const gchar	*button2_label,
55 					 const gchar	*button3_label,
56 					 AlertFocus    focus,
57 					 gboolean	 can_disable,
58 					 GtkWidget	*custom_widget,
59 					 gint		 alert_type);
60 
61 static void alertpanel_button_toggled	(GtkToggleButton	*button,
62 					 gpointer		 data);
63 static void alertpanel_button_clicked	(GtkWidget		*widget,
64 					 gpointer		 data);
65 static gint alertpanel_deleted		(GtkWidget		*widget,
66 					 GdkEventAny		*event,
67 					 gpointer		 data);
68 static gboolean alertpanel_close	(GtkWidget		*widget,
69 					 GdkEventAny		*event,
70 					 gpointer		 data);
71 
alertpanel_with_widget(const gchar * title,const gchar * message,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label,AlertFocus focus,gboolean can_disable,GtkWidget * widget)72 AlertValue alertpanel_with_widget(const gchar *title,
73 				  const gchar *message,
74 				  const gchar *button1_label,
75 				  const gchar *button2_label,
76 				  const gchar *button3_label,
77 					AlertFocus   focus,
78 				  gboolean     can_disable,
79 				  GtkWidget   *widget)
80 {
81 	return alertpanel_full(title, message, button1_label,
82 				    button2_label, button3_label, focus,
83 				    can_disable, widget, ALERT_QUESTION);
84 }
85 
alertpanel_full(const gchar * title,const gchar * message,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label,AlertFocus focus,gboolean can_disable,GtkWidget * widget,AlertType alert_type)86 AlertValue alertpanel_full(const gchar *title, const gchar *message,
87 			   const gchar *button1_label,
88 			   const gchar *button2_label,
89 			   const gchar *button3_label,
90 				 AlertFocus   focus,
91 			   gboolean     can_disable,
92 			   GtkWidget   *widget,
93 			   AlertType    alert_type)
94 {
95 	if (alertpanel_is_open)
96 		return -1;
97 	else {
98 		alertpanel_is_open = TRUE;
99 		hooks_invoke(ALERTPANEL_OPENED_HOOKLIST, &alertpanel_is_open);
100 	}
101 	alertpanel_create(title, message, button1_label, button2_label,
102 			  button3_label, focus, can_disable, widget, alert_type);
103 	alertpanel_show();
104 
105 	debug_print("return value = %d\n", value);
106 	return value;
107 }
108 
alertpanel(const gchar * title,const gchar * message,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label,AlertFocus focus)109 AlertValue alertpanel(const gchar *title,
110 		      const gchar *message,
111 		      const gchar *button1_label,
112 		      const gchar *button2_label,
113 		      const gchar *button3_label,
114 					AlertFocus   focus)
115 {
116 	return alertpanel_full(title, message, button1_label, button2_label,
117 			       button3_label, focus, FALSE, NULL, ALERT_QUESTION);
118 }
119 
alertpanel_message(const gchar * title,const gchar * message,gint type)120 static void alertpanel_message(const gchar *title, const gchar *message, gint type)
121 {
122 	if (alertpanel_is_open)
123 		return;
124 	else {
125 		alertpanel_is_open = TRUE;
126 		hooks_invoke(ALERTPANEL_OPENED_HOOKLIST, &alertpanel_is_open);
127 	}
128 
129 	alertpanel_create(title, message, GTK_STOCK_CLOSE, NULL, NULL,
130 			  ALERTFOCUS_FIRST, FALSE, NULL, type);
131 	alertpanel_show();
132 }
133 
alertpanel_notice(const gchar * format,...)134 void alertpanel_notice(const gchar *format, ...)
135 {
136 	va_list args;
137 	gchar buf[ALERT_PANEL_BUFSIZE];
138 
139 	va_start(args, format);
140 	g_vsnprintf(buf, sizeof(buf), format, args);
141 	va_end(args);
142 	strretchomp(buf);
143 
144 	alertpanel_message(_("Notice"), buf, ALERT_NOTICE);
145 }
146 
alertpanel_warning(const gchar * format,...)147 void alertpanel_warning(const gchar *format, ...)
148 {
149 	va_list args;
150 	gchar buf[ALERT_PANEL_BUFSIZE];
151 
152 	va_start(args, format);
153 	g_vsnprintf(buf, sizeof(buf), format, args);
154 	va_end(args);
155 	strretchomp(buf);
156 
157 	alertpanel_message(_("Warning"), buf, ALERT_WARNING);
158 }
159 
alertpanel_error(const gchar * format,...)160 void alertpanel_error(const gchar *format, ...)
161 {
162 	va_list args;
163 	gchar buf[ALERT_PANEL_BUFSIZE];
164 
165 	va_start(args, format);
166 	g_vsnprintf(buf, sizeof(buf), format, args);
167 	va_end(args);
168 	strretchomp(buf);
169 
170 	alertpanel_message(_("Error"), buf, ALERT_ERROR);
171 }
172 
173 /*!
174  *\brief	display an error with a View Log button
175  *
176  */
alertpanel_error_log(const gchar * format,...)177 void alertpanel_error_log(const gchar *format, ...)
178 {
179 	va_list args;
180 	int val;
181 	MainWindow *mainwin;
182 	gchar buf[ALERT_PANEL_BUFSIZE];
183 
184 	va_start(args, format);
185 	g_vsnprintf(buf, sizeof(buf), format, args);
186 	va_end(args);
187 	strretchomp(buf);
188 
189 	mainwin = mainwindow_get_mainwindow();
190 
191 	if (mainwin && mainwin->logwin) {
192 		mainwindow_clear_error(mainwin);
193 		val = alertpanel_full(_("Error"), buf, GTK_STOCK_CLOSE,
194 				      _("_View log"), NULL, ALERTFOCUS_FIRST, FALSE, NULL,
195 				      ALERT_ERROR);
196 		if (val == G_ALERTALTERNATE)
197 			log_window_show(mainwin->logwin);
198 	} else
199 		alertpanel_error("%s", buf);
200 }
201 
alertpanel_show(void)202 static void alertpanel_show(void)
203 {
204 	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
205 	manage_window_set_transient(GTK_WINDOW(dialog));
206 	gtk_widget_show_all(dialog);
207 	value = G_ALERTWAIT;
208 
209 	if (gdk_pointer_is_grabbed())
210 		gdk_pointer_ungrab(GDK_CURRENT_TIME);
211 	inc_lock();
212 	while ((value & G_ALERT_VALUE_MASK) == G_ALERTWAIT)
213 		gtk_main_iteration();
214 
215 	gtk_widget_destroy(dialog);
216 	GTK_EVENTS_FLUSH();
217 
218 	alertpanel_is_open = FALSE;
219 	hooks_invoke(ALERTPANEL_OPENED_HOOKLIST, &alertpanel_is_open);
220 
221 	inc_unlock();
222 }
223 
alertpanel_create(const gchar * title,const gchar * message,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label,AlertFocus focus,gboolean can_disable,GtkWidget * custom_widget,gint alert_type)224 static void alertpanel_create(const gchar *title,
225 			      const gchar *message,
226 			      const gchar *button1_label,
227 			      const gchar *button2_label,
228 			      const gchar *button3_label,
229 						AlertFocus   focus,
230 			      gboolean	   can_disable,
231 			      GtkWidget   *custom_widget,
232 			      gint	   alert_type)
233 {
234 	static PangoFontDescription *font_desc;
235 	GtkWidget *image;
236 	GtkWidget *label;
237 	GtkWidget *hbox;
238 	GtkWidget *vbox;
239 	GtkWidget *disable_checkbtn;
240 	GtkWidget *confirm_area;
241 	GtkWidget *button1;
242 	GtkWidget *button2;
243 	GtkWidget *button3;
244 	GtkWidget *focusbutton;
245 	const gchar *label2;
246 	const gchar *label3;
247 	gchar *tmp = title?g_markup_printf_escaped("%s", title)
248 			:g_strdup("");
249 	gchar *title_full = g_strdup_printf("<span weight=\"bold\" "
250 				"size=\"larger\">%s</span>",
251 				tmp);
252 	g_free(tmp);
253 	debug_print("Creating alert panel dialog...\n");
254 
255 	dialog = gtk_dialog_new();
256 	gtk_window_set_title(GTK_WINDOW(dialog), title);
257 	gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
258 
259 	gtk_window_set_default_size(GTK_WINDOW(dialog), 375, 100);
260 
261 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
262 	g_signal_connect(G_OBJECT(dialog), "delete_event",
263 			 G_CALLBACK(alertpanel_deleted),
264 			 (gpointer)G_ALERTCANCEL);
265 	g_signal_connect(G_OBJECT(dialog), "key_press_event",
266 			 G_CALLBACK(alertpanel_close),
267 			 (gpointer)G_ALERTCANCEL);
268 
269 	/* for title icon, label and message */
270 	hbox = gtk_hbox_new(FALSE, 12);
271 	gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
272 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
273 			   hbox, FALSE, FALSE, 0);
274 
275 	/* title icon */
276 	switch (alert_type) {
277 	case ALERT_QUESTION:
278 		image = gtk_image_new_from_stock
279 			(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
280 		break;
281 	case ALERT_WARNING:
282 		image = gtk_image_new_from_stock
283 			(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG);
284 		break;
285 	case ALERT_ERROR:
286 		image = gtk_image_new_from_stock
287 			(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG);
288 		break;
289 	case ALERT_NOTICE:
290 	default:
291 		image = gtk_image_new_from_stock
292 			(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
293 		break;
294 	}
295 	gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0);
296 	gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
297 
298 	vbox = gtk_vbox_new (FALSE, 12);
299 	gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
300 	gtk_widget_show (vbox);
301 
302 	label = gtk_label_new(title_full);
303 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
304 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
305 	gtk_label_set_use_markup(GTK_LABEL (label), TRUE);
306 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
307 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
308 	if (!font_desc) {
309 		gint size;
310 
311 		size = pango_font_description_get_size
312 			(gtk_widget_get_style(label)->font_desc);
313 		font_desc = pango_font_description_new();
314 		pango_font_description_set_weight
315 			(font_desc, PANGO_WEIGHT_BOLD);
316 		pango_font_description_set_size
317 			(font_desc, size * PANGO_SCALE_LARGE);
318 	}
319 	if (font_desc)
320 		gtk_widget_modify_font(label, font_desc);
321 	g_free(title_full);
322 
323 	label = gtk_label_new(message);
324 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
325 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
326 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
327 	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
328 	gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
329 	gtk_widget_set_can_focus(label, FALSE);
330 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
331 	gtk_widget_show(label);
332 
333 	/* Claws: custom widget */
334 	if (custom_widget) {
335 		gtk_box_pack_start(GTK_BOX(vbox), custom_widget, FALSE,
336 				   FALSE, 0);
337 	}
338 
339 	if (can_disable) {
340 		hbox = gtk_hbox_new(FALSE, 0);
341 		gtk_box_pack_start(GTK_BOX(
342 			gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox,
343 				   FALSE, FALSE, 0);
344 
345 		disable_checkbtn = gtk_check_button_new_with_label
346 			(_("Show this message next time"));
347 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(disable_checkbtn),
348 					     TRUE);
349 		gtk_box_pack_start(GTK_BOX(hbox), disable_checkbtn,
350 				   FALSE, FALSE, 12);
351 		g_signal_connect(G_OBJECT(disable_checkbtn), "toggled",
352 				 G_CALLBACK(alertpanel_button_toggled),
353 				 GUINT_TO_POINTER(G_ALERTDISABLE));
354 	}
355 
356 	/* for button(s) */
357 	if (!button1_label)
358 		button1_label = GTK_STOCK_OK;
359 	label2 = button2_label;
360 	label3 = button3_label;
361 
362 	gtkut_stock_button_set_create(&confirm_area,
363 				      &button1, button1_label,
364 				      button2_label ? &button2 : NULL, label2,
365 				      button3_label ? &button3 : NULL, label3);
366 
367 	gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dialog))),
368 			 confirm_area, FALSE, FALSE, 0);
369 	gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
370 
371 	/* Set focus on correct button as requested. */
372 	focusbutton = button1;
373 	switch (focus) {
374 		case ALERTFOCUS_SECOND:
375 			if (button2_label != NULL)
376 				focusbutton = button2;
377 			break;
378 		case ALERTFOCUS_THIRD:
379 			if (button3_label != NULL)
380 				focusbutton = button3;
381 			break;
382 		case ALERTFOCUS_FIRST:
383 		default:
384 			focusbutton = button1;
385 			break;
386 	}
387 	gtk_widget_grab_default(focusbutton);
388 	gtk_widget_grab_focus(focusbutton);
389 
390 	g_signal_connect(G_OBJECT(button1), "clicked",
391 			 G_CALLBACK(alertpanel_button_clicked),
392 			 GUINT_TO_POINTER(G_ALERTDEFAULT));
393 	if (button2_label)
394 		g_signal_connect(G_OBJECT(button2), "clicked",
395 				 G_CALLBACK(alertpanel_button_clicked),
396 				 GUINT_TO_POINTER(G_ALERTALTERNATE));
397 	if (button3_label)
398 		g_signal_connect(G_OBJECT(button3), "clicked",
399 				 G_CALLBACK(alertpanel_button_clicked),
400 				 GUINT_TO_POINTER(G_ALERTOTHER));
401 
402 	gtk_widget_show_all(dialog);
403 }
404 
alertpanel_button_toggled(GtkToggleButton * button,gpointer data)405 static void alertpanel_button_toggled(GtkToggleButton *button,
406 				      gpointer data)
407 {
408 	if (gtk_toggle_button_get_active(button))
409 		value &= ~GPOINTER_TO_UINT(data);
410 	else
411 		value |= GPOINTER_TO_UINT(data);
412 }
413 
alertpanel_button_clicked(GtkWidget * widget,gpointer data)414 static void alertpanel_button_clicked(GtkWidget *widget, gpointer data)
415 {
416 	value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data;
417 }
418 
alertpanel_deleted(GtkWidget * widget,GdkEventAny * event,gpointer data)419 static gint alertpanel_deleted(GtkWidget *widget, GdkEventAny *event,
420 			       gpointer data)
421 {
422 	value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data;
423 	return TRUE;
424 }
425 
alertpanel_close(GtkWidget * widget,GdkEventAny * event,gpointer data)426 static gboolean alertpanel_close(GtkWidget *widget, GdkEventAny *event,
427 				 gpointer data)
428 {
429 	if (event->type == GDK_KEY_PRESS)
430 		if (((GdkEventKey *)event)->keyval != GDK_KEY_Escape)
431 			return FALSE;
432 
433 	value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data;
434 	return FALSE;
435 }
436