[balsa] Printing of HTML message parts



commit b33ac92f890f0cbfb1844865b9ac493b1240545e
Author: Albrecht Dreß <albrecht dress arcor de>
Date:   Fri Feb 15 17:17:49 2019 -0500

    Printing of HTML message parts
    
    * libbalsa/html.[ch]: implement function for rendering to a
    Cairo surface; refactor for extracting common code
    * src/balsa-print-object-html.[ch]: implement printing the HTML
    Cairo surface (new module)
    * src/Makefile.am, src/meson.build: add new module
    * src/balsa-print-object.c: use the new module for HTML (falls
    back to default if built w/o HTML support)
    * src/balsa-print-object.h: add the “volatile” HTML printing
    options to BalsaPrintSetup
    * src/print-gtk.c: refactor print dialogue to use GtkGrid;
    add new options; add function for selecting the proper part
    from multipart/alternative
    * src/balsa-print-object-html.[ch]: new files
    
    Signed-off-by: Peter Bloomfield <PeterBloomfield bellsouth net>

 ChangeLog                |  18 ++++
 libbalsa/html.c          | 239 ++++++++++++++++++++++++++++++------------
 libbalsa/html.h          |   6 +-
 src/Makefile.am          |   2 +
 src/balsa-print-object.c |   5 +-
 src/balsa-print-object.h |   9 +-
 src/meson.build          |   2 +
 src/print-gtk.c          | 268 +++++++++++++++++++++++++++--------------------
 8 files changed, 366 insertions(+), 183 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 509b4fbbb..70a21926e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2018-02-15  Albrecht Dreß  <albrecht dress arcor de>
+
+       Printing of HTML message parts
+
+       * libbalsa/html.[ch]: implement function for rendering to a
+       Cairo surface; refactor for extracting common code
+       * src/balsa-print-object-html.[ch]: implement printing the HTML
+       Cairo surface (new module)
+       * src/Makefile.am, src/meson.build: add new module
+       * src/balsa-print-object.c: use the new module for HTML (falls
+       back to default if built w/o HTML support)
+       * src/balsa-print-object.h: add the “volatile” HTML printing
+       options to BalsaPrintSetup
+       * src/print-gtk.c: refactor print dialogue to use GtkGrid;
+       add new options; add function for selecting the proper part
+       from multipart/alternative
+       * src/balsa-print-object-html.[ch]: new files
+
 2019-02-15  Peter Bloomfield  <pbloomfield bellsouth net>
 
        * src/mailbox-node.c (balsa_mailbox_node_dispose): now that we
diff --git a/libbalsa/html.c b/libbalsa/html.c
index f4a2f2ef6..ea50bc453 100644
--- a/libbalsa/html.c
+++ b/libbalsa/html.c
@@ -1,7 +1,7 @@
 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
 /* Balsa E-Mail Client
  *
- * Copyright (C) 1997-2016 Stuart Parmenter and others,
+ * Copyright (C) 1997-2019 Stuart Parmenter and others,
  *                         See the file AUTHORS for a list.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -42,6 +42,16 @@
 #endif
 #define G_LOG_DOMAIN "html"
 
+
+#define CID_REGEX      "<[^>]*src\\s*=\\s*['\"]?\\s*cid:"
+#define SRC_REGEX      "<[^>]*src\\s*=\\s*['\"]?\\s*[^c][^i][^d][^:]"
+
+/* approximate image resolution for printing */
+#define HTML_PRINT_DPI                 200.0
+/* zoom level for printing */
+#define HTML_PRINT_ZOOM                        2.0
+
+
 /*
  * lbh_get_body_content
  *
@@ -150,6 +160,10 @@ typedef struct {
     LibBalsaHtmlSearchCallback search_cb;
     gpointer                   search_cb_data;
     gchar                    * search_text;
+    /* stuff used for printing only */
+    gboolean              webprocess_error;
+       cairo_surface_t      *surface;
+       volatile gint         screenshot_done;
 } LibBalsaWebKitInfo;
 
 #define LIBBALSA_HTML_INFO "libbalsa-webkit2-info"
@@ -190,6 +204,8 @@ lbh_get_body_content_utf8(LibBalsaMessageBody  * body,
 
 /*
  * GDestroyNotify func
+ *
+ * Note: do *not* destroy the surface in this function!
  */
 static void
 lbh_webkit_info_free(LibBalsaWebKitInfo * info)
@@ -480,6 +496,7 @@ lbh_web_process_terminated_cb(WebKitWebView                     *web_view,
                                                  WebKitWebProcessTerminationReason  reason,
                                                          gpointer                           user_data)
 {
+    LibBalsaWebKitInfo *info = (LibBalsaWebKitInfo *) user_data;
        const gchar *reason_str;
 
        switch (reason) {
@@ -494,16 +511,19 @@ lbh_web_process_terminated_cb(WebKitWebView                     *web_view,
                break;
        }
        g_warning("webkit process terminated abnormally: %s", reason_str);
+       info->webprocess_error = TRUE;
 }
 #else
 /*
  * Callback for the "web-process-crashed" signal
  */
 static gboolean
-lbh_web_process_crashed_cb(WebKitWebView * web_view,
-                           gpointer        data)
+lbh_web_process_crashed_cb(WebKitWebView          *web_view,
+                           gpointer                       data)
 {
+    LibBalsaWebKitInfo *info = (LibBalsaWebKitInfo *) data;
     g_debug("%s", __func__);
+       info->webprocess_error = TRUE;
     return FALSE;
 }
 #endif
@@ -568,6 +588,147 @@ lbh_context_menu_cb(WebKitWebView       * web_view,
     return retval;
 }
 
+static WebKitWebView *
+lbh_web_view_new(LibBalsaWebKitInfo *info,
+                                gint                            width,
+                                gint                            height,
+                                gboolean            auto_load_images)
+{
+       WebKitWebView *view;
+       WebKitSettings *settings;
+       static guint have_registered_cid = 0U;
+
+       view = WEBKIT_WEB_VIEW(webkit_web_view_new());
+    g_object_set_data_full(G_OBJECT(view), LIBBALSA_HTML_INFO, info, (GDestroyNotify) lbh_webkit_info_free);
+    gtk_widget_set_size_request(GTK_WIDGET(view), width, height);
+
+       settings = webkit_web_view_get_settings(view);
+    webkit_settings_set_enable_plugins(settings, FALSE);
+    webkit_settings_set_enable_javascript(settings, FALSE);
+       webkit_settings_set_enable_java(settings, FALSE);
+       webkit_settings_set_enable_hyperlink_auditing(settings, TRUE);
+       webkit_settings_set_auto_load_images(settings, auto_load_images);
+
+       if (g_atomic_int_or(&have_registered_cid, 1U) == 0U) {
+        WebKitWebContext *context;
+        /* Apparently, WebKitWebContext is static, and does not like to
+         * have the scheme registered many times (13? 15?), after which
+         * the web process crashes and does not get respawned.
+         * We register it once with the address of a static pointer to
+         * LibBalsaWebKitInfo. */
+
+        context = webkit_web_view_get_context(view);
+        webkit_web_context_register_uri_scheme(context, "cid", lbh_cid_cb, &info, NULL);
+        g_debug("%s_ registered “cid:” scheme", __func__);
+       }
+
+#if WEBKIT_CHECK_VERSION(2,20,0)
+       g_signal_connect(view, "web-process-terminated", G_CALLBACK(lbh_web_process_terminated_cb), info);
+#else
+       g_signal_connect(view, "web-process-crashed", G_CALLBACK(lbh_web_process_crashed_cb), info);
+#endif
+    g_signal_connect(view, "decide-policy", G_CALLBACK(lbh_decide_policy_cb), info);
+    g_signal_connect(view, "resource-load-started", G_CALLBACK(lbh_resource_load_started_cb), info);
+
+       return view;
+}
+
+
+static void
+dump_snapshot(GObject      *source_object,
+              GAsyncResult *res,
+                         gpointer      user_data)
+{
+       LibBalsaWebKitInfo *info = (LibBalsaWebKitInfo *) user_data;
+       WebKitWebView *webview = WEBKIT_WEB_VIEW(source_object);
+       GError *error = NULL;
+
+       info->surface = webkit_web_view_get_snapshot_finish(webview, res, &error);
+       if (info->surface != NULL) {
+               g_debug("%s: html snapshot done, surface %p", __func__, info->surface);
+               cairo_surface_reference(info->surface);
+       } else {
+               g_warning("%s: error taking html snapshot: %s", __func__, error->message);
+               g_clear_error(&error);
+       }
+       g_atomic_int_inc(&info->screenshot_done);
+}
+
+
+/** \brief Render a HMTL part into a Cairo surface
+ *
+ * \param body HTML message body part
+ * \param width rendering width in Cairo units (1/72")
+ * \param load_external_images whether external images referenced by the HTML shall be loaded
+ * \return a cairo surface on success, or NULL on error
+ */
+cairo_surface_t *
+libbalsa_html_print_bitmap(LibBalsaMessageBody *body,
+                                                  gdouble                              width,
+                                                  gboolean                     load_external_images)
+{
+       gint render_width;
+    gchar *text;
+    gboolean have_src_cid;
+    gboolean have_src_oth;
+    gssize len;
+       GtkWidget *offline_window;
+       WebKitWebView *view;
+       LibBalsaWebKitInfo *info;
+       cairo_surface_t *html_surface = NULL;
+
+       g_return_val_if_fail(body != NULL, NULL);
+    len = lbh_get_body_content_utf8(body, &text);
+    if (len < 0) {
+        return NULL;
+    }
+
+    have_src_cid = g_regex_match_simple(CID_REGEX, text, G_REGEX_CASELESS, 0);
+    have_src_oth = g_regex_match_simple(SRC_REGEX, text, G_REGEX_CASELESS, 0);
+
+    info = g_new0(LibBalsaWebKitInfo, 1);
+       offline_window = gtk_offscreen_window_new();
+       render_width = (gint) (width * HTML_PRINT_DPI / 72.0);
+       g_debug("%s: request Cairo width %g, render width %d", __func__, width, render_width);
+    gtk_window_set_default_size(GTK_WINDOW(offline_window), render_width, 200);
+    view = lbh_web_view_new(info, render_width, 200, load_external_images || (have_src_cid && 
!have_src_oth));
+    webkit_web_view_set_zoom_level(view, HTML_PRINT_ZOOM);                     /* heuristic setting, any way 
to calculate it? */
+    gtk_container_add(GTK_CONTAINER(offline_window), GTK_WIDGET(view));
+    gtk_widget_show_all(offline_window);
+
+    webkit_web_view_load_html(view, text, NULL);
+    g_free(text);
+
+    /* wait until the page is loaded */
+    while (webkit_web_view_is_loading(view)) {
+       gtk_main_iteration_do(FALSE);
+       g_usleep(100);
+    }
+
+    /* get the snapshot of the rendered html */
+    if (info->webprocess_error) {
+       g_warning("%s: web process terminated abnormally, cannot take snapshot", __func__);
+    } else {
+       g_debug("%s: html loaded, taking snapshot", __func__);
+       webkit_web_view_get_snapshot(view, WEBKIT_SNAPSHOT_REGION_FULL_DOCUMENT, 
WEBKIT_SNAPSHOT_OPTIONS_NONE, NULL, dump_snapshot,
+               info);
+       while (g_atomic_int_get(&info->screenshot_done) == 0) {
+               gtk_main_iteration_do(FALSE);
+               g_usleep(100);
+       }
+       g_debug("%s: snapshot done, size %dx%d", __func__, cairo_image_surface_get_width(info->surface),
+               cairo_image_surface_get_height(info->surface));
+       html_surface = info->surface;
+    }
+
+    /* destroy the offscreen window */
+    gtk_widget_destroy(offline_window);
+
+    /* return the surface */
+    return html_surface;
+}
+
+
 /* Create a new WebKitWebView widget:
  * body                LibBalsaMessageBody that belongs to the
  *                      LibBalsaMessage from which to extract any
@@ -575,7 +736,6 @@ lbh_context_menu_cb(WebKitWebView       * web_view,
  * hover_cb             callback for link-hover signal;
  * clicked_cb          callback for the "link-clicked" signal;
  */
-
 GtkWidget *
 libbalsa_html_new(LibBalsaMessageBody * body,
                   LibBalsaHtmlCallback  hover_cb,
@@ -583,16 +743,8 @@ libbalsa_html_new(LibBalsaMessageBody * body,
 {
     gchar *text;
     gssize len;
-    GtkWidget *widget;
     GtkWidget *vbox;
-    WebKitWebView *web_view;
-    static LibBalsaWebKitInfo *info;
-    static gboolean have_registered_cid = FALSE;
-    WebKitSettings *settings;
-    static const gchar cid_regex[] =
-        "<[^>]*src\\s*=\\s*['\"]?\\s*cid:";
-    static const gchar src_regex[] =
-        "<[^>]*src\\s*=\\s*['\"]?\\s*[^c][^i][^d][^:]";
+    LibBalsaWebKitInfo *info;
     gboolean have_src_cid;
     gboolean have_src_oth;
 
@@ -600,67 +752,24 @@ libbalsa_html_new(LibBalsaMessageBody * body,
     if (len < 0)
         return NULL;
 
-    info = g_new(LibBalsaWebKitInfo, 1);
+    info = g_new0(LibBalsaWebKitInfo, 1);
     info->body            = body;
     info->hover_cb        = hover_cb;
     info->clicked_cb      = clicked_cb;
-    info->info_bar        = NULL;
-    info->uri             = NULL;
-    info->search_text     = NULL;
-
-    widget = webkit_web_view_new();
-    /* WebkitWebView is uncontrollably scrollable, so if we don't set a
-     * minimum size it may be just a few pixels high. */
-    gtk_widget_set_size_request(widget, -1, 200);
-
-    info->web_view = web_view = WEBKIT_WEB_VIEW(widget);
-    g_object_set_data_full(G_OBJECT(web_view), LIBBALSA_HTML_INFO, info,
-                           (GDestroyNotify) lbh_webkit_info_free);
-
-    if (!have_registered_cid) {
-        WebKitWebContext *context;
-        /* Apparently, WebKitWebContext is static, and does not like to
-         * have the scheme registered many times (13? 15?), after which
-         * the web process crashes and does not get respawned.
-         * We register it once with the address of a static pointer to
-         * LibBalsaWebKitInfo. */
-
-        context = webkit_web_view_get_context(web_view);
-        webkit_web_context_register_uri_scheme(context, "cid", lbh_cid_cb,
-                                               &info, NULL);
-        have_registered_cid = TRUE;
-        g_debug("%s registered cid: scheme", __func__);
-    }
 
-    have_src_cid = g_regex_match_simple(cid_regex, text, G_REGEX_CASELESS, 0);
-    have_src_oth = g_regex_match_simple(src_regex, text, G_REGEX_CASELESS, 0);
+    have_src_cid = g_regex_match_simple(CID_REGEX, text, G_REGEX_CASELESS, 0);
+    have_src_oth = g_regex_match_simple(SRC_REGEX, text, G_REGEX_CASELESS, 0);
 
-    settings = webkit_web_view_get_settings(web_view);
-    webkit_settings_set_enable_plugins(settings, FALSE);
-    webkit_settings_set_enable_javascript(settings, FALSE);
-       webkit_settings_set_enable_java(settings, FALSE);
-       webkit_settings_set_enable_hyperlink_auditing(settings, TRUE);
-    webkit_settings_set_auto_load_images(settings, have_src_cid && !have_src_oth);
+    info->web_view = lbh_web_view_new(info, -1, 200, have_src_cid && !have_src_oth);
 
-    g_signal_connect(web_view, "mouse-target-changed",
+    g_signal_connect(info->web_view, "mouse-target-changed",
                      G_CALLBACK(lbh_mouse_target_changed_cb), info);
-    g_signal_connect(web_view, "decide-policy",
-                     G_CALLBACK(lbh_decide_policy_cb), info);
-    g_signal_connect(web_view, "resource-load-started",
-                     G_CALLBACK(lbh_resource_load_started_cb), info);
-#if WEBKIT_CHECK_VERSION(2,20,0)
-       g_signal_connect(web_view, "web-process-terminated",
-                     G_CALLBACK(lbh_web_process_terminated_cb), info);
-#else
-       g_signal_connect(web_view, "web-process-crashed",
-                     G_CALLBACK(lbh_web_process_crashed_cb), info);
-#endif
-    g_signal_connect(web_view, "context-menu",
+    g_signal_connect(info->web_view, "context-menu",
                      G_CALLBACK(lbh_context_menu_cb), info);
 
     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    g_object_set_data(G_OBJECT(vbox), "libbalsa-html-web-view", web_view);
-    gtk_box_pack_end(GTK_BOX(vbox), widget, TRUE, TRUE, 0);
+    g_object_set_data(G_OBJECT(vbox), "libbalsa-html-web-view", info->web_view);
+    gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(info->web_view), TRUE, TRUE, 0);
 
     /* Simple check for possible resource requests: */
     if (have_src_oth) {
@@ -669,7 +778,7 @@ libbalsa_html_new(LibBalsaMessageBody * body,
         g_debug("%s shows info_bar", __func__);
     }
 
-    webkit_web_view_load_html(web_view, text, NULL);
+    webkit_web_view_load_html(info->web_view, text, NULL);
     g_free(text);
 
     return vbox;
diff --git a/libbalsa/html.h b/libbalsa/html.h
index 2a067a18f..fc987bd91 100644
--- a/libbalsa/html.h
+++ b/libbalsa/html.h
@@ -1,7 +1,7 @@
 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
 /* Balsa E-Mail Client
  *
- * Copyright (C) 1997-2016 Stuart Parmenter and others,
+ * Copyright (C) 1997-2019 Stuart Parmenter and others,
  *                         See the file AUTHORS for a list.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -72,6 +72,10 @@ GtkWidget *libbalsa_html_get_view_widget(GtkWidget * widget);
 
 gboolean libbalsa_html_can_print(GtkWidget * widget);
 void libbalsa_html_print(GtkWidget * widget);
+cairo_surface_t *libbalsa_html_print_bitmap(LibBalsaMessageBody *body,
+                                                                                   gdouble                   
           width,
+                                                                                       gboolean              
           load_external_images)
+       G_GNUC_WARN_UNUSED_RESULT;
 
 # endif                                /* HAVE_HTML_WIDGET */
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 34830f3ee..55d855b30 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -78,6 +78,8 @@ balsa_print_source = print-gtk.c      \
        balsa-print-object-default.h    \
        balsa-print-object-header.c     \
        balsa-print-object-header.h     \
+       balsa-print-object-html.c       \
+       balsa-print-object-html.h       \
        balsa-print-object-image.c      \
        balsa-print-object-image.h      \
        balsa-print-object-text.c       \
diff --git a/src/balsa-print-object.c b/src/balsa-print-object.c
index 4ad4bb2d6..853496513 100644
--- a/src/balsa-print-object.c
+++ b/src/balsa-print-object.c
@@ -1,6 +1,6 @@
 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
 /* Balsa E-Mail Client
- * Copyright (C) 1997-2016 Stuart Parmenter and others
+ * Copyright (C) 1997-2019 Stuart Parmenter and others
  * Written by (C) Albrecht Dreß <albrecht dress arcor de> 2007
  *
  * This program is free software; you can redistribute it and/or modify
@@ -32,6 +32,7 @@
 #include "balsa-print-object-header.h"
 #include "balsa-print-object-image.h"
 #include "balsa-print-object-text.h"
+#include "balsa-print-object-html.h"
 
 
 /* object related functions */
@@ -138,7 +139,7 @@ balsa_print_objects_append_from_body(GList * list,
         GList * (*handler)(GList *, GtkPrintContext *, LibBalsaMessageBody *,
                            BalsaPrintSetup *);
     } pr_handlers[] = {
-        { "text/html",                     -1, balsa_print_object_default },
+        { "text/html",                     -1, balsa_print_object_html },
         { "text/enriched",                 -1, balsa_print_object_default },
         { "text/richtext",                 -1, balsa_print_object_default },
         { "text/x-vcard",                  -1, balsa_print_object_text_vcard },
diff --git a/src/balsa-print-object.h b/src/balsa-print-object.h
index 4216287a1..4e5fc97e7 100644
--- a/src/balsa-print-object.h
+++ b/src/balsa-print-object.h
@@ -1,7 +1,7 @@
 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
 /* Balsa E-Mail Client
- * Copyright (C) 1997-2016 Stuart Parmenter and others
- * Written by (C) Albrecht Dre� <albrecht dress arcor de> 2007
+ * Copyright (C) 1997-2019 Stuart Parmenter and others
+ * Written by (C) Albrecht Dreß <albrecht dress arcor de> 2007
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -45,6 +45,11 @@ typedef struct {
 
     gint page_count;
     guint curr_depth;
+
+    /* note: the following two fields are relevant only if HTML support is enabled;
+     * don't hide them for code simplicity even if HTML support is disabled */
+    gboolean print_alt_html;   /* print text/html in multipart/alternative */
+    gboolean html_load_images; /* load external images when printing text/html */
 } BalsaPrintSetup;
 
 
diff --git a/src/meson.build b/src/meson.build
index 2f41d5947..2623d86bb 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -80,6 +80,8 @@ balsa_print_source = [
   'balsa-print-object-default.h',
   'balsa-print-object-header.c',
   'balsa-print-object-header.h',
+  'balsa-print-object-html.c',
+  'balsa-print-object-html.h',
   'balsa-print-object-image.c',
   'balsa-print-object-image.h',
   'balsa-print-object-text.c',
diff --git a/src/print-gtk.c b/src/print-gtk.c
index 373ff32a1..6b1e36b77 100644
--- a/src/print-gtk.c
+++ b/src/print-gtk.c
@@ -1,6 +1,6 @@
 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
 /* Balsa E-Mail Client
- * Copyright (C) 1997-2016 Stuart Parmenter and others,
+ * Copyright (C) 1997-2019 Stuart Parmenter and others,
  *                         See the file AUTHORS for a list.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -47,9 +47,13 @@ typedef struct {
     GtkWidget *margin_bottom;
     GtkWidget *margin_left;
     GtkWidget *margin_right;
+#ifdef HAVE_HTML_WIDGET
+    GtkWidget *html_print;
+    GtkWidget *html_load_imgs;
+    BalsaPrintSetup *setup;
+#endif
 } BalsaPrintPrefs;
 
-
 typedef struct {
     /* related message */
     LibBalsaMessage *message;
@@ -111,6 +115,71 @@ print_header_footer(GtkPrintContext * context, cairo_t * cairo_ctx,
 }
 
 
+/*
+ * Scan the parts of a multipart/alternative as to find the proper one for printing.  According to RFC 2046, 
Sect. 5.1.4, the first
+ * /should/ be the "plain" one, and the following the "fancier" ones.  This is not guaranteed, though.
+ * 
+ * Furthermore, we may have cases like
+ * +- multipart/alternative
+ *      +- text/plain
+ *      +- multipart/mixed
+ *           +- text/html
+ *           +- ...
+ */
+static LibBalsaMessageBody *
+find_alt_part(LibBalsaMessageBody *parts,
+                         gboolean                         print_alt_html)
+{
+       LibBalsaMessageBody *use_part;
+
+       /* scan the parts */
+       for (use_part = parts; use_part != NULL; use_part = use_part->next) {
+               gchar *mime_type;
+               
+               mime_type = libbalsa_message_body_get_mime_type(use_part);
+               if ((g_ascii_strncasecmp(mime_type, "multipart/", 10U) == 0) && (use_part->parts != NULL)) {
+                       /* consider the first child of a multipart */
+                       g_free(mime_type);
+                       mime_type = libbalsa_message_body_get_mime_type(use_part->parts);
+               }
+
+               if (((g_ascii_strcasecmp(mime_type, "text/plain") == 0) && !print_alt_html) ||
+                       ((g_ascii_strcasecmp(mime_type, "text/html") == 0) && print_alt_html)) {
+                       g_free(mime_type);
+                       return use_part;
+               }
+               g_free(mime_type);
+       }
+       
+       /* nothing found, fall back to the first part in the chain */
+       return parts;
+}
+
+
+static GList *
+print_single_part(GList                          *bpo_list,
+                                 GtkPrintContext     *context,
+                                 BalsaPrintSetup     *psetup,
+                                 LibBalsaMessageBody *body,
+                                 gboolean                         no_first_sep,
+                                 gboolean                         add_signature)
+{
+       if (!no_first_sep) {
+               bpo_list = balsa_print_object_separator(bpo_list, psetup);
+       }
+       if (add_signature) {
+#ifdef HAVE_GPGME
+               if (body->was_encrypted) {
+                       bpo_list = balsa_print_object_frame_begin(bpo_list, _("Signed and encrypted matter"), 
psetup);
+               } else {
+                       bpo_list = balsa_print_object_frame_begin(bpo_list, _("Signed matter"), psetup);
+               }
+#endif                         /* HAVE_GPGME */
+       }
+       return balsa_print_objects_append_from_body(bpo_list, context, body, psetup);
+}
+
+
 /*
  * scan the body list and prepare print data according to the content type
  */
@@ -118,11 +187,10 @@ static GList *
 scan_body(GList *bpo_list, GtkPrintContext * context, BalsaPrintSetup * psetup,
          LibBalsaMessageBody * body, gboolean no_first_sep)
 {
+    gboolean add_signature = FALSE;
 #ifdef HAVE_GPGME
-    gboolean add_signature;
     gboolean have_crypto_frame;
 #endif                         /* HAVE_GPGME */
-
     while (body) {
        gchar *conttype;
 
@@ -158,29 +226,20 @@ scan_body(GList *bpo_list, GtkPrintContext * context, BalsaPrintSetup * psetup,
 #endif                         /* HAVE_GPGME */
 
        if (g_ascii_strncasecmp(conttype, "multipart/", 10)) {
-           if (no_first_sep)
+               bpo_list = print_single_part(bpo_list, context, psetup, body, no_first_sep, add_signature);
                no_first_sep = FALSE;
-           else
-               bpo_list = balsa_print_object_separator(bpo_list, psetup);
-#ifdef HAVE_GPGME
-           if (add_signature) {
-               if (body->was_encrypted)
-                   bpo_list = balsa_print_object_frame_begin(bpo_list,
-                                                             _("Signed and encrypted matter"),
-                                                             psetup);
-               else
-                   bpo_list = balsa_print_object_frame_begin(bpo_list,
-                                                             _("Signed matter"),
-                                                             psetup);
-           }
-#endif                         /* HAVE_GPGME */
-           bpo_list = balsa_print_objects_append_from_body(bpo_list, context,
-                                                           body, psetup);
        }
 
        if (body->parts) {
-           bpo_list = scan_body(bpo_list, context, psetup, body->parts, no_first_sep);
-           no_first_sep = FALSE;
+               if (g_ascii_strcasecmp(conttype, "multipart/alternative") == 0) {
+                       LibBalsaMessageBody *print_part;
+
+                       print_part = find_alt_part(body->parts, psetup->print_alt_html);
+                       bpo_list = print_single_part(bpo_list, context, psetup, print_part, no_first_sep, 
add_signature);
+               } else {
+                       bpo_list = scan_body(bpo_list, context, psetup, body->parts, no_first_sep);
+               }
+               no_first_sep = FALSE;
        }
 
        /* end the frame for an embedded message or encrypted stuff */
@@ -417,12 +476,12 @@ add_font_button(const gchar * text, const gchar * font, GtkGrid * grid,
     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_grid_attach(grid, label, 0, row, 1, 1);
+    gtk_grid_attach(grid, label, 1, row, 1, 1);
 
     font_button = gtk_font_button_new_with_font(font);
     gtk_label_set_mnemonic_widget(GTK_LABEL(label), font_button);
     gtk_widget_set_hexpand(font_button, TRUE);
-    gtk_grid_attach(grid, font_button, 1, row, 1, 1);
+    gtk_grid_attach(grid, font_button, 2, row, 1, 1);
 
     return font_button;
 }
@@ -440,7 +499,7 @@ add_margin_spinbtn(const gchar * text, gdouble min, gdouble max, gdouble dflt,
     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_grid_attach(grid, label, 0, row, 1, 1);
+    gtk_grid_attach(grid, label, 1, row, 1, 1);
 
     if (get_default_user_units() == GTK_UNIT_INCH) {
        unit = _("inch");
@@ -459,13 +518,13 @@ add_margin_spinbtn(const gchar * text, gdouble min, gdouble max, gdouble dflt,
     }
     gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(spinbtn), TRUE);
     gtk_label_set_mnemonic_widget(GTK_LABEL(label), spinbtn);
-    gtk_grid_attach(grid, spinbtn, 1, row, 1, 1);
+    gtk_grid_attach(grid, spinbtn, 2, row, 1, 1);
 
     label = gtk_label_new(unit);
     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_grid_attach(grid, label, 2, row, 1, 1);
+    gtk_grid_attach(grid, label, 3, row, 1, 1);
 
     return spinbtn;
 }
@@ -482,115 +541,89 @@ check_margins(GtkAdjustment * adjustment, GtkAdjustment * other)
 }
 
 static GtkWidget *
-message_prefs_widget(GtkPrintOperation * operation,
-                    BalsaPrintPrefs * print_prefs)
+create_options_group(const gchar *label_str, GtkWidget *parent_grid, gint parent_col, gint parent_row, gint 
parent_width)
 {
-    GtkWidget *page;
-    GtkWidget *group;
-    GtkWidget *label;
-    GtkWidget *hbox;
-    GtkWidget *vbox;
+       GtkWidget *group;
+       GtkWidget *label;
     GtkWidget *grid;
-    GtkPageSetup *pg_setup;
     gchar *markup;
 
-    gtk_print_operation_set_custom_tab_label(operation, _("Message"));
-
-    page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 18);
-    gtk_container_set_border_width(GTK_CONTAINER(page), 12);
-
     group = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
-    gtk_box_pack_start(GTK_BOX(page), group, FALSE, TRUE, 0);
+    gtk_grid_attach(GTK_GRID(parent_grid), group, parent_col, parent_row, parent_width, 1);
 
     label = gtk_label_new(NULL);
-    markup = g_strdup_printf("<b>%s</b>", _("Fonts"));
+    markup = g_strdup_printf("<b>%s</b>", label_str);
     gtk_label_set_markup(GTK_LABEL(label), markup);
     g_free(markup);
     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
     gtk_box_pack_start(GTK_BOX(group), label, FALSE, FALSE, 0);
 
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-    gtk_box_pack_start(GTK_BOX(group), hbox, TRUE, TRUE, 0);
-    gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("    "),
-                      FALSE, FALSE, 0);
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
-    gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
-
-    grid = gtk_grid_new();
-    gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
-    gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
-
-    gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, TRUE, 0);
-
-    print_prefs->header_font =
-       add_font_button(_("_Header Font:"), balsa_app.print_header_font,
-                       GTK_GRID(grid), 0);
-    print_prefs->body_font =
-       add_font_button(_("B_ody Font:"), balsa_app.print_body_font,
-                       GTK_GRID(grid), 1);
-    print_prefs->footer_font =
-       add_font_button(_("_Footer Font:"), balsa_app.print_footer_font,
-                       GTK_GRID(grid), 2);
+       grid = gtk_grid_new();
+    gtk_box_pack_start(GTK_BOX(group), grid, FALSE, FALSE, 0);
+       gtk_grid_set_column_spacing(GTK_GRID(grid), 0);
+       gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
+       gtk_grid_attach(GTK_GRID(grid), gtk_label_new("    "), 0, 0, 1, 1);
+       return grid;
+}
 
-    group = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
-    gtk_box_pack_start(GTK_BOX(page), group, FALSE, TRUE, 0);
+static GtkWidget *
+message_prefs_widget(GtkPrintOperation * operation,
+                    BalsaPrintPrefs * print_prefs)
+{
+    GtkWidget *page;
+    GtkWidget *grid;
+#ifdef HAVE_HTML_WIDGET
+    GtkWidget *dummy;
+#endif
+    GtkPageSetup *pg_setup;
 
-    label = gtk_label_new(NULL);
-    markup = g_strdup_printf("<b>%s</b>", _("Highlighting"));
-    gtk_label_set_markup(GTK_LABEL(label), markup);
-    g_free(markup);
-    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-    gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_box_pack_start(GTK_BOX(group), label, FALSE, FALSE, 0);
+    gtk_print_operation_set_custom_tab_label(operation, _("Message"));
+
+    page = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(page), 18);
+    gtk_grid_set_row_spacing(GTK_GRID(page), 18);
+    gtk_container_set_border_width(GTK_CONTAINER(page), 12);
+
+    /* fonts */
+    grid = create_options_group(_("Fonts"), page, 0, 0, 3);
 
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-    gtk_box_pack_start(GTK_BOX(group), hbox, TRUE, TRUE, 0);
-    gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("    "),
-                      FALSE, FALSE, 0);
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
-    gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+    print_prefs->header_font = add_font_button(_("_Header Font:"), balsa_app.print_header_font, 
GTK_GRID(grid), 0);
+    print_prefs->body_font = add_font_button(_("B_ody Font:"), balsa_app.print_body_font, GTK_GRID(grid), 1);
+    print_prefs->footer_font = add_font_button(_("_Footer Font:"), balsa_app.print_footer_font, 
GTK_GRID(grid), 2);
+
+    /* syntax highlighting */
+    grid = create_options_group(_("Highlighting"), page, 0, 1, 1);
 
     print_prefs->highlight_cited =
        gtk_check_button_new_with_mnemonic(_("Highlight _cited text"));
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
-                                (print_prefs->highlight_cited),
-                                balsa_app.print_highlight_cited);
-    gtk_box_pack_start(GTK_BOX(vbox), print_prefs->highlight_cited, FALSE,
-                      TRUE, 0);
-
-    print_prefs->highlight_phrases =
-       gtk_check_button_new_with_mnemonic(_
-                                          ("Highlight _structured phrases"));
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
-                                (print_prefs->highlight_phrases),
-                                balsa_app.print_highlight_phrases);
-    gtk_box_pack_start(GTK_BOX(vbox), print_prefs->highlight_phrases,
-                      FALSE, TRUE, 0);
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_prefs->highlight_cited), 
balsa_app.print_highlight_cited);
+    gtk_grid_attach(GTK_GRID(grid), print_prefs->highlight_cited, 1, 0, 1, 1);
 
-    group = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
-    gtk_box_pack_start(GTK_BOX(page), group, FALSE, TRUE, 0);
+    print_prefs->highlight_phrases = gtk_check_button_new_with_mnemonic(_("Highlight _structured phrases"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_prefs->highlight_phrases), 
balsa_app.print_highlight_phrases);
+    gtk_grid_attach(GTK_GRID(grid), print_prefs->highlight_phrases, 1, 1, 1, 1);
 
-    label = gtk_label_new(NULL);
-    markup = g_strdup_printf("<b>%s</b>", _("Margins"));
-    gtk_label_set_markup(GTK_LABEL(label), markup);
-    g_free(markup);
-    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-    gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_box_pack_start(GTK_BOX(group), label, FALSE, FALSE, 0);
+#ifdef HAVE_HTML_WIDGET
+    /* treatment of HTML messages and parts */
+    grid = create_options_group(_("Highlighting"), page, 1, 1, 1);
 
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-    gtk_box_pack_start(GTK_BOX(group), hbox, TRUE, TRUE, 0);
-    gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("    "),
-                      FALSE, FALSE, 0);
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
-    gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+    print_prefs->html_print = gtk_check_button_new_with_mnemonic(_("Prefer text/plain over HTML"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_prefs->html_print), balsa_app.display_alt_plain);
+    gtk_grid_attach(GTK_GRID(grid), print_prefs->html_print, 1, 0, 1, 1);
 
-    grid = gtk_grid_new();
-    gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
-    gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
+    print_prefs->html_load_imgs = gtk_check_button_new_with_mnemonic(_("Download images from remote servers 
(may be dangerous)"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_prefs->html_load_imgs), FALSE);
+    gtk_grid_attach(GTK_GRID(grid), print_prefs->html_load_imgs, 1, 1, 1, 1);
 
-    gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, TRUE, 0);
+    /* phantom alignment */
+    dummy = gtk_label_new(" ");
+    gtk_widget_set_hexpand(dummy, TRUE);
+    gtk_grid_attach(GTK_GRID(grid), dummy, 2, 1, 1, 1);
+#endif
+
+    /* margins */
+    grid = create_options_group(_("Margins"), page, 0, 2, 2);
 
     pg_setup = gtk_print_operation_get_default_page_setup(operation);
     print_prefs->margin_top =
@@ -671,6 +704,12 @@ message_prefs_apply(GtkPrintOperation * operation, GtkWidget * widget,
        balsa_app.margin_left /= 25.4;
        balsa_app.margin_right /= 25.4;
     }
+#ifdef HAVE_HTML_WIDGET
+    print_prefs->setup->print_alt_html =
+       !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_prefs->html_print));
+    print_prefs->setup->html_load_images =
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_prefs->html_load_imgs));
+#endif
 }
 
 
@@ -721,6 +760,9 @@ message_print(LibBalsaMessage * msg, GtkWindow * parent)
     /* create a print context */
     print_data = g_new0(BalsaPrintData, 1);
     print_data->message = msg;
+#ifdef HAVE_HTML_WIDGET
+    print_prefs.setup = &print_data->setup;
+#endif
 
     g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), print_data);
     g_signal_connect(print, "draw_page", G_CALLBACK(draw_page), print_data);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]