1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2014 Hiroyuki Yamamoto
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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23 
24 #include "defs.h"
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30 
31 #include "alertpanel.h"
32 #include "mainwindow.h"
33 #include "manage_window.h"
34 #include "utils.h"
35 #include "gtkutils.h"
36 #include "inc.h"
37 #include "prefs_common.h"
38 
39 #define ALERT_PANEL_WIDTH	380
40 #define TITLE_HEIGHT		72
41 #define MESSAGE_HEIGHT		62
42 
43 static gboolean alertpanel_is_open = FALSE;
44 static AlertValue value;
45 
46 static GtkWidget *dialog;
47 
48 static void alertpanel_show		(void);
49 static void alertpanel_create		(const gchar	*title,
50 					 const gchar	*message,
51 					 AlertType	 type,
52 					 AlertValue      default_value,
53 					 gboolean	 can_disable,
54 					 const gchar	*button1_label,
55 					 const gchar	*button2_label,
56 					 const gchar	*button3_label);
57 
58 static void alertpanel_button_toggled	(GtkToggleButton	*button,
59 					 gpointer		 data);
60 static void alertpanel_button_clicked	(GtkWidget		*widget,
61 					 gpointer		 data);
62 static gint alertpanel_deleted		(GtkWidget		*widget,
63 					 GdkEventAny		*event,
64 					 gpointer		 data);
65 static gboolean alertpanel_close	(GtkWidget		*widget,
66 					 GdkEventAny		*event,
67 					 gpointer		 data);
68 static gint alertpanel_focus_out	(GtkWidget		*widget,
69 					 GdkEventFocus		*event,
70 					 gpointer		 data);
71 
alertpanel_full(const gchar * title,const gchar * message,AlertType type,AlertValue default_value,gboolean can_disable,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label)72 AlertValue alertpanel_full(const gchar *title, const gchar *message,
73 			   AlertType type, AlertValue default_value,
74 			   gboolean can_disable,
75 			   const gchar *button1_label,
76 			   const gchar *button2_label,
77 			   const gchar *button3_label)
78 {
79 	if (alertpanel_is_open)
80 		return -1;
81 	else
82 		alertpanel_is_open = TRUE;
83 
84 	alertpanel_create(title, message, type, default_value, can_disable,
85 			  button1_label, button2_label, button3_label);
86 	alertpanel_show();
87 
88 	debug_print("return value = %d\n", value);
89 	return value;
90 }
91 
alertpanel(const gchar * title,const gchar * message,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label)92 AlertValue alertpanel(const gchar *title,
93 		      const gchar *message,
94 		      const gchar *button1_label,
95 		      const gchar *button2_label,
96 		      const gchar *button3_label)
97 {
98 	return alertpanel_full(title, message, ALERT_QUESTION, G_ALERTDEFAULT,
99 			       FALSE,
100 			       button1_label, button2_label, button3_label);
101 }
102 
alertpanel_message(const gchar * title,const gchar * message,AlertType type)103 void alertpanel_message(const gchar *title, const gchar *message,
104 			AlertType type)
105 {
106 	if (alertpanel_is_open)
107 		return;
108 	else
109 		alertpanel_is_open = TRUE;
110 
111 	alertpanel_create(title, message, type, G_ALERTDEFAULT, FALSE,
112 			  NULL, NULL, NULL);
113 	alertpanel_show();
114 }
115 
alertpanel_message_with_disable(const gchar * title,const gchar * message,AlertType type)116 AlertValue alertpanel_message_with_disable(const gchar *title,
117 					   const gchar *message,
118 					   AlertType type)
119 {
120 	if (alertpanel_is_open)
121 		return 0;
122 	else
123 		alertpanel_is_open = TRUE;
124 
125 	alertpanel_create(title, message, type, G_ALERTDEFAULT, TRUE,
126 			  NULL, NULL, NULL);
127 	alertpanel_show();
128 
129 	return value;
130 }
131 
alertpanel_notice(const gchar * format,...)132 void alertpanel_notice(const gchar *format, ...)
133 {
134 	va_list args;
135 	gchar buf[256];
136 
137 	va_start(args, format);
138 	g_vsnprintf(buf, sizeof(buf), format, args);
139 	va_end(args);
140 	strretchomp(buf);
141 
142 	alertpanel_message(_("Notice"), buf, ALERT_NOTICE);
143 }
144 
alertpanel_warning(const gchar * format,...)145 void alertpanel_warning(const gchar *format, ...)
146 {
147 	va_list args;
148 	gchar buf[256];
149 
150 	va_start(args, format);
151 	g_vsnprintf(buf, sizeof(buf), format, args);
152 	va_end(args);
153 	strretchomp(buf);
154 
155 	alertpanel_message(_("Warning"), buf, ALERT_WARNING);
156 }
157 
alertpanel_error(const gchar * format,...)158 void alertpanel_error(const gchar *format, ...)
159 {
160 	va_list args;
161 	gchar buf[256];
162 
163 	va_start(args, format);
164 	g_vsnprintf(buf, sizeof(buf), format, args);
165 	va_end(args);
166 	strretchomp(buf);
167 
168 	alertpanel_message(_("Error"), buf, ALERT_ERROR);
169 }
170 
alertpanel_show(void)171 static void alertpanel_show(void)
172 {
173 	gint x, y, w, h, sx, sy;
174 	value = G_ALERTWAIT;
175 
176 	inc_lock();
177 
178 	sx = gdk_screen_width();
179 	sy = gdk_screen_height();
180 	gdk_window_get_origin(dialog->window, &x, &y);
181 	w = dialog->allocation.width;
182 	h = dialog->allocation.height;
183 	if (x < 0 || y < 0 || x + w > sx || y + h > sy) {
184 		debug_print("sx, sy,  x, y,  w, h = %d, %d,  %d, %d,  %d, %d\n",
185 			    sx, sy, x, y, w, h);
186 		debug_print("alert dialog position out of range\n");
187 		gtk_window_set_position(GTK_WINDOW(dialog),
188 					GTK_WIN_POS_CENTER_ALWAYS);
189 	}
190 
191 	while ((value & G_ALERT_VALUE_MASK) == G_ALERTWAIT)
192 		gtk_main_iteration();
193 
194 	gtk_widget_destroy(dialog);
195 	GTK_EVENTS_FLUSH();
196 
197 	alertpanel_is_open = FALSE;
198 	inc_unlock();
199 }
200 
alertpanel_create(const gchar * title,const gchar * message,AlertType type,AlertValue default_value,gboolean can_disable,const gchar * button1_label,const gchar * button2_label,const gchar * button3_label)201 static void alertpanel_create(const gchar *title,
202 			      const gchar *message,
203 			      AlertType    type,
204 			      AlertValue   default_value,
205 			      gboolean	   can_disable,
206 			      const gchar *button1_label,
207 			      const gchar *button2_label,
208 			      const gchar *button3_label)
209 {
210 	static PangoFontDescription *font_desc;
211 	GtkWidget *image;
212 	GtkWidget *label;
213 	GtkWidget *hbox;
214 	GtkWidget *vbox;
215 	GtkWidget *disable_chkbtn;
216 	GtkWidget *confirm_area;
217 	GtkWidget *button1;
218 	GtkWidget *button2;
219 	GtkWidget *button3;
220 	const gchar *label2;
221 	const gchar *label3;
222 	gint spacing;
223 
224 	debug_print(_("Creating alert panel dialog...\n"));
225 
226 	dialog = gtk_dialog_new();
227 	gtk_window_set_title(GTK_WINDOW(dialog), title);
228 	gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
229 	gtk_window_set_position(GTK_WINDOW(dialog),
230 				GTK_WIN_POS_CENTER_ON_PARENT);
231 	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
232 	manage_window_set_transient(GTK_WINDOW(dialog));
233 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
234 	gtk_widget_realize(dialog);
235 	g_signal_connect(G_OBJECT(dialog), "delete_event",
236 			 G_CALLBACK(alertpanel_deleted),
237 			 (gpointer)G_ALERTCANCEL);
238 	g_signal_connect(G_OBJECT(dialog), "key_press_event",
239 			 G_CALLBACK(alertpanel_close),
240 			 (gpointer)G_ALERTCANCEL);
241 	g_signal_connect(G_OBJECT(dialog), "focus_out_event",
242 			 G_CALLBACK(alertpanel_focus_out), NULL);
243 
244 	/* for title icon, label and message */
245 	spacing = 12 * gtkut_get_dpi_multiplier();
246 	hbox = gtk_hbox_new(FALSE, spacing);
247 	gtk_container_set_border_width(GTK_CONTAINER(hbox), spacing);
248 	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
249 			   hbox, FALSE, FALSE, 0);
250 
251 	/* title icon */
252 	switch (type) {
253 	case ALERT_QUESTION:
254 		image = gtk_image_new_from_stock
255 			(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
256 		break;
257 	case ALERT_WARNING:
258 		image = gtk_image_new_from_stock
259 			(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG);
260 		break;
261 	case ALERT_ERROR:
262 		image = gtk_image_new_from_stock
263 			(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG);
264 		break;
265 	case ALERT_NOTICE:
266 	default:
267 		image = gtk_image_new_from_stock
268 			(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
269 		break;
270 	}
271 	gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0);
272 	gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
273 
274 	/* for title and message */
275 	vbox = gtk_vbox_new(FALSE, spacing);
276 	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
277 
278 	label = gtk_label_new(title);
279 	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
280 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
281 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
282 	if (!font_desc) {
283 		gint size;
284 
285 		size = pango_font_description_get_size
286 			(label->style->font_desc);
287 		font_desc = pango_font_description_new();
288 		pango_font_description_set_weight
289 			(font_desc, PANGO_WEIGHT_BOLD);
290 		pango_font_description_set_size
291 			(font_desc, size * PANGO_SCALE_LARGE);
292 	}
293 	if (font_desc)
294 		gtk_widget_modify_font(label, font_desc);
295 
296 	/* message label */
297 	label = gtk_label_new(message);
298 	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
299 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
300 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
301 	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
302 	GTK_WIDGET_UNSET_FLAGS(label, GTK_CAN_FOCUS);
303 #ifdef G_OS_WIN32
304 	{
305 		GtkStyle *style;
306 		style = gtk_widget_get_style(dialog);
307 		gtk_widget_modify_base(label, GTK_STATE_ACTIVE,
308 				       &style->base[GTK_STATE_SELECTED]);
309 		gtk_widget_modify_text(label, GTK_STATE_ACTIVE,
310 				       &style->text[GTK_STATE_SELECTED]);
311 	}
312 #endif
313 
314 	if (can_disable) {
315 		hbox = gtk_hbox_new(FALSE, 0);
316 		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox,
317 				   FALSE, FALSE, 0);
318 
319 		disable_chkbtn = gtk_check_button_new_with_label
320 			(_("Show this message next time"));
321 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(disable_chkbtn),
322 					     TRUE);
323 		gtk_box_pack_start(GTK_BOX(hbox), disable_chkbtn,
324 				   FALSE, FALSE, spacing);
325 		g_signal_connect(G_OBJECT(disable_chkbtn), "toggled",
326 				 G_CALLBACK(alertpanel_button_toggled),
327 				 GUINT_TO_POINTER(G_ALERTDISABLE));
328 	}
329 
330 	/* for button(s) */
331 	if (!button1_label)
332 		button1_label = GTK_STOCK_OK;
333 	label2 = button2_label;
334 	label3 = button3_label;
335 	if (label2 && *label2 == '+') label2++;
336 	if (label3 && *label3 == '+') label3++;
337 
338 	gtkut_stock_button_set_create(&confirm_area,
339 				      &button1, button1_label,
340 				      button2_label ? &button2 : NULL, label2,
341 				      button3_label ? &button3 : NULL, label3);
342 
343 	gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
344 			 confirm_area, FALSE, FALSE, 0);
345 	gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5 * gtkut_get_dpi_multiplier());
346 	gtk_widget_grab_default(button1);
347 	gtk_widget_grab_focus(button1);
348 	if (button2_label &&
349 	    (default_value == G_ALERTALTERNATE || *button2_label == '+')) {
350 		gtk_widget_grab_default(button2);
351 		gtk_widget_grab_focus(button2);
352 	}
353 	if (button3_label &&
354 	    (default_value == G_ALERTOTHER || *button3_label == '+')) {
355 		gtk_widget_grab_default(button3);
356 		gtk_widget_grab_focus(button3);
357 	}
358 
359 	g_signal_connect(G_OBJECT(button1), "clicked",
360 			 G_CALLBACK(alertpanel_button_clicked),
361 			 GUINT_TO_POINTER(G_ALERTDEFAULT));
362 	if (button2_label)
363 		g_signal_connect(G_OBJECT(button2), "clicked",
364 				 G_CALLBACK(alertpanel_button_clicked),
365 				 GUINT_TO_POINTER(G_ALERTALTERNATE));
366 	if (button3_label)
367 		g_signal_connect(G_OBJECT(button3), "clicked",
368 				 G_CALLBACK(alertpanel_button_clicked),
369 				 GUINT_TO_POINTER(G_ALERTOTHER));
370 
371 	gtk_widget_show_all(dialog);
372 }
373 
alertpanel_button_toggled(GtkToggleButton * button,gpointer data)374 static void alertpanel_button_toggled(GtkToggleButton *button,
375 				      gpointer data)
376 {
377 	if (gtk_toggle_button_get_active(button))
378 		value &= ~GPOINTER_TO_UINT(data);
379 	else
380 		value |= GPOINTER_TO_UINT(data);
381 }
382 
alertpanel_button_clicked(GtkWidget * widget,gpointer data)383 static void alertpanel_button_clicked(GtkWidget *widget, gpointer data)
384 {
385 	value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data;
386 }
387 
alertpanel_deleted(GtkWidget * widget,GdkEventAny * event,gpointer data)388 static gint alertpanel_deleted(GtkWidget *widget, GdkEventAny *event,
389 			       gpointer data)
390 {
391 	value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data;
392 	return TRUE;
393 }
394 
alertpanel_close(GtkWidget * widget,GdkEventAny * event,gpointer data)395 static gboolean alertpanel_close(GtkWidget *widget, GdkEventAny *event,
396 				 gpointer data)
397 {
398 	if (event->type == GDK_KEY_PRESS)
399 		if (((GdkEventKey *)event)->keyval != GDK_Escape)
400 			return FALSE;
401 
402 	value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data;
403 	return FALSE;
404 }
405 
406 
alertpanel_focus_out(GtkWidget * widget,GdkEventFocus * event,gpointer data)407 static gint alertpanel_focus_out(GtkWidget *widget, GdkEventFocus *event,
408 				 gpointer data)
409 {
410 #ifdef G_OS_WIN32
411 	gtk_window_present(GTK_WINDOW(widget));
412 #endif
413 	return FALSE;
414 }
415