geany/src/printing.c
Colomban Wendling 7698bf60a7 Improve usage of G_(UN)?LIKELY()
G_(UN)?LIKELY() should be only used on whole conditional expressions,
and only if the branching is very highly predictable, not if it is only
more probable.

These macros should be used with care because a wrong prediction may
be a lot worst than what a good prediction can give.

git-svn-id: https://geany.svn.sourceforge.net/svnroot/geany/trunk@5625 ea778897-0a13-0410-b9d1-a72fbfd435f5
2011-03-24 22:00:18 +00:00

929 lines
29 KiB
C

/*
* printing.c - this file is part of Geany, a fast and lightweight IDE
*
* Copyright 2007-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
* Copyright 2007-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
/*
* GTK 2.10 printing support
* (basic code layout were adopted from Sylpheed's printing implementation, thanks)
*/
#include <math.h>
#include <time.h>
#include <string.h>
#include "geany.h"
#include "printing.h"
#include "document.h"
#include "sciwrappers.h"
#include "editor.h"
#include "sciwrappers.h"
#include "utils.h"
#include "support.h"
#include "dialogs.h"
#include "utils.h"
#include "ui_utils.h"
#include "msgwindow.h"
PrintingPrefs printing_prefs;
#if GTK_CHECK_VERSION(2, 10, 0)
#define ROTATE_RGB(color) \
(((color) & 0xFF0000) >> 16) + ((color) & 0x00FF00) + (((color) & 0x0000FF) << 16)
#define ADD_ATTR(l, a) \
pango_attr_list_insert((l), (a)); \
(a)->start_index = 0; \
(a)->end_index = -1;
enum
{
FORE = 0,
BACK,
BOLD,
ITALIC,
MAX_TYPES
};
/* document-related variables */
typedef struct
{
GeanyDocument *doc;
gint font_width;
gint lines;
gint n_pages;
gint lines_per_page;
gint max_line_number_margin;
gint cur_line;
gint cur_pos;
gint styles[STYLE_MAX + 1][MAX_TYPES];
gdouble line_height;
/* whether we have a wrapped line on page end to take care of on next page */
gboolean long_line;
/* set in begin_print() to hold the time when printing was started to ensure all printed
* pages have the same date and time (in case of slow machines and many pages where rendering
* takes more than a second) */
time_t print_time;
PangoLayout *layout; /* commonly used layout object */
} DocInfo;
/* widget references for the custom widget in the print dialog */
typedef struct
{
GtkWidget *check_print_linenumbers;
GtkWidget *check_print_pagenumbers;
GtkWidget *check_print_pageheader;
GtkWidget *check_print_basename;
GtkWidget *entry_print_dateformat;
} PrintWidgets;
static GtkPrintSettings *settings = NULL;
static GtkPageSetup *page_setup = NULL;
/* returns the "width" (count of needed characters) for the given number */
static gint get_line_numbers_arity(gint x)
{
gint a = 0;
while ((x /= 10) != 0)
a++;
return a;
}
/* split a RGB colour into the three colour components */
static void get_rgb_values(gint c, gint *r, gint *g, gint *b)
{
c = ROTATE_RGB(c);
if (interface_prefs.highlighting_invert_all)
c = utils_invert_color(c);
*r = c % 256;
*g = (c & - 16711936) / 256;
*b = (c & 0xff0000) / 65536;
*r *= 257;
*g *= 257;
*b *= 257;
}
/* creates a commonly used layout object from the given context for use in get_page_count and
* draw_page */
static PangoLayout *setup_pango_layout(GtkPrintContext *context, PangoFontDescription *desc)
{
PangoLayout *layout;
layout = gtk_print_context_create_pango_layout(context);
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
pango_layout_set_spacing(layout, 0);
pango_layout_set_attributes(layout, NULL);
pango_layout_set_font_description(layout, desc);
return layout;
}
static gboolean utils_font_desc_check_monospace(PangoContext *pc, PangoFontDescription *desc)
{
PangoFontFamily **families;
gint n_families, i;
const gchar *font;
gboolean ret = TRUE;
font = pango_font_description_get_family(desc);
pango_context_list_families(pc, &families, &n_families);
for (i = 0; i < n_families; i++)
{
if (utils_str_equal(font, pango_font_family_get_name(families[i])))
{
if (!pango_font_family_is_monospace(families[i]))
{
ret = FALSE;
}
}
}
g_free(families);
return ret;
}
/* We don't support variable width fonts (yet) */
static gint get_font_width(GtkPrintContext *context, PangoFontDescription *desc)
{
PangoContext *pc;
PangoFontMetrics *metrics;
gint width;
pc = gtk_print_context_create_pango_context(context);
if (!utils_font_desc_check_monospace(pc, desc))
dialogs_show_msgbox_with_secondary(GTK_MESSAGE_WARNING,
_("The editor font is not a monospaced font!"),
_("Text will be wrongly spaced."));
metrics = pango_context_get_metrics(pc, desc, pango_context_get_language(pc));
/** TODO is this the best result we can get? */
/* digit and char width are mostly equal for monospace fonts, char width might be
* for dual width characters(e.g. Japanese) so use digit width to get sure we get the width
* for one character */
width = pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE;
pango_font_metrics_unref(metrics);
g_object_unref(pc);
return width;
}
static gint get_page_count(GtkPrintContext *context, DocInfo *dinfo)
{
gdouble width, height;
gint layout_h;
gint i, j, lines_left;
gchar *line_buf;
if (dinfo == NULL)
return -1;
width = gtk_print_context_get_width(context);
height = gtk_print_context_get_height(context);
if (printing_prefs.print_line_numbers)
/* remove line number margin space from overall width */
width -= dinfo->max_line_number_margin * dinfo->font_width;
pango_layout_set_width(dinfo->layout, width * PANGO_SCALE);
/* add test text to get line height */
pango_layout_set_text(dinfo->layout, "Test 1", -1);
pango_layout_get_size(dinfo->layout, NULL, &layout_h);
if (layout_h <= 0)
{
geany_debug("Invalid layout_h (%d). Falling back to default height (%d)",
layout_h, 100 * PANGO_SCALE);
layout_h = 100 * PANGO_SCALE;
}
dinfo->line_height = (gdouble)layout_h / PANGO_SCALE;
dinfo->lines_per_page = ceil((height - dinfo->line_height) / dinfo->line_height);
#ifdef GEANY_PRINT_DEBUG
geany_debug("max lines_per_page: %d", dinfo->lines_per_page);
#endif
if (printing_prefs.print_page_numbers)
dinfo->lines_per_page -= 2;
if (printing_prefs.print_page_header)
dinfo->lines_per_page -= 3;
lines_left = dinfo->lines_per_page;
i = 0;
for (j = 0; j < dinfo->lines; j++)
{
gint lines = 1;
gint line_width;
line_buf = sci_get_line(dinfo->doc->editor->sci, j);
line_width = (g_utf8_strlen(line_buf, -1) + 1) * dinfo->font_width;
if (line_width > width)
lines = ceil(line_width / width);
#ifdef GEANY_PRINT_DEBUG
if (lines != 1) geany_debug("%d %d", j+1, lines);
#endif
while (lines_left < lines)
{
lines -= lines_left;
lines_left = dinfo->lines_per_page;
i++;
}
lines_left -= lines;
g_free(line_buf);
}
return i + 1;
}
static void add_page_header(DocInfo *dinfo, cairo_t *cr, gint width, gint page_nr)
{
gint ph_height = dinfo->line_height * 3;
gchar *data;
gchar *datetime;
gchar *tmp_file_name = (dinfo->doc->file_name != NULL) ?
dinfo->doc->file_name : GEANY_STRING_UNTITLED;
gchar *file_name = (printing_prefs.page_header_basename) ?
g_path_get_basename(tmp_file_name) : g_strdup(tmp_file_name);
PangoLayout *layout = dinfo->layout;
/* draw the frame */
cairo_set_line_width(cr, 0.3);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_rectangle(cr, 2, 2, width - 4, ph_height - 4);
cairo_stroke(cr);
/* width - 8: 2px between doc border and frame border, 2px between frame border and text
* and this on left and right side, so (2 + 2) * 2 */
pango_layout_set_width(layout, (width - 8) * PANGO_SCALE);
if ((g_utf8_strlen(file_name, -1) * dinfo->font_width) >= ((width - 4) - (dinfo->font_width * 2)))
/* if the filename is wider than the available space on the line, skip parts of it */
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE);
data = g_strdup_printf("<b>%s</b>", file_name);
pango_layout_set_markup(layout, data, -1);
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
cairo_move_to(cr, 4, dinfo->line_height * 0.5);
pango_cairo_show_layout(cr, layout);
g_free(data);
g_free(file_name);
data = g_strdup_printf(_("<b>Page %d of %d</b>"), page_nr + 1, dinfo->n_pages);
pango_layout_set_markup(layout, data, -1);
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
cairo_move_to(cr, 4, dinfo->line_height * 1.5);
pango_cairo_show_layout(cr, layout);
g_free(data);
datetime = utils_get_date_time(printing_prefs.page_header_datefmt, &(dinfo->print_time));
if (G_LIKELY(NZV(datetime)))
{
data = g_strdup_printf("<b>%s</b>", datetime);
pango_layout_set_markup(layout, data, -1);
pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
cairo_move_to(cr, 2, dinfo->line_height * 1.5);
pango_cairo_show_layout(cr, layout);
g_free(data);
}
g_free(datetime);
/* reset layout and re-position cairo context */
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
pango_layout_set_ellipsize(layout, FALSE);
pango_layout_set_justify(layout, FALSE);
pango_layout_set_width(layout, width * PANGO_SCALE);
cairo_move_to(cr, 0, dinfo->line_height * 3);
}
static void custom_widget_apply(GtkPrintOperation *operation, GtkWidget *widget, gpointer user_data)
{
PrintWidgets *w = user_data;
printing_prefs.print_line_numbers =
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->check_print_linenumbers));
printing_prefs.print_page_numbers =
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->check_print_pagenumbers));
printing_prefs.print_page_header =
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->check_print_pageheader));
printing_prefs.page_header_basename =
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->check_print_basename));
g_free(printing_prefs.page_header_datefmt);
printing_prefs.page_header_datefmt =
g_strdup(gtk_entry_get_text(GTK_ENTRY(w->entry_print_dateformat)));
}
static void on_page_header_toggled(GtkToggleButton *togglebutton, gpointer user_data)
{
gboolean sens = gtk_toggle_button_get_active(togglebutton);
PrintWidgets *w = user_data;
gtk_widget_set_sensitive(w->check_print_basename, sens);
gtk_widget_set_sensitive(w->entry_print_dateformat, sens);
}
static GtkWidget *create_custom_widget(GtkPrintOperation *operation, gpointer user_data)
{ /* copied from interface.c */
GtkWidget *page;
GtkWidget *frame33;
GtkWidget *alignment36;
GtkWidget *vbox30;
GtkWidget *hbox10;
GtkWidget *label203;
PrintWidgets *w = user_data;
gtk_print_operation_set_custom_tab_label(operation, _("Document Setup"));
page = gtk_vbox_new(FALSE, 0);
gtk_container_set_border_width(GTK_CONTAINER(page), 5);
w->check_print_linenumbers = gtk_check_button_new_with_mnemonic(_("Print line numbers"));
gtk_box_pack_start(GTK_BOX(page), w->check_print_linenumbers, FALSE, FALSE, 0);
ui_widget_set_tooltip_text(w->check_print_linenumbers, _("Add line numbers to the printed page"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->check_print_linenumbers), printing_prefs.print_line_numbers);
w->check_print_pagenumbers = gtk_check_button_new_with_mnemonic(_("Print page numbers"));
gtk_box_pack_start(GTK_BOX(page), w->check_print_pagenumbers, FALSE, FALSE, 0);
ui_widget_set_tooltip_text(w->check_print_pagenumbers, _("Add page numbers at the bottom of each page. It takes 2 lines of the page."));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->check_print_pagenumbers), printing_prefs.print_page_numbers);
w->check_print_pageheader = gtk_check_button_new_with_mnemonic(_("Print page header"));
gtk_box_pack_start(GTK_BOX(page), w->check_print_pageheader, FALSE, FALSE, 0);
ui_widget_set_tooltip_text(w->check_print_pageheader, _("Add a little header to every page containing the page number, the filename and the current date (see below). It takes 3 lines of the page."));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->check_print_pageheader), printing_prefs.print_page_header);
g_signal_connect(w->check_print_pageheader, "toggled", G_CALLBACK(on_page_header_toggled), w);
frame33 = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(page), frame33, FALSE, FALSE, 0);
gtk_frame_set_label_align(GTK_FRAME(frame33), 0, 0);
gtk_frame_set_shadow_type(GTK_FRAME(frame33), GTK_SHADOW_NONE);
alignment36 = gtk_alignment_new(0, 0.5, 1, 1);
gtk_container_add(GTK_CONTAINER(frame33), alignment36);
gtk_alignment_set_padding(GTK_ALIGNMENT(alignment36), 0, 0, 12, 0);
vbox30 = gtk_vbox_new(FALSE, 1);
gtk_container_add(GTK_CONTAINER(alignment36), vbox30);
w->check_print_basename = gtk_check_button_new_with_mnemonic(_("Use the basename of the printed file"));
gtk_box_pack_start(GTK_BOX(vbox30), w->check_print_basename, FALSE, FALSE, 0);
ui_widget_set_tooltip_text(w->check_print_basename, _("Print only the basename(without the path) of the printed file"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->check_print_basename), printing_prefs.page_header_basename);
hbox10 = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox30), hbox10, TRUE, TRUE, 0);
label203 = gtk_label_new(_("Date format:"));
gtk_box_pack_start(GTK_BOX(hbox10), label203, FALSE, FALSE, 0);
w->entry_print_dateformat = gtk_entry_new();
ui_entry_add_clear_icon(GTK_ENTRY(w->entry_print_dateformat));
gtk_box_pack_start(GTK_BOX(hbox10), w->entry_print_dateformat, TRUE, TRUE, 0);
ui_widget_set_tooltip_text(w->entry_print_dateformat, _("Specify a format for the date and time stamp which is added to the page header on each page. You can use any conversion specifiers which can be used with the ANSI C strftime function."));
gtk_entry_set_text(GTK_ENTRY(w->entry_print_dateformat), printing_prefs.page_header_datefmt);
on_page_header_toggled(GTK_TOGGLE_BUTTON(w->check_print_pageheader), w);
gtk_widget_show_all(page);
return page;
}
static void end_print(GtkPrintOperation *operation, GtkPrintContext *context, gpointer user_data)
{
DocInfo *dinfo = user_data;
if (dinfo == NULL)
return;
gtk_widget_hide(main_widgets.progressbar);
g_object_unref(dinfo->layout);
}
static void begin_print(GtkPrintOperation *operation, GtkPrintContext *context, gpointer user_data)
{
DocInfo *dinfo = user_data;
PangoFontDescription *desc;
gint i;
gint style_max;
if (dinfo == NULL)
return;
gtk_widget_show(main_widgets.progressbar);
desc = pango_font_description_from_string(interface_prefs.editor_font);
/* init dinfo fields */
dinfo->lines = sci_get_line_count(dinfo->doc->editor->sci);
dinfo->lines_per_page = 0;
dinfo->cur_line = 0;
dinfo->cur_pos = 0;
dinfo->long_line = FALSE;
dinfo->print_time = time(NULL);
dinfo->max_line_number_margin = get_line_numbers_arity(dinfo->lines) + 1;
/* increase font width by 1 (looks better) */
dinfo->font_width = get_font_width(context, desc) + 1;
/* create a PangoLayout to be commonly used in get_page_count and draw_page */
dinfo->layout = setup_pango_layout(context, desc);
/* this is necessary because of possible line breaks on the printed page and then
* lines_per_page differs from document line count */
dinfo->n_pages = get_page_count(context, dinfo);
/* read all styles from Scintilla */
style_max = pow(2, scintilla_send_message(dinfo->doc->editor->sci, SCI_GETSTYLEBITS, 0, 0));
/* if the lexer uses only the first 32 styles(style bits = 5),
* we need to add the pre-defined styles */
if (style_max == 32)
style_max = STYLE_LASTPREDEFINED;
for (i = 0; i < style_max; i++)
{
dinfo->styles[i][FORE] = ROTATE_RGB(scintilla_send_message(
dinfo->doc->editor->sci, SCI_STYLEGETFORE, i, 0));
if (i == STYLE_LINENUMBER)
{ /* ignore background colour for line number margin to avoid trouble with wrapped lines */
dinfo->styles[STYLE_LINENUMBER][BACK] = ROTATE_RGB(scintilla_send_message(
dinfo->doc->editor->sci, SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
}
else
{
dinfo->styles[i][BACK] = ROTATE_RGB(scintilla_send_message(
dinfo->doc->editor->sci, SCI_STYLEGETBACK, i, 0));
}
/* use white background color unless foreground is white to save ink */
if (dinfo->styles[i][FORE] != 0xffffff)
dinfo->styles[i][BACK] = 0xffffff;
dinfo->styles[i][BOLD] =
scintilla_send_message(dinfo->doc->editor->sci, SCI_STYLEGETBOLD, i, 0);
dinfo->styles[i][ITALIC] =
scintilla_send_message(dinfo->doc->editor->sci, SCI_STYLEGETITALIC, i, 0);
}
if (dinfo->n_pages >= 0)
gtk_print_operation_set_n_pages(operation, dinfo->n_pages);
pango_font_description_free(desc);
}
static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context,
gint page_nr, gpointer user_data)
{
DocInfo *dinfo = user_data;
GeanyEditor *editor;
cairo_t *cr;
gdouble width, height;
gdouble x, y;
/*gint layout_h;*/
gint count;
GString *str;
if (dinfo == NULL || page_nr >= dinfo->n_pages)
return;
editor = dinfo->doc->editor;
if (dinfo->n_pages > 0)
{
gdouble fraction = (page_nr + 1) / (gdouble) dinfo->n_pages;
gchar *text = g_strdup_printf(_("Page %d of %d"), page_nr, dinfo->n_pages);
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(main_widgets.progressbar), fraction);
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
g_free(text);
}
#ifdef GEANY_PRINT_DEBUG
geany_debug("draw_page = %d, pages = %d, (real) lines_per_page = %d",
page_nr, dinfo->n_pages, dinfo->lines_per_page);
#endif
str = g_string_sized_new(256);
cr = gtk_print_context_get_cairo_context(context);
width = gtk_print_context_get_width(context);
height = gtk_print_context_get_height(context);
cairo_set_source_rgb(cr, 0, 0, 0);
#ifdef GEANY_PRINT_DEBUG
cairo_set_line_width(cr, 0.2);
cairo_rectangle(cr, 0, 0, width, height);
cairo_stroke(cr);
#endif
cairo_move_to(cr, 0, 0);
pango_layout_set_width(dinfo->layout, width * PANGO_SCALE);
pango_layout_set_alignment(dinfo->layout, PANGO_ALIGN_LEFT);
pango_layout_set_ellipsize(dinfo->layout, FALSE);
pango_layout_set_justify(dinfo->layout, FALSE);
if (printing_prefs.print_page_header)
add_page_header(dinfo, cr, width, page_nr);
count = 0; /* the actual line counter for the current page, might be different from
* dinfo->cur_line due to possible line breaks */
while (count < dinfo->lines_per_page)
{
gchar c = 'a';
gint style = -1;
PangoAttrList *layout_attr;
PangoAttribute *attr;
gint colours[3] = { 0 };
gboolean add_linenumber = TRUE;
gboolean at_eol;
while (count < dinfo->lines_per_page && c != '\0')
{
at_eol = FALSE;
g_string_erase(str, 0, str->len); /* clear the string */
/* line numbers */
if (printing_prefs.print_line_numbers && add_linenumber)
{
/* if we had a wrapped line on the last page which needs to be continued, don't
* add a line number */
if (dinfo->long_line)
{
add_linenumber = FALSE;
}
else
{
gchar *line_number = NULL;
gint cur_line_number_margin = get_line_numbers_arity(dinfo->cur_line + 1);
gchar *fill = g_strnfill(
dinfo->max_line_number_margin - cur_line_number_margin - 1, ' ');
line_number = g_strdup_printf("%s%d ", fill, dinfo->cur_line + 1);
g_string_append(str, line_number);
dinfo->cur_line++; /* increase document line */
add_linenumber = FALSE;
style = STYLE_LINENUMBER;
c = 'a'; /* dummy value */
g_free(fill);
g_free(line_number);
}
}
/* data */
else
{
style = sci_get_style_at(dinfo->doc->editor->sci, dinfo->cur_pos);
c = sci_get_char_at(dinfo->doc->editor->sci, dinfo->cur_pos);
if (c == '\0' || style == -1)
{ /* if c gets 0, we are probably out of document boundaries,
* so stop to break out of outer loop */
count = dinfo->lines_per_page;
break;
}
dinfo->cur_pos++;
/* convert tabs to spaces which seems to be better than using Pango tabs */
if (c == '\t')
{
gint tab_width = sci_get_tab_width(editor->sci);
gchar *s = g_strnfill(tab_width, ' ');
g_string_append(str, s);
g_free(s);
}
/* don't add line breaks, they are handled manually below */
else if (c == '\r' || c == '\n')
{
gchar c_next = sci_get_char_at(dinfo->doc->editor->sci, dinfo->cur_pos);
at_eol = TRUE;
if (c == '\r' && c_next == '\n')
dinfo->cur_pos++; /* skip LF part of CR/LF */
}
else
{
g_string_append_c(str, c); /* finally add the character */
/* handle UTF-8: since we add char by char (better: byte by byte), we need to
* keep UTF-8 characters together(e.g. two bytes for one character)
* the input is always UTF-8 and c is signed, so all non-Ascii
* characters are less than 0 and consist of all bytes less than 0.
* style doesn't change since it is only one character with multiple bytes. */
while (c < 0)
{
c = sci_get_char_at(dinfo->doc->editor->sci, dinfo->cur_pos);
if (c < 0)
{ /* only add the byte when it is part of the UTF-8 character
* otherwise we could add e.g. a '\n' and it won't be visible in the
* printed document */
g_string_append_c(str, c);
dinfo->cur_pos++;
}
}
}
}
if (! at_eol)
{
/* set text */
pango_layout_set_text(dinfo->layout, str->str, -1);
/* attributes */
layout_attr = pango_attr_list_new();
/* foreground colour */
get_rgb_values(dinfo->styles[style][FORE], &colours[0], &colours[1], &colours[2]);
attr = pango_attr_foreground_new(colours[0], colours[1], colours[2]);
ADD_ATTR(layout_attr, attr);
/* background colour */
get_rgb_values(dinfo->styles[style][BACK], &colours[0], &colours[1], &colours[2]);
attr = pango_attr_background_new(colours[0], colours[1], colours[2]);
ADD_ATTR(layout_attr, attr);
/* bold text */
if (dinfo->styles[style][BOLD])
{
attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
ADD_ATTR(layout_attr, attr);
}
/* italic text */
if (dinfo->styles[style][ITALIC])
{
attr = pango_attr_style_new(PANGO_STYLE_ITALIC);
ADD_ATTR(layout_attr, attr);
}
pango_layout_set_attributes(dinfo->layout, layout_attr);
pango_layout_context_changed(dinfo->layout);
pango_attr_list_unref(layout_attr);
}
cairo_get_current_point(cr, &x, &y);
/* normal line break at eol character in document */
if (at_eol)
{
/*pango_layout_get_size(dinfo->layout, NULL, &layout_h);*/
/*cairo_move_to(cr, 0, y + (gdouble)layout_h / PANGO_SCALE);*/
cairo_move_to(cr, 0, y + dinfo->line_height);
count++;
/* we added a new document line so request a new line number */
add_linenumber = TRUE;
}
else
{
gint x_offset = 0;
/* maybe we need to force a line break because of too long line */
if (x >= (width - dinfo->font_width))
{
/* don't start the line at horizontal origin because we need to skip the
* line number margin */
if (printing_prefs.print_line_numbers)
{
x_offset = (dinfo->max_line_number_margin + 1) * dinfo->font_width;
}
/*pango_layout_get_size(dinfo->layout, NULL, &layout_h);*/
/*cairo_move_to(cr, x_offset, y + (gdouble)layout_h / PANGO_SCALE);*/
/* this is faster but not exactly the same as above */
cairo_move_to(cr, x_offset, y + dinfo->line_height);
cairo_get_current_point(cr, &x, &y);
count++;
}
if (count < dinfo->lines_per_page)
{
/* str->len is counted in bytes not characters, so use g_utf8_strlen() */
x_offset = (g_utf8_strlen(str->str, -1) * dinfo->font_width);
if (dinfo->long_line && count == 0)
{
x_offset = (dinfo->max_line_number_margin + 1) * dinfo->font_width;
dinfo->long_line = FALSE;
}
pango_cairo_show_layout(cr, dinfo->layout);
cairo_move_to(cr, x + x_offset, y);
}
else
/* we are on a wrapped line but we are out of lines on this page, so continue
* the current line on the next page and remember to continue in current line */
dinfo->long_line = TRUE;
}
}
}
if (printing_prefs.print_line_numbers)
{ /* print a thin line between the line number margin and the data */
gint y_start = 0;
if (printing_prefs.print_page_header)
y_start = (dinfo->line_height * 3) - 2; /* "- 2": to connect the line number line to
* the page header frame */
cairo_set_line_width(cr, 0.3);
cairo_move_to(cr, (dinfo->max_line_number_margin * dinfo->font_width) + 1, y_start);
cairo_line_to(cr, (dinfo->max_line_number_margin * dinfo->font_width) + 1,
y + dinfo->line_height); /* y is last added line, we reuse it */
cairo_stroke(cr);
}
if (printing_prefs.print_page_numbers)
{
gchar *line = g_strdup_printf("<small>- %d -</small>", page_nr + 1);
pango_layout_set_markup(dinfo->layout, line, -1);
pango_layout_set_alignment(dinfo->layout, PANGO_ALIGN_CENTER);
cairo_move_to(cr, 0, height - dinfo->line_height);
pango_cairo_show_layout(cr, dinfo->layout);
g_free(line);
#ifdef GEANY_PRINT_DEBUG
cairo_set_line_width(cr, 0.3);
cairo_move_to(cr, 0, height - (1.25 * dinfo->line_height));
cairo_line_to(cr, width - 1, height - (1.25 * dinfo->line_height));
cairo_stroke(cr);
#endif
}
g_string_free(str, TRUE);
}
static void status_changed(GtkPrintOperation *op, gpointer data)
{
gchar *filename = (data != NULL) ? data : GEANY_STRING_UNTITLED;
if (gtk_print_operation_get_status(op) == GTK_PRINT_STATUS_FINISHED_ABORTED)
msgwin_status_add(_("Did not send document %s to the printing subsystem."), filename);
else if (gtk_print_operation_get_status(op) == GTK_PRINT_STATUS_FINISHED)
msgwin_status_add(_("Document %s was sent to the printing subsystem."), filename);
}
static void printing_print_gtk(GeanyDocument *doc)
{
GtkPrintOperation *op;
GtkPrintOperationResult res = GTK_PRINT_OPERATION_RESULT_ERROR;
GError *error = NULL;
DocInfo *dinfo;
PrintWidgets *widgets;
/** TODO check for monospace font, detect the widest character in the font and
* use it at font_width */
widgets = g_new0(PrintWidgets, 1);
dinfo = g_new0(DocInfo, 1);
/* all other fields are initialised in begin_print() */
dinfo->doc = doc;
op = gtk_print_operation_new();
gtk_print_operation_set_unit(op, GTK_UNIT_POINTS);
gtk_print_operation_set_show_progress(op, TRUE);
#if GTK_CHECK_VERSION(2, 18, 0)
gtk_print_operation_set_embed_page_setup(op, TRUE);
#endif
g_signal_connect(op, "begin-print", G_CALLBACK(begin_print), dinfo);
g_signal_connect(op, "end-print", G_CALLBACK(end_print), dinfo);
g_signal_connect(op, "draw-page", G_CALLBACK(draw_page), dinfo);
g_signal_connect(op, "status-changed", G_CALLBACK(status_changed), doc->file_name);
g_signal_connect(op, "create-custom-widget", G_CALLBACK(create_custom_widget), widgets);
g_signal_connect(op, "custom-widget-apply", G_CALLBACK(custom_widget_apply), widgets);
if (settings != NULL)
gtk_print_operation_set_print_settings(op, settings);
if (page_setup != NULL)
gtk_print_operation_set_default_page_setup(op, page_setup);
res = gtk_print_operation_run(
op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(main_widgets.window), &error);
if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
{
if (settings != NULL)
g_object_unref(settings);
settings = g_object_ref(gtk_print_operation_get_print_settings(op));
/* status message is printed in the status-changed handler */
}
else if (res == GTK_PRINT_OPERATION_RESULT_ERROR)
{
dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Printing of %s failed (%s)."),
doc->file_name, error->message);
g_error_free(error);
}
g_object_unref(op);
g_free(dinfo);
g_free(widgets);
}
void printing_page_setup_gtk(void)
{
GtkPageSetup *new_page_setup;
if (settings == NULL)
settings = gtk_print_settings_new();
new_page_setup = gtk_print_run_page_setup_dialog(
GTK_WINDOW(main_widgets.window), page_setup, settings);
if (page_setup != NULL)
g_object_unref(page_setup);
page_setup = new_page_setup;
}
#endif /* GTK 2.10 */
/* simple file print using an external tool */
static void print_external(GeanyDocument *doc)
{
gchar *cmdline;
if (doc->file_name == NULL)
return;
if (! NZV(printing_prefs.external_print_cmd))
{
dialogs_show_msgbox(GTK_MESSAGE_ERROR,
_("Please set a print command in the preferences dialog first."));
return;
}
cmdline = g_strdup(printing_prefs.external_print_cmd);
utils_str_replace_all(&cmdline, "%f", doc->file_name);
if (dialogs_show_question(
_("The file \"%s\" will be printed with the following command:\n\n%s"),
doc->file_name, cmdline))
{
GError *error = NULL;
#ifdef G_OS_WIN32
gchar *tmp_cmdline = g_strdup(cmdline);
#else
/* /bin/sh -c emulates the system() call and makes complex commands possible
* but only needed on non-win32 systems due to the lack of win32's shell capabilities */
gchar *tmp_cmdline = g_strconcat("/bin/sh -c \"", cmdline, "\"", NULL);
#endif
if (! g_spawn_command_line_async(tmp_cmdline, &error))
{
dialogs_show_msgbox(GTK_MESSAGE_ERROR,
_("Printing of \"%s\" failed (return code: %s)."),
doc->file_name, error->message);
g_error_free(error);
}
else
{
msgwin_status_add(_("File %s printed."), doc->file_name);
}
g_free(tmp_cmdline);
}
g_free(cmdline);
}
void printing_print_doc(GeanyDocument *doc)
{
if (doc == NULL)
return;
#if GTK_CHECK_VERSION(2, 10, 0)
if (gtk_check_version(2, 10, 0) == NULL && printing_prefs.use_gtk_printing)
printing_print_gtk(doc);
else
#endif
print_external(doc);
}