Files
evolution/mail/e-mail-printer.c
Matthew Barnes 27c92e7f3d Add EMailPrintConfigHeaders.
This splits the print dialog's "Headers" tab into a separate widget.

EMailPrintConfigHeaders takes an EMailPartHeaders and displays its print
model, which is a representation of all message headers (except subject)
with an on/off flag for each.  The headers can be toggled and reordered,
and the changes are written back to the print model.

During printing, EMailFormatterPrintHeaders uses the same print model
to determine which headers to show and in what order (except subject).

This approach is much saner than the old method, which was trying to
manipulate WebKitWebView DOM directly to toggle and reorder headers.
This approach also happens to work, whereas the old method did not.
2013-06-08 00:25:15 -04:00

624 lines
16 KiB
C

/*
* 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; either
* version 2 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
* Copyright (C) 2011 Dan Vratil <dvratil@redhat.com>
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <webkit/webkitdom.h>
#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))
enum {
BUTTON_SELECT_ALL,
BUTTON_SELECT_NONE,
BUTTON_TOP,
BUTTON_UP,
BUTTON_DOWN,
BUTTON_BOTTOM,
BUTTONS_COUNT
};
typedef struct _AsyncContext AsyncContext;
struct _EMailPrinterPrivate {
EMailFormatter *formatter;
EMailPartList *part_list;
gchar *export_filename;
WebKitWebView *webview; /* WebView to print from */
gchar *uri;
GtkWidget *buttons[BUTTONS_COUNT];
GtkWidget *treeview;
GtkPrintOperation *operation;
GtkPrintOperationAction print_action;
};
struct _AsyncContext {
WebKitWebView *web_view;
gulong load_status_handler_id;
GCancellable *cancellable;
GMainContext *main_context;
GtkPrintOperationAction print_action;
GtkPrintOperationResult print_result;
};
enum {
PROP_0,
PROP_PART_LIST
};
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_object (&async_context->cancellable);
g_main_context_unref (async_context->main_context);
g_slice_free (AsyncContext, async_context);
}
static GtkWidget *
mail_printer_create_custom_widget_cb (GtkPrintOperation *operation,
AsyncContext *async_context)
{
EMailDisplay *display;
EMailPartList *part_list;
EMailPart *part;
GtkWidget *widget;
gtk_print_operation_set_custom_tab_label (operation, _("Headers"));
display = E_MAIL_DISPLAY (async_context->web_view);
part_list = e_mail_display_get_parts_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 (GtkPrintOperation *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);
}
static gboolean
mail_printer_print_timeout_cb (gpointer user_data)
{
GSimpleAsyncResult *simple;
AsyncContext *async_context;
GCancellable *cancellable;
GtkPrintOperation *print_operation;
GtkPrintOperationAction print_action;
EMailPrinter *printer;
WebKitWebFrame *web_frame;
gulong create_custom_widget_handler_id;
gulong custom_widget_apply_handler_id;
gulong draw_page_handler_id;
GError *error = NULL;
simple = G_SIMPLE_ASYNC_RESULT (user_data);
async_context = g_simple_async_result_get_op_res_gpointer (simple);
cancellable = async_context->cancellable;
print_action = async_context->print_action;
/* Check for cancellation one last time before printing. */
if (g_cancellable_set_error_if_cancelled (cancellable, &error))
goto exit;
/* This returns a new reference. */
printer = (EMailPrinter *) g_async_result_get_source_object (
G_ASYNC_RESULT (simple));
print_operation = e_print_operation_new ();
gtk_print_operation_set_show_progress (print_operation, TRUE);
gtk_print_operation_set_unit (print_operation, GTK_UNIT_PIXEL);
if (async_context->print_action == GTK_PRINT_OPERATION_ACTION_EXPORT) {
const gchar *export_filename;
export_filename =
e_mail_printer_get_export_filename (printer);
gtk_print_operation_set_export_filename (
print_operation, export_filename);
}
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);
draw_page_handler_id = g_signal_connect (
print_operation, "draw-page",
G_CALLBACK (mail_printer_draw_footer_cb),
async_context->cancellable);
web_frame = webkit_web_view_get_main_frame (async_context->web_view);
async_context->print_result = webkit_web_frame_print_full (
web_frame, print_operation, print_action, &error);
/* Sanity check. */
switch (async_context->print_result) {
case GTK_PRINT_OPERATION_RESULT_ERROR:
if (error == NULL)
g_warning (
"WebKit print operation returned "
"ERROR result without setting a "
"GError");
break;
case GTK_PRINT_OPERATION_RESULT_APPLY:
if (error != NULL)
g_warning (
"WebKit print operation returned "
"APPLY result but also set a GError");
break;
case GTK_PRINT_OPERATION_RESULT_CANCEL:
if (error != NULL)
g_warning (
"WebKit print operation returned "
"CANCEL result but also set a GError");
break;
default:
g_warn_if_reached ();
}
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_object_unref (print_operation);
g_object_unref (printer);
exit:
if (error != NULL)
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete_in_idle (simple);
return FALSE;
}
static void
mail_printer_load_status_cb (WebKitWebView *web_view,
GParamSpec *pspec,
GSimpleAsyncResult *simple)
{
AsyncContext *async_context;
WebKitLoadStatus load_status;
GCancellable *cancellable;
GError *error = NULL;
/* Note: we disregard WEBKIT_LOAD_FAILED and print what we can. */
load_status = webkit_web_view_get_load_status (web_view);
if (load_status != WEBKIT_LOAD_FINISHED)
return;
/* Signal handlers are holding the only GSimpleAsyncResult
* references. This is to avoid finalizing it prematurely. */
g_object_ref (simple);
async_context = g_simple_async_result_get_op_res_gpointer (simple);
cancellable = async_context->cancellable;
/* 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_cancellable_set_error_if_cancelled (cancellable, &error)) {
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete_in_idle (simple);
/* Give WebKit some time to perform layouting and rendering before
* we start printing. 500ms should be enough in most cases. */
} else {
GSource *timeout_source;
timeout_source = g_timeout_source_new (500);
g_source_set_callback (
timeout_source,
mail_printer_print_timeout_cb,
g_object_ref (simple),
(GDestroyNotify) g_object_unref);
g_source_attach (
timeout_source, async_context->main_context);
g_source_unref (timeout_source);
}
g_object_unref (simple);
}
static WebKitWebView *
mail_printer_new_web_view (const gchar *charset,
const gchar *default_charset)
{
WebKitWebView *web_view;
EMailFormatter *formatter;
web_view = g_object_new (
E_TYPE_MAIL_DISPLAY,
"mode", E_MAIL_FORMATTER_MODE_PRINTING, NULL);
e_web_view_set_enable_frame_flattening (E_WEB_VIEW (web_view), FALSE);
e_mail_display_set_force_load_images (E_MAIL_DISPLAY (web_view), TRUE);
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_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;
}
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;
}
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->webview);
g_clear_object (&priv->operation);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_mail_printer_parent_class)->dispose (object);
}
static void
mail_printer_finalize (GObject *object)
{
EMailPrinterPrivate *priv;
priv = E_MAIL_PRINTER_GET_PRIVATE (object);
g_free (priv->uri);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_mail_printer_parent_class)->finalize (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;
object_class->finalize = mail_printer_finalize;
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));
}
static void
e_mail_printer_init (EMailPrinter *printer)
{
printer->priv = E_MAIL_PRINTER_GET_PRIVATE (printer);
printer->priv->formatter = e_mail_formatter_print_new ();
}
EMailPrinter *
e_mail_printer_new (EMailPartList *part_list)
{
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, 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);
}
void
e_mail_printer_print (EMailPrinter *printer,
GtkPrintOperationAction action,
EMailFormatter *formatter,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *simple;
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_action = action;
async_context->main_context = g_main_context_ref_thread_default ();
if (G_IS_CANCELLABLE (cancellable))
async_context->cancellable = g_object_ref (cancellable);
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 = "";
simple = g_simple_async_result_new (
G_OBJECT (printer), callback,
user_data, e_mail_printer_print);
g_simple_async_result_set_check_cancellable (simple, cancellable);
g_simple_async_result_set_op_res_gpointer (
simple, async_context, (GDestroyNotify) async_context_free);
web_view = mail_printer_new_web_view (charset, default_charset);
e_mail_display_set_parts_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, "notify::load-status",
G_CALLBACK (mail_printer_load_status_cb),
g_object_ref (simple),
(GClosureNotify) g_object_unref, 0);
async_context->load_status_handler_id = handler_id;
mail_uri = e_mail_part_build_uri (
folder, message_uid,
"__evo-load-image", G_TYPE_BOOLEAN, TRUE,
"mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_PRINTING,
"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 (simple);
g_object_unref (part_list);
}
GtkPrintOperationResult
e_mail_printer_print_finish (EMailPrinter *printer,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
AsyncContext *async_context;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (printer), e_mail_printer_print),
GTK_PRINT_OPERATION_RESULT_ERROR);
simple = G_SIMPLE_ASYNC_RESULT (result);
async_context = g_simple_async_result_get_op_res_gpointer (simple);
if (g_simple_async_result_propagate_error (simple, error))
return GTK_PRINT_OPERATION_RESULT_ERROR;
g_warn_if_fail (
async_context->print_result !=
GTK_PRINT_OPERATION_RESULT_ERROR);
return async_context->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);
}