1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
10 *
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
13 *
14 * Copyright (C) 2011 Dan Vratil <dvratil@redhat.com>
15 *
16 */
17
18 #include "evolution-config.h"
19
20 #include <string.h>
21 #include <glib/gi18n.h>
22 #include <gtk/gtk.h>
23
24 #include <camel/camel.h>
25
26 #include "e-util/e-util.h"
27
28 #include "em-format/e-mail-formatter-print.h"
29 #include "em-format/e-mail-part-utils.h"
30
31 #include "e-mail-printer.h"
32 #include "e-mail-display.h"
33 #include "e-mail-print-config-headers.h"
34
35 #define w(x)
36
37 #define E_MAIL_PRINTER_GET_PRIVATE(obj) \
38 (G_TYPE_INSTANCE_GET_PRIVATE \
39 ((obj), E_TYPE_MAIL_PRINTER, EMailPrinterPrivate))
40
41 typedef struct _AsyncContext AsyncContext;
42
43 struct _EMailPrinterPrivate {
44 EMailFormatter *formatter;
45 EMailPartList *part_list;
46 EMailRemoteContent *remote_content;
47 EMailFormatterMode mode;
48
49 gchar *export_filename;
50 };
51
52 struct _AsyncContext {
53 WebKitWebView *web_view;
54 gulong load_status_handler_id;
55 GError *error;
56
57 GtkPrintOperationResult print_result;
58 };
59
60 enum {
61 PROP_0,
62 PROP_PART_LIST,
63 PROP_REMOTE_CONTENT
64 };
65
66 enum {
67 COLUMN_ACTIVE,
68 COLUMN_HEADER_NAME,
69 COLUMN_HEADER_VALUE,
70 COLUMN_HEADER_STRUCT,
71 LAST_COLUMN
72 };
73
74 G_DEFINE_TYPE (
75 EMailPrinter,
76 e_mail_printer,
77 G_TYPE_OBJECT);
78
79 static void
async_context_free(AsyncContext * async_context)80 async_context_free (AsyncContext *async_context)
81 {
82 if (async_context->load_status_handler_id > 0)
83 g_signal_handler_disconnect (
84 async_context->web_view,
85 async_context->load_status_handler_id);
86
87 g_clear_object (&async_context->web_view);
88 g_clear_error (&async_context->error);
89
90 g_slice_free (AsyncContext, async_context);
91 }
92
93 #if 0 /* FIXME WK2 */
94 static GtkWidget *
95 mail_printer_create_custom_widget_cb (WebKitPrintOperation *operation,
96 AsyncContext *async_context)
97 {
98 EMailDisplay *display;
99 EMailPartList *part_list;
100 EMailPart *part;
101 GtkWidget *widget;
102
103 webkit_print_operation_set_custom_tab_label (operation, _("Headers"));
104
105 display = E_MAIL_DISPLAY (async_context->web_view);
106 part_list = e_mail_display_get_part_list (display);
107
108 /* FIXME Hard-coding the part ID works for now but could easily
109 * break silently. Need a less brittle way of extracting
110 * specific parts by either MIME type or GType. */
111 part = e_mail_part_list_ref_part (part_list, ".message.headers");
112
113 widget = e_mail_print_config_headers_new (E_MAIL_PART_HEADERS (part));
114
115 g_object_unref (part);
116
117 return widget;
118 }
119
120 static void
121 mail_printer_custom_widget_apply_cb (WebKitPrintOperation *operation,
122 GtkWidget *widget,
123 AsyncContext *async_context)
124 {
125 webkit_web_view_reload (async_context->web_view);
126 }
127
128 static void
129 mail_printer_draw_footer_cb (GtkPrintOperation *operation,
130 GtkPrintContext *context,
131 gint page_nr)
132 {
133 PangoFontDescription *desc;
134 PangoLayout *layout;
135 gint n_pages;
136 gdouble width, height;
137 gchar *text;
138 cairo_t *cr;
139
140 cr = gtk_print_context_get_cairo_context (context);
141 width = gtk_print_context_get_width (context);
142 height = gtk_print_context_get_height (context);
143
144 g_object_get (operation, "n-pages", &n_pages, NULL);
145 text = g_strdup_printf (_("Page %d of %d"), page_nr + 1, n_pages);
146
147 cairo_set_source_rgb (cr, 0.1, 0.1, 0.1);
148 cairo_fill (cr);
149
150 desc = pango_font_description_from_string ("Sans Regular 10");
151 layout = gtk_print_context_create_pango_layout (context);
152 pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
153 pango_layout_set_font_description (layout, desc);
154 pango_layout_set_text (layout, text, -1);
155 pango_layout_set_width (layout, width * PANGO_SCALE);
156 pango_font_description_free (desc);
157
158 cairo_move_to (cr, 0, height + 5);
159 pango_cairo_show_layout (cr, layout);
160
161 g_object_unref (layout);
162 g_free (text);
163 }
164 #endif
165 static void
mail_printer_print_finished_cb(WebKitPrintOperation * print_operation,GTask * task)166 mail_printer_print_finished_cb (WebKitPrintOperation *print_operation,
167 GTask *task)
168 {
169 AsyncContext *async_context;
170
171 if (camel_debug ("webkit:preview"))
172 printf ("%s\n", G_STRFUNC);
173
174 async_context = g_task_get_task_data (task);
175 g_return_if_fail (async_context != NULL);
176
177 if (async_context->print_result == GTK_PRINT_OPERATION_RESULT_IN_PROGRESS) {
178 async_context->print_result = GTK_PRINT_OPERATION_RESULT_APPLY;
179 g_task_return_boolean (task, TRUE);
180 } else if (async_context->error) {
181 g_task_return_error (task, g_error_copy (async_context->error));
182 } else {
183 g_task_return_boolean (task, FALSE);
184 }
185
186 g_object_unref (task);
187 }
188
189 static void
mail_printer_print_failed_cb(WebKitPrintOperation * print_operation,const GError * error,GTask * task)190 mail_printer_print_failed_cb (WebKitPrintOperation *print_operation,
191 const GError *error,
192 GTask *task)
193 {
194 AsyncContext *async_context;
195
196 if (camel_debug ("webkit:preview"))
197 printf ("%s\n", G_STRFUNC);
198
199 async_context = g_task_get_task_data (task);
200 g_return_if_fail (async_context != NULL);
201 async_context->print_result = GTK_PRINT_OPERATION_RESULT_ERROR;
202 async_context->error = error ? g_error_copy (error) : NULL;
203 }
204
205 static gboolean
mail_printer_print_timeout_cb(GTask * task)206 mail_printer_print_timeout_cb (GTask *task)
207 {
208 AsyncContext *async_context;
209 gpointer source_object;
210 const gchar *export_filename;
211 GtkPrintSettings *print_settings = NULL;
212 WebKitPrintOperation *print_operation = NULL;
213 WebKitPrintOperationResponse response;
214 /* FIXME WK2
215 gulong draw_page_handler_id;
216 gulong create_custom_widget_handler_id;
217 gulong custom_widget_apply_handler_id;*/
218
219 async_context = g_task_get_task_data (task);
220
221 g_return_val_if_fail (async_context != NULL, G_SOURCE_REMOVE);
222
223 source_object = g_task_get_source_object (task);
224
225 g_return_val_if_fail (E_IS_MAIL_PRINTER (source_object), G_SOURCE_REMOVE);
226
227 print_settings = gtk_print_settings_new ();
228 export_filename = e_mail_printer_get_export_filename (E_MAIL_PRINTER (source_object));
229 gtk_print_settings_set (
230 print_settings,
231 GTK_PRINT_SETTINGS_OUTPUT_BASENAME,
232 export_filename);
233
234 print_operation = webkit_print_operation_new (async_context->web_view);
235 webkit_print_operation_set_print_settings (print_operation, print_settings);
236
237 g_signal_connect_data (
238 print_operation, "failed",
239 G_CALLBACK (mail_printer_print_failed_cb),
240 g_object_ref (task),
241 (GClosureNotify) g_object_unref, 0);
242
243 g_signal_connect_data (
244 print_operation, "finished",
245 G_CALLBACK (mail_printer_print_finished_cb),
246 g_object_ref (task),
247 (GClosureNotify) g_object_unref, 0);
248
249 /* FIXME WK2
250 create_custom_widget_handler_id = g_signal_connect (
251 print_operation, "create-custom-widget",
252 G_CALLBACK (mail_printer_create_custom_widget_cb),
253 async_context);
254
255 custom_widget_apply_handler_id = g_signal_connect (
256 print_operation, "custom-widget-apply",
257 G_CALLBACK (mail_printer_custom_widget_apply_cb),
258 async_context); */
259
260 /* FIXME WK2 - this will be hard to add back to WK2 API.. There is a CSS draft
261 * that can be used to add a page numbers, but it is not in WebKit yet.
262 * http://www.w3.org/TR/css3-page/
263 draw_page_handler_id = g_signal_connect (
264 print_operation, "draw-page",
265 G_CALLBACK (mail_printer_draw_footer_cb),
266 async_context->cancellable); */
267
268 response = webkit_print_operation_run_dialog (print_operation, NULL);
269
270 /* FIXME WK2
271 g_signal_handler_disconnect (
272 print_operation, create_custom_widget_handler_id);
273
274 g_signal_handler_disconnect (
275 print_operation, custom_widget_apply_handler_id);
276
277 g_signal_handler_disconnect (
278 print_operation, draw_page_handler_id); */
279
280 g_clear_object (&print_operation);
281 g_clear_object (&print_settings);
282
283 if (response == WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL) {
284 async_context->print_result = GTK_PRINT_OPERATION_RESULT_CANCEL;
285 g_task_return_boolean (task, TRUE);
286 g_object_unref (task);
287 }
288
289 return G_SOURCE_REMOVE;
290 }
291
292 static void
mail_printer_load_changed_cb(WebKitWebView * web_view,WebKitLoadEvent load_event,GTask * task)293 mail_printer_load_changed_cb (WebKitWebView *web_view,
294 WebKitLoadEvent load_event,
295 GTask *task)
296 {
297 AsyncContext *async_context;
298
299 /* Note: we disregard WEBKIT_LOAD_FAILED and print what we can. */
300 if (load_event != WEBKIT_LOAD_FINISHED)
301 return;
302
303 async_context = g_task_get_task_data (task);
304
305 g_return_if_fail (async_context != NULL);
306
307 /* WebKit reloads the page once more right before starting to print,
308 * so disconnect this handler after the first time to avoid starting
309 * another print operation. */
310 g_signal_handler_disconnect (
311 async_context->web_view,
312 async_context->load_status_handler_id);
313 async_context->load_status_handler_id = 0;
314
315 /* Check if we've been cancelled. */
316 if (g_task_return_error_if_cancelled (task)) {
317 g_object_unref (task);
318 return;
319 } else {
320 GSource *timeout_source;
321
322 /* Give WebKit some time to perform layouting and rendering before
323 * we start printing. 500ms should be enough in most cases. */
324 timeout_source = g_timeout_source_new (500);
325 g_task_attach_source (
326 task,
327 timeout_source,
328 (GSourceFunc) mail_printer_print_timeout_cb);
329 g_source_unref (timeout_source);
330 }
331 }
332
333 static WebKitWebView *
mail_printer_new_web_view(const gchar * charset,const gchar * default_charset,EMailFormatterMode mode)334 mail_printer_new_web_view (const gchar *charset,
335 const gchar *default_charset,
336 EMailFormatterMode mode)
337 {
338 WebKitWebView *web_view;
339 EMailFormatter *formatter;
340
341 web_view = g_object_new (
342 E_TYPE_MAIL_DISPLAY,
343 "mode", mode, NULL);
344
345 /* Do not load remote images, print what user sees in the preview panel */
346 e_mail_display_set_force_load_images (E_MAIL_DISPLAY (web_view), FALSE);
347
348 formatter = e_mail_display_get_formatter (E_MAIL_DISPLAY (web_view));
349 if (charset != NULL && *charset != '\0')
350 e_mail_formatter_set_charset (formatter, charset);
351 if (default_charset != NULL && *default_charset != '\0')
352 e_mail_formatter_set_default_charset (formatter, default_charset);
353
354 return web_view;
355 }
356
357 static void
mail_printer_set_part_list(EMailPrinter * printer,EMailPartList * part_list)358 mail_printer_set_part_list (EMailPrinter *printer,
359 EMailPartList *part_list)
360 {
361 g_return_if_fail (E_IS_MAIL_PART_LIST (part_list));
362 g_return_if_fail (printer->priv->part_list == NULL);
363
364 printer->priv->part_list = g_object_ref (part_list);
365 }
366
367 static void
mail_printer_set_remote_content(EMailPrinter * printer,EMailRemoteContent * remote_content)368 mail_printer_set_remote_content (EMailPrinter *printer,
369 EMailRemoteContent *remote_content)
370 {
371 g_return_if_fail (E_IS_MAIL_REMOTE_CONTENT (remote_content));
372 g_return_if_fail (printer->priv->remote_content == NULL);
373
374 printer->priv->remote_content = g_object_ref (remote_content);
375 }
376
377 static void
mail_printer_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)378 mail_printer_set_property (GObject *object,
379 guint property_id,
380 const GValue *value,
381 GParamSpec *pspec)
382 {
383 switch (property_id) {
384 case PROP_PART_LIST:
385 mail_printer_set_part_list (
386 E_MAIL_PRINTER (object),
387 g_value_get_object (value));
388 return;
389
390 case PROP_REMOTE_CONTENT:
391 mail_printer_set_remote_content (
392 E_MAIL_PRINTER (object),
393 g_value_get_object (value));
394 return;
395 }
396
397 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
398 }
399
400 static void
mail_printer_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)401 mail_printer_get_property (GObject *object,
402 guint property_id,
403 GValue *value,
404 GParamSpec *pspec)
405 {
406 switch (property_id) {
407 case PROP_PART_LIST:
408 g_value_take_object (
409 value,
410 e_mail_printer_ref_part_list (
411 E_MAIL_PRINTER (object)));
412 return;
413
414 case PROP_REMOTE_CONTENT:
415 g_value_take_object (
416 value,
417 e_mail_printer_ref_remote_content (
418 E_MAIL_PRINTER (object)));
419 return;
420 }
421
422 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
423 }
424
425 static void
mail_printer_dispose(GObject * object)426 mail_printer_dispose (GObject *object)
427 {
428 EMailPrinterPrivate *priv;
429
430 priv = E_MAIL_PRINTER_GET_PRIVATE (object);
431
432 g_clear_object (&priv->formatter);
433 g_clear_object (&priv->part_list);
434 g_clear_object (&priv->remote_content);
435 g_free (priv->export_filename);
436
437 /* Chain up to parent's dispose() method. */
438 G_OBJECT_CLASS (e_mail_printer_parent_class)->dispose (object);
439 }
440
441 static void
e_mail_printer_class_init(EMailPrinterClass * class)442 e_mail_printer_class_init (EMailPrinterClass *class)
443 {
444 GObjectClass *object_class;
445
446 g_type_class_add_private (class, sizeof (EMailPrinterPrivate));
447
448 object_class = G_OBJECT_CLASS (class);
449 object_class->set_property = mail_printer_set_property;
450 object_class->get_property = mail_printer_get_property;
451 object_class->dispose = mail_printer_dispose;
452
453 g_object_class_install_property (
454 object_class,
455 PROP_PART_LIST,
456 g_param_spec_object (
457 "part-list",
458 "Part List",
459 NULL,
460 E_TYPE_MAIL_PART_LIST,
461 G_PARAM_READWRITE |
462 G_PARAM_CONSTRUCT_ONLY));
463
464 g_object_class_install_property (
465 object_class,
466 PROP_REMOTE_CONTENT,
467 g_param_spec_object (
468 "remote-content",
469 "Remote Content",
470 NULL,
471 E_TYPE_MAIL_REMOTE_CONTENT,
472 G_PARAM_READWRITE |
473 G_PARAM_CONSTRUCT_ONLY));
474 }
475
476 static void
e_mail_printer_init(EMailPrinter * printer)477 e_mail_printer_init (EMailPrinter *printer)
478 {
479 printer->priv = E_MAIL_PRINTER_GET_PRIVATE (printer);
480
481 printer->priv->formatter = e_mail_formatter_print_new ();
482 printer->priv->mode = E_MAIL_FORMATTER_MODE_PRINTING;
483 }
484
485 EMailPrinter *
e_mail_printer_new(EMailPartList * part_list,EMailRemoteContent * remote_content)486 e_mail_printer_new (EMailPartList *part_list,
487 EMailRemoteContent *remote_content)
488 {
489 g_return_val_if_fail (E_IS_MAIL_PART_LIST (part_list), NULL);
490
491 return g_object_new (E_TYPE_MAIL_PRINTER,
492 "part-list", part_list,
493 "remote-content", remote_content,
494 NULL);
495 }
496
497 EMailPartList *
e_mail_printer_ref_part_list(EMailPrinter * printer)498 e_mail_printer_ref_part_list (EMailPrinter *printer)
499 {
500 g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL);
501
502 return g_object_ref (printer->priv->part_list);
503 }
504
505 EMailRemoteContent *
e_mail_printer_ref_remote_content(EMailPrinter * printer)506 e_mail_printer_ref_remote_content (EMailPrinter *printer)
507 {
508 g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL);
509
510 if (!printer->priv->remote_content)
511 return NULL;
512
513 return g_object_ref (printer->priv->remote_content);
514 }
515
516 void
e_mail_printer_set_mode(EMailPrinter * printer,EMailFormatterMode mode)517 e_mail_printer_set_mode (EMailPrinter *printer,
518 EMailFormatterMode mode)
519 {
520 g_return_if_fail (E_IS_MAIL_PRINTER (printer));
521
522 printer->priv->mode = mode;
523 }
524
525 EMailFormatterMode
e_mail_printer_get_mode(EMailPrinter * printer)526 e_mail_printer_get_mode (EMailPrinter *printer)
527 {
528 g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), E_MAIL_FORMATTER_MODE_PRINTING);
529
530 return printer->priv->mode;
531 }
532
533 void
e_mail_printer_print(EMailPrinter * printer,GtkPrintOperationAction action,EMailFormatter * formatter,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)534 e_mail_printer_print (EMailPrinter *printer,
535 GtkPrintOperationAction action, /* unused */
536 EMailFormatter *formatter,
537 GCancellable *cancellable,
538 GAsyncReadyCallback callback,
539 gpointer user_data)
540 {
541 GTask *task;
542 AsyncContext *async_context;
543 WebKitWebView *web_view;
544 EMailPartList *part_list;
545 CamelFolder *folder;
546 const gchar *message_uid;
547 const gchar *charset = NULL;
548 const gchar *default_charset = NULL;
549 gchar *mail_uri;
550 gulong handler_id;
551
552 g_return_if_fail (E_IS_MAIL_PRINTER (printer));
553 /* EMailFormatter can be NULL. */
554
555 async_context = g_slice_new0 (AsyncContext);
556 async_context->print_result = GTK_PRINT_OPERATION_RESULT_IN_PROGRESS;
557 async_context->error = NULL;
558
559 part_list = e_mail_printer_ref_part_list (printer);
560 folder = e_mail_part_list_get_folder (part_list);
561 message_uid = e_mail_part_list_get_message_uid (part_list);
562
563 if (formatter != NULL) {
564 charset =
565 e_mail_formatter_get_charset (formatter);
566 default_charset =
567 e_mail_formatter_get_default_charset (formatter);
568 }
569
570 if (charset == NULL)
571 charset = "";
572 if (default_charset == NULL)
573 default_charset = "";
574
575 task = g_task_new (printer, cancellable, callback, user_data);
576
577 web_view = mail_printer_new_web_view (charset, default_charset, e_mail_printer_get_mode (printer));
578 e_mail_display_set_part_list (E_MAIL_DISPLAY (web_view), part_list);
579
580 async_context->web_view = g_object_ref_sink (web_view);
581
582 handler_id = g_signal_connect_data (
583 web_view, "load-changed",
584 G_CALLBACK (mail_printer_load_changed_cb),
585 g_object_ref (task),
586 (GClosureNotify) g_object_unref, 0);
587 async_context->load_status_handler_id = handler_id;
588 g_task_set_task_data (task, async_context, (GDestroyNotify) async_context_free);
589
590 mail_uri = e_mail_part_build_uri (
591 folder, message_uid,
592 "__evo-load-image", G_TYPE_BOOLEAN, TRUE,
593 "mode", G_TYPE_INT, e_mail_printer_get_mode (printer),
594 "formatter_default_charset", G_TYPE_STRING, default_charset,
595 "formatter_charset", G_TYPE_STRING, charset,
596 NULL);
597
598 webkit_web_view_load_uri (web_view, mail_uri);
599
600 g_free (mail_uri);
601 g_object_unref (part_list);
602 }
603
604 GtkPrintOperationResult
e_mail_printer_print_finish(EMailPrinter * printer,GAsyncResult * result,GError ** error)605 e_mail_printer_print_finish (EMailPrinter *printer,
606 GAsyncResult *result,
607 GError **error)
608 {
609 GTask *task;
610 GtkPrintOperationResult print_result;
611 AsyncContext *async_context;
612
613 g_return_val_if_fail (g_task_is_valid (result, printer), GTK_PRINT_OPERATION_RESULT_ERROR);
614
615 task = G_TASK (result);
616 async_context = g_task_get_task_data (task);
617 if (!g_task_propagate_boolean (task, error))
618 return GTK_PRINT_OPERATION_RESULT_ERROR;
619
620 g_return_val_if_fail (async_context != NULL, GTK_PRINT_OPERATION_RESULT_ERROR);
621
622 print_result = async_context->print_result;
623 g_warn_if_fail (print_result != GTK_PRINT_OPERATION_RESULT_ERROR);
624
625 return print_result;
626 }
627
628 const gchar *
e_mail_printer_get_export_filename(EMailPrinter * printer)629 e_mail_printer_get_export_filename (EMailPrinter *printer)
630 {
631 g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL);
632
633 return printer->priv->export_filename;
634 }
635
636 void
e_mail_printer_set_export_filename(EMailPrinter * printer,const gchar * filename)637 e_mail_printer_set_export_filename (EMailPrinter *printer,
638 const gchar *filename)
639 {
640 g_return_if_fail (E_IS_MAIL_PRINTER (printer));
641
642 g_free (printer->priv->export_filename);
643 printer->priv->export_filename = g_strdup (filename);
644 }
645