/* * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * * Copyright (C) 2011 Dan Vratil * */ #include "evolution-config.h" #include #include #include #include #include "e-util/e-util.h" #include "em-format/e-mail-formatter-print.h" #include "em-format/e-mail-part-utils.h" #include "e-mail-printer.h" #include "e-mail-display.h" #include "e-mail-print-config-headers.h" #define w(x) #define E_MAIL_PRINTER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_PRINTER, EMailPrinterPrivate)) typedef struct _AsyncContext AsyncContext; struct _EMailPrinterPrivate { EMailFormatter *formatter; EMailPartList *part_list; EMailRemoteContent *remote_content; EMailFormatterMode mode; gchar *export_filename; }; struct _AsyncContext { WebKitWebView *web_view; gulong load_status_handler_id; GError *error; GtkPrintOperationResult print_result; }; enum { PROP_0, PROP_PART_LIST, PROP_REMOTE_CONTENT }; enum { COLUMN_ACTIVE, COLUMN_HEADER_NAME, COLUMN_HEADER_VALUE, COLUMN_HEADER_STRUCT, LAST_COLUMN }; G_DEFINE_TYPE ( EMailPrinter, e_mail_printer, G_TYPE_OBJECT); static void async_context_free (AsyncContext *async_context) { if (async_context->load_status_handler_id > 0) g_signal_handler_disconnect ( async_context->web_view, async_context->load_status_handler_id); g_clear_object (&async_context->web_view); g_clear_error (&async_context->error); g_slice_free (AsyncContext, async_context); } #if 0 /* FIXME WK2 */ static GtkWidget * mail_printer_create_custom_widget_cb (WebKitPrintOperation *operation, AsyncContext *async_context) { EMailDisplay *display; EMailPartList *part_list; EMailPart *part; GtkWidget *widget; webkit_print_operation_set_custom_tab_label (operation, _("Headers")); display = E_MAIL_DISPLAY (async_context->web_view); part_list = e_mail_display_get_part_list (display); /* FIXME Hard-coding the part ID works for now but could easily * break silently. Need a less brittle way of extracting * specific parts by either MIME type or GType. */ part = e_mail_part_list_ref_part (part_list, ".message.headers"); widget = e_mail_print_config_headers_new (E_MAIL_PART_HEADERS (part)); g_object_unref (part); return widget; } static void mail_printer_custom_widget_apply_cb (WebKitPrintOperation *operation, GtkWidget *widget, AsyncContext *async_context) { webkit_web_view_reload (async_context->web_view); } static void mail_printer_draw_footer_cb (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr) { PangoFontDescription *desc; PangoLayout *layout; gint n_pages; gdouble width, height; gchar *text; cairo_t *cr; cr = gtk_print_context_get_cairo_context (context); width = gtk_print_context_get_width (context); height = gtk_print_context_get_height (context); g_object_get (operation, "n-pages", &n_pages, NULL); text = g_strdup_printf (_("Page %d of %d"), page_nr + 1, n_pages); cairo_set_source_rgb (cr, 0.1, 0.1, 0.1); cairo_fill (cr); desc = pango_font_description_from_string ("Sans Regular 10"); layout = gtk_print_context_create_pango_layout (context); pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); pango_layout_set_font_description (layout, desc); pango_layout_set_text (layout, text, -1); pango_layout_set_width (layout, width * PANGO_SCALE); pango_font_description_free (desc); cairo_move_to (cr, 0, height + 5); pango_cairo_show_layout (cr, layout); g_object_unref (layout); g_free (text); } #endif static void mail_printer_print_finished_cb (WebKitPrintOperation *print_operation, GTask *task) { AsyncContext *async_context; if (camel_debug ("webkit:preview")) printf ("%s\n", G_STRFUNC); async_context = g_task_get_task_data (task); g_return_if_fail (async_context != NULL); if (async_context->print_result == GTK_PRINT_OPERATION_RESULT_IN_PROGRESS) { async_context->print_result = GTK_PRINT_OPERATION_RESULT_APPLY; g_task_return_boolean (task, TRUE); } else if (async_context->error) { g_task_return_error (task, g_error_copy (async_context->error)); } else { g_task_return_boolean (task, FALSE); } g_object_unref (task); } static void mail_printer_print_failed_cb (WebKitPrintOperation *print_operation, const GError *error, GTask *task) { AsyncContext *async_context; if (camel_debug ("webkit:preview")) printf ("%s\n", G_STRFUNC); async_context = g_task_get_task_data (task); g_return_if_fail (async_context != NULL); async_context->print_result = GTK_PRINT_OPERATION_RESULT_ERROR; async_context->error = error ? g_error_copy (error) : NULL; } static gboolean mail_printer_print_timeout_cb (GTask *task) { AsyncContext *async_context; gpointer source_object; const gchar *export_filename; GtkPrintSettings *print_settings = NULL; WebKitPrintOperation *print_operation = NULL; WebKitPrintOperationResponse response; /* FIXME WK2 gulong draw_page_handler_id; gulong create_custom_widget_handler_id; gulong custom_widget_apply_handler_id;*/ async_context = g_task_get_task_data (task); g_return_val_if_fail (async_context != NULL, G_SOURCE_REMOVE); source_object = g_task_get_source_object (task); g_return_val_if_fail (E_IS_MAIL_PRINTER (source_object), G_SOURCE_REMOVE); print_settings = gtk_print_settings_new (); export_filename = e_mail_printer_get_export_filename (E_MAIL_PRINTER (source_object)); gtk_print_settings_set ( print_settings, GTK_PRINT_SETTINGS_OUTPUT_BASENAME, export_filename); print_operation = webkit_print_operation_new (async_context->web_view); webkit_print_operation_set_print_settings (print_operation, print_settings); g_signal_connect_data ( print_operation, "failed", G_CALLBACK (mail_printer_print_failed_cb), g_object_ref (task), (GClosureNotify) g_object_unref, 0); g_signal_connect_data ( print_operation, "finished", G_CALLBACK (mail_printer_print_finished_cb), g_object_ref (task), (GClosureNotify) g_object_unref, 0); /* FIXME WK2 create_custom_widget_handler_id = g_signal_connect ( print_operation, "create-custom-widget", G_CALLBACK (mail_printer_create_custom_widget_cb), async_context); custom_widget_apply_handler_id = g_signal_connect ( print_operation, "custom-widget-apply", G_CALLBACK (mail_printer_custom_widget_apply_cb), async_context); */ /* FIXME WK2 - this will be hard to add back to WK2 API.. There is a CSS draft * that can be used to add a page numbers, but it is not in WebKit yet. * http://www.w3.org/TR/css3-page/ draw_page_handler_id = g_signal_connect ( print_operation, "draw-page", G_CALLBACK (mail_printer_draw_footer_cb), async_context->cancellable); */ response = webkit_print_operation_run_dialog (print_operation, NULL); /* FIXME WK2 g_signal_handler_disconnect ( print_operation, create_custom_widget_handler_id); g_signal_handler_disconnect ( print_operation, custom_widget_apply_handler_id); g_signal_handler_disconnect ( print_operation, draw_page_handler_id); */ g_clear_object (&print_operation); g_clear_object (&print_settings); if (response == WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL) { async_context->print_result = GTK_PRINT_OPERATION_RESULT_CANCEL; g_task_return_boolean (task, TRUE); g_object_unref (task); } return G_SOURCE_REMOVE; } static void mail_printer_load_changed_cb (WebKitWebView *web_view, WebKitLoadEvent load_event, GTask *task) { AsyncContext *async_context; /* Note: we disregard WEBKIT_LOAD_FAILED and print what we can. */ if (load_event != WEBKIT_LOAD_FINISHED) return; async_context = g_task_get_task_data (task); g_return_if_fail (async_context != NULL); /* WebKit reloads the page once more right before starting to print, * so disconnect this handler after the first time to avoid starting * another print operation. */ g_signal_handler_disconnect ( async_context->web_view, async_context->load_status_handler_id); async_context->load_status_handler_id = 0; /* Check if we've been cancelled. */ if (g_task_return_error_if_cancelled (task)) { g_object_unref (task); return; } else { GSource *timeout_source; /* Give WebKit some time to perform layouting and rendering before * we start printing. 500ms should be enough in most cases. */ timeout_source = g_timeout_source_new (500); g_task_attach_source ( task, timeout_source, (GSourceFunc) mail_printer_print_timeout_cb); g_source_unref (timeout_source); } } static WebKitWebView * mail_printer_new_web_view (const gchar *charset, const gchar *default_charset, EMailFormatterMode mode) { WebKitWebView *web_view; EMailFormatter *formatter; web_view = g_object_new ( E_TYPE_MAIL_DISPLAY, "mode", mode, NULL); /* Do not load remote images, print what user sees in the preview panel */ e_mail_display_set_force_load_images (E_MAIL_DISPLAY (web_view), FALSE); formatter = e_mail_display_get_formatter (E_MAIL_DISPLAY (web_view)); if (charset != NULL && *charset != '\0') e_mail_formatter_set_charset (formatter, charset); if (default_charset != NULL && *default_charset != '\0') e_mail_formatter_set_default_charset (formatter, default_charset); return web_view; } static void mail_printer_set_part_list (EMailPrinter *printer, EMailPartList *part_list) { g_return_if_fail (E_IS_MAIL_PART_LIST (part_list)); g_return_if_fail (printer->priv->part_list == NULL); printer->priv->part_list = g_object_ref (part_list); } static void mail_printer_set_remote_content (EMailPrinter *printer, EMailRemoteContent *remote_content) { g_return_if_fail (E_IS_MAIL_REMOTE_CONTENT (remote_content)); g_return_if_fail (printer->priv->remote_content == NULL); printer->priv->remote_content = g_object_ref (remote_content); } static void mail_printer_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_PART_LIST: mail_printer_set_part_list ( E_MAIL_PRINTER (object), g_value_get_object (value)); return; case PROP_REMOTE_CONTENT: mail_printer_set_remote_content ( E_MAIL_PRINTER (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void mail_printer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_PART_LIST: g_value_take_object ( value, e_mail_printer_ref_part_list ( E_MAIL_PRINTER (object))); return; case PROP_REMOTE_CONTENT: g_value_take_object ( value, e_mail_printer_ref_remote_content ( E_MAIL_PRINTER (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void mail_printer_dispose (GObject *object) { EMailPrinterPrivate *priv; priv = E_MAIL_PRINTER_GET_PRIVATE (object); g_clear_object (&priv->formatter); g_clear_object (&priv->part_list); g_clear_object (&priv->remote_content); g_free (priv->export_filename); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_mail_printer_parent_class)->dispose (object); } static void e_mail_printer_class_init (EMailPrinterClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (EMailPrinterPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = mail_printer_set_property; object_class->get_property = mail_printer_get_property; object_class->dispose = mail_printer_dispose; g_object_class_install_property ( object_class, PROP_PART_LIST, g_param_spec_object ( "part-list", "Part List", NULL, E_TYPE_MAIL_PART_LIST, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_REMOTE_CONTENT, g_param_spec_object ( "remote-content", "Remote Content", NULL, E_TYPE_MAIL_REMOTE_CONTENT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void e_mail_printer_init (EMailPrinter *printer) { printer->priv = E_MAIL_PRINTER_GET_PRIVATE (printer); printer->priv->formatter = e_mail_formatter_print_new (); printer->priv->mode = E_MAIL_FORMATTER_MODE_PRINTING; } EMailPrinter * e_mail_printer_new (EMailPartList *part_list, EMailRemoteContent *remote_content) { g_return_val_if_fail (E_IS_MAIL_PART_LIST (part_list), NULL); return g_object_new (E_TYPE_MAIL_PRINTER, "part-list", part_list, "remote-content", remote_content, NULL); } EMailPartList * e_mail_printer_ref_part_list (EMailPrinter *printer) { g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL); return g_object_ref (printer->priv->part_list); } EMailRemoteContent * e_mail_printer_ref_remote_content (EMailPrinter *printer) { g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL); if (!printer->priv->remote_content) return NULL; return g_object_ref (printer->priv->remote_content); } void e_mail_printer_set_mode (EMailPrinter *printer, EMailFormatterMode mode) { g_return_if_fail (E_IS_MAIL_PRINTER (printer)); printer->priv->mode = mode; } EMailFormatterMode e_mail_printer_get_mode (EMailPrinter *printer) { g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), E_MAIL_FORMATTER_MODE_PRINTING); return printer->priv->mode; } void e_mail_printer_print (EMailPrinter *printer, GtkPrintOperationAction action, /* unused */ EMailFormatter *formatter, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; AsyncContext *async_context; WebKitWebView *web_view; EMailPartList *part_list; CamelFolder *folder; const gchar *message_uid; const gchar *charset = NULL; const gchar *default_charset = NULL; gchar *mail_uri; gulong handler_id; g_return_if_fail (E_IS_MAIL_PRINTER (printer)); /* EMailFormatter can be NULL. */ async_context = g_slice_new0 (AsyncContext); async_context->print_result = GTK_PRINT_OPERATION_RESULT_IN_PROGRESS; async_context->error = NULL; part_list = e_mail_printer_ref_part_list (printer); folder = e_mail_part_list_get_folder (part_list); message_uid = e_mail_part_list_get_message_uid (part_list); if (formatter != NULL) { charset = e_mail_formatter_get_charset (formatter); default_charset = e_mail_formatter_get_default_charset (formatter); } if (charset == NULL) charset = ""; if (default_charset == NULL) default_charset = ""; task = g_task_new (printer, cancellable, callback, user_data); web_view = mail_printer_new_web_view (charset, default_charset, e_mail_printer_get_mode (printer)); e_mail_display_set_part_list (E_MAIL_DISPLAY (web_view), part_list); async_context->web_view = g_object_ref_sink (web_view); handler_id = g_signal_connect_data ( web_view, "load-changed", G_CALLBACK (mail_printer_load_changed_cb), g_object_ref (task), (GClosureNotify) g_object_unref, 0); async_context->load_status_handler_id = handler_id; g_task_set_task_data (task, async_context, (GDestroyNotify) async_context_free); mail_uri = e_mail_part_build_uri ( folder, message_uid, "__evo-load-image", G_TYPE_BOOLEAN, TRUE, "mode", G_TYPE_INT, e_mail_printer_get_mode (printer), "formatter_default_charset", G_TYPE_STRING, default_charset, "formatter_charset", G_TYPE_STRING, charset, NULL); webkit_web_view_load_uri (web_view, mail_uri); g_free (mail_uri); g_object_unref (part_list); } GtkPrintOperationResult e_mail_printer_print_finish (EMailPrinter *printer, GAsyncResult *result, GError **error) { GTask *task; GtkPrintOperationResult print_result; AsyncContext *async_context; g_return_val_if_fail (g_task_is_valid (result, printer), GTK_PRINT_OPERATION_RESULT_ERROR); task = G_TASK (result); async_context = g_task_get_task_data (task); if (!g_task_propagate_boolean (task, error)) return GTK_PRINT_OPERATION_RESULT_ERROR; g_return_val_if_fail (async_context != NULL, GTK_PRINT_OPERATION_RESULT_ERROR); print_result = async_context->print_result; g_warn_if_fail (print_result != GTK_PRINT_OPERATION_RESULT_ERROR); return print_result; } const gchar * e_mail_printer_get_export_filename (EMailPrinter *printer) { g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL); return printer->priv->export_filename; } void e_mail_printer_set_export_filename (EMailPrinter *printer, const gchar *filename) { g_return_if_fail (E_IS_MAIL_PRINTER (printer)); g_free (printer->priv->export_filename); printer->priv->export_filename = g_strdup (filename); }