aboutsummaryrefslogblamecommitdiffstats
path: root/mail/e-mail-printer.c
blob: 1ea021494dda96d72bad0e8e5da07c518603caec (plain) (tree)

























                                                                             
                             
 
                          
 

                                             

                           
                           
                                        
 





                                                          
      






                           

  

                                          
                             

                                  
 
                               
 





                                                           
                                             

  








                                             


      

                      









                             




                       















                                                               


                                                                   
 







                                                                           
                                                           


















                                                                              


           


                                                          
































                                                                          










                                                  
                                              

















                                                                           
 













                                                                               






                                                                  
















                                                                             
                                                                         






                                                                              
                              













                                                                               


                                                                 




































































                                                                            




                                                        
                                        




                                                              








                                                                              










                                                                                  
           

                                                     
 

                                                            
 

                                                            
 






                                               
                                    


                                                            






                                                                       



                                             
 
                              
                                    



                                                              






                                                                       
                                      
 
                                  
 
                                                   
 

                                          

                                          
 


                                                                       
 



                                       
 
                                                   
 


                                                     
                                                                        


           
                                                    


                                   
                                                                       
 
                                              



                                                               


                                         
                               


                                     
                             
                                              

                                                 


           
                                           
 
                                                             
 
                                                                 


              
                                             
 
                                                                     
 
                             
                                    

                                              
 





                                                                 


    
                                            
                                                     
                                                


                                                   
 










                                            
                                                       
                                         
 


                                                                           
 




























                                                                            
                                                                            












































                                                                            
 


                                                  
 
                                           






                                                                 
                                              






                                                          
 
                                                
                                                             
 
/*
 * 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_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 (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;
    WebKitWebSettings *web_settings;
    EMailFormatter *formatter;

    web_view = g_object_new (
        E_TYPE_MAIL_DISPLAY,
        "mode", E_MAIL_FORMATTER_MODE_PRINTING, NULL);

    /* XXX EMailDisplay enables frame flattening to prevent scrollable
     *     subparts in an email, which understandable.  This resets it
     *     to allow scrollable subparts for reasons I don't understand. */
    web_settings = webkit_web_view_get_settings (web_view);
    g_object_set (
        G_OBJECT (web_settings),
        "enable-frame-flattening", FALSE, NULL);

    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_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, "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);
}