/* * printing.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2007-2009 Enrico Tröger * Copyright 2007-2009 Nick Treleaven * * 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 #include #include #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("%s", 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(_("Page %d of %d"), 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 (NZV(datetime)) { data = g_strdup_printf("%s", 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(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)); } 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("- %d -", 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(_("Printing of file %s was cancelled."), filename); else if (gtk_print_operation_get_status(op) == GTK_PRINT_STATUS_FINISHED) msgwin_status_add(_("File %s printed."), 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); 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); }