From: Friedrich Beckmann Date: Fri, 1 May 2015 09:04:20 +0000 (+0200) Subject: merge master->gtk3, fixed psppire-output-view.c refactoring; this compiles and runs... X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0df9cdd3df66caf4353128feff3008289cda8115;hp=-c;p=pspp merge master->gtk3, fixed psppire-output-view.c refactoring; this compiles and runs but the window update is only working partly --- 0df9cdd3df66caf4353128feff3008289cda8115 diff --combined INSTALL index 3d6d20e9dc,47b965b5cf..5e486ebc87 --- a/INSTALL +++ b/INSTALL @@@ -2,7 -2,8 +2,8 @@@ Installation Instructions for GNU psp ************************************** These instructions are based on the generic GNU installation - instructions, but they have been tailored for PSPP. + instructions, but they have been tailored for PSPP. These instructions + apply only to people wishing to build and install PSPP from source. Overview ======== @@@ -22,6 -23,8 +23,8 @@@ the tarball you are installing In 99% of cases, that is all you have to do - FINISHED! + + If any part of the above process fails, then it is likely that one or more of the necessary prerequisites is missing from your system. The following paragraphs contain highly detailed @@@ -33,7 -36,11 +36,11 @@@ Before You Instal Before you install PSPP, you will need to install certain prerequisite packages. You may also want to install other packages that enable - additional functionality in PSPP. + additional functionality in PSPP. Please note, if you are installing + any of the libararies mentioned below using pre-prepared binary + packages provided by popular GNU/Linux vendors, you may need to ensure + that you install the "development" versions (normally postfixed with + -dev or -devel). If you do not know whether you have these installed already, you may proceed to "Basic Installation", below. The PSPP configuration @@@ -48,7 -55,7 +55,7 @@@ The following packages are required to MinGW (http://www.mingw.org/) are known to work. * The GNU Scientific Library (http://www.gnu.org/software/gsl/), - version 1.8 or later, including libgslcblas included with GSL. + version 1.13 or later, including libgslcblas included with GSL. * Perl (http://www.perl.org/), version 5.005_03 or later. Perl is required during build but not after installation. @@@ -57,7 -64,7 +64,7 @@@ If you don't have a version already, you can install GNU libiconv (http://www.gnu.org/software/libiconv/). - * libintl, from GNU gettext (http://ww.gnu.org/software/gettext). + * libintl, from GNU gettext (http://www.gnu.org/software/gettext). GNU libc includes an integrated libintl, so there is no need to separately install libintl on a GNU/Linux system. @@@ -68,7 -75,7 +75,7 @@@ features. If you cannot arrange to ins `configure' with --without-cairo (in which case you will get no graphing capability). - * Cairo (http://cairographics.org/), version 1.5 or later. + * Cairo (http://cairographics.org/), version 1.12 or later. * Pango (http://www.pango.org/), version 1.22 or later. @@@ -80,10 -87,12 +87,10 @@@ use the GUI, you must run `configure' w 0.18 and 0.19 have a bug that will prevent library detection, but other versions should be fine. - * GTK+ (http://www.gtk.org/), version 2.24.0 - The Gtk+-3.x series will NOT work! - - * Glib (http://www.gtk.org), version 2.32 or later + * GTK+ (http://www.gtk.org/), version 3.4.0 or later. * GtkSourceView (http://projects.gnome.org/gtksourceview/) - version 2.2 or later. + version 3.4.0 or later. The following packages are optional: @@@ -211,7 -220,7 +218,7 @@@ details on some of the pertinent enviro by setting variables in the command line or in the environment. Here is an example: - ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + ./configure CC=c89 CFLAGS=-O0 LIBS=-lposix To cross-compile PSPP, you will likely need to set the PKG_CONFIG_LIBDIR environment variable to point to an diff --combined configure.ac index b0f16685b5,733923fb01..cc1dbba063 --- a/configure.ac +++ b/configure.ac @@@ -2,7 -2,7 +2,7 @@@ dnl Process this file with autoconf to dnl Initialize. AC_PREREQ(2.63) - AC_INIT([GNU PSPP], [0.8.1.1], [bug-gnu-pspp@gnu.org], [pspp]) + AC_INIT([GNU PSPP], [0.8.4], [bug-gnu-pspp@gnu.org], [pspp]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_TESTDIR([tests]) @@@ -20,7 -20,6 +20,6 @@@ PKG_PROG_PKG_CONFI m4_pattern_forbid([PKG_CHECK_MODULES]) PSPP_CHECK_CLICKSEQUENCE - PSPP_ENABLE_OPTION(-Wdeclaration-after-statement) PSPP_ENABLE_WERROR AM_CONDITIONAL(cc_is_gcc, test x"$GCC" = x"yes" ) @@@ -71,12 -70,16 +70,15 @@@ if test "$with_cairo" != no && test "$w PKG_CHECK_MODULES([GTHREAD], [gthread-2.0], [], [PSPP_REQUIRED_PREREQ([gthread 2.0 (or use --without-gui)])]) - PKG_CHECK_MODULES([GTK], [gtk+-2.0 >= 2.24], [], - [PSPP_REQUIRED_PREREQ([gtk+ 2.0 version 2.24 or later (or use --without-gui)])]) + PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= 3.4.2], [], + [PSPP_REQUIRED_PREREQ([gtk+ 3.0 version 3.4.2 or later (or use --without-gui)])]) + + PKG_CHECK_MODULES([GTKSOURCEVIEW], [gtksourceview-3.0 >= 3.4.2], [], + [PSPP_REQUIRED_PREREQ([gtksourceview 3.0 version 3.4.2 or later (or use --without-gui)])]) + PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32], [], + [PSPP_REQUIRED_PREREQ([glib 2.0 version 2.32 or later (or use --without-gui)])]) + - - PKG_CHECK_MODULES([GTKSOURCEVIEW], [gtksourceview-2.0 >= 2.2], [], - [PSPP_REQUIRED_PREREQ([gtksourceview 2.0 version 2.2 or later (or use --without-gui)])]) - AC_ARG_VAR([GLIB_GENMARSHAL]) AC_CHECK_PROGS([GLIB_GENMARSHAL], [glib-genmarshal]) if test "x$GLIB_GENMARSHAL" = x; then @@@ -241,8 -244,8 +243,8 @@@ AC_SUBST([WITH_PERL_MODULE] AM_CONDITIONAL(WITH_PERL_MODULE, test $WITH_PERL_MODULE = yes) AC_SEARCH_LIBS([cblas_dsdot], [gslcblas],,[PSPP_REQUIRED_PREREQ([libgslcblas])]) - PKG_CHECK_MODULES([GSL], [gsl >= 1.12], [], - AC_SEARCH_LIBS([gsl_linalg_cholesky_invert], [gsl],,[PSPP_REQUIRED_PREREQ([gsl 2.0 version 1.12 or later])])) + PKG_CHECK_MODULES([GSL], [gsl >= 1.13], [], + AC_SEARCH_LIBS([gsl_poly_eval_derivs], [gsl],,[PSPP_REQUIRED_PREREQ([gsl version 1.13 or later])])) PSPP_GSL_NEEDS_FGNU89_INLINE @@@ -305,10 -308,6 +307,6 @@@ gl_INI AC_C_INLINE - AC_CHECK_SIZEOF([size_t]) - SIZEOF_SIZE_T=$ac_cv_sizeof_size_t - AC_SUBST([SIZEOF_SIZE_T]) - AC_C_BIGENDIAN AC_CHECK_FUNCS([__setfpucw fork execl isinf isnan finite getpid feholdexcept fpsetmask popen round]) @@@ -340,6 -339,10 +338,10 @@@ PSPP_CHECK_PREREQ AC_CONFIG_FILES( [Makefile gl/Makefile po/Makefile tests/atlocal perl-module/lib/PSPP.pm]) + AC_CONFIG_COMMANDS([doc/dummy], [:]) + + m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) + AC_OUTPUT echo "PSPP configured successfully." diff --combined src/output/cairo.c index 2debb27f47,4af75caae4..a3070dddef --- a/src/output/cairo.c +++ b/src/output/cairo.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc. + Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc. 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 @@@ -30,10 -30,12 +30,12 @@@ #include "output/charts/boxplot.h" #include "output/charts/np-plot.h" #include "output/charts/piechart.h" + #include "output/charts/barchart.h" #include "output/charts/plot-hist.h" #include "output/charts/roc-chart.h" #include "output/charts/spreadlevel-plot.h" #include "output/charts/scree.h" + #include "output/charts/scatterplot.h" #include "output/driver-provider.h" #include "output/message-item.h" #include "output/options.h" @@@ -47,6 -49,7 +49,7 @@@ #include #include #include + #include #include #include #include @@@ -64,8 -67,8 +67,8 @@@ #define H TABLE_HORZ #define V TABLE_VERT - /* Measurements as we present to the rest of PSPP. */ - #define XR_POINT 1000 + /* The unit used for internal measurements is inch/(72 * XR_POINT). */ + #define XR_POINT PANGO_SCALE /* Conversions to and from points. */ static double @@@ -88,6 -91,7 +91,7 @@@ enum xr_font_typ XR_FONT_PROPORTIONAL, XR_FONT_EMPHASIS, XR_FONT_FIXED, + XR_FONT_MARKER, XR_N_FONTS }; @@@ -121,15 -125,19 +125,19 @@@ struct xr_drive int width; /* Page width minus margins. */ int length; /* Page length minus margins and header. */ - int left_margin; /* Left margin in XR units. */ - int right_margin; /* Right margin in XR units. */ - int top_margin; /* Top margin in XR units. */ - int bottom_margin; /* Bottom margin in XR units. */ + int left_margin; /* Left margin in inch/(72 * XR_POINT). */ + int right_margin; /* Right margin in inch/(72 * XR_POINT). */ + int top_margin; /* Top margin in inch/(72 * XR_POINT). */ + int bottom_margin; /* Bottom margin in inch/(72 * XR_POINT). */ int line_gutter; /* Space around lines. */ int line_space; /* Space between lines. */ int line_width; /* Width of lines. */ + int cell_margin; + + int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */ + struct xr_color bg; /* Background color */ struct xr_color fg; /* Foreground color */ @@@ -141,8 -149,9 +149,9 @@@ char *subtitle; cairo_t *cairo; int page_number; /* Current page number. */ - int y; + int x, y; struct xr_render_fsm *fsm; + int nest; }; static const struct output_driver_class cairo_driver_class; @@@ -153,12 -162,14 +162,14 @@@ static void xr_driver_run_fsm (struct x static void xr_draw_line (void *, int bb[TABLE_N_AXES][2], enum render_line_style styles[TABLE_N_AXES][2]); static void xr_measure_cell_width (void *, const struct table_cell *, - int *min, int *max); + int footnote_idx, int *min, int *max); static int xr_measure_cell_height (void *, const struct table_cell *, - int width); - static void xr_draw_cell (void *, const struct table_cell *, + int footnote_idx, int width); + static void xr_draw_cell (void *, const struct table_cell *, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); + static int xr_adjust_break (void *, const struct table_cell *, int footnote_idx, + int width, int height); static struct xr_render_fsm *xr_render_output_item ( struct xr_driver *, const struct output_item *); @@@ -213,7 -224,7 +224,7 @@@ parse_color (struct output_driver *d, s static PangoFontDescription * parse_font (struct output_driver *d, struct string_map *options, const char *key, const char *default_value, - int default_points) + int default_size) { PangoFontDescription *desc; char *string; @@@ -233,10 -244,10 +244,10 @@@ free (string); /* If the font description didn't include an explicit font size, then set it - to DEFAULT_POINTS. */ + to DEFAULT_SIZE, which is in inch/72000 units. */ if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE)) pango_font_description_set_size (desc, - default_points / 1000.0 * PANGO_SCALE); + (default_size / 1000.0) * PANGO_SCALE); return desc; } @@@ -247,9 -258,17 +258,17 @@@ apply_options (struct xr_driver *xr, st { struct output_driver *d = &xr->driver; - int paper_width, paper_length, i; + /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */ + int left_margin, right_margin; + int top_margin, bottom_margin; + int paper_width, paper_length; + int font_size; + int min_break[TABLE_N_AXES]; - int font_points = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000); + /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */ + const double scale = XR_POINT / 1000.; + + int i; for (i = 0; i < XR_N_FONTS; i++) { @@@ -259,14 -278,17 +278,17 @@@ pango_font_description_free (font->desc); } + font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000); xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace", - font_points); + font_size); xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font", - "serif", font_points); + "serif", font_size); xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font", - "serif italic", font_points); + "serif italic", font_size); + xr->fonts[XR_FONT_MARKER].desc = parse_font (d, o, "marker-font", "serif", + font_size * PANGO_SCALE_X_SMALL); - xr->line_gutter = parse_dimension (opt (d, o, "gutter", "3pt")); + xr->line_gutter = XR_POINT / 2; xr->line_space = XR_POINT; xr->line_width = XR_POINT / 2; xr->page_number = 0; @@@ -274,14 -296,25 +296,25 @@@ parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg); parse_color (d, o, "foreground-color", "#000000000000", &xr->fg); + /* Get dimensions. */ parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length); - xr->left_margin = parse_dimension (opt (d, o, "left-margin", ".5in")); - xr->right_margin = parse_dimension (opt (d, o, "right-margin", ".5in")); - xr->top_margin = parse_dimension (opt (d, o, "top-margin", ".5in")); - xr->bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in")); - - xr->width = paper_width - xr->left_margin - xr->right_margin; - xr->length = paper_length - xr->top_margin - xr->bottom_margin; + left_margin = parse_dimension (opt (d, o, "left-margin", ".5in")); + right_margin = parse_dimension (opt (d, o, "right-margin", ".5in")); + top_margin = parse_dimension (opt (d, o, "top-margin", ".5in")); + bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in")); + + min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale; + min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale; + + /* Convert to inch/(XR_POINT * 72). */ + xr->left_margin = left_margin * scale; + xr->right_margin = right_margin * scale; + xr->top_margin = top_margin * scale; + xr->bottom_margin = bottom_margin * scale; + xr->width = (paper_width - left_margin - right_margin) * scale; + xr->length = (paper_length - top_margin - bottom_margin) * scale; + xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2; + xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2; } static struct xr_driver * @@@ -297,6 -330,22 +330,22 @@@ xr_allocate (const char *name, int devi return xr; } + static int + pango_to_xr (int pango) + { + return (XR_POINT != PANGO_SCALE + ? ceil (pango * (1. * XR_POINT / PANGO_SCALE)) + : pango); + } + + static int + xr_to_pango (int xr) + { + return (XR_POINT != PANGO_SCALE + ? ceil (xr * (1. / XR_POINT * PANGO_SCALE)) + : xr); + } + static bool xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) { @@@ -318,9 -367,10 +367,10 @@@ pango_layout_set_text (font->layout, "0", 1); pango_layout_get_size (font->layout, &char_width, &char_height); - xr->char_width = MAX (xr->char_width, char_width); - xr->char_height = MAX (xr->char_height, char_height); + xr->char_width = MAX (xr->char_width, pango_to_xr (char_width)); + xr->char_height = MAX (xr->char_height, pango_to_xr (char_height)); } + xr->cell_margin = xr->char_width; if (xr->params == NULL) { @@@ -330,6 -380,7 +380,7 @@@ xr->params->draw_line = xr_draw_line; xr->params->measure_cell_width = xr_measure_cell_width; xr->params->measure_cell_height = xr_measure_cell_height; + xr->params->adjust_break = xr_adjust_break; xr->params->draw_cell = xr_draw_cell; xr->params->aux = xr; xr->params->size[H] = xr->width; @@@ -345,6 -396,9 +396,9 @@@ xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width; xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width; } + + for (i = 0; i < TABLE_N_AXES; i++) + xr->params->min_break[i] = xr->min_break[i]; } cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue); @@@ -364,8 -418,8 +418,8 @@@ xr_create (const char *file_name, enum xr = xr_allocate (file_name, device_type, o); - width_pt = (xr->width + xr->left_margin + xr->right_margin) / 1000.0; - length_pt = (xr->length + xr->top_margin + xr->bottom_margin) / 1000.0; + width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin); + length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin); if (file_type == XR_PDF) surface = cairo_pdf_surface_create (file_name, width_pt, length_pt); else if (file_type == XR_PS) @@@ -485,38 -539,6 +539,6 @@@ xr_flush (struct output_driver *driver cairo_surface_flush (cairo_get_target (xr->cairo)); } - static void - xr_init_caption_cell (const char *caption, struct table_cell *cell) - { - cell->contents = caption; - cell->options = TAB_LEFT; - cell->destructor = NULL; - } - - static struct render_page * - xr_render_table_item (struct xr_driver *xr, const struct table_item *item, - int *caption_widthp, int *caption_heightp) - { - const char *caption = table_item_get_caption (item); - - if (caption != NULL) - { - /* XXX doesn't do well with very large captions */ - int min_width, max_width; - struct table_cell cell; - - xr_init_caption_cell (caption, &cell); - - xr_measure_cell_width (xr, &cell, &min_width, &max_width); - *caption_widthp = MIN (max_width, xr->width); - *caption_heightp = xr_measure_cell_height (xr, &cell, *caption_widthp); - } - else - *caption_heightp = 0; - - return render_page_create (xr->params, table_item_get_table (item)); - } - static void xr_submit (struct output_driver *driver, const struct output_item *output_item) { @@@ -542,28 -564,23 +564,23 @@@ See the big comment in cairo.h for intended usage. */ - /* Gives new page CAIRO to XR for output. CAIRO may be null to skip actually - rendering the page (which might be useful to find out how many pages an - output document has without actually rendering it). */ + /* Gives new page CAIRO to XR for output. */ void xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo) { - if (cairo != NULL) - { - cairo_save (cairo); - cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue); - cairo_rectangle (cairo, 0, 0, xr->width, xr->length); - cairo_fill (cairo); - cairo_restore (cairo); - - cairo_translate (cairo, - xr_to_pt (xr->left_margin), - xr_to_pt (xr->top_margin)); - } + cairo_save (cairo); + cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue); + cairo_rectangle (cairo, 0, 0, xr->width, xr->length); + cairo_fill (cairo); + cairo_restore (cairo); + + cairo_translate (cairo, + xr_to_pt (xr->left_margin), + xr_to_pt (xr->top_margin)); xr->page_number++; xr->cairo = cairo; - xr->y = 0; + xr->x = xr->y = 0; xr_driver_run_fsm (xr); } @@@ -613,16 -630,28 +630,28 @@@ xr_driver_run_fsm (struct xr_driver *xr } static void - xr_layout_cell (struct xr_driver *, const struct table_cell *, + xr_layout_cell (struct xr_driver *, const struct table_cell *, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], - PangoWrapMode, int *width, int *height); + int *width, int *height, int *brk); static void dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1) { cairo_new_path (xr->cairo); - cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y)); - cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y)); + cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y)); + cairo_stroke (xr->cairo); + } + + static void UNUSED + dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1) + { + cairo_new_path (xr->cairo); + cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y)); + cairo_close_path (xr->cairo); cairo_stroke (xr->cairo); } @@@ -779,7 -808,7 +808,7 @@@ xr_draw_line (void *xr_, int bb[TABLE_N static void xr_measure_cell_width (void *xr_, const struct table_cell *cell, - int *min_width, int *max_width) + int footnote_idx, int *min_width, int *max_width) { struct xr_driver *xr = xr_; int bb[TABLE_N_AXES][2]; @@@ -791,14 -820,20 +820,20 @@@ bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, max_width, &h); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, max_width, &h, NULL); bb[H][1] = 1; - xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, min_width, &h); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, min_width, &h, NULL); + + if (*min_width > 0) + *min_width += xr->cell_margin * 2; + if (*max_width > 0) + *max_width += xr->cell_margin * 2; } static int - xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width) + xr_measure_cell_height (void *xr_, const struct table_cell *cell, + int footnote_idx, int width) { struct xr_driver *xr = xr_; int bb[TABLE_N_AXES][2]; @@@ -806,93 -841,395 +841,395 @@@ int w, h; bb[H][0] = 0; - bb[H][1] = width; + bb[H][1] = width - xr->cell_margin * 2; + if (bb[H][1] <= 0) + return 0; bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h); + xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, NULL); return h; } static void - xr_draw_cell (void *xr_, const struct table_cell *cell, + xr_draw_cell (void *xr_, const struct table_cell *cell, int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]) { struct xr_driver *xr = xr_; - int w, h; + int w, h, brk; - xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h); + bb[H][0] += xr->cell_margin; + bb[H][1] -= xr->cell_margin; + if (bb[H][0] >= bb[H][1]) + return; + xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk); + } + + static int + xr_adjust_break (void *xr_, const struct table_cell *cell, int footnote_idx, + int width, int height) + { + struct xr_driver *xr = xr_; + int bb[TABLE_N_AXES][2]; + int clip[TABLE_N_AXES][2]; + int w, h, brk; + + if (xr_measure_cell_height (xr_, cell, footnote_idx, width) < height) + return -1; + + bb[H][0] = 0; + bb[H][1] = width - 2 * xr->cell_margin; + if (bb[H][1] <= 0) + return 0; + bb[V][0] = 0; + bb[V][1] = height; + clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; + xr_layout_cell (xr, cell, footnote_idx, bb, clip, &w, &h, &brk); + return brk; } static void - xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, - int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], - PangoWrapMode wrap, int *width, int *height) + xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2]) + { + if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX) + { + double x0 = xr_to_pt (clip[H][0] + xr->x); + double y0 = xr_to_pt (clip[V][0] + xr->y); + double x1 = xr_to_pt (clip[H][1] + xr->x); + double y1 = xr_to_pt (clip[V][1] + xr->y); + + cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0); + cairo_clip (xr->cairo); + } + } + + static void + add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index) + { + attr->start_index = start_index; + pango_attr_list_insert (list, attr); + } + + static int + xr_layout_cell_text (struct xr_driver *xr, + const struct cell_contents *contents, int footnote_idx, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int y, int *widthp, int *brk) { + unsigned int options = contents->options; struct xr_font *font; + bool merge_footnotes; + size_t length; + int w, h; + + if (contents->n_footnotes == 0) + merge_footnotes = false; + else if (contents->n_footnotes == 1 && (options & TAB_ALIGNMENT) == TAB_RIGHT) + { + PangoAttrList *attrs; + char marker[16]; + + font = &xr->fonts[XR_FONT_MARKER]; + + str_format_26adic (footnote_idx + 1, false, marker, sizeof marker); + pango_layout_set_text (font->layout, marker, strlen (marker)); + + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_rise_new (7000)); + pango_layout_set_attributes (font->layout, attrs); + pango_attr_list_unref (attrs); + + pango_layout_get_size (font->layout, &w, &h); + merge_footnotes = w > xr->cell_margin; + if (!merge_footnotes && clip[H][0] != clip[H][1]) + { + cairo_save (xr->cairo); + xr_clip (xr, clip); + cairo_translate (xr->cairo, + xr_to_pt (bb[H][1] + xr->x), + xr_to_pt (y + xr->y)); + pango_layout_set_alignment (font->layout, PANGO_ALIGN_LEFT); + pango_layout_set_width (font->layout, -1); + pango_cairo_show_layout (xr->cairo, font->layout); + cairo_restore (xr->cairo); + } + + pango_layout_set_attributes (font->layout, NULL); + } + else + merge_footnotes = true; - font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED] - : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS] + font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED] + : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS] : &xr->fonts[XR_FONT_PROPORTIONAL]); - pango_layout_set_text (font->layout, cell->contents, -1); + length = strlen (contents->text); + if (merge_footnotes) + { + PangoAttrList *attrs; + struct string s; + size_t i; + + bb[H][1] += xr->cell_margin; + + ds_init_empty (&s); + ds_extend (&s, length + contents->n_footnotes * 10); + ds_put_cstr (&s, contents->text); + for (i = 0; i < contents->n_footnotes; i++) + { + char marker[16]; + + if (i > 0) + ds_put_byte (&s, ','); + str_format_26adic (footnote_idx + i + 1, false, marker, sizeof marker); + ds_put_cstr (&s, marker); + } + pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s)); + ds_destroy (&s); + + attrs = pango_attr_list_new (); + add_attr_with_start (attrs, pango_attr_rise_new (7000), length); + add_attr_with_start ( + attrs, pango_attr_font_desc_new (xr->fonts[XR_FONT_MARKER].desc), length); + pango_layout_set_attributes (font->layout, attrs); + pango_attr_list_unref (attrs); + } + else + pango_layout_set_text (font->layout, contents->text, -1); pango_layout_set_alignment ( font->layout, - ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT - : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT + ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT + : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT : PANGO_ALIGN_CENTER)); - pango_layout_set_width (font->layout, - bb[H][1] == INT_MAX ? -1 : bb[H][1] - bb[H][0]); - pango_layout_set_wrap (font->layout, wrap); + pango_layout_set_width ( + font->layout, + bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0])); + pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD); if (clip[H][0] != clip[H][1]) { cairo_save (xr->cairo); + xr_clip (xr, clip); + cairo_translate (xr->cairo, + xr_to_pt (bb[H][0] + xr->x), + xr_to_pt (y + xr->y)); + pango_cairo_show_layout (xr->cairo, font->layout); - if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX) + /* If enabled, this draws a blue rectangle around the extents of each + line of text, which can be rather useful for debugging layout + issues. */ + if (0) { - double x0 = xr_to_pt (clip[H][0]); - double y0 = xr_to_pt (clip[V][0] + xr->y); - double x1 = xr_to_pt (clip[H][1]); - double y1 = xr_to_pt (clip[V][1] + xr->y); - - cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0); - cairo_clip (xr->cairo); + PangoLayoutIter *iter; + iter = pango_layout_get_iter (font->layout); + do + { + PangoRectangle extents; + + pango_layout_iter_get_line_extents (iter, &extents, NULL); + cairo_save (xr->cairo); + cairo_set_source_rgb (xr->cairo, 1, 0, 0); + dump_rectangle (xr, + pango_to_xr (extents.x) - xr->x, + pango_to_xr (extents.y) - xr->y, + pango_to_xr (extents.x + extents.width) - xr->x, + pango_to_xr (extents.y + extents.height) - xr->y); + cairo_restore (xr->cairo); + } + while (pango_layout_iter_next_line (iter)); + pango_layout_iter_free (iter); } - cairo_translate (xr->cairo, - xr_to_pt (bb[H][0]), - xr_to_pt (bb[V][0] + xr->y)); - pango_cairo_show_layout (xr->cairo, font->layout); cairo_restore (xr->cairo); } - if (width != NULL || height != NULL) + pango_layout_get_size (font->layout, &w, &h); + w = pango_to_xr (w); + h = pango_to_xr (h); + if (w > *widthp) + *widthp = w; + if (y + h >= bb[V][1]) { - int w, h; + PangoLayoutIter *iter; + int best UNUSED = 0; - pango_layout_get_size (font->layout, &w, &h); - if (width != NULL) - *width = w; - if (height != NULL) - *height = h; + /* Choose a breakpoint between lines instead of in the middle of one. */ + iter = pango_layout_get_iter (font->layout); + do + { + PangoRectangle extents; + int y0, y1; + int bottom; + + pango_layout_iter_get_line_extents (iter, NULL, &extents); + pango_layout_iter_get_line_yrange (iter, &y0, &y1); + extents.x = pango_to_xr (extents.x); + extents.y = pango_to_xr (y0); + extents.width = pango_to_xr (extents.width); + extents.height = pango_to_xr (y1 - y0); + bottom = y + extents.y + extents.height; + if (bottom < bb[V][1]) + { + if (brk && clip[H][0] != clip[H][1]) + best = bottom; + *brk = bottom; + } + else + break; + } + while (pango_layout_iter_next_line (iter)); + + /* If enabled, draws a green line across the chosen breakpoint, which can + be useful for debugging issues with breaking. */ + if (0) + { + if (best && !xr->nest) + { + cairo_save (xr->cairo); + cairo_set_source_rgb (xr->cairo, 0, 1, 0); + dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best); + cairo_restore (xr->cairo); + } + } } + + pango_layout_set_attributes (font->layout, NULL); + return y + h; + } + + static int + xr_layout_cell_subtable (struct xr_driver *xr, + const struct cell_contents *contents, + int footnote_idx UNUSED, + int bb[TABLE_N_AXES][2], + int clip[TABLE_N_AXES][2], int *widthp, int *brk) + { + int single_width, double_width; + struct render_params params; + struct render_pager *p; + int r[TABLE_N_AXES][2]; + int width, height; + int i; + + params.draw_line = xr_draw_line; + params.measure_cell_width = xr_measure_cell_width; + params.measure_cell_height = xr_measure_cell_height; + params.adjust_break = NULL; + params.draw_cell = xr_draw_cell; + params.aux = xr; + params.size[H] = bb[H][1] - bb[H][0]; + params.size[V] = bb[V][1] - bb[V][0]; + params.font_size[H] = xr->char_width; + params.font_size[V] = xr->char_height; + + single_width = 2 * xr->line_gutter + xr->line_width; + double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width; + for (i = 0; i < TABLE_N_AXES; i++) + { + params.line_widths[i][RENDER_LINE_NONE] = 0; + params.line_widths[i][RENDER_LINE_SINGLE] = single_width; + params.line_widths[i][RENDER_LINE_DOUBLE] = double_width; + } + + xr->nest++; + p = render_pager_create (¶ms, contents->table); + width = render_pager_get_size (p, H); + height = render_pager_get_size (p, V); + if (bb[V][0] + height >= bb[V][1]) + *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]); + + /* r = intersect(bb, clip) - bb. */ + for (i = 0; i < TABLE_N_AXES; i++) + { + r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0]; + r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0]; + } + + if (r[H][0] < r[H][1] && r[V][0] < r[V][1]) + { + unsigned int alignment = contents->options & TAB_ALIGNMENT; + int save_x = xr->x; + + cairo_save (xr->cairo); + xr_clip (xr, clip); + xr->x += bb[H][0]; + if (alignment == TAB_RIGHT) + xr->x += params.size[H] - width; + else if (alignment == TAB_CENTER) + xr->x += (params.size[H] - width) / 2; + xr->y += bb[V][0]; + render_pager_draw_region (p, r[H][0], r[V][0], + r[H][1] - r[H][0], r[V][1] - r[V][0]); + xr->y -= bb[V][0]; + xr->x = save_x; + cairo_restore (xr->cairo); + } + render_pager_destroy (p); + xr->nest--; + + if (width > *widthp) + *widthp = width; + return bb[V][0] + height; } static void - xr_draw_title (struct xr_driver *xr, const char *title, - int title_width, int title_height) + xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, + int footnote_idx, + int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *width, int *height, int *brk) { - struct table_cell cell; int bb[TABLE_N_AXES][2]; + size_t i; - xr_init_caption_cell (title, &cell); - bb[H][0] = 0; - bb[H][1] = title_width; - bb[V][0] = 0; - bb[V][1] = title_height; - xr_draw_cell (xr, &cell, bb, bb); + *width = 0; + *height = 0; + if (brk) + *brk = 0; + + memcpy (bb, bb_, sizeof bb); + + /* If enabled, draws a blue rectangle around the cell extents, which can be + useful for debugging layout. */ + if (0) + { + if (clip[H][0] != clip[H][1]) + { + int offset = (xr->nest) * XR_POINT; + + cairo_save (xr->cairo); + cairo_set_source_rgb (xr->cairo, 0, 0, 1); + dump_rectangle (xr, + bb[H][0] + offset, bb[V][0] + offset, + bb[H][1] - offset, bb[V][1] - offset); + cairo_restore (xr->cairo); + } + } + + for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++) + { + const struct cell_contents *contents = &cell->contents[i]; + + if (brk) + *brk = bb[V][0]; + if (i > 0) + { + bb[V][0] += xr->char_height / 2; + if (bb[V][0] >= bb[V][1]) + break; + if (brk) + *brk = bb[V][0]; + } + + if (contents->text) + bb[V][0] = xr_layout_cell_text (xr, contents, footnote_idx, bb, clip, + bb[V][0], width, brk); + else + bb[V][0] = xr_layout_cell_subtable (xr, contents, footnote_idx, + bb, clip, width, brk); + footnote_idx += contents->n_footnotes; + } + *height = bb[V][0] - bb_[V][0]; } struct output_driver_factory pdf_driver_factory = @@@ -917,10 -1254,8 +1254,8 @@@ struct xr_renderin struct output_item *item; /* Table items. */ - struct render_page *page; + struct render_pager *p; struct xr_driver *xr; - int title_width; - int title_height; }; #define CHART_WIDTH 500 @@@ -958,7 -1293,8 +1293,8 @@@ xr_rendering_create_text (struct xr_dri struct table_item *table_item; struct xr_rendering *r; - table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL); + table_item = table_item_create (table_from_string (TAB_LEFT, text), + NULL, NULL); r = xr_rendering_create (xr, &table_item->output_item, cr); table_item_unref (table_item); @@@ -995,8 -1331,7 +1331,7 @@@ xr_rendering_create (struct xr_driver * r->item = output_item_ref (item); r->xr = xr; xr_set_cairo (xr, cr); - r->page = xr_render_table_item (xr, to_table_item (item), - &r->title_width, &r->title_height); + r->p = render_pager_create (xr->params, to_table_item (item)); } else if (is_chart_item (item)) { @@@ -1007,15 -1342,24 +1342,24 @@@ return r; } + void + xr_rendering_destroy (struct xr_rendering *r) + { + if (r) + { + output_item_unref (r->item); + render_pager_destroy (r->p); + free (r); + } + } + void xr_rendering_measure (struct xr_rendering *r, int *w, int *h) { if (is_table_item (r->item)) { - int w0 = render_page_get_size (r->page, H); - int w1 = r->title_width; - *w = MAX (w0, w1) / XR_POINT; - *h = (render_page_get_size (r->page, V) + r->title_height) / XR_POINT; + *w = render_pager_get_size (r->p, H) / XR_POINT; + *h = render_pager_get_size (r->p, V) / XR_POINT; } else { @@@ -1027,9 -1371,11 +1371,9 @@@ static void xr_draw_chart (const struct chart_item *, cairo_t *, double x, double y, double width, double height); -/* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H), - and possibly some additional parts. */ +/* Draws onto CR */ void -xr_rendering_draw (struct xr_rendering *r, cairo_t *cr, - int x, int y, int w, int h) +xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr) { if (is_table_item (r->item)) { @@@ -1037,15 -1383,10 +1381,8 @@@ xr_set_cairo (xr, cr); - if (r->title_height > 0) - { - xr->y = 0; - xr_draw_title (xr, table_item_get_caption (to_table_item (r->item)), - r->title_width, r->title_height); - } - xr->y = 0; - render_pager_draw_region (r->p, - x * XR_POINT, y * XR_POINT, - w * XR_POINT, h * XR_POINT); ++ render_pager_draw (r->p); + - xr->y = r->title_height; - render_page_draw (r->page); } else xr_draw_chart (to_chart_item (r->item), cr, @@@ -1070,12 -1411,16 +1407,16 @@@ xr_draw_chart (const struct chart_item xrchart_draw_np_plot (chart_item, cr, &geom); else if (is_piechart (chart_item)) xrchart_draw_piechart (chart_item, cr, &geom); + else if (is_barchart (chart_item)) + xrchart_draw_barchart (chart_item, cr, &geom); else if (is_roc_chart (chart_item)) xrchart_draw_roc (chart_item, cr, &geom); else if (is_scree (chart_item)) xrchart_draw_scree (chart_item, cr, &geom); else if (is_spreadlevel_plot_chart (chart_item)) xrchart_draw_spreadlevel (chart_item, cr, &geom); + else if (is_scatterplot_chart (chart_item)) + xrchart_draw_scatterplot (chart_item, cr, &geom); else NOT_REACHED (); xrchart_geometry_free (cr, &geom); @@@ -1131,9 -1476,7 +1472,7 @@@ struct xr_table_stat { struct xr_render_fsm fsm; struct table_item *table_item; - struct render_break x_break; - struct render_break y_break; - int caption_height; + struct render_pager *p; }; static bool @@@ -1141,46 -1484,20 +1480,20 @@@ xr_table_render (struct xr_render_fsm * { struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm); - for (;;) + while (render_pager_has_next (ts->p)) { - struct render_page *y_slice; - int space; + int used; - while (!render_break_has_next (&ts->y_break)) - { - struct render_page *x_slice; - - render_break_destroy (&ts->y_break); - if (!render_break_has_next (&ts->x_break)) - return false; - - x_slice = render_break_next (&ts->x_break, xr->width); - render_break_init (&ts->y_break, x_slice, V); - } - - space = xr->length - xr->y; - if (render_break_next_size (&ts->y_break) > space) + used = render_pager_draw_next (ts->p, xr->length - xr->y); + if (!used) { assert (xr->y > 0); return true; } - - y_slice = render_break_next (&ts->y_break, space); - if (ts->caption_height) - { - if (xr->cairo) - xr_draw_title (xr, table_item_get_caption (ts->table_item), - xr->width, ts->caption_height); - - xr->y += ts->caption_height; - ts->caption_height = 0; - } - - if (xr->cairo) - render_page_draw (y_slice); - xr->y += render_page_get_size (y_slice, V); - render_page_unref (y_slice); + else + xr->y += used; } + return false; } static void @@@ -1189,8 -1506,7 +1502,7 @@@ xr_table_destroy (struct xr_render_fsm struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm); table_item_unref (ts->table_item); - render_break_destroy (&ts->x_break); - render_break_destroy (&ts->y_break); + render_pager_destroy (ts->p); free (ts); } @@@ -1198,8 -1514,6 +1510,6 @@@ static struct xr_render_fsm xr_render_table (struct xr_driver *xr, const struct table_item *table_item) { struct xr_table_state *ts; - struct render_page *page; - int caption_width; ts = xmalloc (sizeof *ts); ts->fsm.render = xr_table_render; @@@ -1209,12 -1523,7 +1519,7 @@@ if (xr->y > 0) xr->y += xr->char_height; - page = xr_render_table_item (xr, table_item, - &caption_width, &ts->caption_height); - xr->params->size[V] = xr->length - ts->caption_height; - - render_break_init (&ts->x_break, page, H); - render_break_init_empty (&ts->y_break); + ts->p = render_pager_create (xr->params, table_item); return &ts->fsm; } @@@ -1293,7 -1602,8 +1598,8 @@@ xr_create_text_renderer (struct xr_driv struct table_item *table_item; struct xr_render_fsm *fsm; - table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL); + table_item = table_item_create (table_from_string (TAB_LEFT, text), + NULL, NULL); fsm = xr_render_table (xr, table_item); table_item_unref (table_item); diff --combined src/output/cairo.h index 3c13768177,cdd5536900..425811b450 --- a/src/output/cairo.h +++ b/src/output/cairo.h @@@ -1,5 -1,5 +1,5 @@@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2010 Free Software Foundation, Inc. + Copyright (C) 2009, 2010, 2014 Free Software Foundation, Inc. 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 @@@ -38,10 -38,12 +38,11 @@@ void xr_driver_destroy (struct xr_drive struct xr_rendering *xr_rendering_create (struct xr_driver *, const struct output_item *, cairo_t *); + void xr_rendering_destroy (struct xr_rendering *); void xr_rendering_apply_options (struct xr_rendering *, struct string_map *o); void xr_rendering_measure (struct xr_rendering *, int *w, int *h); -void xr_rendering_draw (struct xr_rendering *, cairo_t *, - int x, int y, int w, int h); +void xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr); /* Functions for rendering a series of output items to a series of Cairo contexts, with pagination, possibly including headers. diff --combined src/output/render.c index c178839a0b,d282a8fce6..94d89665b7 --- a/src/output/render.c +++ b/src/output/render.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2010, 2011, 2013 Free Software Foundation, Inc. + Copyright (C) 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc. 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 @@@ -18,6 -18,7 +18,7 @@@ #include #include + #include #include #include @@@ -25,6 -26,8 +26,8 @@@ #include "libpspp/hash-functions.h" #include "libpspp/hmap.h" #include "output/render.h" + #include "output/tab.h" + #include "output/table-item.h" #include "output/table.h" #include "gl/minmax.h" @@@ -38,7 -41,11 +41,11 @@@ May represent the layout of an entire table presented to render_page_create(), or a rectangular subregion of a table broken out using - render_page_next() to allow a table to be broken across multiple pages. */ + render_break_next() to allow a table to be broken across multiple pages. + + A page's size is not limited to the size passed in as part of render_params. + render_pager breaks a render_page into smaller render_pages that will fit in + the available space. */ struct render_page { const struct render_params *params; /* Parameters of the target device. */ @@@ -61,7 -68,7 +68,7 @@@ Similarly, cp[V] represents y positions within the table. cp[V][0] = 0. cp[V][1] = the height of the topmost horizontal rule. - cp[V][2] = cp[V][1] + the height of the topmost column. + cp[V][2] = cp[V][1] + the height of the topmost row. cp[V][3] = cp[V][2] + the height of the second-from-top horizontal rule. and so on: cp[V][2 * nr] = y position of the bottommost horizontal rule. @@@ -84,6 -91,15 +91,15 @@@ entire page can overflow on all four sides!) */ struct hmap overflows; + /* Contains "struct render_footnote"s, one for each cell with one or more + footnotes. + + 'n_footnotes' is the number of footnotes in the table. There might be + more than hmap_count(&page->footnotes) because there can be more than + one footnote in a cell. */ + struct hmap footnotes; + size_t n_footnotes; + /* If a single column (or row) is too wide (or tall) to fit on a page reasonably, then render_break_next() will split a single row or column across multiple render_pages. This member indicates when this has @@@ -120,6 -136,12 +136,12 @@@ int *join_crossing[TABLE_N_AXES]; }; + static struct render_page *render_page_create (const struct render_params *, + const struct table *); + + struct render_page *render_page_ref (const struct render_page *page_); + static void render_page_unref (struct render_page *); + /* Returns the offset in struct render_page's cp[axis] array of the rule with index RULE_IDX. That is, if RULE_IDX is 0, then the offset is that of the leftmost or topmost rule; if RULE_IDX is 1, then the offset is that of the @@@ -178,6 -200,21 +200,21 @@@ cell_width (const struct render_page *p return axis_width (page, axis, cell_ofs (x), cell_ofs (x) + 1); } + /* Returns the width of rule X along AXIS in PAGE. */ + static int + rule_width (const struct render_page *page, int axis, int x) + { + return axis_width (page, axis, rule_ofs (x), rule_ofs (x) + 1); + } + + /* Returns the width of rule X along AXIS in PAGE. */ + static int + rule_width_r (const struct render_page *page, int axis, int x) + { + int ofs = rule_ofs_r (page, axis, x); + return axis_width (page, axis, ofs, ofs + 1); + } + /* Returns the width of cells X0 through X1, exclusive, along AXIS in PAGE. */ static int joined_width (const struct render_page *page, int axis, int x0, int x1) @@@ -274,9 -311,9 +311,9 @@@ struct render_overflo int overflow[TABLE_N_AXES][2]; }; - /* Returns a hash value for (X,Y). */ + /* Returns a hash value for (,Y). */ static unsigned int - hash_overflow (int x, int y) + hash_cell (int x, int y) { return hash_int (x + (y << 16), 0); } @@@ -291,7 -328,7 +328,7 @@@ find_overflow (const struct render_pag const struct render_overflow *of; HMAP_FOR_EACH_WITH_HASH (of, struct render_overflow, node, - hash_overflow (x, y), &page->overflows) + hash_cell (x, y), &page->overflows) if (x == of->d[H] && y == of->d[V]) return of; } @@@ -299,6 -336,55 +336,55 @@@ return NULL; } + /* A footnote. */ + struct render_footnote + { + struct hmap_node node; + + /* The area of the table covered by the cell that has the footnote. + + d[H][0] is the leftmost column. + d[H][1] is the rightmost column, plus 1. + d[V][0] is the top row. + d[V][1] is the bottom row, plus 1. + + The cell in its original table might occupy a larger region. This + member reflects the size of the cell in the current render_page, after + trimming off any rows or columns due to page-breaking. */ + int d[TABLE_N_AXES][2]; + + /* The index of the first footnote in the cell. */ + int idx; + }; + + static int + count_footnotes (const struct table_cell *cell) + { + size_t i; + int n; + + n = 0; + for (i = 0; i < cell->n_contents; i++) + n += cell->contents[i].n_footnotes; + return n; + } + + static int + find_footnote_idx (const struct table_cell *cell, const struct hmap *footnotes) + { + const struct render_footnote *f; + + if (!count_footnotes (cell)) + return 0; + + HMAP_FOR_EACH_WITH_HASH (f, struct render_footnote, node, + hash_cell (cell->d[H][0], cell->d[V][0]), footnotes) + if (f->d[H][0] == cell->d[H][0] && f->d[V][0] == cell->d[V][0]) + return f->idx; + + NOT_REACHED (); + } + /* Row or column dimensions. Used to figure the size of a table in render_page_create() and discarded after that. */ struct render_row @@@ -502,6 -588,8 +588,8 @@@ render_page_allocate (const struct rend } hmap_init (&page->overflows); + hmap_init (&page->footnotes); + page->n_footnotes = 0; memset (page->is_edge_cutoff, 0, sizeof page->is_edge_cutoff); return page; @@@ -592,7 -680,7 +680,7 @@@ set_join_crossings (struct render_page The new render_page will be suitable for rendering on a device whose page size is PARAMS->size, but the caller is responsible for actually breaking it up to fit on such a device, using the render_break abstraction. */ - struct render_page * + static struct render_page * render_page_create (const struct render_params *params, const struct table *table_) { @@@ -603,6 -691,8 +691,8 @@@ struct render_row *rows; int table_widths[2]; int *rules[TABLE_N_AXES]; + struct hmap footnotes; + int footnote_idx; int nr, nc; int x, y; int i; @@@ -624,7 -714,9 +714,9 @@@ } /* Calculate minimum and maximum widths of cells that do not - span multiple columns. */ + span multiple columns. Assign footnote markers. */ + hmap_init (&footnotes); + footnote_idx = 0; for (i = 0; i < 2; i++) columns[i] = xzalloc (nc * sizeof *columns[i]); for (y = 0; y < nr; y++) @@@ -633,15 -725,35 +725,35 @@@ struct table_cell cell; table_get_cell (table, x, y, &cell); - if (y == cell.d[V][0] && table_cell_colspan (&cell) == 1) + if (y == cell.d[V][0]) { - int w[2]; - int i; - - params->measure_cell_width (params->aux, &cell, &w[MIN], &w[MAX]); - for (i = 0; i < 2; i++) - if (columns[i][x].unspanned < w[i]) - columns[i][x].unspanned = w[i]; + int n; + + if (table_cell_colspan (&cell) == 1) + { + int w[2]; + int i; + + params->measure_cell_width (params->aux, &cell, footnote_idx, + &w[MIN], &w[MAX]); + for (i = 0; i < 2; i++) + if (columns[i][x].unspanned < w[i]) + columns[i][x].unspanned = w[i]; + } + + n = count_footnotes (&cell); + if (n > 0) + { + struct render_footnote *f = xmalloc (sizeof *f); + f->d[H][0] = cell.d[H][0]; + f->d[H][1] = cell.d[H][1]; + f->d[V][0] = cell.d[V][0]; + f->d[V][1] = cell.d[V][1]; + f->idx = footnote_idx; + hmap_insert (&footnotes, &f->node, hash_cell (x, y)); + + footnote_idx += n; + } } x = cell.d[H][1]; table_cell_free (&cell); @@@ -661,7 -773,9 +773,9 @@@ { int w[2]; - params->measure_cell_width (params->aux, &cell, &w[MIN], &w[MAX]); + params->measure_cell_width (params->aux, &cell, + find_footnote_idx (&cell, &footnotes), + &w[MIN], &w[MAX]); for (i = 0; i < 2; i++) distribute_spanned_width (w[i], &columns[i][cell.d[H][0]], rules[H], table_cell_colspan (&cell)); @@@ -710,7 -824,8 +824,8 @@@ if (table_cell_rowspan (&cell) == 1) { int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); - int h = params->measure_cell_height (params->aux, &cell, w); + int h = params->measure_cell_height ( + params->aux, &cell, find_footnote_idx (&cell, &footnotes), w); if (h > r->unspanned) r->unspanned = r->width = h; } @@@ -737,7 -852,8 +852,8 @@@ if (y == cell.d[V][0] && table_cell_rowspan (&cell) > 1) { int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); - int h = params->measure_cell_height (params->aux, &cell, w); + int h = params->measure_cell_height ( + params->aux, &cell, find_footnote_idx (&cell, &footnotes), w); distribute_spanned_width (h, &rows[cell.d[V][0]], rules[V], table_cell_rowspan (&cell)); } @@@ -762,6 -878,10 +878,10 @@@ } } + hmap_swap (&page->footnotes, &footnotes); + hmap_destroy (&footnotes); + page->n_footnotes = footnote_idx; + free (rules[H]); free (rules[V]); @@@ -779,7 -899,7 +899,7 @@@ render_page_ref (const struct render_pa /* Decreases PAGE's reference count and destroys PAGE if this causes the reference count to fall to zero. */ - void + static void render_page_unref (struct render_page *page) { if (page != NULL && --page->ref_cnt == 0) @@@ -812,6 -932,23 +932,23 @@@ render_page_get_size (const struct rend { return page->cp[axis][page->n[axis] * 2 + 1]; } + + int + render_page_get_best_breakpoint (const struct render_page *page, int height) + { + int y; + + /* If there's no room for at least the top row and the rules above and below + it, don't include any of the table. */ + if (page->cp[V][3] > height) + return 0; + + /* Otherwise include as many rows and rules as we can. */ + for (y = 5; y <= 2 * page->n[V] + 1; y += 2) + if (page->cp[V][y] > height) + return page->cp[V][y - 2]; + return height; + } /* Drawing render_pages. */ @@@ -830,7 -967,8 +967,8 @@@ is_rule (int z } static void - render_rule (const struct render_page *page, const int d[TABLE_N_AXES]) + render_rule (const struct render_page *page, const int ofs[TABLE_N_AXES], + const int d[TABLE_N_AXES]) { enum render_line_style styles[TABLE_N_AXES][2]; enum table_axis a; @@@ -869,25 -1007,26 +1007,26 @@@ { int bb[TABLE_N_AXES][2]; - bb[H][0] = page->cp[H][d[H]]; - bb[H][1] = page->cp[H][d[H] + 1]; - bb[V][0] = page->cp[V][d[V]]; - bb[V][1] = page->cp[V][d[V] + 1]; + bb[H][0] = ofs[H] + page->cp[H][d[H]]; + bb[H][1] = ofs[H] + page->cp[H][d[H] + 1]; + bb[V][0] = ofs[V] + page->cp[V][d[V]]; + bb[V][1] = ofs[V] + page->cp[V][d[V] + 1]; page->params->draw_line (page->params->aux, bb, styles); } } static void - render_cell (const struct render_page *page, const struct table_cell *cell) + render_cell (const struct render_page *page, const int ofs[TABLE_N_AXES], + const struct table_cell *cell) { const struct render_overflow *of; int bb[TABLE_N_AXES][2]; int clip[TABLE_N_AXES][2]; - bb[H][0] = clip[H][0] = page->cp[H][cell->d[H][0] * 2 + 1]; - bb[H][1] = clip[H][1] = page->cp[H][cell->d[H][1] * 2]; - bb[V][0] = clip[V][0] = page->cp[V][cell->d[V][0] * 2 + 1]; - bb[V][1] = clip[V][1] = page->cp[V][cell->d[V][1] * 2]; + bb[H][0] = clip[H][0] = ofs[H] + page->cp[H][cell->d[H][0] * 2 + 1]; + bb[H][1] = clip[H][1] = ofs[H] + page->cp[H][cell->d[H][1] * 2]; + bb[V][0] = clip[V][0] = ofs[V] + page->cp[V][cell->d[V][0] * 2 + 1]; + bb[V][1] = clip[V][1] = ofs[V] + page->cp[V][cell->d[V][1] * 2]; of = find_overflow (page, cell->d[H][0], cell->d[V][0]); if (of) @@@ -899,25 -1038,26 +1038,26 @@@ if (of->overflow[axis][0]) { bb[axis][0] -= of->overflow[axis][0]; - if (cell->d[axis][0] == 0) - clip[axis][0] = page->cp[axis][cell->d[axis][0] * 2]; + if (cell->d[axis][0] == 0 && !page->is_edge_cutoff[axis][0]) + clip[axis][0] = ofs[axis] + page->cp[axis][cell->d[axis][0] * 2]; } if (of->overflow[axis][1]) { bb[axis][1] += of->overflow[axis][1]; - if (cell->d[axis][1] == page->n[axis]) - clip[axis][1] = page->cp[axis][cell->d[axis][1] * 2 + 1]; + if (cell->d[axis][1] == page->n[axis] && !page->is_edge_cutoff[axis][1]) + clip[axis][1] = ofs[axis] + page->cp[axis][cell->d[axis][1] * 2 + 1]; } } } - page->params->draw_cell (page->params->aux, cell, bb, clip); + page->params->draw_cell (page->params->aux, cell, + find_footnote_idx (cell, &page->footnotes), bb, clip); } /* Draws the cells of PAGE indicated in BB. */ static void render_page_draw_cells (const struct render_page *page, - int bb[TABLE_N_AXES][2]) + int ofs[TABLE_N_AXES], int bb[TABLE_N_AXES][2]) { int x, y; @@@ -928,7 -1068,7 +1068,7 @@@ int d[TABLE_N_AXES]; d[H] = x; d[V] = y; - render_rule (page, d); + render_rule (page, ofs, d); x++; } else @@@ -936,8 -1076,8 +1076,8 @@@ struct table_cell cell; table_get_cell (page->table, x / 2, y / 2, &cell); - if (y == bb[V][0] || y / 2 == cell.d[V][0]) - render_cell (page, &cell); + if (y / 2 == bb[V][0] / 2 || y / 2 == cell.d[V][0]) + render_cell (page, ofs, &cell); x = rule_ofs (cell.d[H][1]); table_cell_free (&cell); } @@@ -946,7 -1086,7 +1086,7 @@@ /* Renders PAGE, by calling the 'draw_line' and 'draw_cell' functions from the render_params provided to render_page_create(). */ void - render_page_draw (const struct render_page *page) + render_page_draw (const struct render_page *page, int ofs[TABLE_N_AXES]) { int bb[TABLE_N_AXES][2]; @@@ -955,12 -1095,88 +1095,88 @@@ bb[V][0] = 0; bb[V][1] = page->n[V] * 2 + 1; - render_page_draw_cells (page, bb); + render_page_draw_cells (page, ofs, bb); + } + + /* Returns the greatest value i, 0 <= i < n, such that cp[i] <= x0. */ + static int + get_clip_min_extent (int x0, const int cp[], int n) + { + int low, high, best; + + low = 0; + high = n; + best = 0; + while (low < high) + { + int middle = low + (high - low) / 2; + + if (cp[middle] <= x0) + { + best = middle; + low = middle + 1; + } + else + high = middle; + } + + return best; + } + + /* Returns the least value i, 0 <= i < n, such that cp[i] >= x1. */ + static int + get_clip_max_extent (int x1, const int cp[], int n) + { + int low, high, best; + + low = 0; + high = n; + best = n; + while (low < high) + { + int middle = low + (high - low) / 2; + + if (cp[middle] >= x1) + best = high = middle; + else + low = middle + 1; + } + + while (best > 0 && cp[best - 1] == cp[best]) + best--; + + return best; + } + + /* Renders the cells of PAGE that intersect (X,Y)-(X+W,Y+H), by calling the + 'draw_line' and 'draw_cell' functions from the render_params provided to + render_page_create(). */ + void + render_page_draw_region (const struct render_page *page, + int ofs[TABLE_N_AXES], int clip[TABLE_N_AXES][2]) + { + int bb[TABLE_N_AXES][2]; + + bb[H][0] = get_clip_min_extent (clip[H][0], page->cp[H], page->n[H] * 2 + 1); + bb[H][1] = get_clip_max_extent (clip[H][1], page->cp[H], page->n[H] * 2 + 1); + bb[V][0] = get_clip_min_extent (clip[V][0], page->cp[V], page->n[V] * 2 + 1); + bb[V][1] = get_clip_max_extent (clip[V][1], page->cp[V], page->n[V] * 2 + 1); + + render_page_draw_cells (page, ofs, bb); } - + - /* Breaking up tables to fit on a page. */ + /* An iterator for breaking render_pages into smaller chunks. */ + struct render_break + { + struct render_page *page; /* Page being broken up. */ + enum table_axis axis; /* Axis along which 'page' is being broken. */ + int z; /* Next cell along 'axis'. */ + int pixel; /* Pixel offset within cell 'z' (usually 0). */ + int hw; /* Width of headers of 'page' along 'axis'. */ + }; + static int needed_size (const struct render_break *, int cell); static bool cell_is_breakable (const struct render_break *, int cell); static struct render_page *render_page_select (const struct render_page *, @@@ -968,35 -1184,32 +1184,32 @@@ int z0, int p0, int z1, int p1); - /* Initializes render_break B for breaking PAGE along AXIS. - - Ownership of PAGE is transferred to B. The caller must use - render_page_ref() if it needs to keep a copy of PAGE. */ - void - render_break_init (struct render_break *b, struct render_page *page, + /* Initializes render_break B for breaking PAGE along AXIS. */ + static void + render_break_init (struct render_break *b, const struct render_page *page, enum table_axis axis) { - b->page = page; + b->page = render_page_ref (page); b->axis = axis; - b->cell = page->h[axis][0]; + b->z = page->h[axis][0]; b->pixel = 0; b->hw = headers_width (page, axis); } /* Initializes B as a render_break structure for which render_break_has_next() always returns false. */ - void + static void render_break_init_empty (struct render_break *b) { b->page = NULL; b->axis = TABLE_HORZ; - b->cell = 0; + b->z = 0; b->pixel = 0; b->hw = 0; } /* Frees B and unrefs the render_page that it owns. */ - void + static void render_break_destroy (struct render_break *b) { if (b != NULL) @@@ -1008,26 -1221,13 +1221,13 @@@ /* Returns true if B still has cells that are yet to be returned, false if all of B's page has been processed. */ - bool + static bool render_break_has_next (const struct render_break *b) { const struct render_page *page = b->page; enum table_axis axis = b->axis; - return page != NULL && b->cell < page->n[axis] - page->h[axis][1]; - } - - /* Returns the minimum SIZE argument that, if passed to render_break_next(), - will avoid a null return value (if cells are still left). */ - int - render_break_next_size (const struct render_break *b) - { - const struct render_page *page = b->page; - enum table_axis axis = b->axis; - - return (!render_break_has_next (b) ? 0 - : !cell_is_breakable (b, b->cell) ? needed_size (b, b->cell + 1) - : b->hw + page->params->font_size[axis]); + return page != NULL && b->z < page->n[axis] - page->h[axis][1]; } /* Returns a new render_page that is up to SIZE pixels wide along B's axis. @@@ -1035,38 -1235,114 +1235,114 @@@ SIZE is too small to reasonably render any cells. The latter will never happen if SIZE is at least as large as the page size passed to render_page_create() along B's axis. */ - struct render_page * + static struct render_page * render_break_next (struct render_break *b, int size) { const struct render_page *page = b->page; enum table_axis axis = b->axis; struct render_page *subpage; - int cell, pixel; + int z, pixel; if (!render_break_has_next (b)) return NULL; pixel = 0; - for (cell = b->cell; cell < page->n[axis] - page->h[axis][1]; cell++) - if (needed_size (b, cell + 1) > size) - { - if (!cell_is_breakable (b, cell)) - { - if (cell == b->cell) - return NULL; - } - else - pixel = (cell == b->cell - ? b->pixel + size - b->hw - : size - needed_size (b, cell)); - break; - } + for (z = b->z; z < page->n[axis] - page->h[axis][1]; z++) + { + int needed = needed_size (b, z + 1); + if (needed > size) + { + if (cell_is_breakable (b, z)) + { + /* If there is no right header and we render a partial cell on + the right side of the body, then we omit the rightmost rule of + the body. Otherwise the rendering is deceptive because it + looks like the whole cell is present instead of a partial + cell. + + This is similar to code for the left side in needed_size(). */ + int rule_allowance = (page->h[axis][1] + ? 0 + : rule_width (page, axis, z)); + + /* The amount that, if we added cell 'z', the rendering would + overfill the allocated 'size'. */ + int overhang = needed - size - rule_allowance; + + /* The width of cell 'z'. */ + int cell_size = cell_width (page, axis, z); + + /* The amount trimmed off the left side of 'z', + and the amount left to render. */ + int cell_ofs = z == b->z ? b->pixel : 0; + int cell_left = cell_size - cell_ofs; + + /* A small but visible width. */ + int em = page->params->font_size[axis]; + + /* If some of the cell remains to render, + and there would still be some of the cell left afterward, + then partially render that much of the cell. */ + pixel = (cell_left && cell_left > overhang + ? cell_left - overhang + cell_ofs + : 0); + + /* If there would be only a tiny amount of the cell left after + rendering it partially, reduce the amount rendered slightly + to make the output look a little better. */ + if (pixel + em > cell_size) + pixel = MAX (pixel - em, 0); + + /* If we're breaking vertically, then consider whether the cells + being broken have a better internal breakpoint than the exact + number of pixels available, which might look bad e.g. because + it breaks in the middle of a line of text. */ + if (axis == TABLE_VERT && page->params->adjust_break) + { + int x; + + for (x = 0; x < page->n[H]; ) + { + struct table_cell cell; + int better_pixel; + int w; + + table_get_cell (page->table, x, z, &cell); + w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); + better_pixel = page->params->adjust_break ( + page->params->aux, &cell, + find_footnote_idx (&cell, &page->footnotes), w, pixel); + x = cell.d[H][1]; + table_cell_free (&cell); + + if (better_pixel < pixel) + { + if (better_pixel > (z == b->z ? b->pixel : 0)) + { + pixel = better_pixel; + break; + } + else if (better_pixel == 0 && z != b->z) + { + pixel = 0; + break; + } + } + } + } + } + break; + } + } - subpage = render_page_select (page, axis, b->cell, b->pixel, - pixel ? cell + 1 : cell, - pixel ? cell_width (page, axis, cell) - pixel + if (z == b->z && !pixel) + return NULL; + + subpage = render_page_select (page, axis, b->z, b->pixel, + pixel ? z + 1 : z, + pixel ? cell_width (page, axis, z) - pixel : 0); - b->cell = cell; + b->z = z; b->pixel = pixel; return subpage; } @@@ -1080,9 -1356,35 +1356,35 @@@ needed_size (const struct render_break enum table_axis axis = b->axis; int size; - size = joined_width (page, axis, b->cell, cell) + b->hw - b->pixel; + /* Width of left header not including its rightmost rule. */ + size = axis_width (page, axis, 0, rule_ofs (page->h[axis][0])); + + /* If we have a pixel offset and there is no left header, then we omit the + leftmost rule of the body. Otherwise the rendering is deceptive because + it looks like the whole cell is present instead of a partial cell. + + Otherwise (if there are headers) we will be merging two rules: the + rightmost rule in the header and the leftmost rule in the body. We assume + that the width of a merged rule is the larger of the widths of either rule + invidiually. */ + if (b->pixel == 0 || page->h[axis][0]) + size += MAX (rule_width (page, axis, page->h[axis][0]), + rule_width (page, axis, b->z)); + + /* Width of body, minus any pixel offset in the leftmost cell. */ + size += joined_width (page, axis, b->z, cell) - b->pixel; + + /* Width of rightmost rule in body merged with leftmost rule in headers. */ + size += MAX (rule_width_r (page, axis, page->h[axis][1]), + rule_width (page, axis, cell)); + + /* Width of right header not including its leftmost rule. */ + size += axis_width (page, axis, rule_ofs_r (page, axis, page->h[axis][1]), + rule_ofs_r (page, axis, 0)); + + /* Join crossing. */ if (page->h[axis][0] && page->h[axis][1]) - size += page->join_crossing[axis][b->cell]; + size += page->join_crossing[axis][b->z]; return size; } @@@ -1097,7 -1399,263 +1399,263 @@@ cell_is_breakable (const struct render_ const struct render_page *page = b->page; enum table_axis axis = b->axis; - return cell_width (page, axis, cell) > page->params->size[axis] / 2; + return cell_width (page, axis, cell) >= page->params->min_break[axis]; + } + + /* render_pager. */ + + struct render_pager + { + const struct render_params *params; + + struct render_page **pages; + size_t n_pages, allocated_pages; + + size_t cur_page; + struct render_break x_break; + struct render_break y_break; + }; + + static const struct render_page * + render_pager_add_table (struct render_pager *p, struct table *table) + { + struct render_page *page; + + if (p->n_pages >= p->allocated_pages) + p->pages = x2nrealloc (p->pages, &p->allocated_pages, sizeof *p->pages); + page = p->pages[p->n_pages++] = render_page_create (p->params, table); + return page; + } + + static void + render_pager_start_page (struct render_pager *p) + { + render_break_init (&p->x_break, p->pages[p->cur_page++], H); + render_break_init_empty (&p->y_break); + } + + static void + add_footnote_page (struct render_pager *p, const struct render_page *body) + { + const struct table *table = body->table; + int nc = table_nc (table); + int nr = table_nr (table); + int footnote_idx = 0; + struct tab_table *t; + int x, y; + + if (!body->n_footnotes) + return; + + t = tab_create (2, body->n_footnotes); + for (y = 0; y < nr; y++) + for (x = 0; x < nc; ) + { + struct table_cell cell; + + table_get_cell (table, x, y, &cell); + if (y == cell.d[V][0]) + { + size_t i; + + for (i = 0; i < cell.n_contents; i++) + { + const struct cell_contents *cc = &cell.contents[i]; + size_t j; + + for (j = 0; j < cc->n_footnotes; j++) + { + const char *f = cc->footnotes[j]; + + tab_text (t, 0, footnote_idx, TAB_LEFT, ""); + tab_footnote (t, 0, footnote_idx, "(none)"); + tab_text (t, 1, footnote_idx, TAB_LEFT, f); + footnote_idx++; + } + } + } + x = cell.d[H][1]; + table_cell_free (&cell); + } + render_pager_add_table (p, &t->table); + } + + /* Creates and returns a new render_pager for rendering TABLE_ITEM on the + device with the given PARAMS. */ + struct render_pager * + render_pager_create (const struct render_params *params, + const struct table_item *table_item) + { + const char *caption = table_item_get_caption (table_item); + const char *title = table_item_get_title (table_item); + const struct render_page *body_page; + struct render_pager *p; + + p = xzalloc (sizeof *p); + p->params = params; + + /* Title. */ + if (title) + render_pager_add_table (p, table_from_string (TAB_LEFT, title)); + + /* Body. */ + body_page = render_pager_add_table (p, table_ref (table_item_get_table ( + table_item))); + + /* Caption. */ + if (caption) + render_pager_add_table (p, table_from_string (TAB_LEFT, caption)); + + /* Footnotes. */ + add_footnote_page (p, body_page); + + render_pager_start_page (p); + + return p; + } + + /* Destroys P. */ + void + render_pager_destroy (struct render_pager *p) + { + if (p) + { + size_t i; + + render_break_destroy (&p->x_break); + render_break_destroy (&p->y_break); + for (i = 0; i < p->n_pages; i++) + render_page_unref (p->pages[i]); + free (p->pages); + free (p); + } + } + + /* Returns true if P has content remaining to render, false if rendering is + done. */ + bool + render_pager_has_next (const struct render_pager *p_) + { + struct render_pager *p = CONST_CAST (struct render_pager *, p_); + + while (!render_break_has_next (&p->y_break)) + { + render_break_destroy (&p->y_break); + if (!render_break_has_next (&p->x_break)) + { + render_break_destroy (&p->x_break); + if (p->cur_page >= p->n_pages) + { + render_break_init_empty (&p->x_break); + render_break_init_empty (&p->y_break); + return false; + } + render_pager_start_page (p); + } + else + render_break_init (&p->y_break, + render_break_next (&p->x_break, p->params->size[H]), V); + } + return true; + } + + /* Draws a chunk of content from P to fit in a space that has vertical size + SPACE and the horizontal size specified in the render_params passed to + render_page_create(). Returns the amount of space actually used by the + rendered chunk, which will be 0 if SPACE is too small to render anything or + if no content remains (use render_pager_has_next() to distinguish these + cases). */ + int + render_pager_draw_next (struct render_pager *p, int space) + { + int ofs[TABLE_N_AXES] = { 0, 0 }; + size_t start_page = SIZE_MAX; + + while (render_pager_has_next (p)) + { + struct render_page *page; + + if (start_page == p->cur_page) + break; + start_page = p->cur_page; + + page = render_break_next (&p->y_break, space - ofs[V]); + if (!page) + break; + + render_page_draw (page, ofs); + ofs[V] += render_page_get_size (page, V); + render_page_unref (page); + } + return ofs[V]; + } + + /* Draws all of P's content. */ + void + render_pager_draw (const struct render_pager *p) + { + render_pager_draw_region (p, 0, 0, INT_MAX, INT_MAX); + } + + /* Draws the region of P's content that lies in the region (X,Y)-(X+W,Y+H). + Some extra content might be drawn; the device should perform clipping as + necessary. */ + void + render_pager_draw_region (const struct render_pager *p, + int x, int y, int w, int h) + { + int ofs[TABLE_N_AXES] = { 0, 0 }; + int clip[TABLE_N_AXES][2]; + size_t i; + + clip[H][0] = x; + clip[H][1] = x + w; + for (i = 0; i < p->n_pages; i++) + { + const struct render_page *page = p->pages[i]; + int size = render_page_get_size (page, V); + + clip[V][0] = MAX (y, ofs[V]) - ofs[V]; + clip[V][1] = MIN (y + h, ofs[V] + size) - ofs[V]; + if (clip[V][1] > clip[V][0]) + render_page_draw_region (page, ofs, clip); + + ofs[V] += size; + } + } + + /* Returns the size of P's content along AXIS; i.e. the content's width if AXIS + is TABLE_HORZ and its length if AXIS is TABLE_VERT. */ + int + render_pager_get_size (const struct render_pager *p, enum table_axis axis) + { + int size = 0; + size_t i; + + for (i = 0; i < p->n_pages; i++) + { + int subsize = render_page_get_size (p->pages[i], axis); + size = axis == H ? MAX (size, subsize) : size + subsize; + } + + return size; + } + + int + render_pager_get_best_breakpoint (const struct render_pager *p, int height) + { + int y = 0; + size_t i; + + for (i = 0; i < p->n_pages; i++) + { + int size = render_page_get_size (p->pages[i], V); + if (y + size >= height) + return render_page_get_best_breakpoint (p->pages[i], height - y) + y; + y += size; + } + + return height; } /* render_page_select() and helpers. */ @@@ -1123,8 -1681,8 +1681,8 @@@ static struct render_overflow *insert_o const struct table_cell *); /* Creates and returns a new render_page whose contents are a subregion of - PAGE's contents. The new render_page includes cells Z0 through Z1 along - AXIS, plus any headers on AXIS. + PAGE's contents. The new render_page includes cells Z0 through Z1 + (exclusive) along AXIS, plus any headers on AXIS. If P0 is nonzero, then it is a number of pixels to exclude from the left or top (according to AXIS) of cell Z0. Similarly, P1 is a number of pixels to @@@ -1140,6 -1698,7 +1698,7 @@@ static struct render_page render_page_select (const struct render_page *page, enum table_axis axis, int z0, int p0, int z1, int p1) { + const struct render_footnote *f; struct render_page_selection s; enum table_axis a = axis; enum table_axis b = !a; @@@ -1194,7 -1753,12 +1753,12 @@@ dcp = subpage->cp[a]; *dcp = 0; for (z = 0; z <= rule_ofs (subpage->h[a][0]); z++, dcp++) - dcp[1] = dcp[0] + (scp[z + 1] - scp[z]); + { + if (z == 0 && subpage->is_edge_cutoff[a][0]) + dcp[1] = dcp[0]; + else + dcp[1] = dcp[0] + (scp[z + 1] - scp[z]); + } for (z = cell_ofs (z0); z <= cell_ofs (z1 - 1); z++, dcp++) { dcp[1] = dcp[0] + (scp[z + 1] - scp[z]); @@@ -1209,7 -1773,12 +1773,12 @@@ } for (z = rule_ofs_r (page, a, subpage->h[a][1]); z <= rule_ofs_r (page, a, 0); z++, dcp++) - dcp[1] = dcp[0] + (scp[z + 1] - scp[z]); + { + if (z == rule_ofs_r (page, a, 0) && subpage->is_edge_cutoff[a][1]) + dcp[1] = dcp[0]; + else + dcp[1] = dcp[0] + (scp[z + 1] - scp[z]); + } assert (dcp == &subpage->cp[a][2 * subpage->n[a] + 1]); for (z = 0; z < page->n[b] * 2 + 2; z++) @@@ -1225,50 -1794,64 +1794,64 @@@ s.p1 = p1; s.subpage = subpage; - for (z = 0; z < page->n[b]; z++) - { - struct table_cell cell; - int d[TABLE_N_AXES]; + if (!page->h[a][0] || z0 > page->h[a][0] || p0) + for (z = 0; z < page->n[b]; ) + { + struct table_cell cell; + int d[TABLE_N_AXES]; + bool overflow0; + bool overflow1; - d[a] = z0; - d[b] = z; - table_get_cell (page->table, d[H], d[V], &cell); - if ((z == cell.d[b][0] && (p0 || cell.d[a][0] < z0)) - || (z == cell.d[b][1] - 1 && p1)) - { - ro = insert_overflow (&s, &cell); - ro->overflow[a][0] += p0 + axis_width (page, a, - cell_ofs (cell.d[a][0]), - cell_ofs (z0)); - if (z1 == z0 + 1) - ro->overflow[a][1] += p1; - if (page->h[a][0] && page->h[a][1]) - ro->overflow[a][0] -= page->join_crossing[a][cell.d[a][0] + 1]; - if (cell.d[a][1] > z1) - ro->overflow[a][1] += axis_width (page, a, cell_ofs (z1), - cell_ofs (cell.d[a][1])); - } - table_cell_free (&cell); - } + d[a] = z0; + d[b] = z; - for (z = 0; z < page->n[b]; z++) - { - struct table_cell cell; - int d[TABLE_N_AXES]; + table_get_cell (page->table, d[H], d[V], &cell); + overflow0 = p0 || cell.d[a][0] < z0; + overflow1 = cell.d[a][1] > z1 || (cell.d[a][1] == z1 && p1); + if (overflow0 || overflow1) + { + ro = insert_overflow (&s, &cell); + + if (overflow0) + { + ro->overflow[a][0] += p0 + axis_width ( + page, a, cell_ofs (cell.d[a][0]), cell_ofs (z0)); + if (page->h[a][0] && page->h[a][1]) + ro->overflow[a][0] -= page->join_crossing[a][cell.d[a][0] + + 1]; + } + + if (overflow1) + { + ro->overflow[a][1] += p1 + axis_width ( + page, a, cell_ofs (z1), cell_ofs (cell.d[a][1])); + if (page->h[a][0] && page->h[a][1]) + ro->overflow[a][1] -= page->join_crossing[a][cell.d[a][1]]; + } + } + z = cell.d[b][1]; + table_cell_free (&cell); + } - /* XXX need to handle p1 below */ - d[a] = z1 - 1; - d[b] = z; - table_get_cell (page->table, d[H], d[V], &cell); - if (z == cell.d[b][0] && cell.d[a][1] > z1 - && find_overflow_for_cell (&s, &cell) == NULL) - { - ro = insert_overflow (&s, &cell); - ro->overflow[a][1] += axis_width (page, a, cell_ofs (z1), - cell_ofs (cell.d[a][1])); - } - table_cell_free (&cell); - } + if (!page->h[a][1] || z1 < page->n[a] - page->h[a][1] || p1) + for (z = 0; z < page->n[b]; ) + { + struct table_cell cell; + int d[TABLE_N_AXES]; + + d[a] = z1 - 1; + d[b] = z; + table_get_cell (page->table, d[H], d[V], &cell); + if ((cell.d[a][1] > z1 || (cell.d[a][1] == z1 && p1)) + && find_overflow_for_cell (&s, &cell) == NULL) + { + ro = insert_overflow (&s, &cell); + ro->overflow[a][1] += p1 + axis_width (page, a, cell_ofs (z1), + cell_ofs (cell.d[a][1])); + } + z = cell.d[b][1]; + table_cell_free (&cell); + } /* Copy overflows from PAGE into subpage. */ HMAP_FOR_EACH (ro, struct render_overflow, node, &page->overflows) @@@ -1282,6 -1865,21 +1865,21 @@@ table_cell_free (&cell); } + /* Copy footnotes from PAGE into subpage. */ + HMAP_FOR_EACH (f, struct render_footnote, node, &page->footnotes) + if ((f->d[a][0] >= z0 && f->d[a][0] < z1) + || (f->d[a][1] - 1 >= z0 && f->d[a][1] - 1 < z1)) + { + struct render_footnote *nf = xmalloc (sizeof *nf); + nf->d[a][0] = MAX (z0, f->d[a][0]) - z0 + page->h[a][0]; + nf->d[a][1] = MIN (z1, f->d[a][1]) - z0 + page->h[a][0]; + nf->d[b][0] = f->d[b][0]; + nf->d[b][1] = f->d[b][1]; + nf->idx = f->idx; + hmap_insert (&subpage->footnotes, &nf->node, + hash_cell (nf->d[H][0], nf->d[V][0])); + } + return subpage; } @@@ -1334,7 -1932,7 +1932,7 @@@ insert_overflow (struct render_page_sel of = xzalloc (sizeof *of); cell_to_subpage (s, cell, of->d); hmap_insert (&s->subpage->overflows, &of->node, - hash_overflow (of->d[H], of->d[V])); + hash_cell (of->d[H], of->d[V])); old = find_overflow (s->page, cell->d[H][0], cell->d[V][0]); if (old != NULL) diff --combined src/output/render.h index bdcc01264f,c3f852f80a..3e9d8a83ad --- a/src/output/render.h +++ b/src/output/render.h @@@ -1,5 -1,5 +1,5 @@@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc. + Copyright (C) 2009, 2010, 2011, 2014 Free Software Foundation, Inc. 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 @@@ -21,7 -21,7 +21,7 @@@ #include #include "output/table-provider.h" - struct table; + struct table_item; enum render_line_style { @@@ -31,18 -31,62 +31,62 @@@ RENDER_N_LINES }; + /* Parameters for rendering a table_item to a device. + + + Coordinate system + ================= + + The rendering code assumes that larger 'x' is to the right and larger 'y' + toward the bottom of the page. + + The rendering code assumes that the table being rendered has its upper left + corner at (0,0) in device coordinates. This is usually not the case from + the driver's perspective, so the driver should expect to apply its own + offset to coordinates passed to callback functions. + + + Callback functions + ================== + + For each of the callback functions, AUX is passed as the 'aux' member of the + render_params structure. + + The device is expected to transform numerical footnote index numbers into + footnote markers. The existing drivers use str_format_26adic() to transform + index 0 to "a", index 1 to "b", and so on. The FOOTNOTE_IDX supplied to + each function is the footnote index number for the first footnote in the + cell. If a cell contains more than one footnote, then the additional + footnote indexes increase sequentially, e.g. the second footnote has index + FOOTNOTE_IDX + 1. + */ struct render_params { /* Measures CELL's width. Stores in *MIN_WIDTH the minimum width required to avoid splitting a single word across multiple lines (normally, this is the width of the longest word in the cell) and in *MAX_WIDTH the - minimum width required to avoid line breaks other than at new-lines. */ + minimum width required to avoid line breaks other than at new-lines. + */ void (*measure_cell_width) (void *aux, const struct table_cell *cell, + int footnote_idx, int *min_width, int *max_width); /* Returns the height required to render CELL given a width of WIDTH. */ int (*measure_cell_height) (void *aux, const struct table_cell *cell, - int width); + int footnote_idx, int width); + + /* Given that there is space measuring WIDTH by HEIGHT to render CELL, + where HEIGHT is insufficient to render the entire height of the cell, + returns the largest height less than HEIGHT at which it is appropriate + to break the cell. For example, if breaking at the specified HEIGHT + would break in the middle of a line of text, the return value would be + just sufficiently less that the breakpoint would be between lines of + text. + + Optional. If NULL, the rendering engine assumes that all breakpoints + are acceptable. */ + int (*adjust_break) (void *aux, const struct table_cell *cell, + int footnote_idx, int width, int height); /* Draws a generalized intersection of lines in the rectangle whose top-left corner is (BB[TABLE_HORZ][0], BB[TABLE_VERT][0]) and whose @@@ -62,6 -106,7 +106,7 @@@ of the cell that lies within CLIP should actually be drawn, although BB should used to determine the layout of the cell. */ void (*draw_cell) (void *aux, const struct table_cell *cell, + int footnote_idx, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); /* Auxiliary data passed to each of the above functions. */ @@@ -78,39 -123,27 +123,26 @@@ /* Width of different kinds of lines. */ int line_widths[TABLE_N_AXES][RENDER_N_LINES]; - }; - - /* A "page" of content that is ready to be rendered. - - A page's size is not limited to the size passed in as part of render_params. - Use render_break (see below) to break a too-big render_page into smaller - render_pages that will fit in the available space. */ - struct render_page *render_page_create (const struct render_params *, - const struct table *); - struct render_page *render_page_ref (const struct render_page *); - void render_page_unref (struct render_page *); + /* Minimum cell width or height before allowing the cell to be broken + across two pages. (Joined cells may always be broken at join + points.) */ + int min_break[TABLE_N_AXES]; + }; - - + - int render_page_get_size (const struct render_page *, enum table_axis); - void render_page_draw (const struct render_page *); - /* An iterator for breaking render_pages into smaller chunks. */ - struct render_break - { - struct render_page *page; /* Page being broken up. */ - enum table_axis axis; /* Axis along which 'page' is being broken. */ - int cell; /* Next cell. */ - int pixel; /* Pixel offset within 'cell' (usually 0). */ - int hw; /* Width of headers of 'page' along 'axis'. */ - }; + struct render_pager *render_pager_create (const struct render_params *, + const struct table_item *); + void render_pager_destroy (struct render_pager *); + + bool render_pager_has_next (const struct render_pager *); + int render_pager_draw_next (struct render_pager *, int space); - void render_break_init (struct render_break *, struct render_page *, - enum table_axis); - void render_break_init_empty (struct render_break *); - void render_break_destroy (struct render_break *); + void render_pager_draw (const struct render_pager *); + void render_pager_draw_region (const struct render_pager *, + int x, int y, int w, int h); - bool render_break_has_next (const struct render_break *); - int render_break_next_size (const struct render_break *); - struct render_page *render_break_next (struct render_break *, int size); + int render_pager_get_size (const struct render_pager *, enum table_axis); + int render_pager_get_best_breakpoint (const struct render_pager *, int height); #endif /* output/render.h */ diff --combined src/ui/gui/automake.mk index b19ea4b3f9,1b3acc3c59..e6da60e164 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@@ -42,23 -42,33 +42,33 @@@ UI_FILES = src/ui/gui/val-labs-dialog.ui \ src/ui/gui/variable-info.ui \ src/ui/gui/data-editor.ui \ - src/ui/gui/output-viewer.ui \ + src/ui/gui/output-window.ui \ src/ui/gui/syntax-editor.ui \ src/ui/gui/var-sheet.ui \ src/ui/gui/var-type-dialog.ui + + $(srcdir)/doc/help-pages-list: $(UI_FILES) + cat $^ | grep '"help-page"' | \ + sed -e 's% *\([^<]*\)%//*[@id='"'"'\1'"'"']%' \ + -e 's%#%'"'"']/*[@id='"'"'%g' > $@ + EXTRA_DIST += doc/help-pages-list + + EXTRA_DIST += \ - src/ui/gui/OChangeLog \ + src/ui/gui/artwork/actions/.empty \ + src/ui/gui/artwork/apps/scalable/.empty \ + src/ui/gui/gen-dot-desktop.sh \ src/ui/gui/marshaller-list \ - src/ui/gui/gen-dot-desktop.sh + src/ui/gui/pspplogo.svg if HAVE_GUI bin_PROGRAMS += src/ui/gui/psppire - noinst_PROGRAMS += src/ui/gui/spreadsheet-test + noinst_PROGRAMS += src/ui/gui/spreadsheet-test -src_ui_gui_psppire_CFLAGS = $(GTK_CFLAGS) $(GTKSOURCEVIEW_CFLAGS) -Wall -DGDK_MULTIHEAD_SAFE=1 -src_ui_gui_spreadsheet_test_CFLAGS = $(GTK_CFLAGS) -Wall -DGDK_MULTIHEAD_SAFE=1 +src_ui_gui_psppire_CFLAGS = $(GTK_CFLAGS) $(GTKSOURCEVIEW_CFLAGS) -Wall -DGDK_MULTIHEAD_SAFE=1 +src_ui_gui_spreadsheet_test_CFLAGS = $(GTK_CFLAGS) -Wall -DGDK_MULTIHEAD_SAFE=1 src_ui_gui_psppire_LDFLAGS = \ @@@ -94,7 -104,6 +104,6 @@@ src_ui_gui_spreadsheet_test_LDADD = src_ui_gui_spreadsheet_test_SOURCES = src/ui/gui/spreadsheet-test.c src/ui/gui/psppire-spreadsheet-model.c - src_ui_gui_psppiredir = $(pkgdatadir) @@@ -251,6 -260,8 +260,8 @@@ src_ui_gui_psppire_SOURCES = src/ui/gui/psppire-lex-reader.h \ src/ui/gui/psppire-means-layer.c \ src/ui/gui/psppire-means-layer.h \ + src/ui/gui/psppire-output-view.c \ + src/ui/gui/psppire-output-view.h \ src/ui/gui/psppire-output-window.c \ src/ui/gui/psppire-output-window.h \ src/ui/gui/psppire-var-view.c \ @@@ -331,16 -342,16 +342,16 @@@ PHONY += yelp-chec AM_CPPFLAGS += -Isrc src/ui/gui/pspp.desktop: src/ui/gui/gen-dot-desktop.sh $(POFILES) - POFILES="$(POFILES)" top_builddir="$(top_builddir)" $(SHELL) $< > $@ + $(AM_V_GEN)POFILES="$(POFILES)" top_builddir="$(top_builddir)" $(SHELL) $< > $@ CLEANFILES+=src/ui/gui/pspp.desktop src/ui/gui/psppire-marshal.c: src/ui/gui/marshaller-list - echo '#include ' > $@ - $(GLIB_GENMARSHAL) --body --prefix=psppire_marshal $? >> $@ + $(AM_V_GEN)echo '#include ' > $@ + $(AM_V_at)$(GLIB_GENMARSHAL) --body --prefix=psppire_marshal $? >> $@ src/ui/gui/psppire-marshal.h: src/ui/gui/marshaller-list - $(GLIB_GENMARSHAL) --header --prefix=psppire_marshal $? > $@ + $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=psppire_marshal $? > $@ desktopdir = $(datadir)/applications desktop_DATA = src/ui/gui/pspp.desktop diff --combined src/ui/gui/main.c index 24ac6c7581,28f99a9ff7..1e59f8431f --- a/src/ui/gui/main.c +++ b/src/ui/gui/main.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2004, 2005, 2006, 2010, 2011, 2012, 2013 Free Software Foundation + Copyright (C) 2004, 2005, 2006, 2010, 2011, 2012, 2013, 2014 Free Software Foundation 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 @@@ -199,14 -199,12 +199,14 @@@ print_startup_time (gpointer data return FALSE; } +/* static gboolean quit_one_loop (gpointer data) { gtk_main_quit (); return FALSE; } +*/ struct initialisation_parameters { @@@ -289,7 -287,12 +289,12 @@@ main (int argc, char *argv[] set_program_name (argv[0]); g_mem_set_vtable (&vtable); + + #if !GLIB_CHECK_VERSION(2,32,0) + /* g_thread_init() was required before glib 2.32, but it is deprecated since + then and calling it yields a compile-time warning. */ g_thread_init (NULL); + #endif gtk_disable_setlocale (); @@@ -331,14 -334,13 +336,14 @@@ init_p.splash_window = create_splash_window (); init_p.data_file = optind < argc ? argv[optind] : NULL; - if ( show_splash ) - gtk_widget_show (init_p.splash_window); + // if ( show_splash ) + // gtk_widget_show (init_p.splash_window); - g_idle_add (quit_one_loop, 0); + // g_idle_add (quit_one_loop, 0); - gtk_quit_add (0, run_inner_loop, &init_p); - gtk_main (); + // gtk_quit_add (0, run_inner_loop, &init_p); + run_inner_loop (&init_p); + // gtk_main (); return 0; } diff --combined src/ui/gui/pspp-widget-facade.c index e5070bff3e,705250bc87..be785dc133 --- a/src/ui/gui/pspp-widget-facade.c +++ b/src/ui/gui/pspp-widget-facade.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2011, 2012 Free Software Foundation, Inc. + Copyright (C) 2011, 2012, 2014 Free Software Foundation, Inc. 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 @@@ -270,9 -270,7 +270,7 @@@ get_layout_location (GtkWidget *base PangoRectangle logical; GtkRequisition req; - if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_LTR) - xalign = xalign; - else + if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_RTL) xalign = 1.0 - xalign; pango_layout_get_pixel_extents (layout, NULL, &logical); @@@ -313,8 -311,10 +311,8 @@@ void facade_button_render (GtkWidget *base, - GdkDrawable *window, - GdkRectangle *expose_area, - - GdkRectangle *button_area, + cairo_t *cr, + const GdkRectangle *button_area, gint border_width, GtkStyle *button_style, GtkStateType state_type, @@@ -332,9 -332,9 +330,9 @@@ gint x, y; /* Paint the button. */ - gtk_paint_box (button_style, window, + gtk_paint_box (button_style, cr, state_type, - GTK_SHADOW_OUT, expose_area, base, "button", + GTK_SHADOW_OUT, base, "button", button_area->x + border_width, button_area->y + border_width, button_area->width - border_width * 2, @@@ -348,10 -348,8 +346,10 @@@ layout = facade_label_get_layout (base, label); get_layout_location (base, &label_area, layout, xpad, ypad, xalign, yalign, &x, &y); - gtk_paint_layout (label_style, window, state_type, FALSE, expose_area, + + gtk_paint_layout (label_style, cr, state_type, FALSE, base, "label", x, y, layout); + g_object_unref (layout); } diff --combined src/ui/gui/psppire-data-window.c index 5a00a64a8a,28ec605205..126980c8bb --- a/src/ui/gui/psppire-data-window.c +++ b/src/ui/gui/psppire-data-window.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation 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 @@@ -35,6 -35,7 +35,7 @@@ #include "ui/gui/helper.h" #include "ui/gui/psppire-data-window.h" #include "ui/gui/psppire-dialog-action.h" + #include "ui/gui/psppire-encoding-selector.h" #include "ui/gui/psppire-syntax-window.h" #include "ui/gui/psppire-window.h" #include "ui/gui/psppire.h" @@@ -331,7 -332,8 +332,8 @@@ name_has_suffix (const gchar *name } static gboolean - load_file (PsppireWindow *de, const gchar *file_name, gpointer syn) + load_file (PsppireWindow *de, const gchar *file_name, const char *encoding, + gpointer syn) { const char *mime_type = NULL; gchar *syntax = NULL; @@@ -341,17 -343,23 +343,23 @@@ { gchar *utf8_file_name; struct string filename; - ds_init_empty (&filename); utf8_file_name = g_filename_to_utf8 (file_name, -1, NULL, NULL, NULL); - + + if (NULL == utf8_file_name) + return FALSE; + + ds_init_empty (&filename); syntax_gen_string (&filename, ss_cstr (utf8_file_name)); g_free (utf8_file_name); - - syntax = g_strdup_printf ("GET FILE=%s.", ds_cstr (&filename)); - ds_destroy (&filename); + if (encoding && encoding[0]) + syntax = g_strdup_printf ("GET FILE=%s ENCODING='%s'.", + ds_cstr (&filename), encoding); + else + syntax = g_strdup_printf ("GET FILE=%s.", ds_cstr (&filename)); + ds_destroy (&filename); } else { @@@ -369,7 -377,7 +377,7 @@@ else if (name_has_sav_suffix (file_name)) mime_type = "application/x-spss-sav"; - add_most_recent (file_name, mime_type); + add_most_recent (file_name, mime_type, encoding); } return ok; @@@ -443,10 -451,12 +451,12 @@@ sysfile_info (PsppireDataWindow *de struct string filename; gchar *file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); - gchar *utf8_file_name = g_filename_to_utf8 (file_name, -1, NULL, NULL, NULL); + const gchar *encoding = psppire_encoding_selector_get_encoding ( + gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog))); + gchar *syntax; ds_init_empty (&filename); @@@ -455,7 -465,11 +465,11 @@@ g_free (utf8_file_name); - syntax = g_strdup_printf ("SYSFILE INFO %s.", ds_cstr (&filename)); + if (encoding) + syntax = g_strdup_printf ("SYSFILE INFO %s ENCODING='%s'.", + ds_cstr (&filename), encoding); + else + syntax = g_strdup_printf ("SYSFILE INFO %s.", ds_cstr (&filename)); g_free (execute_syntax_string (de, syntax)); } @@@ -684,10 -698,14 +698,10 @@@ static voi fonts_activate (PsppireDataWindow *de) { GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (de)); - PangoFontDescription *current_font; - gchar *font_name; - GtkWidget *dialog = - gtk_font_selection_dialog_new (_("Font Selection")); - - - current_font = GTK_WIDGET(de->data_editor)->style->font_desc; - font_name = pango_font_description_to_string (current_font); + GtkWidget *dialog = gtk_font_selection_dialog_new (_("Font Selection")); + GtkStyle *style = gtk_widget_get_style (GTK_WIDGET(de->data_editor)); + PangoFontDescription *current_font = style->font_desc; + gchar *font_name = pango_font_description_to_string (current_font); gtk_font_selection_dialog_set_font_name (GTK_FONT_SELECTION_DIALOG (dialog), font_name); @@@ -749,7 -767,7 +763,7 @@@ on_recent_data_select (GtkMenuShell *me g_free (uri); - open_data_window (window, file, NULL); + open_data_window (window, file, NULL, NULL); g_free (file); } @@@ -812,7 -830,7 +826,7 @@@ on_recent_files_select (GtkMenuShell *m free (encoding); - if ( psppire_window_load (PSPPIRE_WINDOW (se), file, NULL) ) + if ( psppire_window_load (PSPPIRE_WINDOW (se), file, encoding, NULL) ) gtk_widget_show (se); else gtk_widget_destroy (se); @@@ -902,14 -920,12 +916,14 @@@ enable_save (PsppireDataWindow *dw static void psppire_data_window_init (PsppireDataWindow *de) { + GtkWidget *w ; de->builder = builder_new ("data-editor.ui"); de->ui_manager = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER)); - PSPPIRE_WINDOW (de)->menu = - GTK_MENU_SHELL (gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/windows/windows_minimise_all")->parent); + w = gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/windows/windows_minimise_all"); + + PSPPIRE_WINDOW (de)->menu = GTK_MENU_SHELL (gtk_widget_get_parent (w)); de->uim = NULL; de->merge_id = 0; @@@ -1357,7 -1373,8 +1371,8 @@@ create_data_window (void } void - open_data_window (PsppireWindow *victim, const char *file_name, gpointer hint) + open_data_window (PsppireWindow *victim, const char *file_name, + const char *encoding, gpointer hint) { GtkWidget *window; @@@ -1370,7 -1387,7 +1385,7 @@@ else window = psppire_data_window_new (NULL); - psppire_window_load (PSPPIRE_WINDOW (window), file_name, hint); + psppire_window_load (PSPPIRE_WINDOW (window), file_name, encoding, hint); gtk_widget_show_all (window); } diff --combined src/ui/gui/psppire-output-view.c index 0000000000,d6e75f32f5..9800c2ba2d mode 000000,100644..100644 --- a/src/ui/gui/psppire-output-view.c +++ b/src/ui/gui/psppire-output-view.c @@@ -1,0 -1,966 +1,963 @@@ + /* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008-2015 Free Software Foundation. + + 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 3 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, see . */ + + #include + + #include "ui/gui/psppire-output-view.h" + + #include + #include + + #include "libpspp/assertion.h" + #include "libpspp/string-map.h" + #include "output/cairo.h" + #include "output/driver-provider.h" + #include "output/driver.h" + #include "output/chart-item.h" + #include "output/message-item.h" + #include "output/output-item.h" + #include "output/table-item.h" + #include "output/text-item.h" + + #include "gl/c-xvasprintf.h" + #include "gl/minmax.h" + #include "gl/tmpdir.h" + #include "gl/xalloc.h" + + #include + #define _(msgid) gettext (msgid) + + struct output_view_item + { + struct output_item *item; + GtkWidget *drawing_area; + }; + + struct psppire_output_view + { + struct xr_driver *xr; + int font_height; + + GtkLayout *output; + int render_width; + int max_width; + int y; + + struct string_map render_opts; + GtkTreeView *overview; + GtkTreeIter cur_command; + bool in_command; + + GtkWidget *toplevel; + + struct output_view_item *items; + size_t n_items, allocated_items; + + /* Variables pertaining to printing */ + GtkPrintSettings *print_settings; + struct xr_driver *print_xrd; + int print_item; + int print_n_pages; + gboolean paginated; + }; + + enum + { + COL_NAME, /* Table name. */ + COL_ADDR, /* Pointer to the table */ + COL_Y, /* Y position of top of name. */ + N_COLS + }; + + static void on_dwgarea_realize (GtkWidget *widget, gpointer data); + + static gboolean -expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data) ++draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data) + { + struct psppire_output_view *view = data; + struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering"); - cairo_t *cr = gdk_cairo_create (widget->window); + + const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output)); + + PangoFontDescription *font_desc; + char *font_name; + + gchar *fgc = + gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]); + + string_map_replace (&view->render_opts, "foreground-color", fgc); + + free (fgc); + + /* Use GTK+ default font as proportional font. */ + font_name = pango_font_description_to_string (style->font_desc); + string_map_replace (&view->render_opts, "prop-font", font_name); + g_free (font_name); + + /* Derived emphasized font from proportional font. */ + font_desc = pango_font_description_copy (style->font_desc); + pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC); + font_name = pango_font_description_to_string (font_desc); + string_map_replace (&view->render_opts, "emph-font", font_name); + g_free (font_name); + pango_font_description_free (font_desc); + + xr_rendering_apply_options (r, &view->render_opts); + - xr_rendering_draw (r, cr, event->area.x, event->area.y, - event->area.width, event->area.height); - cairo_destroy (cr); ++ xr_rendering_draw_all (r, cr); + + return TRUE; + } + + static void + free_rendering (gpointer rendering_) + { + struct xr_rendering *rendering = rendering_; + xr_rendering_destroy (rendering); + } + + static void + create_xr (struct psppire_output_view *view) + { + const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output)); + struct text_item *text_item; + PangoFontDescription *font_desc; + struct xr_rendering *r; + char *font_name; + int font_width; + cairo_t *cr; + gchar *fgc; + - cr = gdk_cairo_create (GTK_WIDGET (view->output)->window); ++ cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (view->output))); + + /* Set the widget's text color as the foreground color for the output driver */ + fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]); + + string_map_insert (&view->render_opts, "foreground-color", fgc); + g_free (fgc); + + /* Use GTK+ default font as proportional font. */ + font_name = pango_font_description_to_string (style->font_desc); + string_map_insert (&view->render_opts, "prop-font", font_name); + g_free (font_name); + + /* Derived emphasized font from proportional font. */ + font_desc = pango_font_description_copy (style->font_desc); + pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC); + font_name = pango_font_description_to_string (font_desc); + string_map_insert (&view->render_opts, "emph-font", font_name); + g_free (font_name); + pango_font_description_free (font_desc); + + /* Pretend that the "page" has a reasonable width and a very big length, + so that most tables can be conveniently viewed on-screen with vertical + scrolling only. (The length should not be increased very much because + it is already close enough to INT_MAX when expressed as thousands of a + point.) */ + string_map_insert_nocopy (&view->render_opts, xstrdup ("paper-size"), + xasprintf ("%dx1000000pt", view->render_width)); + string_map_insert (&view->render_opts, "left-margin", "0"); + string_map_insert (&view->render_opts, "right-margin", "0"); + string_map_insert (&view->render_opts, "top-margin", "0"); + string_map_insert (&view->render_opts, "bottom-margin", "0"); + + view->xr = xr_driver_create (cr, &view->render_opts); + + text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X"); + r = xr_rendering_create (view->xr, text_item_super (text_item), cr); + xr_rendering_measure (r, &font_width, &view->font_height); + /* xr_rendering_destroy (r); */ + text_item_unref (text_item); + + cairo_destroy (cr); + } + + static void + create_drawing_area (struct psppire_output_view *view, + GtkWidget *drawing_area, struct xr_rendering *r, + int tw, int th) + { + g_object_set_data_full (G_OBJECT (drawing_area), + "rendering", r, free_rendering); + + g_signal_connect (drawing_area, "realize", + G_CALLBACK (on_dwgarea_realize), view); - g_signal_connect (drawing_area, "expose_event", - G_CALLBACK (expose_event_callback), view); ++ g_signal_connect (drawing_area, "draw", ++ G_CALLBACK (draw_callback), view); + + gtk_widget_set_size_request (drawing_area, tw, th); + gtk_layout_put (view->output, drawing_area, 0, view->y); + + gtk_widget_show (drawing_area); + } + + static void + rerender (struct psppire_output_view *view) + { + struct output_view_item *item; + cairo_t *cr; + - if (!view->n_items || !GTK_WIDGET (view->output)->window) ++ if (!view->n_items || !gtk_widget_get_window (GTK_WIDGET (view->output))) + return; + + string_map_clear (&view->render_opts); + xr_driver_destroy (view->xr); + create_xr (view); + - cr = gdk_cairo_create (GTK_WIDGET (view->output)->window); ++ cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (view->output))); + + view->y = 0; + view->max_width = 0; + for (item = view->items; item < &view->items[view->n_items]; item++) + { + struct xr_rendering *r; + int tw, th; + + if (view->y > 0) + view->y += view->font_height / 2; + + r = xr_rendering_create (view->xr, item->item, cr); + if (r == NULL) + { + g_warn_if_reached (); + continue; + } + + xr_rendering_measure (r, &tw, &th); + + if (!item->drawing_area) + { + item->drawing_area = gtk_drawing_area_new (); + create_drawing_area (view, item->drawing_area, r, tw, th); + } + else + { + g_object_set_data_full (G_OBJECT (item->drawing_area), + "rendering", r, free_rendering); + gtk_widget_set_size_request (item->drawing_area, tw, th); + gtk_layout_move (view->output, item->drawing_area, 0, view->y); + } + + if (view->max_width < tw) + view->max_width = tw; + view->y += th; + } + + gtk_layout_set_size (view->output, view->max_width, view->y); + cairo_destroy (cr); + } + + void + psppire_output_view_put (struct psppire_output_view *view, + const struct output_item *item) + { + struct output_view_item *view_item; + GtkWidget *drawing_area; + struct xr_rendering *r; + struct string name; + GtkTreeStore *store; + cairo_t *cr = NULL; + GtkTreePath *path; + GtkTreeIter iter; + int tw, th; + + if (is_text_item (item)) + { + const struct text_item *text_item = to_text_item (item); + enum text_item_type type = text_item_get_type (text_item); + const char *text = text_item_get_text (text_item); + + if (type == TEXT_ITEM_COMMAND_CLOSE) + { + view->in_command = false; + return; + } + else if (text[0] == '\0') + return; + } + + if (view->n_items >= view->allocated_items) + view->items = x2nrealloc (view->items, &view->allocated_items, + sizeof *view->items); + view_item = &view->items[view->n_items++]; + view_item->item = output_item_ref (item); + view_item->drawing_area = NULL; + - if (GTK_WIDGET (view->output)->window) ++ if (gtk_widget_get_window (GTK_WIDGET (view->output))) + { + view_item->drawing_area = drawing_area = gtk_drawing_area_new (); + - cr = gdk_cairo_create (GTK_WIDGET (view->output)->window); ++ cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (view->output))); + if (view->xr == NULL) + create_xr (view); + + if (view->y > 0) + view->y += view->font_height / 2; + + r = xr_rendering_create (view->xr, item, cr); + if (r == NULL) + goto done; + + xr_rendering_measure (r, &tw, &th); + + create_drawing_area (view, drawing_area, r, tw, th); + } + else + tw = th = 0; + + if (view->overview + && (!is_text_item (item) + || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX + || !view->in_command)) + { + store = GTK_TREE_STORE (gtk_tree_view_get_model (view->overview)); + + ds_init_empty (&name); + if (is_text_item (item) + && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN) + { + gtk_tree_store_append (store, &iter, NULL); + view->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */ + view->in_command = true; + } + else + { + GtkTreeIter *p = view->in_command ? &view->cur_command : NULL; + gtk_tree_store_append (store, &iter, p); + } + + ds_clear (&name); + if (is_text_item (item)) + ds_put_cstr (&name, text_item_get_text (to_text_item (item))); + else if (is_message_item (item)) + { + const struct message_item *msg_item = to_message_item (item); + const struct msg *msg = message_item_get_msg (msg_item); + ds_put_format (&name, "%s: %s", _("Message"), + msg_severity_to_string (msg->severity)); + } + else if (is_table_item (item)) + { + const char *title = table_item_get_title (to_table_item (item)); + if (title != NULL) + ds_put_format (&name, "Table: %s", title); + else + ds_put_cstr (&name, "Table"); + } + else if (is_chart_item (item)) + { + const char *s = chart_item_get_title (to_chart_item (item)); + if (s != NULL) + ds_put_format (&name, "Chart: %s", s); + else + ds_put_cstr (&name, "Chart"); + } + gtk_tree_store_set (store, &iter, + COL_NAME, ds_cstr (&name), + COL_ADDR, item, + COL_Y, view->y, + -1); + ds_destroy (&name); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_expand_row (view->overview, path, TRUE); + gtk_tree_path_free (path); + } + + if (view->max_width < tw) + view->max_width = tw; + view->y += th; + + gtk_layout_set_size (view->output, view->max_width, view->y); + + done: + cairo_destroy (cr); + } + + static void + on_row_activate (GtkTreeView *overview, + GtkTreePath *path, + GtkTreeViewColumn *column, + struct psppire_output_view *view) + { + GtkTreeModel *model; + GtkTreeIter iter; + GtkAdjustment *vadj; + GValue value = {0}; + double y, min, max; + + model = gtk_tree_view_get_model (overview); + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + gtk_tree_model_get_value (model, &iter, COL_Y, &value); + y = g_value_get_long (&value); + g_value_unset (&value); + + vadj = gtk_layout_get_vadjustment (view->output); - min = vadj->lower; - max = vadj->upper - vadj->page_size; ++ min = gtk_adjustment_get_lower (vadj); ++ max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj); + if (y < min) + y = min; + else if (y > max) + y = max; + gtk_adjustment_set_value (vadj, y); + } + + static void + copy_base_to_bg (GtkWidget *dest, GtkWidget *src) + { + int i; + for (i = 0; i < 5; ++i) + { + gtk_widget_modify_bg (dest, i, >k_widget_get_style (src)->base[i]); + gtk_widget_modify_fg (dest, i, >k_widget_get_style (src)->text[i]); + } + } + + /* Copy the base style from the parent widget to the container and all its + children. We do this because the container's primary purpose is to display + text. This way psppire appears to follow the chosen gnome theme. */ + static void + on_style_set (GtkWidget *toplevel, GtkStyle *prev, + struct psppire_output_view *view) + { + copy_base_to_bg (GTK_WIDGET (view->output), toplevel); + gtk_container_foreach (GTK_CONTAINER (view->output), + (GtkCallback) copy_base_to_bg, view->output); + } + + static void + on_dwgarea_realize (GtkWidget *dwg_area, gpointer data) + { + copy_base_to_bg (dwg_area, gtk_widget_get_toplevel (dwg_area)); + } + + enum { + SELECT_FMT_NULL, + SELECT_FMT_TEXT, + SELECT_FMT_UTF8, + SELECT_FMT_HTML, + SELECT_FMT_ODT + }; + + /* GNU Hurd doesn't have PATH_MAX. Use a fallback. + Temporary directory names are usually not that long. */ + #ifndef PATH_MAX + # define PATH_MAX 1024 + #endif + + static void + clipboard_get_cb (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer data) + { + struct psppire_output_view *view = data; + + gsize length; + gchar *text = NULL; + struct output_driver *driver = NULL; + char dirname[PATH_MAX], *filename; + struct string_map options; + + GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview); + GtkTreeModel *model = gtk_tree_view_get_model (view->overview); + + GList *rows = gtk_tree_selection_get_selected_rows (sel, &model); + GList *n = rows; + + if ( n == NULL) + return; + + if (path_search (dirname, sizeof dirname, NULL, NULL, true) + || mkdtemp (dirname) == NULL) + { + msg_error (errno, _("failed to create temporary directory during clipboard operation")); + return; + } + filename = xasprintf ("%s/clip.tmp", dirname); + + string_map_init (&options); + string_map_insert (&options, "output-file", filename); + + switch (info) + { + case SELECT_FMT_UTF8: + string_map_insert (&options, "box", "unicode"); + /* fall-through */ + + case SELECT_FMT_TEXT: + string_map_insert (&options, "format", "txt"); + break; + + case SELECT_FMT_HTML: + string_map_insert (&options, "format", "html"); + string_map_insert (&options, "borders", "false"); + string_map_insert (&options, "css", "false"); + break; + + case SELECT_FMT_ODT: + string_map_insert (&options, "format", "odt"); + break; + + default: + g_warning ("unsupported clip target\n"); + goto finish; + break; + } + + driver = output_driver_create (&options); + if (driver == NULL) + goto finish; + + while (n) + { + GtkTreePath *path = n->data ; + GtkTreeIter iter; + struct output_item *item ; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1); + + driver->class->submit (driver, item); + + n = n->next; + } + + if ( driver->class->flush) + driver->class->flush (driver); + + + /* Some drivers (eg: the odt one) don't write anything until they + are closed */ + output_driver_destroy (driver); + driver = NULL; + + if ( g_file_get_contents (filename, &text, &length, NULL) ) + { - gtk_selection_data_set (selection_data, selection_data->target, ++ gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), + 8, + (const guchar *) text, length); + } + + finish: + + if (driver != NULL) + output_driver_destroy (driver); + + g_free (text); + + unlink (filename); + free (filename); + rmdir (dirname); + + g_list_free (rows); + } + + static void + clipboard_clear_cb (GtkClipboard *clipboard, + gpointer data) + { + } + + static const GtkTargetEntry targets[] = { + + { "STRING", 0, SELECT_FMT_TEXT }, + { "TEXT", 0, SELECT_FMT_TEXT }, + { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT }, + { "text/plain", 0, SELECT_FMT_TEXT }, + + { "UTF8_STRING", 0, SELECT_FMT_UTF8 }, + { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 }, + + { "text/html", 0, SELECT_FMT_HTML }, + + { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT } + }; + + static void + on_copy (struct psppire_output_view *view) + { + GtkWidget *widget = GTK_WIDGET (view->overview); + GtkClipboard *cb = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD); + + if (!gtk_clipboard_set_with_data (cb, targets, G_N_ELEMENTS (targets), + clipboard_get_cb, clipboard_clear_cb, + view)) + clipboard_clear_cb (cb, view); + } + + static void + on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action) + { + /* The Copy action is available only if there is something selected */ + gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0); + } + + static void + on_select_all (struct psppire_output_view *view) + { + GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview); + gtk_tree_view_expand_all (view->overview); + gtk_tree_selection_select_all (sel); + } + + static void + on_size_allocate (GtkWidget *widget, + GdkRectangle *allocation, + struct psppire_output_view *view) + { + int new_render_width = MAX (300, allocation->width); + if (view->render_width != new_render_width) + { + view->render_width = new_render_width; + rerender (view); + } + } + + struct psppire_output_view * + psppire_output_view_new (GtkLayout *output, GtkTreeView *overview, + GtkAction *copy_action, GtkAction *select_all_action) + { + struct psppire_output_view *view; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeModel *model; + + view = xmalloc (sizeof *view); + view->xr = NULL; + view->font_height = 0; + view->output = output; + view->render_width = 0; + view->max_width = 0; + view->y = 0; + string_map_init (&view->render_opts); + view->overview = overview; + memset (&view->cur_command, 0, sizeof view->cur_command); + view->in_command = false; + view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output)); + view->items = NULL; + view->n_items = view->allocated_items = 0; + view->print_settings = NULL; + view->print_xrd = NULL; + view->print_item = 0; + view->print_n_pages = 0; + view->paginated = FALSE; + + g_signal_connect (view->toplevel, "style-set", G_CALLBACK (on_style_set), view); + + g_signal_connect (output, "size-allocate", G_CALLBACK (on_size_allocate), view); + + if (overview) + { + model = GTK_TREE_MODEL (gtk_tree_store_new ( + N_COLS, + G_TYPE_STRING, /* COL_NAME */ + G_TYPE_POINTER, /* COL_ADDR */ + G_TYPE_LONG)); /* COL_Y */ + gtk_tree_view_set_model (overview, model); + g_object_unref (model); + + sel = gtk_tree_view_get_selection (overview); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE); + g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), + copy_action); + + column = gtk_tree_view_column_new (); + gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column); + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_add_attribute (column, renderer, "text", COL_NAME); + + g_signal_connect (GTK_TREE_VIEW (overview), + "row-activated", G_CALLBACK (on_row_activate), view); + + gtk_action_set_sensitive (copy_action, FALSE); + g_signal_connect_swapped (copy_action, "activate", + G_CALLBACK (on_copy), view); + g_signal_connect_swapped (select_all_action, "activate", + G_CALLBACK (on_select_all), view); + } + + return view; + } + + void + psppire_output_view_destroy (struct psppire_output_view *view) + { + size_t i; + + if (!view) + return; + + g_signal_handlers_disconnect_by_func (view->toplevel, + G_CALLBACK (on_style_set), view); + + string_map_destroy (&view->render_opts); + + for (i = 0; i < view->n_items; i++) + output_item_unref (view->items[i].item); + free (view->items); + view->items = NULL; + view->n_items = view->allocated_items = 0; + + if (view->print_settings != NULL) + g_object_unref (view->print_settings); + + xr_driver_destroy (view->xr); + + free (view); + } + + void + psppire_output_view_clear (struct psppire_output_view *view) + { + size_t i; + + view->max_width = 0; + view->y = 0; + + for (i = 0; i < view->n_items; i++) + { + gtk_container_remove (GTK_CONTAINER (view->output), + view->items[i].drawing_area); + output_item_unref (view->items[i].item); + } + free (view->items); + view->items = NULL; + view->n_items = view->allocated_items = 0; + } + + /* Export. */ + + void + psppire_output_view_export (struct psppire_output_view *view, + struct string_map *options) + { + struct output_driver *driver; + + driver = output_driver_create (options); + if (driver) + { + size_t i; + + for (i = 0; i < view->n_items; i++) + driver->class->submit (driver, view->items[i].item); + output_driver_destroy (driver); + } + } + + /* Print. */ + + static cairo_t * + get_cairo_context_from_print_context (GtkPrintContext *context) + { + cairo_t *cr = gtk_print_context_get_cairo_context (context); + + /* + For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72. + Windows returns 600. + */ + double xres = gtk_print_context_get_dpi_x (context); + double yres = gtk_print_context_get_dpi_y (context); + + /* This means that the cairo context now has its dimensions in Points */ + cairo_scale (cr, xres / 72.0, yres / 72.0); + + return cr; + } + + + static void + create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view) + { + struct string_map options; + GtkPageSetup *page_setup; + double width, height; + double left_margin; + double right_margin; + double top_margin; + double bottom_margin; + + page_setup = gtk_print_context_get_page_setup (context); + width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM); + height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM); + left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM); + right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM); + top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM); + bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM); + + string_map_init (&options); + string_map_insert_nocopy (&options, xstrdup ("paper-size"), + c_xasprintf("%.2fx%.2fmm", width, height)); + string_map_insert_nocopy (&options, xstrdup ("left-margin"), + c_xasprintf ("%.2fmm", left_margin)); + string_map_insert_nocopy (&options, xstrdup ("right-margin"), + c_xasprintf ("%.2fmm", right_margin)); + string_map_insert_nocopy (&options, xstrdup ("top-margin"), + c_xasprintf ("%.2fmm", top_margin)); + string_map_insert_nocopy (&options, xstrdup ("bottom-margin"), + c_xasprintf ("%.2fmm", bottom_margin)); + + view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options); + + string_map_destroy (&options); + } + + static gboolean + paginate (GtkPrintOperation *operation, + GtkPrintContext *context, + struct psppire_output_view *view) + { + if (view->paginated) + { + /* Sometimes GTK+ emits this signal again even after pagination is + complete. Don't let that screw up printing. */ + return TRUE; + } + else if ( view->print_item < view->n_items ) + { + xr_driver_output_item (view->print_xrd, + view->items[view->print_item++].item); + while (xr_driver_need_new_page (view->print_xrd)) + { + xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context)); + view->print_n_pages ++; + } + return FALSE; + } + else + { + gtk_print_operation_set_n_pages (operation, view->print_n_pages); + + /* Re-create the driver to do the real printing. */ + xr_driver_destroy (view->print_xrd); + create_xr_print_driver (context, view); + view->print_item = 0; + view->paginated = TRUE; + + return TRUE; + } + } + + static void + begin_print (GtkPrintOperation *operation, + GtkPrintContext *context, + struct psppire_output_view *view) + { + create_xr_print_driver (context, view); + + view->print_item = 0; + view->print_n_pages = 1; + view->paginated = FALSE; + } + + static void + end_print (GtkPrintOperation *operation, + GtkPrintContext *context, + struct psppire_output_view *view) + { + xr_driver_destroy (view->print_xrd); + } + + + static void + draw_page (GtkPrintOperation *operation, + GtkPrintContext *context, + gint page_number, + struct psppire_output_view *view) + { + xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context)); + while (!xr_driver_need_new_page (view->print_xrd) + && view->print_item < view->n_items) + xr_driver_output_item (view->print_xrd, view->items [view->print_item++].item); + } + + + void + psppire_output_view_print (struct psppire_output_view *view, + GtkWindow *parent_window) + { + GtkPrintOperationResult res; + + GtkPrintOperation *print = gtk_print_operation_new (); + + if (view->print_settings != NULL) + gtk_print_operation_set_print_settings (print, view->print_settings); + + g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), view); + g_signal_connect (print, "end_print", G_CALLBACK (end_print), view); + g_signal_connect (print, "paginate", G_CALLBACK (paginate), view); + g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), view); + + res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + parent_window, NULL); + + if (res == GTK_PRINT_OPERATION_RESULT_APPLY) + { + if (view->print_settings != NULL) + g_object_unref (view->print_settings); + view->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print)); + } + + g_object_unref (print); + } + + struct psppire_output_view_driver + { + struct output_driver driver; + struct psppire_output_view *view; + }; + + static struct psppire_output_view_driver * + psppire_output_view_driver_cast (struct output_driver *driver) + { + return UP_CAST (driver, struct psppire_output_view_driver, driver); + } + + static void + psppire_output_view_submit (struct output_driver *this, + const struct output_item *item) + { + struct psppire_output_view_driver *povd = psppire_output_view_driver_cast (this); + + if (is_table_item (item)) + psppire_output_view_put (povd->view, item); + } + + static struct output_driver_class psppire_output_view_driver_class = + { + "PSPPIRE Output View", /* name */ + NULL, /* destroy */ + psppire_output_view_submit, /* submit */ + NULL, /* flush */ + }; + + void + psppire_output_view_register_driver (struct psppire_output_view *view) + { + struct psppire_output_view_driver *povd; + struct output_driver *d; + + povd = xzalloc (sizeof *povd); + povd->view = view; + d = &povd->driver; + output_driver_init (d, &psppire_output_view_driver_class, "PSPPIRE Output View", + SETTINGS_DEVICE_UNFILTERED); + output_driver_register (d); + } diff --combined src/ui/gui/psppire-output-window.c index e4f6c6ec95,695aa77eec..fd6b7b3422 --- a/src/ui/gui/psppire-output-window.c +++ b/src/ui/gui/psppire-output-window.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation 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 @@@ -16,6 -16,8 +16,8 @@@ #include + #include "ui/gui/psppire-output-window.h" + #include #include #include @@@ -36,11 -38,9 +38,9 @@@ #include "output/text-item.h" #include "ui/gui/help-menu.h" #include "ui/gui/builder-wrapper.h" - #include "ui/gui/psppire-output-window.h" + #include "ui/gui/psppire-output-view.h" - #include "gl/tmpdir.h" #include "gl/xalloc.h" - #include "gl/c-xvasprintf.h" #include "helper.h" @@@ -48,20 -48,9 +48,9 @@@ #define _(msgid) gettext (msgid) #define N_(msgid) msgid - enum - { - COL_TITLE, /* Table title. */ - COL_ADDR, /* Pointer to the table */ - COL_Y, /* Y position of top of title. */ - N_COLS - }; - static void psppire_output_window_class_init (PsppireOutputWindowClass *class); static void psppire_output_window_init (PsppireOutputWindow *window); - static void psppire_output_window_style_set (GtkWidget *window, GtkStyle *prev); - - GType psppire_output_window_get_type (void) { @@@ -95,9 -84,6 +84,6 @@@ static GObjectClass *parent_class static void psppire_output_window_finalize (GObject *object) { - string_map_destroy (&PSPPIRE_OUTPUT_WINDOW(object)->render_opts); - - if (G_OBJECT_CLASS (parent_class)->finalize) (*G_OBJECT_CLASS (parent_class)->finalize) (object); } @@@ -106,21 -92,14 +92,14 @@@ static void psppire_output_window_dispose (GObject *obj) { - PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj); - size_t i; + PsppireOutputWindow *window = PSPPIRE_OUTPUT_WINDOW (obj); - if (viewer->dispose_has_run) + if (window->dispose_has_run) return; - viewer->dispose_has_run = TRUE; - for (i = 0; i < viewer->n_items; i++) - output_item_unref (viewer->items[i]); - free (viewer->items); - viewer->items = NULL; - viewer->n_items = viewer->allocated_items = 0; - - if (viewer->print_settings != NULL) - g_object_unref (viewer->print_settings); + window->dispose_has_run = TRUE; + psppire_output_view_destroy (window->view); + window->view = NULL; /* Chain up to the parent class */ G_OBJECT_CLASS (parent_class)->dispose (obj); @@@ -134,20 -113,15 +113,15 @@@ psppire_output_window_class_init (Psppi parent_class = g_type_class_peek_parent (class); object_class->dispose = psppire_output_window_dispose; - GTK_WIDGET_CLASS (object_class)->style_set = psppire_output_window_style_set; object_class->finalize = psppire_output_window_finalize; } - - /* Output driver class. */ struct psppire_output_driver { struct output_driver driver; - PsppireOutputWindow *viewer; - struct xr_driver *xr; - int font_height; + PsppireOutputWindow *window; }; static struct output_driver_class psppire_output_class; @@@ -159,228 -133,34 +133,34 @@@ psppire_output_cast (struct output_driv return UP_CAST (driver, struct psppire_output_driver, driver); } - static void on_dwgarea_realize (GtkWidget *widget, gpointer data); - - static gboolean - draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data) - { - PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data); - struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering"); - const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer)); - - PangoFontDescription *font_desc; - char *font_name; - - gchar *fgc = - gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (widget))]); - - string_map_replace (&viewer->render_opts, "foreground-color", fgc); - - free (fgc); - - /* Use GTK+ default font as proportional font. */ - font_name = pango_font_description_to_string (style->font_desc); - string_map_replace (&viewer->render_opts, "prop-font", font_name); - g_free (font_name); - - /* Derived emphasized font from proportional font. */ - font_desc = pango_font_description_copy (style->font_desc); - pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC); - font_name = pango_font_description_to_string (font_desc); - string_map_replace (&viewer->render_opts, "emph-font", font_name); - g_free (font_name); - pango_font_description_free (font_desc); - - xr_rendering_apply_options (r, &viewer->render_opts); - xr_rendering_draw_all (r, cr); - - return TRUE; - } - - static void psppire_output_submit (struct output_driver *this, const struct output_item *item) { struct psppire_output_driver *pod = psppire_output_cast (this); - PsppireOutputWindow *viewer; - GtkWidget *drawing_area; - struct xr_rendering *r; - struct string title; - GtkTreeStore *store; - GtkTreePath *path; - GtkTreeIter iter; - cairo_t *cr; - int tw, th; + PsppireOutputWindow *window; + bool new; - if (pod->viewer == NULL) + new = pod->window == NULL; + if (new) { - pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ()); - gtk_widget_show_all (GTK_WIDGET (pod->viewer)); - pod->viewer->driver = pod; + pod->window = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ()); + pod->window->driver = pod; } - viewer = pod->viewer; + window = pod->window; - if (viewer->n_items >= viewer->allocated_items) - viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items, - sizeof *viewer->items); - viewer->items[viewer->n_items++] = output_item_ref (item); + psppire_output_view_put (window->view, item); - if (is_text_item (item)) + if (new) { - const struct text_item *text_item = to_text_item (item); - enum text_item_type type = text_item_get_type (text_item); - const char *text = text_item_get_text (text_item); - - if (type == TEXT_ITEM_COMMAND_CLOSE) - { - viewer->in_command = false; - return; - } - else if (text[0] == '\0') - return; - } - - cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (pod->viewer))); - if (pod->xr == NULL) - { - const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer)); - struct text_item *text_item; - PangoFontDescription *font_desc; - char *font_name; - int font_width; - - /* Set the widget's text color as the foreground color for the output driver */ - gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (viewer))]); - - string_map_insert (&pod->viewer->render_opts, "foreground-color", fgc); - g_free (fgc); - - /* Use GTK+ default font as proportional font. */ - font_name = pango_font_description_to_string (style->font_desc); - string_map_insert (&pod->viewer->render_opts, "prop-font", font_name); - g_free (font_name); - - /* Derived emphasized font from proportional font. */ - font_desc = pango_font_description_copy (style->font_desc); - pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC); - font_name = pango_font_description_to_string (font_desc); - string_map_insert (&pod->viewer->render_opts, "emph-font", font_name); - g_free (font_name); - pango_font_description_free (font_desc); - - /* Pretend that the "page" has a reasonable width and a very big length, - so that most tables can be conveniently viewed on-screen with vertical - scrolling only. (The length should not be increased very much because - it is already close enough to INT_MAX when expressed as thousands of a - point.) */ - string_map_insert (&pod->viewer->render_opts, "paper-size", "300x200000mm"); - string_map_insert (&pod->viewer->render_opts, "left-margin", "0"); - string_map_insert (&pod->viewer->render_opts, "right-margin", "0"); - string_map_insert (&pod->viewer->render_opts, "top-margin", "0"); - string_map_insert (&pod->viewer->render_opts, "bottom-margin", "0"); - - pod->xr = xr_driver_create (cr, &pod->viewer->render_opts); - - - text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X"); - r = xr_rendering_create (pod->xr, text_item_super (text_item), cr); - xr_rendering_measure (r, &font_width, &pod->font_height); - /* xr_rendering_destroy (r); */ - text_item_unref (text_item); + /* We could have called this earlier in the previous "if (new)" block, + but doing it here finds, in a plain GTK+ environment, a bug that + otherwise only showed up on an Ubuntu Unity desktop. See bug + #43362. */ + gtk_widget_show_all (GTK_WIDGET (pod->window)); } - else - pod->viewer->y += pod->font_height / 2; - - r = xr_rendering_create (pod->xr, item, cr); - if (r == NULL) - goto done; - - xr_rendering_measure (r, &tw, &th); - - drawing_area = gtk_drawing_area_new (); - - g_object_set_data (G_OBJECT (drawing_area), "rendering", r); - g_signal_connect (drawing_area, "realize", - G_CALLBACK (on_dwgarea_realize), pod->viewer); - - g_signal_connect (drawing_area, "draw", - G_CALLBACK (draw_callback), pod->viewer); - - gtk_widget_set_size_request (drawing_area, tw, th); - gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y); - gtk_widget_show (drawing_area); - - if (!is_text_item (item) - || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX - || !viewer->in_command) - { - store = GTK_TREE_STORE (gtk_tree_view_get_model (viewer->overview)); - - ds_init_empty (&title); - if (is_text_item (item) - && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN) - { - gtk_tree_store_append (store, &iter, NULL); - viewer->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */ - viewer->in_command = true; - } - else - { - GtkTreeIter *p = viewer->in_command ? &viewer->cur_command : NULL; - gtk_tree_store_append (store, &iter, p); - } - - ds_clear (&title); - if (is_text_item (item)) - ds_put_cstr (&title, text_item_get_text (to_text_item (item))); - else if (is_message_item (item)) - { - const struct message_item *msg_item = to_message_item (item); - const struct msg *msg = message_item_get_msg (msg_item); - ds_put_format (&title, "%s: %s", _("Message"), - msg_severity_to_string (msg->severity)); - } - else if (is_table_item (item)) - { - const char *caption = table_item_get_caption (to_table_item (item)); - if (caption != NULL) - ds_put_format (&title, "Table: %s", caption); - else - ds_put_cstr (&title, "Table"); - } - else if (is_chart_item (item)) - { - const char *s = chart_item_get_title (to_chart_item (item)); - if (s != NULL) - ds_put_format (&title, "Chart: %s", s); - else - ds_put_cstr (&title, "Chart"); - } - gtk_tree_store_set (store, &iter, - COL_TITLE, ds_cstr (&title), - COL_ADDR, item, - COL_Y, viewer->y, - -1); - ds_destroy (&title); - - path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); - gtk_tree_view_expand_row (viewer->overview, path, TRUE); - gtk_tree_path_free (path); - } - - if (pod->viewer->max_width < tw) - pod->viewer->max_width = tw; - pod->viewer->y += th; - - gtk_layout_set_size (pod->viewer->output, - pod->viewer->max_width, pod->viewer->y); - - gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE); - - done: - cairo_destroy (cr); + gtk_window_set_urgency_hint (GTK_WINDOW (pod->window), TRUE); } static struct output_driver_class psppire_output_class = @@@ -415,7 -195,7 +195,7 @@@ on_delete (GtkWidget *w, GdkEvent *even gtk_widget_destroy (GTK_WIDGET (ow)); - ow->driver->viewer = NULL; + ow->driver->window = NULL; return FALSE; } @@@ -428,36 -208,6 +208,6 @@@ cancel_urgency (GtkWindow *window, gpo gtk_window_set_urgency_hint (window, FALSE); } - static void - on_row_activate (GtkTreeView *overview, - GtkTreePath *path, - GtkTreeViewColumn *column, - PsppireOutputWindow *window) - { - GtkTreeModel *model; - GtkTreeIter iter; - GtkAdjustment *vadj; - GValue value = {0}; - double y, min, max; - - model = gtk_tree_view_get_model (overview); - if (!gtk_tree_model_get_iter (model, &iter, path)) - return; - - gtk_tree_model_get_value (model, &iter, COL_Y, &value); - y = g_value_get_long (&value); - g_value_unset (&value); - - vadj = gtk_layout_get_vadjustment (window->output); - min = gtk_adjustment_get_lower (vadj); - max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj); - if (y < min) - y = min; - else if (y > max) - y = max; - gtk_adjustment_set_value (vadj, y); - } - static void psppire_output_window_print (PsppireOutputWindow *window); @@@ -465,17 -215,8 +215,8 @@@ static voi export_output (PsppireOutputWindow *window, struct string_map *options, const char *format) { - struct output_driver *driver; - size_t i; - string_map_insert (options, "format", format); - driver = output_driver_create (options); - if (driver == NULL) - return; - - for (i = 0; i < window->n_items; i++) - driver->class->submit (driver, window->items[i]); - output_driver_destroy (driver); + psppire_output_view_export (window->view, options); } @@@ -492,6 -233,7 +233,7 @@@ enu FT_HTML, FT_ODT, FT_TXT, + FT_ASCII, FT_PS, FT_CSV, n_FT @@@ -505,6 -247,7 +247,7 @@@ struct file_types ft[n_FT] = {N_("HTML (*.html)"), ".html"}, {N_("OpenDocument (*.odt)"), ".odt"}, {N_("Text (*.txt)"), ".txt"}, + {N_("Text [plain] (*.txt)"), ".txt"}, {N_("PostScript (*.ps)"), ".ps"}, {N_("Comma-Separated Values (*.csv)"), ".csv"} }; @@@ -703,6 -446,10 +446,10 @@@ psppire_output_window_export (PsppireOu break; case FT_TXT: + string_map_insert (&options, "box", "unicode"); + /* Fall through */ + + case FT_ASCII: string_map_insert (&options, "headers", "false"); string_map_insert (&options, "paginate", "false"); string_map_insert (&options, "squeeze", "true"); @@@ -724,285 -471,23 +471,23 @@@ gtk_widget_destroy (dialog); } - - enum { - SELECT_FMT_NULL, - SELECT_FMT_TEXT, - SELECT_FMT_UTF8, - SELECT_FMT_HTML, - SELECT_FMT_ODT - }; - - /* GNU Hurd doesn't have PATH_MAX. Use a fallback. - Temporary directory names are usually not that long. */ - #ifndef PATH_MAX - # define PATH_MAX 1024 - #endif - - static void - clipboard_get_cb (GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, - gpointer data) - { - PsppireOutputWindow *window = data; - - gsize length; - gchar *text = NULL; - struct output_driver *driver = NULL; - char dirname[PATH_MAX], *filename; - struct string_map options; - - GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview); - GtkTreeModel *model = gtk_tree_view_get_model (window->overview); - - GList *rows = gtk_tree_selection_get_selected_rows (sel, &model); - GList *n = rows; - - if ( n == NULL) - return; - - if (path_search (dirname, sizeof dirname, NULL, NULL, true) - || mkdtemp (dirname) == NULL) - { - msg_error (errno, _("failed to create temporary directory during clipboard operation")); - return; - } - filename = xasprintf ("%s/clip.tmp", dirname); - - string_map_init (&options); - string_map_insert (&options, "output-file", filename); - - switch (info) - { - case SELECT_FMT_UTF8: - string_map_insert (&options, "box", "unicode"); - /* fall-through */ - - case SELECT_FMT_TEXT: - string_map_insert (&options, "format", "txt"); - break; - - case SELECT_FMT_HTML: - string_map_insert (&options, "format", "html"); - string_map_insert (&options, "borders", "false"); - string_map_insert (&options, "css", "false"); - break; - - case SELECT_FMT_ODT: - string_map_insert (&options, "format", "odt"); - break; - - default: - g_warning ("unsupported clip target\n"); - goto finish; - break; - } - - driver = output_driver_create (&options); - if (driver == NULL) - goto finish; - - while (n) - { - GtkTreePath *path = n->data ; - GtkTreeIter iter; - struct output_item *item ; - - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1); - - driver->class->submit (driver, item); - - n = n->next; - } - - if ( driver->class->flush) - driver->class->flush (driver); - - - /* Some drivers (eg: the odt one) don't write anything until they - are closed */ - output_driver_destroy (driver); - driver = NULL; - - if ( g_file_get_contents (filename, &text, &length, NULL) ) - { - gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), - 8, - (const guchar *) text, length); - } - - finish: - - if (driver != NULL) - output_driver_destroy (driver); - - g_free (text); - - unlink (filename); - free (filename); - rmdir (dirname); - - g_list_free (rows); - } - - static void - clipboard_clear_cb (GtkClipboard *clipboard, - gpointer data) - { - } - - static const GtkTargetEntry targets[] = { - - { "STRING", 0, SELECT_FMT_TEXT }, - { "TEXT", 0, SELECT_FMT_TEXT }, - { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT }, - { "text/plain", 0, SELECT_FMT_TEXT }, - - { "UTF8_STRING", 0, SELECT_FMT_UTF8 }, - { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 }, - - { "text/html", 0, SELECT_FMT_HTML }, - - { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT } - }; - - static void - on_copy (PsppireOutputWindow *window) - { - { - GtkClipboard *clipboard = - gtk_widget_get_clipboard (GTK_WIDGET (window), - GDK_SELECTION_CLIPBOARD); - - if (!gtk_clipboard_set_with_data (clipboard, targets, - G_N_ELEMENTS (targets), - clipboard_get_cb, clipboard_clear_cb, - window)) - - clipboard_clear_cb (clipboard, window); - } - } - - static void - on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action) - { - /* The Copy action is available only if there is something selected */ - gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0); - } - - static void - on_select_all (PsppireOutputWindow *window) - { - GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview); - gtk_tree_view_expand_all (window->overview); - gtk_tree_selection_select_all (sel); - } - - - static void - copy_base_to_bg (GtkWidget *dest, GtkWidget *src) - { - int i; - for (i = 0; i < 5; ++i) - { - GdkColor *col = >k_widget_get_style (src)->base[i]; - gtk_widget_modify_bg (dest, i, col); - - col = >k_widget_get_style (src)->text[i]; - gtk_widget_modify_fg (dest, i, col); - } - } - - static void - on_dwgarea_realize (GtkWidget *dwg_area, gpointer data) - { - GtkWidget *viewer = GTK_WIDGET (data); - - copy_base_to_bg (dwg_area, viewer); - } - - - static void - psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev) - { - GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output); - - /* Copy the base style from the parent widget to the container and - all its children. - We do this, because the container's primary purpose is to - display text. This way psppire appears to follow the chosen - gnome theme. - */ - copy_base_to_bg (op, w); - gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg, - PSPPIRE_OUTPUT_WINDOW (w)->output); - - /* Chain up to the parent class */ - GTK_WIDGET_CLASS (parent_class)->style_set (w, prev); - } - static void psppire_output_window_init (PsppireOutputWindow *window) { - GtkTreeViewColumn *column; - GtkCellRenderer *renderer; GtkBuilder *xml; - GtkAction *copy_action; - GtkAction *select_all_action; - GtkTreeSelection *sel; - GtkTreeModel *model; - - string_map_init (&window->render_opts); - - xml = builder_new ("output-viewer.ui"); - - copy_action = get_action_assert (xml, "edit_copy"); - select_all_action = get_action_assert (xml, "edit_select-all"); - gtk_action_set_sensitive (copy_action, FALSE); - - g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window); - - g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window); + xml = builder_new ("output-window.ui"); gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window)); - window->output = GTK_LAYOUT (get_widget_assert (xml, "output")); - window->y = 0; - window->print_settings = NULL; window->dispose_has_run = FALSE; - window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview")); - - sel = gtk_tree_view_get_selection (window->overview); - - gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE); - - g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action); - - model = GTK_TREE_MODEL (gtk_tree_store_new ( - N_COLS, - G_TYPE_STRING, /* COL_TITLE */ - G_TYPE_POINTER, /* COL_ADDR */ - G_TYPE_LONG)); /* COL_Y */ - gtk_tree_view_set_model (window->overview, model); - g_object_unref (model); + window->view = psppire_output_view_new ( + GTK_LAYOUT (get_widget_assert (xml, "output")), + GTK_TREE_VIEW (get_widget_assert (xml, "overview")), + get_action_assert (xml, "edit_copy"), + get_action_assert (xml, "edit_select-all")); - window->in_command = false; - - window->items = NULL; - window->n_items = window->allocated_items = 0; - - column = gtk_tree_view_column_new (); - gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column); - renderer = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, renderer, TRUE); - gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE); - - g_signal_connect (GTK_TREE_VIEW (window->overview), - "row-activated", G_CALLBACK (on_row_activate), window); connect_help (xml); @@@ -1017,14 -502,11 +502,14 @@@ NULL); { + GtkWidget *w; GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER)); merge_help_menu (uim); + w = gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all"); + PSPPIRE_WINDOW (window)->menu = - GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all")->parent); + GTK_MENU_SHELL (gtk_widget_get_parent (w)); } g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate", @@@ -1051,156 -533,8 +536,8 @@@ psppire_output_window_new (void NULL)); } - - - static cairo_t * - get_cairo_context_from_print_context (GtkPrintContext *context) - { - cairo_t *cr = gtk_print_context_get_cairo_context (context); - - /* - For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72. - Windows returns 600. - */ - double xres = gtk_print_context_get_dpi_x (context); - double yres = gtk_print_context_get_dpi_y (context); - - /* This means that the cairo context now has its dimensions in Points */ - cairo_scale (cr, xres / 72.0, yres / 72.0); - - return cr; - } - - - static void - create_xr_print_driver (GtkPrintContext *context, PsppireOutputWindow *window) - { - struct string_map options; - GtkPageSetup *page_setup; - double width, height; - double left_margin; - double right_margin; - double top_margin; - double bottom_margin; - - page_setup = gtk_print_context_get_page_setup (context); - width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM); - height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM); - left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM); - right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM); - top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM); - bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM); - - string_map_init (&options); - string_map_insert_nocopy (&options, xstrdup ("paper-size"), - c_xasprintf("%.2fx%.2fmm", width, height)); - string_map_insert_nocopy (&options, xstrdup ("left-margin"), - c_xasprintf ("%.2fmm", left_margin)); - string_map_insert_nocopy (&options, xstrdup ("right-margin"), - c_xasprintf ("%.2fmm", right_margin)); - string_map_insert_nocopy (&options, xstrdup ("top-margin"), - c_xasprintf ("%.2fmm", top_margin)); - string_map_insert_nocopy (&options, xstrdup ("bottom-margin"), - c_xasprintf ("%.2fmm", bottom_margin)); - - window->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options); - - string_map_destroy (&options); - } - - static gboolean - paginate (GtkPrintOperation *operation, - GtkPrintContext *context, - PsppireOutputWindow *window) - { - if (window->paginated) - { - /* Sometimes GTK+ emits this signal again even after pagination is - complete. Don't let that screw up printing. */ - return TRUE; - } - else if ( window->print_item < window->n_items ) - { - xr_driver_output_item (window->print_xrd, window->items[window->print_item++]); - while (xr_driver_need_new_page (window->print_xrd)) - { - xr_driver_next_page (window->print_xrd, NULL); - window->print_n_pages ++; - } - return FALSE; - } - else - { - gtk_print_operation_set_n_pages (operation, window->print_n_pages); - - /* Re-create the driver to do the real printing. */ - xr_driver_destroy (window->print_xrd); - create_xr_print_driver (context, window); - window->print_item = 0; - window->paginated = TRUE; - - return TRUE; - } - } - - static void - begin_print (GtkPrintOperation *operation, - GtkPrintContext *context, - PsppireOutputWindow *window) - { - create_xr_print_driver (context, window); - - window->print_item = 0; - window->print_n_pages = 1; - window->paginated = FALSE; - } - - static void - end_print (GtkPrintOperation *operation, - GtkPrintContext *context, - PsppireOutputWindow *window) - { - xr_driver_destroy (window->print_xrd); - } - - - static void - draw_page (GtkPrintOperation *operation, - GtkPrintContext *context, - gint page_number, - PsppireOutputWindow *window) - { - xr_driver_next_page (window->print_xrd, get_cairo_context_from_print_context (context)); - while (!xr_driver_need_new_page (window->print_xrd) - && window->print_item < window->n_items) - xr_driver_output_item (window->print_xrd, window->items [window->print_item++]); - } - - static void psppire_output_window_print (PsppireOutputWindow *window) { - GtkPrintOperationResult res; - - GtkPrintOperation *print = gtk_print_operation_new (); - - if (window->print_settings != NULL) - gtk_print_operation_set_print_settings (print, window->print_settings); - - g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), window); - g_signal_connect (print, "end_print", G_CALLBACK (end_print), window); - g_signal_connect (print, "paginate", G_CALLBACK (paginate), window); - g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), window); - - res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, - GTK_WINDOW (window), NULL); - - if (res == GTK_PRINT_OPERATION_RESULT_APPLY) - { - if (window->print_settings != NULL) - g_object_unref (window->print_settings); - window->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print)); - } - - g_object_unref (print); + psppire_output_view_print (window->view, GTK_WINDOW (window)); } diff --combined src/ui/gui/psppire-syntax-window.c index 2402b341c1,ca04756069..efe6a40032 --- a/src/ui/gui/psppire-syntax-window.c +++ b/src/ui/gui/psppire-syntax-window.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2014 Free Software Foundation 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 @@@ -296,7 -296,7 +296,7 @@@ clipboard_get_cb (GtkClipboard *cli PsppireSyntaxWindow *sw = data; g_assert (info == SELECT_FMT_TEXT); - gtk_selection_data_set (selection_data, selection_data->target, + gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), 8, (const guchar *) sw->cliptext, strlen (sw->cliptext)); @@@ -369,27 -369,7 +369,6 @@@ on_edit_copy (PsppireSyntaxWindow *sw set_clip (sw, &begin, &end); } - - /* A callback for when the clipboard contents have been received */ - static void - contents_received_callback (GtkClipboard *clipboard, - GtkSelectionData *sd, - gpointer data) - { - PsppireSyntaxWindow *syntax_window = data; - - if ( gtk_selection_data_get_length (sd) < 0 ) - return; - - if ( gtk_selection_data_get_data_type (sd) != gdk_atom_intern ("UTF8_STRING", FALSE)) - return; - - gtk_text_buffer_insert_at_cursor (GTK_TEXT_BUFFER (syntax_window->buffer), - (gchar *) gtk_selection_data_get_data (sd), - gtk_selection_data_get_length (sd)); - - } -- static void on_edit_paste (PsppireSyntaxWindow *sw) { @@@ -397,10 -377,7 +376,7 @@@ GtkClipboard *clipboard = gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD); - gtk_clipboard_request_contents (clipboard, - gdk_atom_intern ("UTF8_STRING", TRUE), - contents_received_callback, - sw); + gtk_text_buffer_paste_clipboard (GTK_TEXT_BUFFER (sw->buffer), clipboard, NULL, TRUE); } @@@ -664,12 -641,13 +640,13 @@@ on_quit (GtkMenuItem *menuitem, gpointe static void - load_and_show_syntax_window (GtkWidget *se, const gchar *filename) + load_and_show_syntax_window (GtkWidget *se, const gchar *filename, + const gchar *encoding) { gboolean ok; gtk_source_buffer_begin_not_undoable_action (PSPPIRE_SYNTAX_WINDOW (se)->buffer); - ok = psppire_window_load (PSPPIRE_WINDOW (se), filename, NULL); + ok = psppire_window_load (PSPPIRE_WINDOW (se), filename, encoding, NULL); gtk_source_buffer_end_not_undoable_action (PSPPIRE_SYNTAX_WINDOW (se)->buffer); if (ok ) @@@ -688,10 -666,10 +665,10 @@@ create_syntax_window (void void open_syntax_window (const char *file_name, const gchar *encoding) { - GtkWidget *se = psppire_syntax_window_new (encoding); + GtkWidget *se = psppire_syntax_window_new (NULL); if ( file_name) - load_and_show_syntax_window (se, file_name); + load_and_show_syntax_window (se, file_name, encoding); } @@@ -892,11 -870,11 +869,11 @@@ psppire_syntax_window_init (PsppireSynt { GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER)); + GtkWidget *w = gtk_ui_manager_get_widget (uim,"/ui/menubar/windows/windows_minimise_all"); merge_help_menu (uim); - PSPPIRE_WINDOW (window)->menu = - GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows/windows_minimise_all")->parent); + PSPPIRE_WINDOW (window)->menu = GTK_MENU_SHELL (gtk_widget_get_parent (w)); } g_object_unref (xml); @@@ -944,7 -922,8 +921,8 @@@ error_dialog (GtkWindow *w, const gcha Loads the buffer from the file called FILENAME */ gboolean - syntax_load (PsppireWindow *window, const gchar *filename, gpointer not_used) + syntax_load (PsppireWindow *window, const gchar *filename, + const gchar *encoding, gpointer not_used) { GError *err = NULL; gchar *text_locale = NULL; @@@ -954,8 -933,6 +932,6 @@@ GtkTextIter iter; PsppireSyntaxWindow *sw = PSPPIRE_SYNTAX_WINDOW (window); GtkTextBuffer *buffer = GTK_TEXT_BUFFER (sw->buffer); - gchar *encoding; - char *mime_type; /* FIXME: What if it's a very big file ? */ if ( ! g_file_get_contents (filename, &text_locale, &len_locale, &err) ) @@@ -965,15 -942,26 +941,26 @@@ return FALSE; } - /* Determine the file's encoding and update sw->encoding. (The ordering is - important here because encoding_guess_whole_file() often returns its - argument instead of a copy of it.) */ - encoding = g_strdup (encoding_guess_whole_file (sw->encoding, text_locale, - len_locale)); - g_free (sw->encoding); - sw->encoding = encoding; + if (!encoding || !encoding[0]) + { + /* Determine the file's encoding and update sw->encoding. (The ordering + is important here because encoding_guess_whole_file() often returns + its argument instead of a copy of it.) */ + char *guessed_encoding; + + guessed_encoding = g_strdup (encoding_guess_whole_file (sw->encoding, + text_locale, + len_locale)); + g_free (sw->encoding); + sw->encoding = guessed_encoding; + } + else + { + g_free (sw->encoding); + sw->encoding = g_strdup (encoding); + } - text_utf8 = recode_substring_pool ("UTF-8", encoding, + text_utf8 = recode_substring_pool ("UTF-8", sw->encoding, ss_buffer (text_locale, len_locale), NULL).string; free (text_locale); @@@ -993,9 -981,7 +980,7 @@@ free (text_utf8); - mime_type = xasprintf ("text/x-spss-syntax; charset=%s", sw->encoding); - add_most_recent (filename, mime_type); - free (mime_type); + add_most_recent (filename, "text/x-spss-syntax", sw->encoding); return TRUE; } diff --combined src/ui/gui/psppire-window.c index 609b88120d,c233eaa65d..f40dbf0563 --- a/src/ui/gui/psppire-window.c +++ b/src/ui/gui/psppire-window.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2009, 2010, 2011, 2013 Free Software Foundation + Copyright (C) 2009, 2010, 2011, 2013, 2014 Free Software Foundation 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 @@@ -15,6 -15,7 +15,7 @@@ along with this program. If not, see . */ #include + #include #include "psppire-window.h" #include "psppire-window-base.h" @@@ -31,6 -32,7 +32,7 @@@ #include "data/any-reader.h" #include "data/file-name.h" #include "data/dataset.h" + #include "libpspp/version.h" #include "helper.h" #include "psppire-data-window.h" @@@ -310,10 -312,8 +312,10 @@@ psppire_window_base_init (PsppireWindow static void menu_toggled (GtkCheckMenuItem *mi, gpointer data) { +#if GTK3_TRANSITION /* Prohibit changes to the state */ mi->active = !mi->active; +#endif } @@@ -357,11 -357,9 +359,11 @@@ insert_menuitem_into_menu (PsppireWindo gtk_menu_shell_append (window->menu, item); +#if GTK3_TRANSITION /* Set the state without emitting a signal */ GTK_CHECK_MENU_ITEM (item)->active = (psppire_window_register_lookup (psppire_window_register_new (), key) == window); +#endif g_hash_table_insert (window->menuitem_table, key, item); } @@@ -664,7 -662,8 +666,8 @@@ psppire_window_save_as (PsppireWindow * static void delete_recent (const char *file_name); gboolean - psppire_window_load (PsppireWindow *w, const gchar *file, gpointer hint) + psppire_window_load (PsppireWindow *w, const gchar *file, + const gchar *encoding, gpointer hint) { gboolean ok; PsppireWindowIface *i = PSPPIRE_WINDOW_MODEL_GET_IFACE (w); @@@ -675,7 -674,7 +678,7 @@@ g_return_val_if_fail (i->load, FALSE); - ok = i->load (w, file, hint); + ok = i->load (w, file, encoding, hint); if ( ok ) { @@@ -689,27 -688,6 +692,6 @@@ } - static void - on_selection_changed (GtkFileChooser *chooser, GtkWidget *encoding_selector) - { - const gchar *sysname; - - const gchar *name = gtk_file_chooser_get_filename (chooser); - - if ( NULL == name ) - return; - - sysname = convert_glib_filename_to_system_filename (name, NULL); - - if ( ! fn_exists (sysname)) - { - gtk_widget_set_sensitive (encoding_selector, FALSE); - return; - } - - gtk_widget_set_sensitive (encoding_selector, ! any_reader_may_open (sysname)); - } - GtkWidget * psppire_window_file_chooser_dialog (PsppireWindow *toplevel) { @@@ -775,17 -753,9 +757,9 @@@ free (dir_name); } - - { - GtkWidget *encoding_selector = psppire_encoding_selector_new ("Auto", true); - - gtk_widget_set_sensitive (encoding_selector, FALSE); - - g_signal_connect (dialog, "selection-changed", G_CALLBACK (on_selection_changed), - encoding_selector); - - gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), encoding_selector); - } + gtk_file_chooser_set_extra_widget ( + GTK_FILE_CHOOSER (dialog), + psppire_encoding_selector_new ("Auto", true)); return dialog; } @@@ -797,6 -767,8 +771,8 @@@ psppire_window_open (PsppireWindow *de { GtkWidget *dialog = psppire_window_file_chooser_dialog (de); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), relocate (examples_dir), NULL); + switch (gtk_dialog_run (GTK_DIALOG (dialog))) { case GTK_RESPONSE_ACCEPT: @@@ -809,9 -781,10 +785,10 @@@ gchar *encoding = psppire_encoding_selector_get_encoding ( gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog))); - if (any_reader_may_open (sysname)) - open_data_window (de, name, NULL); - else + int retval = any_reader_detect (sysname, NULL); + if (retval == 1) + open_data_window (de, name, encoding, NULL); + else if (retval == 0) open_syntax_window (name, encoding); g_free (encoding); @@@ -831,17 -804,24 +808,24 @@@ with associated MIME_TYPE. If it's already in the list, it moves it to the top. */ void - add_most_recent (const char *file_name, const char *mime_type) + add_most_recent (const char *file_name, + const char *mime_type, const char *encoding) { gchar *uri = g_filename_to_uri (file_name, NULL, NULL); - if ( uri ) { GtkRecentData recent_data; + gchar *full_mime_type; + + if (encoding && encoding[0]) + full_mime_type = g_strdup_printf ("%s; charset=%s", + mime_type, encoding); + else + full_mime_type = g_strdup (mime_type); recent_data.display_name = NULL; recent_data.description = NULL; - recent_data.mime_type = CONST_CAST (gchar *, mime_type); + recent_data.mime_type = full_mime_type; recent_data.app_name = CONST_CAST (gchar *, g_get_application_name ()); recent_data.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL); recent_data.groups = NULL; @@@ -851,6 -831,7 +835,7 @@@ uri, &recent_data); g_free (recent_data.app_exec); + g_free (full_mime_type); } g_free (uri); diff --combined src/ui/gui/roc.ui index 92fd89b370,44b23c55b1..e5cefe74a0 --- a/src/ui/gui/roc.ui +++ b/src/ui/gui/roc.ui @@@ -6,6 -6,7 +6,7 @@@ ROC Curve True + ROC True @@@ -181,7 -182,6 +182,7 @@@ + True True True diff --combined src/ui/gui/spreadsheet-test.c index 8c96ec96a8,af0d18c81c..8e4299cd31 --- a/src/ui/gui/spreadsheet-test.c +++ b/src/ui/gui/spreadsheet-test.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2013 Free Software Foundation + Copyright (C) 2013, 2014 Free Software Foundation 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 @@@ -19,6 -19,7 +19,7 @@@ #include + #include #include #include "psppire-spreadsheet-model.h" @@@ -78,7 -79,7 +79,7 @@@ on_clicked (GtkButton *button, struct x const int width = caseproto_get_width (proto, i); const union value *val = case_data_idx (c, i); if (0 == width) - printf ("%g ", val->f); + printf ("%.*g ", DBL_DIG + 1, val->f); else { char *ss = xzalloc (width + 1); @@@ -139,8 -140,8 +140,8 @@@ main (int argc, char *argv[] tm = psppire_spreadsheet_model_new (stuff.sp); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - hbox = gtk_hbox_new (FALSE, 5); - vbox = gtk_vbox_new (FALSE, 5); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); button = gtk_button_new_with_label ("Test reader"); g_signal_connect (button, "clicked", G_CALLBACK (on_clicked), &stuff); diff --combined src/ui/gui/text-data-import-dialog.c index 76c95c4178,b0f524ec6a..cfdbd62811 --- a/src/ui/gui/text-data-import-dialog.c +++ b/src/ui/gui/text-data-import-dialog.c @@@ -1,5 -1,5 +1,5 @@@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation 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 @@@ -122,7 -122,7 +122,7 @@@ text_data_import_assistant (PsppireData case GTK_RESPONSE_APPLY: { gchar *fn = g_path_get_basename (ia->file.file_name); - open_data_window (PSPPIRE_WINDOW (dw), fn, generate_syntax (ia)); + open_data_window (PSPPIRE_WINDOW (dw), fn, NULL, generate_syntax (ia)); g_free (fn); } break; @@@ -658,7 -658,7 +658,7 @@@ push_watch_cursor (struct import_assist GtkWidget *widget = GTK_WIDGET (ia->asst.assistant); GdkDisplay *display = gtk_widget_get_display (widget); GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH); - gdk_window_set_cursor (widget->window, cursor); + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); gdk_cursor_unref (cursor); gdk_display_flush (display); } @@@ -672,6 -672,6 +672,6 @@@ pop_watch_cursor (struct import_assista if (--ia->asst.watch_cursor == 0) { GtkWidget *widget = GTK_WIDGET (ia->asst.assistant); - gdk_window_set_cursor (widget->window, NULL); + gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); } }