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