1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  * Copyright (C) 1997-2013 Stuart Parmenter and others,
4  *                         See the file AUTHORS for a list.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif                          /* HAVE_CONFIG_H */
25 
26 #include <string.h>
27 #include <gtk/gtk.h>
28 #include "balsa-app.h"
29 #include "print.h"
30 #include "misc.h"
31 #include "balsa-message.h"
32 #include "quote-color.h"
33 #include <glib/gi18n.h>
34 #include "balsa-print-object.h"
35 #include "balsa-print-object-decor.h"
36 #include "balsa-print-object-header.h"
37 
38 #if HAVE__NL_MEASUREMENT_MEASUREMENT
39 #include <langinfo.h>
40 #endif                          /* HAVE__NL_MEASUREMENT_MEASUREMENT */
41 
42 typedef struct {
43     GtkWidget *header_font;
44     GtkWidget *body_font;
45     GtkWidget *footer_font;
46     GtkWidget *highlight_cited;
47     GtkWidget *highlight_phrases;
48     GtkWidget *margin_top;
49     GtkWidget *margin_bottom;
50     GtkWidget *margin_left;
51     GtkWidget *margin_right;
52 } BalsaPrintPrefs;
53 
54 
55 typedef struct {
56     /* related message */
57     LibBalsaMessage *message;
58 
59     /* print setup */
60     BalsaPrintSetup setup;
61 
62     /* print data */
63     GList *print_parts;
64 
65     /* header related stuff */
66     gdouble c_header_y;
67 
68     /* page footer related stuff */
69     gchar *footer;
70     gdouble c_footer_y;
71 } BalsaPrintData;
72 
73 
74 /* print the page header and footer */
75 static void
print_header_footer(GtkPrintContext * context,cairo_t * cairo_ctx,BalsaPrintData * pdata,gint pageno)76 print_header_footer(GtkPrintContext * context, cairo_t * cairo_ctx,
77 		    BalsaPrintData * pdata, gint pageno)
78 {
79     PangoLayout *layout;
80     PangoFontDescription *font;
81     gchar *pagebuf;
82 
83     /* page number */
84     font = pango_font_description_from_string(balsa_app.print_header_font);
85     layout = gtk_print_context_create_pango_layout(context);
86     pango_layout_set_font_description(layout, font);
87     pango_font_description_free(font);
88     pango_layout_set_width(layout, C_TO_P(pdata->setup.c_width));
89     pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
90     pagebuf =
91 	g_strdup_printf(_("Page %d of %d"), pageno + 1,	pdata->setup.page_count);
92     pango_layout_set_text(layout, pagebuf, -1);
93     g_free(pagebuf);
94     cairo_move_to(cairo_ctx, pdata->setup.c_x0, pdata->c_header_y);
95     pango_cairo_show_layout(cairo_ctx, layout);
96     g_object_unref(G_OBJECT(layout));
97 
98     /* footer (if available) */
99     if (pdata->footer) {
100 	font =
101 	    pango_font_description_from_string(balsa_app.
102 					       print_footer_font);
103 	layout = gtk_print_context_create_pango_layout(context);
104 	pango_layout_set_font_description(layout, font);
105 	pango_font_description_free(font);
106 	pango_layout_set_width(layout, C_TO_P(pdata->setup.c_width));
107 	pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
108 	pango_layout_set_text(layout, pdata->footer, -1);
109 	cairo_move_to(cairo_ctx, pdata->setup.c_x0, pdata->c_footer_y);
110 	pango_cairo_show_layout(cairo_ctx, layout);
111 	g_object_unref(G_OBJECT(layout));
112     }
113 }
114 
115 
116 /*
117  * scan the body list and prepare print data according to the content type
118  */
119 static GList *
scan_body(GList * bpo_list,GtkPrintContext * context,BalsaPrintSetup * psetup,LibBalsaMessageBody * body,gboolean no_first_sep)120 scan_body(GList *bpo_list, GtkPrintContext * context, BalsaPrintSetup * psetup,
121 	  LibBalsaMessageBody * body, gboolean no_first_sep)
122 {
123 #ifdef HAVE_GPGME
124     gboolean add_signature;
125     gboolean have_crypto_frame;
126 #endif				/* HAVE_GPGME */
127 
128     while (body) {
129 	gchar *conttype;
130 
131 	conttype = libbalsa_message_body_get_mime_type(body);
132 #ifdef HAVE_GPGME
133 	add_signature = body->sig_info &&
134 	    g_ascii_strcasecmp(conttype, "application/pgp-signature") &&
135 	    g_ascii_strcasecmp(conttype, "application/pkcs7-signature") &&
136 	    g_ascii_strcasecmp(conttype, "application/x-pkcs7-signature");
137 	if (!g_ascii_strcasecmp("multipart/signed", conttype) &&
138 	    body->parts && body->parts->next
139 	    && body->parts->next->sig_info) {
140 	    have_crypto_frame = TRUE;
141 	    bpo_list = balsa_print_object_separator(bpo_list, psetup);
142 	    no_first_sep = TRUE;
143 	    if (body->was_encrypted)
144 		bpo_list = balsa_print_object_frame_begin(bpo_list,
145 							  _("Signed and encrypted matter"),
146 							  psetup);
147 	    else
148 		bpo_list = balsa_print_object_frame_begin(bpo_list,
149 							  _("Signed matter"),
150 							  psetup);
151 	} else if (!add_signature && body->was_encrypted) {
152 	    have_crypto_frame = TRUE;
153 	    bpo_list = balsa_print_object_separator(bpo_list, psetup);
154 	    no_first_sep = TRUE;
155 	    bpo_list = balsa_print_object_frame_begin(bpo_list,
156 						      _("Encrypted matter"),
157 						      psetup);
158 	} else
159 	    have_crypto_frame = FALSE;
160 #endif				/* HAVE_GPGME */
161 
162 	if (g_ascii_strncasecmp(conttype, "multipart/", 10)) {
163 	    if (no_first_sep)
164 		no_first_sep = FALSE;
165 	    else
166 		bpo_list = balsa_print_object_separator(bpo_list, psetup);
167 #ifdef HAVE_GPGME
168 	    if (add_signature) {
169 		if (body->was_encrypted)
170 		    bpo_list = balsa_print_object_frame_begin(bpo_list,
171 							      _("Signed and encrypted matter"),
172 							      psetup);
173 		else
174 		    bpo_list = balsa_print_object_frame_begin(bpo_list,
175 							      _("Signed matter"),
176 							      psetup);
177 	    }
178 #endif				/* HAVE_GPGME */
179 	    bpo_list = balsa_print_objects_append_from_body(bpo_list, context,
180 							    body, psetup);
181 	}
182 
183 	if (body->parts) {
184 	    bpo_list = scan_body(bpo_list, context, psetup, body->parts, no_first_sep);
185 	    no_first_sep = FALSE;
186 	}
187 
188 	/* end the frame for an embedded message or encrypted stuff */
189 	if (!g_ascii_strcasecmp(conttype, "message/rfc822")
190 #ifdef HAVE_GPGME
191 	    || have_crypto_frame
192 #endif
193 	    )
194 	    bpo_list = balsa_print_object_frame_end(bpo_list, psetup);
195 
196 #ifdef HAVE_GPGME
197 	if (add_signature) {
198 	    gchar *header =
199 		g_strdup_printf(_("This is an inline %s signed %s message part:"),
200 				body->sig_info->protocol == GPGME_PROTOCOL_OpenPGP ?
201 				_("OpenPGP") : _("S/MIME"), conttype);
202 	    bpo_list = balsa_print_object_separator(bpo_list, psetup);
203 	    bpo_list = balsa_print_object_header_crypto(bpo_list, context, body, header, psetup);
204 	    g_free(header);
205 	    bpo_list = balsa_print_object_frame_end(bpo_list, psetup);
206 	}
207 #endif				/* HAVE_GPGME */
208 	g_free(conttype);
209 
210 	body = body->next;
211     }
212 
213     return bpo_list;
214 }
215 
216 
217 static void
begin_print(GtkPrintOperation * operation,GtkPrintContext * context,BalsaPrintData * pdata)218 begin_print(GtkPrintOperation * operation, GtkPrintContext * context,
219 	    BalsaPrintData * pdata)
220 {
221     GtkPageSetup *page_setup;
222     PangoLayout *layout;
223     PangoFontDescription *font;
224     gchar *pagebuf;
225     gchar *subject;
226     gchar *date;
227     GString *footer_string;
228 
229     /* initialise the context */
230     page_setup = gtk_print_context_get_page_setup(context);
231 
232     /* calculate the "real" margins */
233     pdata->setup.c_x0 = balsa_app.margin_left -
234 	gtk_page_setup_get_left_margin(page_setup, GTK_UNIT_POINTS);
235     pdata->setup.c_width =
236 	gtk_page_setup_get_page_width(page_setup, GTK_UNIT_POINTS) -
237 	pdata->setup.c_x0 -(balsa_app.margin_right -
238 			    gtk_page_setup_get_right_margin(page_setup, GTK_UNIT_POINTS));
239     pdata->setup.c_y0 = balsa_app.margin_top -
240 	gtk_page_setup_get_top_margin(page_setup, GTK_UNIT_POINTS);
241     pdata->setup.c_height =
242 	gtk_page_setup_get_page_height(page_setup, GTK_UNIT_POINTS) -
243 	pdata->setup.c_y0 -(balsa_app.margin_bottom -
244 			    gtk_page_setup_get_bottom_margin(page_setup, GTK_UNIT_POINTS));
245 
246     pdata->setup.page_count = 1;
247 
248     /* create a layout so we can do some calculations */
249     layout = gtk_print_context_create_pango_layout(context);
250     pagebuf = g_strdup_printf(_("Page %d of %d"), 17, 42);
251 
252     /* basic body font height */
253     font = pango_font_description_from_string(balsa_app.print_body_font);
254     pango_layout_set_font_description(layout, font);
255     pango_font_description_free(font);
256     pdata->setup.p_body_font_height =
257 	p_string_height_from_layout(layout, pagebuf);
258 
259     /* basic header font and header height */
260     font = pango_font_description_from_string(balsa_app.print_header_font);
261     pango_layout_set_font_description(layout, font);
262     pango_font_description_free(font);
263     pdata->setup.p_hdr_font_height =
264 	p_string_height_from_layout(layout, pagebuf);
265     g_free(pagebuf);
266 
267     pdata->c_header_y = pdata->setup.c_y0;
268     pdata->setup.c_y0 += P_TO_C(pdata->setup.p_hdr_font_height) + C_HEADER_SEP;
269     pdata->setup.c_y_pos = pdata->setup.c_y0;
270     pdata->setup.c_height -=
271 	P_TO_C(pdata->setup.p_hdr_font_height) + C_HEADER_SEP;
272 
273     /* now create the footer string so we can reduce the height accordingly */
274     subject = g_strdup(LIBBALSA_MESSAGE_GET_SUBJECT(pdata->message));
275     libbalsa_utf8_sanitize(&subject, balsa_app.convert_unknown_8bit, NULL);
276     if (subject)
277 	footer_string = g_string_new(subject);
278     else
279 	footer_string = NULL;
280 
281     date = libbalsa_message_date_to_utf8(pdata->message, balsa_app.date_string);
282     if (footer_string) {
283 	footer_string = g_string_append(footer_string, " \342\200\224 ");
284 	footer_string = g_string_append(footer_string, date);
285     } else
286 	footer_string = g_string_new(date);
287     g_free(date);
288 
289     if (pdata->message->headers->from) {
290 	gchar *from =
291 	    internet_address_list_to_string(pdata->message->headers->from, FALSE);
292 
293 	libbalsa_utf8_sanitize(&from, balsa_app.convert_unknown_8bit,
294 			       NULL);
295 	if (footer_string) {
296 	    footer_string =
297 		g_string_prepend(footer_string, " \342\200\224 ");
298 	    footer_string = g_string_prepend(footer_string, from);
299 	} else {
300 	    footer_string = g_string_new(from);
301 	}
302 	g_free(from);
303     }
304 
305     /* if a footer is available, remember the string and adjust the height */
306     if (footer_string) {
307 	gint p_height;
308 
309 	/* create a layout to calculate the height of the footer */
310 	font = pango_font_description_from_string(balsa_app.print_footer_font);
311 	pango_layout_set_font_description(layout, font);
312 	pango_font_description_free(font);
313 	pango_layout_set_width(layout, C_TO_P(pdata->setup.c_width));
314 	pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
315 
316 	/* calculate the height and adjust the pringrid region */
317 	p_height = p_string_height_from_layout(layout, footer_string->str);
318 	pdata->c_footer_y =
319 	    pdata->setup.c_y0 + pdata->setup.c_height - P_TO_C(p_height);
320 	pdata->setup.c_height -= P_TO_C(p_height) + C_HEADER_SEP;
321 
322 	/* remember in the context */
323 	pdata->footer = g_string_free(footer_string, FALSE);
324     }
325     g_object_unref(G_OBJECT(layout));
326 
327     /* add the message headers */
328     pdata->setup.c_y_pos = 0.0;	/* to simplify calculating the layout... */
329     pdata->print_parts =
330 	balsa_print_object_header_from_message(NULL, context, pdata->message,
331 					       subject, &pdata->setup);
332     g_free(subject);
333 
334     /* add the mime bodies */
335     pdata->print_parts =
336 	scan_body(pdata->print_parts, context, &pdata->setup,
337 		  pdata->message->body_list, FALSE);
338 
339     /* done */
340     gtk_print_operation_set_n_pages(operation, pdata->setup.page_count);
341 }
342 
343 
344 static void
draw_page(GtkPrintOperation * operation,GtkPrintContext * context,gint page_nr,BalsaPrintData * print_data)345 draw_page(GtkPrintOperation * operation, GtkPrintContext * context,
346 	  gint page_nr, BalsaPrintData * print_data)
347 {
348     cairo_t *cairo_ctx;
349     GList * p;
350 
351     /* emit a warning if we try to print a non-existing page */
352     if (page_nr >= print_data->setup.page_count) {
353 	balsa_information(LIBBALSA_INFORMATION_WARNING,
354 			  ngettext("Cannot print page %d because "
355                                    "the document has only %d page.",
356 			           "Cannot print page %d because "
357                                    "the document has only %d pages.",
358                                    print_data->setup.page_count),
359 			  page_nr + 1, print_data->setup.page_count);
360 	return;
361     }
362 
363     /* get the cairo context */
364     cairo_ctx = gtk_print_context_get_cairo_context(context);
365 
366     /* print the page header and footer */
367     print_header_footer(context, cairo_ctx, print_data, page_nr);
368 
369     /* print parts */
370     p = print_data->print_parts;
371     while (p) {
372 	BalsaPrintObject *po = BALSA_PRINT_OBJECT(p->data);
373 
374 	if (po->on_page == page_nr)
375 	    balsa_print_object_draw(po, context, cairo_ctx);
376 
377 	p = g_list_next(p);
378     }
379 }
380 
381 /* setup gui related stuff */
382 /* shamelessly stolen from gtk+-2.10.6/gtk/gtkpagesetupunixdialog.c */
383 static GtkUnit
get_default_user_units(void)384 get_default_user_units(void)
385 {
386     /* Translate to the default units to use for presenting
387      * lengths to the user. Translate to default:inch if you
388      * want inches, otherwise translate to default:mm.
389      * Do *not* translate it to "predefinito:mm", if it
390      * it isn't default:mm or default:inch it will not work
391      */
392     gchar *e = _("default:mm");
393 
394 #if HAVE__NL_MEASUREMENT_MEASUREMENT
395     gchar *imperial = NULL;
396 
397     imperial = nl_langinfo(_NL_MEASUREMENT_MEASUREMENT);
398     if (imperial && imperial[0] == 2 )
399 	return GTK_UNIT_INCH;  /* imperial */
400     if (imperial && imperial[0] == 1 )
401 	return GTK_UNIT_MM;  /* metric */
402 #endif                          /* HAVE__NL_MEASUREMENT_MEASUREMENT */
403 
404     if (strcmp(e, "default:inch")==0)
405 	return GTK_UNIT_INCH;
406     else if (strcmp(e, "default:mm"))
407 	g_warning("Whoever translated default:mm did so wrongly.\n");
408     return GTK_UNIT_MM;
409 }
410 
411 static GtkWidget *
add_font_button(const gchar * text,const gchar * font,GtkGrid * grid,gint row)412 add_font_button(const gchar * text, const gchar * font, GtkGrid * grid,
413 		gint row)
414 {
415     GtkWidget *label;
416     GtkWidget *font_button;
417 
418     label = gtk_label_new_with_mnemonic(text);
419     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
420     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
421     gtk_widget_set_halign(label, GTK_ALIGN_START);
422     gtk_grid_attach(grid, label, 0, row, 1, 1);
423 
424     font_button = gtk_font_button_new_with_font(font);
425     gtk_label_set_mnemonic_widget(GTK_LABEL(label), font_button);
426     gtk_widget_set_hexpand(font_button, TRUE);
427     gtk_grid_attach(grid, font_button, 1, row, 1, 1);
428 
429     return font_button;
430 }
431 
432 /* note: min and max are passed in points = 1/72" */
433 static GtkWidget *
add_margin_spinbtn(const gchar * text,gdouble min,gdouble max,gdouble dflt,GtkGrid * grid,gint row)434 add_margin_spinbtn(const gchar * text, gdouble min, gdouble max, gdouble dflt,
435 		   GtkGrid * grid, gint row)
436 {
437     GtkWidget *label;
438     GtkWidget *spinbtn;
439     const gchar *unit;
440 
441     label = gtk_label_new_with_mnemonic(text);
442     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
443     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
444     gtk_widget_set_halign(label, GTK_ALIGN_START);
445     gtk_grid_attach(grid, label, 0, row, 1, 1);
446 
447     if (get_default_user_units() == GTK_UNIT_INCH) {
448 	unit = _("inch");
449 	spinbtn = gtk_spin_button_new_with_range(min / 72.0,
450 						 max / 72.0, 0.01);
451 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbtn), dflt / 72.0);
452 	gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinbtn), 2);
453 	gtk_spin_button_set_increments(GTK_SPIN_BUTTON(spinbtn), 0.1, 1.0);
454     } else {
455 	unit = _("mm");
456 	spinbtn = gtk_spin_button_new_with_range(min / 72.0 * 25.4,
457 						 max / 72.0 * 25.4, 0.1);
458 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbtn), dflt / 72.0 * 25.4);
459 	gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinbtn), 1);
460 	gtk_spin_button_set_increments(GTK_SPIN_BUTTON(spinbtn), 1.0, 10.0);
461     }
462     gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(spinbtn), TRUE);
463     gtk_label_set_mnemonic_widget(GTK_LABEL(label), spinbtn);
464     gtk_grid_attach(grid, spinbtn, 1, row, 1, 1);
465 
466     label = gtk_label_new(unit);
467     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
468     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
469     gtk_widget_set_halign(label, GTK_ALIGN_START);
470     gtk_grid_attach(grid, label, 2, row, 1, 1);
471 
472     return spinbtn;
473 }
474 
475 static void
check_margins(GtkAdjustment * adjustment,GtkAdjustment * other)476 check_margins(GtkAdjustment * adjustment, GtkAdjustment * other)
477 {
478     if (gtk_adjustment_get_value(adjustment) +
479         gtk_adjustment_get_value(other) >
480         gtk_adjustment_get_upper(adjustment))
481         gtk_adjustment_set_value(adjustment,
482                                  gtk_adjustment_get_upper(adjustment) -
483                                  gtk_adjustment_get_value(other));
484 }
485 
486 static GtkWidget *
message_prefs_widget(GtkPrintOperation * operation,BalsaPrintPrefs * print_prefs)487 message_prefs_widget(GtkPrintOperation * operation,
488 		     BalsaPrintPrefs * print_prefs)
489 {
490     GtkWidget *page;
491     GtkWidget *group;
492     GtkWidget *label;
493     GtkWidget *hbox;
494     GtkWidget *vbox;
495     GtkWidget *grid;
496     GtkPageSetup *pg_setup;
497     gchar *markup;
498 
499     gtk_print_operation_set_custom_tab_label(operation, _("Message"));
500 
501     page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 18);
502     gtk_container_set_border_width(GTK_CONTAINER(page), 12);
503 
504     group = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
505     gtk_box_pack_start(GTK_BOX(page), group, FALSE, TRUE, 0);
506 
507     label = gtk_label_new(NULL);
508     markup = g_strdup_printf("<b>%s</b>", _("Fonts"));
509     gtk_label_set_markup(GTK_LABEL(label), markup);
510     g_free(markup);
511     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
512     gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
513     gtk_box_pack_start(GTK_BOX(group), label, FALSE, FALSE, 0);
514 
515     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
516     gtk_box_pack_start(GTK_BOX(group), hbox, TRUE, TRUE, 0);
517     gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("    "),
518 		       FALSE, FALSE, 0);
519     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
520     gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
521 
522     grid = gtk_grid_new();
523     gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
524     gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
525 
526     gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, TRUE, 0);
527 
528     print_prefs->header_font =
529 	add_font_button(_("_Header Font:"), balsa_app.print_header_font,
530 			GTK_GRID(grid), 0);
531     print_prefs->body_font =
532 	add_font_button(_("B_ody Font:"), balsa_app.print_body_font,
533 			GTK_GRID(grid), 1);
534     print_prefs->footer_font =
535 	add_font_button(_("_Footer Font:"), balsa_app.print_footer_font,
536 			GTK_GRID(grid), 2);
537 
538     group = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
539     gtk_box_pack_start(GTK_BOX(page), group, FALSE, TRUE, 0);
540 
541     label = gtk_label_new(NULL);
542     markup = g_strdup_printf("<b>%s</b>", _("Highlighting"));
543     gtk_label_set_markup(GTK_LABEL(label), markup);
544     g_free(markup);
545     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
546     gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
547     gtk_box_pack_start(GTK_BOX(group), label, FALSE, FALSE, 0);
548 
549     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
550     gtk_box_pack_start(GTK_BOX(group), hbox, TRUE, TRUE, 0);
551     gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("    "),
552 		       FALSE, FALSE, 0);
553     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
554     gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
555 
556     print_prefs->highlight_cited =
557 	gtk_check_button_new_with_mnemonic(_("Highlight _cited text"));
558     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
559 				 (print_prefs->highlight_cited),
560 				 balsa_app.print_highlight_cited);
561     gtk_box_pack_start(GTK_BOX(vbox), print_prefs->highlight_cited, FALSE,
562 		       TRUE, 0);
563 
564     print_prefs->highlight_phrases =
565 	gtk_check_button_new_with_mnemonic(_
566 					   ("Highlight _structured phrases"));
567     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
568 				 (print_prefs->highlight_phrases),
569 				 balsa_app.print_highlight_phrases);
570     gtk_box_pack_start(GTK_BOX(vbox), print_prefs->highlight_phrases,
571 		       FALSE, TRUE, 0);
572 
573     group = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
574     gtk_box_pack_start(GTK_BOX(page), group, FALSE, TRUE, 0);
575 
576     label = gtk_label_new(NULL);
577     markup = g_strdup_printf("<b>%s</b>", _("Margins"));
578     gtk_label_set_markup(GTK_LABEL(label), markup);
579     g_free(markup);
580     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
581     gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
582     gtk_box_pack_start(GTK_BOX(group), label, FALSE, FALSE, 0);
583 
584     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
585     gtk_box_pack_start(GTK_BOX(group), hbox, TRUE, TRUE, 0);
586     gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("    "),
587 		       FALSE, FALSE, 0);
588     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
589     gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
590 
591     grid = gtk_grid_new();
592     gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
593     gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
594 
595     gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, TRUE, 0);
596 
597     pg_setup = gtk_print_operation_get_default_page_setup(operation);
598     print_prefs->margin_top =
599 	add_margin_spinbtn(_("_Top"),
600 			   gtk_page_setup_get_top_margin(pg_setup, GTK_UNIT_POINTS),
601 			   gtk_page_setup_get_page_height(pg_setup, GTK_UNIT_POINTS),
602 			   balsa_app.margin_top,
603 			   GTK_GRID(grid), 0);
604     print_prefs->margin_bottom =
605 	add_margin_spinbtn(_("_Bottom"),
606 			   gtk_page_setup_get_bottom_margin(pg_setup, GTK_UNIT_POINTS),
607 			   gtk_page_setup_get_page_height(pg_setup, GTK_UNIT_POINTS),
608 			   balsa_app.margin_bottom,
609 			   GTK_GRID(grid), 1);
610     g_signal_connect(G_OBJECT(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_top))),
611 		     "value-changed", G_CALLBACK(check_margins),
612 		     gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_bottom)));
613     g_signal_connect(G_OBJECT(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_bottom))),
614 		     "value-changed", G_CALLBACK(check_margins),
615 		     gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_top)));
616     print_prefs->margin_left =
617 	add_margin_spinbtn(_("_Left"),
618 			   gtk_page_setup_get_left_margin(pg_setup, GTK_UNIT_POINTS),
619 			   gtk_page_setup_get_page_width(pg_setup, GTK_UNIT_POINTS),
620 			   balsa_app.margin_left,
621 			   GTK_GRID(grid), 2);
622     print_prefs->margin_right =
623 	add_margin_spinbtn(_("_Right"),
624 			   gtk_page_setup_get_right_margin(pg_setup, GTK_UNIT_POINTS),
625 			   gtk_page_setup_get_page_width(pg_setup, GTK_UNIT_POINTS),
626 			   balsa_app.margin_right,
627 			   GTK_GRID(grid), 3);
628     g_signal_connect(G_OBJECT(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_left))),
629 		     "value-changed", G_CALLBACK(check_margins),
630 		     gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_right)));
631     g_signal_connect(G_OBJECT(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_right))),
632 		     "value-changed", G_CALLBACK(check_margins),
633 		     gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(print_prefs->margin_left)));
634 
635     gtk_widget_show_all(page);
636 
637     return page;
638 }
639 
640 
641 static void
message_prefs_apply(GtkPrintOperation * operation,GtkWidget * widget,BalsaPrintPrefs * print_prefs)642 message_prefs_apply(GtkPrintOperation * operation, GtkWidget * widget,
643 		    BalsaPrintPrefs * print_prefs)
644 {
645     g_free(balsa_app.print_header_font);
646     balsa_app.print_header_font =
647 	g_strdup(gtk_font_button_get_font_name
648 		 (GTK_FONT_BUTTON(print_prefs->header_font)));
649     g_free(balsa_app.print_body_font);
650     balsa_app.print_body_font =
651 	g_strdup(gtk_font_button_get_font_name
652 		 (GTK_FONT_BUTTON(print_prefs->body_font)));
653     g_free(balsa_app.print_footer_font);
654     balsa_app.print_footer_font =
655 	g_strdup(gtk_font_button_get_font_name
656 		 (GTK_FONT_BUTTON(print_prefs->footer_font)));
657     balsa_app.print_highlight_cited =
658 	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
659                                      (print_prefs->highlight_cited));
660     balsa_app.print_highlight_phrases =
661 	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
662                                      (print_prefs->highlight_phrases));
663 
664     balsa_app.margin_top =
665 	gtk_spin_button_get_value(GTK_SPIN_BUTTON(print_prefs->margin_top)) * 72.0;
666     balsa_app.margin_bottom =
667 	gtk_spin_button_get_value(GTK_SPIN_BUTTON(print_prefs->margin_bottom)) * 72.0;
668     balsa_app.margin_left =
669 	gtk_spin_button_get_value(GTK_SPIN_BUTTON(print_prefs->margin_left)) * 72.0;
670     balsa_app.margin_right =
671 	gtk_spin_button_get_value(GTK_SPIN_BUTTON(print_prefs->margin_right)) * 72.0;
672     if (get_default_user_units() != GTK_UNIT_INCH) {
673 	/* adjust for mm */
674 	balsa_app.margin_top /= 25.4;
675 	balsa_app.margin_bottom /= 25.4;
676 	balsa_app.margin_left /= 25.4;
677 	balsa_app.margin_right /= 25.4;
678     }
679 }
680 
681 
682 void
message_print_page_setup(GtkWindow * parent)683 message_print_page_setup(GtkWindow * parent)
684 {
685     GtkPageSetup *new_page_setup;
686 
687     if (!balsa_app.print_settings)
688 	balsa_app.print_settings = gtk_print_settings_new();
689 
690     new_page_setup =
691 	gtk_print_run_page_setup_dialog(parent, balsa_app.page_setup,
692 					balsa_app.print_settings);
693 
694     if (balsa_app.page_setup)
695 	g_object_unref(G_OBJECT(balsa_app.page_setup));
696 
697     balsa_app.page_setup = new_page_setup;
698 }
699 
700 
701 void
message_print(LibBalsaMessage * msg,GtkWindow * parent)702 message_print(LibBalsaMessage * msg, GtkWindow * parent)
703 {
704     GtkPrintOperation *print;
705     GtkPrintOperationResult res;
706     BalsaPrintData *print_data;
707     BalsaPrintPrefs print_prefs;
708     GError *err = NULL;
709 
710     print = gtk_print_operation_new();
711     g_assert(print != NULL);
712 
713     g_object_ref(G_OBJECT(msg));
714 
715     gtk_print_operation_set_n_pages(print, 1);
716     gtk_print_operation_set_unit(print, GTK_UNIT_POINTS);
717     gtk_print_operation_set_use_full_page(print, FALSE);
718 
719     if (balsa_app.print_settings != NULL)
720 	gtk_print_operation_set_print_settings(print,
721 					       balsa_app.print_settings);
722     if (balsa_app.page_setup != NULL)
723 	gtk_print_operation_set_default_page_setup(print,
724 						   balsa_app.page_setup);
725 
726     /* create a print context */
727     print_data = g_new0(BalsaPrintData, 1);
728     print_data->message = msg;
729 
730     g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), print_data);
731     g_signal_connect(print, "draw_page", G_CALLBACK(draw_page), print_data);
732     g_signal_connect(print, "create-custom-widget",
733 		     G_CALLBACK(message_prefs_widget), &print_prefs);
734     g_signal_connect(print, "custom-widget-apply",
735 		     G_CALLBACK(message_prefs_apply), &print_prefs);
736 
737     res =
738 	gtk_print_operation_run(print,
739 				GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
740 				parent, &err);
741 
742     if (res == GTK_PRINT_OPERATION_RESULT_APPLY) {
743 	if (balsa_app.print_settings != NULL)
744 	    g_object_unref(balsa_app.print_settings);
745 	balsa_app.print_settings =
746 	    g_object_ref(gtk_print_operation_get_print_settings(print));
747     } else if (res == GTK_PRINT_OPERATION_RESULT_ERROR)
748 	balsa_information(LIBBALSA_INFORMATION_ERROR,
749 			  _("Error printing message: %s"), err->message);
750 
751     /* clean up */
752     if (err)
753 	g_error_free(err);
754     g_list_foreach(print_data->print_parts, (GFunc) g_object_unref, NULL);
755     g_list_free(print_data->print_parts);
756     g_free(print_data->footer);
757     g_free(print_data);
758     g_object_unref(G_OBJECT(print));
759     g_object_unref(G_OBJECT(msg));
760 }
761