merge master->gtk3, fixed psppire-output-view.c refactoring; this compiles and runs...
authorFriedrich Beckmann <friedrich.beckmann@gmx.de>
Fri, 1 May 2015 09:04:20 +0000 (11:04 +0200)
committerFriedrich Beckmann <friedrich.beckmann@gmx.de>
Fri, 1 May 2015 09:04:20 +0000 (11:04 +0200)
17 files changed:
1  2 
INSTALL
configure.ac
src/output/cairo.c
src/output/cairo.h
src/output/render.c
src/output/render.h
src/ui/gui/automake.mk
src/ui/gui/main.c
src/ui/gui/pspp-widget-facade.c
src/ui/gui/psppire-data-window.c
src/ui/gui/psppire-output-view.c
src/ui/gui/psppire-output-window.c
src/ui/gui/psppire-syntax-window.c
src/ui/gui/psppire-window.c
src/ui/gui/roc.ui
src/ui/gui/spreadsheet-test.c
src/ui/gui/text-data-import-dialog.c

diff --combined INSTALL
index 3d6d20e9dce77c863bf58c4b63ddff519a24158e,47b965b5cff8c952974b0312f14cb17d5912ef71..5e486ebc87962dd9d6aa71a7955ab3191939a95f
+++ 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 b0f16685b599c12fb827513abc3cba0d58822433,733923fb01d34c45b33e46743128a51099755372..cc1dbba0635a410c799638687fff6f5f72bee3b3
@@@ -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([GTKSOURCEVIEW], [gtksourceview-2.0 >= 2.2], [],
 -    [PSPP_REQUIRED_PREREQ([gtksourceview 2.0 version 2.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)])])
    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 2debb27f4725a344e3a9a53a98059cfa95878e64,4af75caae455f3a5f03f1c687bcfba7efc45c324..a3070dddef3e2e69dc65ff58a3c4c8f1b82d7739
@@@ -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
  #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 <cairo/cairo-ps.h>
  #include <cairo/cairo-svg.h>
  #include <cairo/cairo.h>
+ #include <math.h>
  #include <pango/pango-font.h>
  #include <pango/pango-layout.h>
  #include <pango/pango.h>
@@@ -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 */
  
      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;
    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++)
      {
          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;
    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)
  {
  
        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)
      {
        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;
            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)
  {
  
     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
  }
  \f
  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];
    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];
    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;
  }
  \f
  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 (&params, 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];
  }
  \f
  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))
      {
    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
      {
  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))
      {
  
        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;
    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 3c137681773cbcade850405a85a2dc25509ffb44,cdd55369005364f1a69b303617b38727b713147f..425811b450ff89e37260e52342040609389c1d7a
@@@ -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 c178839a0bfd1d7f79348f371b4d15cead9b3daf,d282a8fce68797e0430fc7a95cf86a5c1dbe35dd..94d89665b73f09cc2da9f4c367226524f47bb379
@@@ -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 <math.h>
  #include <stdio.h>
+ #include <stdint.h>
  #include <stdlib.h>
  #include <string.h>
  
@@@ -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"
  
     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.
         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
      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;
      }
    return NULL;
  }
  \f
+ /* 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 ();
+ }
\f
  /* 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_)
  {
    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;
      }
  
    /* 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++)
          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);
            {
              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));
                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;
                  }
          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));
            }
          }
      }
  
+   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;
+ }
  \f
  /* 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;
      {
        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)
            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;
  
            int d[TABLE_N_AXES];
            d[H] = x;
            d[V] = y;
-           render_rule (page, d);
+           render_rule (page, ofs, d);
            x++;
          }
        else
            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);
          }
  /* 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];
  
    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);
  }
 -\f
 +
\f
  /* 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 *,
                                                 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)
  
  /* 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.
     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];
+ }
\f
+ /* 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;
  }
  \f
  /* 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;
    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]);
      }
    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++)
    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)
        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 bdcc01264f92022536d1df6a7cf4d5fd66e7dc73,c3f852f80a6b811e64c363c78279bea7516b68c8..3e9d8a83adaf04f8c89e830e0d63d51ca555225b
@@@ -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 <stddef.h>
  #include "output/table-provider.h"
  
- struct table;
+ struct table_item;
  
  enum render_line_style
    {
      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
         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. */
  
      /* Width of different kinds of lines. */
      int line_widths[TABLE_N_AXES][RENDER_N_LINES];
-   };
\f
- /* 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];
+   };
 -\f
 -\f
 +
- int render_page_get_size (const struct render_page *, enum table_axis);
- void render_page_draw (const struct render_page *);
\f
  /* 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 b19ea4b3f99748172a59951fe78084fe1d992a94,1b3acc3c5919c5c0823032a2fe385b94da66ca07..e6da60e164e50e4681941ce41c425c12759eb71c
@@@ -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% *<property name="help-page">\([^<]*\)</property>%//*[@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 <config.h>' > $@
-       $(GLIB_GENMARSHAL) --body --prefix=psppire_marshal $? >> $@
+       $(AM_V_GEN)echo '#include <config.h>' > $@
+       $(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 24ac6c7581b550a238dc82be243a48be54553942,28f99a9ff73b6ffbf84d340c30892b6a1bb10fc7..1e59f8431fc5fa8783ca3c910180001b69e08a08
@@@ -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 ();
  
    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;
  }
index e5070bff3ec97f0b5446e6dd9c2bc3c96ae80807,705250bc87d32a476c1aa2681818e304ad5a36ce..be785dc133c4ca69aa0134a95975b398ff0df5a5
@@@ -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);
  
  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,
    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,
    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);
  }
  
index 5a00a64a8a3c1aab1cbde668ab2687cc29046e3c,28ec605205106c9f8e4ee7e3924a66279572ef4f..126980c8bb6f6e4fa1ac2a95dbd1022aa6836f07
@@@ -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;
      {
        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
      {
        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);
  
        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;
  
    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);
  }
  
index 0000000000000000000000000000000000000000,d6e75f32f5b63f7a31a808085843304c9ab747e3..9800c2ba2d1b1b0ed373ec1cbedfdf343d959d9d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,966 +1,963 @@@
 -expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+ /* 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 <http://www.gnu.org/licenses/>. */
+ #include <config.h>
+ #include "ui/gui/psppire-output-view.h"
+ #include <errno.h>
+ #include <stdbool.h>
+ #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 <gettext.h>
+ #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
 -  cairo_t *cr = gdk_cairo_create (widget->window);
++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");
 -  xr_rendering_draw (r, cr, event->area.x, event->area.y,
 -                     event->area.width, event->area.height);
 -  cairo_destroy (cr);
+   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);
 -  cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
++  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;
 -  g_signal_connect (drawing_area, "expose_event",
 -                    G_CALLBACK (expose_event_callback), view);
++  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);
 -  if (!view->n_items || !GTK_WIDGET (view->output)->window)
++  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;
 -  cr = gdk_cairo_create (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);
 -  if (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;
 -      cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
++  if (gtk_widget_get_window (GTK_WIDGET (view->output)))
+     {
+       view_item->drawing_area = drawing_area = gtk_drawing_area_new ();
 -  min = vadj->lower;
 -  max = vadj->upper - vadj->page_size;
++      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);
 -      gtk_selection_data_set (selection_data, selection_data->target,
++  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, &gtk_widget_get_style (src)->base[i]);
+       gtk_widget_modify_fg (dest, i, &gtk_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, 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;
+ }
\f
+ /* 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);
+     }
+ }
\f
+ /* 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);
+ }
\f
+ 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);
+ }
index e4f6c6ec95de0c2d83e68799b3b21899fbdb4cce,695aa77eec81589a6e60319b29fff7fbe9135e47..fd6b7b3422f39569ed2ac30b0c450c55036c8189
@@@ -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 <config.h>
  
+ #include "ui/gui/psppire-output-window.h"
  #include <errno.h>
  #include <gtk/gtk.h>
  #include <stdlib.h>
  #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"
  
  #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);
  }
  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;
  }
  \f
  /* 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");
    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 = &gtk_widget_get_style (src)->base[i];
-       gtk_widget_modify_bg (dest, i, col);
-       col = &gtk_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);
  
                    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));
  }
  
\f
- 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));
  }
index 2402b341c117d0c831c47755aef8f97a0c6ebb47,ca047560696b40ad9d5fc77b8086ac5b72c695f2..efe6a4003207a92df4ad925a57047168b21cb22c
@@@ -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)
  {
    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;
    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) )
        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);
  
    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;
  }
index 609b88120d2219116ec2ff1aa5329fd23d81090b,c233eaa65d53e01a48a1819dd26c2e02134fd28d..f40dbf0563ffe168600058d8d86bb2fba25ff85b
@@@ -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 <http://www.gnu.org/licenses/>. */
  
  #include <config.h>
+ #include <gl/relocatable.h>
  
  #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);
  
    g_return_val_if_fail (i->load, FALSE);
  
-   ok = i->load (w, file, hint);
+   ok = i->load (w, file, encoding, hint);
  
    if ( ok )
      {
  }
  
  
- 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)
  {
        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:
          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);
     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;
                                     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 92fd89b370f8dc5f8579ca013c4d5b45075d3d7b,44b23c55b1ea1261807ee1c52a0093623a5d4aea..e5cefe74a0048900aececa36aa6c1c2ab7912012
@@@ -6,6 -6,7 +6,7 @@@
    <object class="PsppireDialog" id="roc-dialog">
      <property name="title" translatable="yes">ROC Curve</property>
      <property name="modal">True</property>
+     <property name="help-page">ROC</property>
      <child internal-child="hbox">
        <object class="GtkHBox" id="dialog-hbox1">
          <property name="visible">True</property>
                      </child>
                      <child>
                        <object class="PsppireValueEntry" id="entry2">
 +                      <property name="has-entry">True</property>
                          <property name="visible">True</property>
                          <property name="can_focus">True</property>
                        </object>
index 8c96ec96a886607beedc0a010be7837dc9e17ebb,af0d18c81c8f6e4b082ff5092aa684198b529ceb..8e4299cd3159c23d31cb6dfaf633499fb2e730f8
@@@ -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 <config.h>
  
+ #include <float.h>
  #include <gtk/gtk.h>
  
  #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);
index 76c95c41781270bd23ca80e279224af17e4a2d73,b0f524ec6a182adc6beb376747ef6f9afbc63f25..cfdbd628117933d91a52cb64a5d23052a2f991d9
@@@ -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);
      }
  }