1 /*
2  *
3  * This program is free software; you can redistribute it and/or modify it
4  * under the terms of the GNU Lesser General Public License as published by
5  * the Free Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful, but
8  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10  * for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public License
13  * along with this program; if not, see <http://www.gnu.org/licenses/>.
14  *
15  *
16  * Authors:
17  *		Sankar P <psankar@novell.com>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  *
21  */
22 
23 #include "evolution-config.h"
24 
25 #include "composer/e-msg-composer.h"
26 #include <gtk/gtk.h>
27 #include <glib/gi18n-lib.h>
28 #include <mail/em-event.h>
29 
30 #define d(x)
31 
32 #define SETTINGS_KEY "insert-face-picture"
33 
34 /* see http://quimby.gnus.org/circus/face/ */
35 #define MAX_PNG_DATA_LENGTH 723
36 
37 static gboolean
get_include_face_by_default(void)38 get_include_face_by_default (void)
39 {
40 	GSettings *settings = e_util_ref_settings ("org.gnome.evolution.plugin.face-picture");
41 	gboolean res;
42 
43 	res = g_settings_get_boolean (settings, SETTINGS_KEY);
44 
45 	g_object_unref (settings);
46 
47 	return res;
48 }
49 
50 static void
set_include_face_by_default(gboolean value)51 set_include_face_by_default (gboolean value)
52 {
53 	GSettings *settings = e_util_ref_settings ("org.gnome.evolution.plugin.face-picture");
54 
55 	g_settings_set_boolean (settings, SETTINGS_KEY, value);
56 
57 	g_object_unref (settings);
58 }
59 
60 static gchar *
get_filename(void)61 get_filename (void)
62 {
63 	return g_build_filename (e_get_user_data_dir (), "faces", NULL);
64 }
65 
66 static gchar *
get_face_base64(void)67 get_face_base64 (void)
68 {
69 	gchar *filename = get_filename (), *file_contents = NULL;
70 	gsize length = 0;
71 
72 	if (g_file_get_contents (filename, &file_contents, &length, NULL)) {
73 		if (length > 0) {
74 			file_contents = g_realloc (file_contents, length + 1);
75 			file_contents[length] = 0;
76 		} else {
77 			g_free (file_contents);
78 			file_contents = NULL;
79 		}
80 	} else {
81 		file_contents = NULL;
82 	}
83 
84 	g_free (filename);
85 
86 	return file_contents;
87 }
88 
89 static void
set_face_raw(gchar * content,gsize length)90 set_face_raw (gchar *content,
91               gsize length)
92 {
93 	gchar *filename = get_filename ();
94 
95 	if (content) {
96 		gchar *file_contents;
97 
98 		file_contents = g_base64_encode ((guchar *) content, length);
99 		g_file_set_contents (filename, file_contents, -1, NULL);
100 		g_free (file_contents);
101 	} else {
102 		g_file_set_contents (filename, "", -1, NULL);
103 	}
104 
105 	g_free (filename);
106 }
107 
108 /* g_object_unref returned pointer when done with it */
109 static GdkPixbuf *
get_active_face(gsize * image_data_length)110 get_active_face (gsize *image_data_length)
111 {
112 	GdkPixbufLoader *loader;
113 	GdkPixbuf *res = NULL;
114 	gchar *face;
115 	guchar *data;
116 	gsize data_len = 0;
117 
118 	face = get_face_base64 ();
119 
120 	if (!face || !*face) {
121 		g_free (face);
122 		return NULL;
123 	}
124 
125 	data = g_base64_decode (face, &data_len);
126 	if (!data || !data_len) {
127 		g_free (face);
128 		g_free (data);
129 		return NULL;
130 	}
131 
132 	g_free (face);
133 
134 	loader = gdk_pixbuf_loader_new ();
135 
136 	if (gdk_pixbuf_loader_write (loader, data, data_len, NULL)
137 	    && gdk_pixbuf_loader_close (loader, NULL)) {
138 		res = gdk_pixbuf_loader_get_pixbuf (loader);
139 		if (res) {
140 			g_object_ref (res);
141 			if (image_data_length)
142 				*image_data_length = data_len;
143 		}
144 	}
145 
146 	g_object_unref (loader);
147 
148 	g_free (data);
149 
150 	return res;
151 }
152 
153 static gboolean
prepare_image(const gchar * image_filename,gchar ** file_contents,gsize * length,GdkPixbuf ** use_pixbuf,gboolean can_claim)154 prepare_image (const gchar *image_filename,
155                gchar **file_contents,
156                gsize *length,
157                GdkPixbuf **use_pixbuf,
158                gboolean can_claim)
159 {
160 	gboolean res = FALSE;
161 
162 	g_return_val_if_fail (image_filename != NULL, FALSE);
163 	g_return_val_if_fail (file_contents != NULL, FALSE);
164 	g_return_val_if_fail (length != NULL, FALSE);
165 
166 	if (e_util_can_preview_filename (image_filename) &&
167 	    g_file_get_contents (image_filename, file_contents, length, NULL)) {
168 		GError *error = NULL;
169 		GdkPixbuf *pixbuf;
170 		GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
171 
172 		if (!gdk_pixbuf_loader_write (loader, (const guchar *)(*file_contents), *length, &error)
173 		    || !gdk_pixbuf_loader_close (loader, &error)
174 		    || (pixbuf = gdk_pixbuf_loader_get_pixbuf (loader)) == NULL) {
175 			const gchar *err = _("Unknown error");
176 
177 			if (error && error->message)
178 				err = error->message;
179 
180 			if (can_claim)
181 				e_alert_run_dialog_for_args (NULL, "org.gnome.evolution.plugins.face:not-an-image", err, NULL);
182 
183 			if (error != NULL)
184 				g_error_free (error);
185 		} else {
186 			gint width, height;
187 
188 			height = gdk_pixbuf_get_height (pixbuf);
189 			width = gdk_pixbuf_get_width (pixbuf);
190 
191 			if (height <= 0 || width <= 0) {
192 				if (can_claim)
193 					e_alert_run_dialog_for_args (NULL, "org.gnome.evolution.plugins.face:invalid-image-size", NULL, NULL);
194 			} else if (height != 48 || width != 48) {
195 				GdkPixbuf *copy, *scale;
196 				guchar *pixels;
197 				guint32 fill;
198 
199 				if (width >= height) {
200 					if (width > 48) {
201 						gdouble ratio = (gdouble) width / 48.0;
202 						width = 48;
203 						height = height / ratio;
204 
205 						if (height == 0)
206 							height = 1;
207 					}
208 				} else {
209 					if (height > 48) {
210 						gdouble ratio = (gdouble) height / 48.0;
211 						height = 48;
212 						width = width / ratio;
213 						if (width == 0)
214 							width = 1;
215 					}
216 				}
217 
218 				scale = e_icon_factory_pixbuf_scale (pixbuf, width, height);
219 				copy = e_icon_factory_pixbuf_scale (pixbuf, 48, 48);
220 
221 				width = gdk_pixbuf_get_width (scale);
222 				height = gdk_pixbuf_get_height (scale);
223 
224 				pixels = gdk_pixbuf_get_pixels (scale);
225 				/* fill with a pixel color at [0,0] */
226 				fill = (pixels[0] << 24) | (pixels[1] << 16) | (pixels[2] << 8) | (pixels[0]);
227 				gdk_pixbuf_fill (copy, fill);
228 
229 				gdk_pixbuf_copy_area (scale, 0, 0, width, height, copy, width < 48 ? (48 - width) / 2 : 0, height < 48 ? (48 - height) / 2 : 0);
230 
231 				g_free (*file_contents);
232 				*file_contents = NULL;
233 				*length = 0;
234 
235 				res = gdk_pixbuf_save_to_buffer (copy, file_contents, length, "png", NULL, "compression", "9", NULL);
236 
237 				if (res && use_pixbuf)
238 					*use_pixbuf = g_object_ref (copy);
239 				g_object_unref (copy);
240 				g_object_unref (scale);
241 			} else {
242 				res = TRUE;
243 				if (use_pixbuf)
244 					*use_pixbuf = g_object_ref (pixbuf);
245 			}
246 		}
247 
248 		g_object_unref (loader);
249 	} else {
250 		if (can_claim)
251 			e_alert_run_dialog_for_args (NULL, "org.gnome.evolution.plugins.face:file-not-found", NULL, NULL);
252 	}
253 
254 	return res;
255 }
256 
257 static void
update_preview_cb(GtkFileChooser * file_chooser,gpointer data)258 update_preview_cb (GtkFileChooser *file_chooser,
259                    gpointer data)
260 {
261 	GtkWidget *preview;
262 	gchar *filename, *file_contents = NULL;
263 	GdkPixbuf *pixbuf = NULL;
264 	gboolean have_preview;
265 	gsize length = 0;
266 
267 	preview = GTK_WIDGET (data);
268 	filename = gtk_file_chooser_get_preview_filename (file_chooser);
269 
270 	have_preview = filename && prepare_image (filename, &file_contents, &length, &pixbuf, FALSE);
271 	if (have_preview) {
272 		g_free (file_contents);
273 		have_preview = pixbuf != NULL;
274 	}
275 
276 	g_free (filename);
277 
278 	gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf);
279 	if (pixbuf)
280 		g_object_unref (pixbuf);
281 
282 	gtk_file_chooser_set_preview_widget_active (file_chooser, have_preview);
283 }
284 
285 static GdkPixbuf *
choose_new_face(GtkWidget * parent,gsize * image_data_length)286 choose_new_face (GtkWidget *parent,
287 		 gsize *image_data_length)
288 {
289 	GtkFileChooserNative *native;
290 	GdkPixbuf *res = NULL;
291 	GtkWidget *preview;
292 	GtkFileFilter *filter;
293 
294 	native = gtk_file_chooser_native_new (
295 		_("Select a Face Picture"),
296 		GTK_IS_WINDOW (parent) ? GTK_WINDOW (parent) : NULL,
297 		GTK_FILE_CHOOSER_ACTION_OPEN,
298 		_("_Open"), _("_Cancel"));
299 
300 	filter = gtk_file_filter_new ();
301 	gtk_file_filter_set_name (filter, _("Image files"));
302 	gtk_file_filter_add_mime_type (filter, "image/*");
303 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (native), filter);
304 
305 	preview = gtk_image_new ();
306 	gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (native), preview);
307 	g_signal_connect (
308 		native, "update-preview",
309 		G_CALLBACK (update_preview_cb), preview);
310 
311 	if (GTK_RESPONSE_ACCEPT == gtk_native_dialog_run (GTK_NATIVE_DIALOG (native))) {
312 		gchar *image_filename, *file_contents = NULL;
313 		gsize length = 0;
314 
315 		image_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (native));
316 		g_object_unref (native);
317 
318 		if (prepare_image (image_filename, &file_contents, &length, &res, TRUE)) {
319 			set_face_raw (file_contents, length);
320 			if (image_data_length)
321 				*image_data_length = length;
322 		}
323 
324 		g_free (file_contents);
325 		g_free (image_filename);
326 	} else {
327 		g_object_unref (native);
328 	}
329 
330 	return res;
331 }
332 
333 static void
toggled_check_include_by_default_cb(GtkWidget * widget,gpointer data)334 toggled_check_include_by_default_cb (GtkWidget *widget,
335                                      gpointer data)
336 {
337 	set_include_face_by_default (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
338 }
339 
340 static EAlert *
face_create_byte_size_alert(gsize byte_size)341 face_create_byte_size_alert (gsize byte_size)
342 {
343 	EAlert *alert;
344 	gchar *str;
345 
346 	str = g_strdup_printf ("%" G_GSIZE_FORMAT, byte_size);
347 	alert = e_alert_new ("org.gnome.evolution.plugins.face:incorrect-image-byte-size", str, NULL);
348 	g_free (str);
349 
350 	return alert;
351 }
352 
353 static void
click_load_face_cb(GtkButton * butt,GtkImage * image)354 click_load_face_cb (GtkButton *butt,
355                     GtkImage *image)
356 {
357 	EAlertBar *alert_bar;
358 	GdkPixbuf *face;
359 	gsize image_data_length = 0;
360 
361 	alert_bar = g_object_get_data (G_OBJECT (butt), "alert-bar");
362 	e_alert_bar_clear (alert_bar);
363 
364 	face = choose_new_face (gtk_widget_get_toplevel (GTK_WIDGET (butt)), &image_data_length);
365 
366 	if (face) {
367 		gtk_image_set_from_pixbuf (image, face);
368 		g_object_unref (face);
369 
370 		if (image_data_length > MAX_PNG_DATA_LENGTH) {
371 			EAlert *alert;
372 
373 			alert = face_create_byte_size_alert (image_data_length);
374 			e_alert_bar_add_alert (alert_bar, alert);
375 			g_clear_object (&alert);
376 		}
377 	}
378 }
379 
380 static GtkWidget *
get_cfg_widget(void)381 get_cfg_widget (void)
382 {
383 	GtkWidget *vbox, *check, *img, *butt, *alert_bar;
384 	GdkPixbuf *face;
385 	gsize image_data_length = 0;
386 
387 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
388 
389 	check = gtk_check_button_new_with_mnemonic (_("_Insert Face picture by default"));
390 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), get_include_face_by_default ());
391 	g_signal_connect (
392 		check, "toggled",
393 		G_CALLBACK (toggled_check_include_by_default_cb), NULL);
394 
395 	gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, FALSE, 0);
396 
397 	face = get_active_face (&image_data_length);
398 	img = gtk_image_new_from_pixbuf (face);
399 	if (face)
400 		g_object_unref (face);
401 
402 	butt = gtk_button_new_with_mnemonic (_("Load new _Face picture"));
403 	g_signal_connect (
404 		butt, "clicked",
405 		G_CALLBACK (click_load_face_cb), img);
406 
407 	alert_bar = e_alert_bar_new ();
408 	g_object_set_data (G_OBJECT (butt), "alert-bar", alert_bar);
409 
410 	gtk_box_pack_start (GTK_BOX (vbox), butt, FALSE, FALSE, 0);
411 	gtk_box_pack_start (GTK_BOX (vbox), img, FALSE, FALSE, 0);
412 	gtk_box_pack_end (GTK_BOX (vbox), alert_bar, FALSE, FALSE, 0);
413 
414 	gtk_widget_show_all (vbox);
415 	gtk_widget_hide (alert_bar);
416 
417 	if (image_data_length > MAX_PNG_DATA_LENGTH) {
418 		EAlert *alert;
419 
420 		alert = face_create_byte_size_alert (image_data_length);
421 		e_alert_bar_add_alert (E_ALERT_BAR (alert_bar), alert);
422 		g_clear_object (&alert);
423 	}
424 
425 	return vbox;
426 }
427 
428 static void
429 face_change_image_in_composer_cb (GtkButton *button,
430 				  EMsgComposer *composer);
431 
432 static void
face_manage_composer_alert(EMsgComposer * composer,gsize image_data_length)433 face_manage_composer_alert (EMsgComposer *composer,
434 			    gsize image_data_length)
435 {
436 	EHTMLEditor *editor;
437 	EAlert *alert;
438 
439 	editor = e_msg_composer_get_editor (composer);
440 
441 	if (image_data_length > MAX_PNG_DATA_LENGTH) {
442 		GtkWidget *button;
443 
444 		alert = face_create_byte_size_alert (image_data_length);
445 
446 		button = gtk_button_new_with_label (_("Change Face Image"));
447 		gtk_widget_show (button);
448 		g_signal_connect (button, "clicked", G_CALLBACK (face_change_image_in_composer_cb), composer);
449 		e_alert_add_widget (alert, button);
450 
451 		e_alert_sink_submit_alert (E_ALERT_SINK (editor), alert);
452 		g_object_set_data_full (G_OBJECT (editor), "face-image-alert", alert, g_object_unref);
453 	} else {
454 		alert = g_object_get_data (G_OBJECT (editor), "face-image-alert");
455 		if (alert) {
456 			e_alert_response (alert, GTK_RESPONSE_CLOSE);
457 			g_object_set_data (G_OBJECT (editor), "face-image-alert", NULL);
458 		}
459 	}
460 }
461 
462 static void
face_change_image_in_composer_cb(GtkButton * button,EMsgComposer * composer)463 face_change_image_in_composer_cb (GtkButton *button,
464 				  EMsgComposer *composer)
465 {
466 	GdkPixbuf *pixbuf;
467 	gsize image_data_length = 0;
468 
469 	/* Hide any previous alerts first */
470 	face_manage_composer_alert (composer, 0);
471 
472 	pixbuf = choose_new_face (GTK_WIDGET (composer), &image_data_length);
473 
474 	if (pixbuf) {
475 		g_object_unref (pixbuf);
476 
477 		face_manage_composer_alert (composer, image_data_length);
478 	}
479 }
480 
481 static void
action_toggle_face_cb(GtkToggleAction * action,EMsgComposer * composer)482 action_toggle_face_cb (GtkToggleAction *action,
483                        EMsgComposer *composer)
484 {
485 	if (gtk_toggle_action_get_active (action)) {
486 		gsize image_data_length = 0;
487 		gchar *face = get_face_base64 ();
488 
489 		if (!face) {
490 			GdkPixbuf *pixbuf = choose_new_face (GTK_WIDGET (composer), &image_data_length);
491 
492 			if (pixbuf) {
493 				g_object_unref (pixbuf);
494 			} else {
495 				/* cannot load a face image, uncheck the option */
496 				gtk_toggle_action_set_active (action, FALSE);
497 			}
498 		} else {
499 			g_free (g_base64_decode (face, &image_data_length));
500 			g_free (face);
501 		}
502 
503 		face_manage_composer_alert (composer, image_data_length);
504 	} else {
505 		face_manage_composer_alert (composer, 0);
506 	}
507 }
508 
509 /* ----------------------------------------------------------------- */
510 
511 gint e_plugin_lib_enable (EPlugin *ep, gint enable);
512 gboolean e_plugin_ui_init (GtkUIManager *ui_manager, EMsgComposer *composer);
513 GtkWidget *e_plugin_lib_get_configure_widget (EPlugin *epl);
514 void face_handle_send (EPlugin *ep, EMEventTargetComposer *target);
515 
516 /* ----------------------------------------------------------------- */
517 
518 gint
e_plugin_lib_enable(EPlugin * ep,gint enable)519 e_plugin_lib_enable (EPlugin *ep,
520                      gint enable)
521 {
522 	return 0;
523 }
524 
525 gboolean
e_plugin_ui_init(GtkUIManager * ui_manager,EMsgComposer * composer)526 e_plugin_ui_init (GtkUIManager *ui_manager,
527                   EMsgComposer *composer)
528 {
529 	EHTMLEditor *editor;
530 
531 	GtkToggleActionEntry entries[] = {
532 		{ "face-plugin",
533 		NULL,
534 		N_("Include _Face"),
535 		NULL,
536 		NULL,
537 		G_CALLBACK (action_toggle_face_cb),
538 		FALSE }
539 	};
540 
541 	if (get_include_face_by_default ()) {
542 		gchar *face = get_face_base64 ();
543 
544 		/* activate it only if has a face image available */
545 		entries[0].is_active = face && *face;
546 
547 		g_free (face);
548 	}
549 
550 	editor = e_msg_composer_get_editor (composer);
551 
552 	/* Add actions to the "composer" action group. */
553 	gtk_action_group_add_toggle_actions (
554 		e_html_editor_get_action_group (editor, "composer"),
555 		entries, G_N_ELEMENTS (entries), composer);
556 
557 	if (entries[0].is_active) {
558 		gsize image_data_length = 0;
559 		gchar *face = get_face_base64 ();
560 
561 		if (face) {
562 			g_free (g_base64_decode (face, &image_data_length));
563 			g_free (face);
564 		}
565 
566 		face_manage_composer_alert (composer, image_data_length);
567 	}
568 
569 	return TRUE;
570 }
571 
572 GtkWidget *
e_plugin_lib_get_configure_widget(EPlugin * epl)573 e_plugin_lib_get_configure_widget (EPlugin *epl)
574 {
575 	return get_cfg_widget ();
576 }
577 
578 void
face_handle_send(EPlugin * ep,EMEventTargetComposer * target)579 face_handle_send (EPlugin *ep,
580                   EMEventTargetComposer *target)
581 {
582 	EHTMLEditor *editor;
583 	GtkAction *action;
584 
585 	editor = e_msg_composer_get_editor (target->composer);
586 	action = e_html_editor_get_action (editor, "face-plugin");
587 
588 	g_return_if_fail (action != NULL);
589 
590 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
591 		gchar *face = get_face_base64 ();
592 
593 		if (face)
594 			e_msg_composer_set_header (target->composer, "Face", face);
595 
596 		g_free (face);
597 	}
598 }
599