psppire-output-window: Factor out output view as psppire-output-view.
[pspp] / src / ui / gui / psppire-output-view.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008-2014 Free Software Foundation.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "ui/gui/psppire-output-view.h"
20
21 #include <errno.h>
22 #include <stdbool.h>
23
24 #include "libpspp/assertion.h"
25 #include "libpspp/string-map.h"
26 #include "output/cairo.h"
27 #include "output/driver-provider.h"
28 #include "output/driver.h"
29 #include "output/chart-item.h"
30 #include "output/message-item.h"
31 #include "output/output-item.h"
32 #include "output/table-item.h"
33 #include "output/text-item.h"
34
35 #include "gl/c-xvasprintf.h"
36 #include "gl/tmpdir.h"
37 #include "gl/xalloc.h"
38
39 #include <gettext.h>
40 #define _(msgid) gettext (msgid)
41
42 struct psppire_output_view
43   {
44     struct xr_driver *xr;
45     int font_height;
46
47     GtkLayout *output;
48     int max_width;
49     int y;
50
51     struct string_map render_opts;
52     GtkTreeView *overview;
53     GtkTreeIter cur_command;
54     bool in_command;
55
56     GtkWidget *toplevel;
57
58     struct output_item **items;
59     size_t n_items, allocated_items;
60
61     /* Variables pertaining to printing */
62     GtkPrintSettings *print_settings;
63     struct xr_driver *print_xrd;
64     int print_item;
65     int print_n_pages;
66     gboolean paginated;
67   };
68
69 enum
70   {
71     COL_TITLE,                  /* Table title. */
72     COL_ADDR,                   /* Pointer to the table */
73     COL_Y,                      /* Y position of top of title. */
74     N_COLS
75   };
76
77 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
78
79 static gboolean
80 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
81 {
82   struct psppire_output_view *view = data;
83   struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
84   cairo_t *cr = gdk_cairo_create (widget->window);
85
86   const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
87
88   PangoFontDescription *font_desc;
89   char *font_name;
90
91   gchar *fgc =
92     gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
93
94   string_map_replace (&view->render_opts, "foreground-color", fgc);
95
96   free (fgc);
97
98   /* Use GTK+ default font as proportional font. */
99   font_name = pango_font_description_to_string (style->font_desc);
100   string_map_replace (&view->render_opts, "prop-font", font_name);
101   g_free (font_name);
102
103   /* Derived emphasized font from proportional font. */
104   font_desc = pango_font_description_copy (style->font_desc);
105   pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
106   font_name = pango_font_description_to_string (font_desc);
107   string_map_replace (&view->render_opts, "emph-font", font_name);
108   g_free (font_name);
109   pango_font_description_free (font_desc);
110
111   xr_rendering_apply_options (r, &view->render_opts);
112
113   xr_rendering_draw (r, cr, event->area.x, event->area.y,
114                      event->area.width, event->area.height);
115   cairo_destroy (cr);
116
117   return TRUE;
118 }
119
120 void
121 psppire_output_view_put (struct psppire_output_view *view,
122                          const struct output_item *item)
123 {
124   GtkWidget *drawing_area;
125   struct xr_rendering *r;
126   struct string title;
127   GtkTreeStore *store;
128   GtkTreePath *path;
129   GtkTreeIter iter;
130   cairo_t *cr;
131   int tw, th;
132
133   if (view->n_items >= view->allocated_items)
134     view->items = x2nrealloc (view->items, &view->allocated_items,
135                                 sizeof *view->items);
136   view->items[view->n_items++] = output_item_ref (item);
137
138   if (is_text_item (item))
139     {
140       const struct text_item *text_item = to_text_item (item);
141       enum text_item_type type = text_item_get_type (text_item);
142       const char *text = text_item_get_text (text_item);
143
144       if (type == TEXT_ITEM_COMMAND_CLOSE)
145         {
146           view->in_command = false;
147           return;
148         }
149       else if (text[0] == '\0')
150         return;
151     }
152
153   cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
154   if (view->xr == NULL)
155     {
156       const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
157       struct text_item *text_item;
158       PangoFontDescription *font_desc;
159       char *font_name;
160       int font_width;
161
162       /* Set the widget's text color as the foreground color for the output driver */
163       gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
164
165       string_map_insert (&view->render_opts, "foreground-color", fgc);
166       g_free (fgc);
167
168       /* Use GTK+ default font as proportional font. */
169       font_name = pango_font_description_to_string (style->font_desc);
170       string_map_insert (&view->render_opts, "prop-font", font_name);
171       g_free (font_name);
172
173       /* Derived emphasized font from proportional font. */
174       font_desc = pango_font_description_copy (style->font_desc);
175       pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
176       font_name = pango_font_description_to_string (font_desc);
177       string_map_insert (&view->render_opts, "emph-font", font_name);
178       g_free (font_name);
179       pango_font_description_free (font_desc);
180
181       /* Pretend that the "page" has a reasonable width and a very big length,
182          so that most tables can be conveniently viewed on-screen with vertical
183          scrolling only.  (The length should not be increased very much because
184          it is already close enough to INT_MAX when expressed as thousands of a
185          point.) */
186       string_map_insert (&view->render_opts, "paper-size", "300x200000mm");
187       string_map_insert (&view->render_opts, "left-margin", "0");
188       string_map_insert (&view->render_opts, "right-margin", "0");
189       string_map_insert (&view->render_opts, "top-margin", "0");
190       string_map_insert (&view->render_opts, "bottom-margin", "0");
191
192       view->xr = xr_driver_create (cr, &view->render_opts);
193
194       text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
195       r = xr_rendering_create (view->xr, text_item_super (text_item), cr);
196       xr_rendering_measure (r, &font_width, &view->font_height);
197       /* xr_rendering_destroy (r); */
198       text_item_unref (text_item);
199     }
200
201   if (view->y > 0)
202     view->y += view->font_height / 2;
203
204   r = xr_rendering_create (view->xr, item, cr);
205   if (r == NULL)
206     goto done;
207
208   xr_rendering_measure (r, &tw, &th);
209
210   drawing_area = gtk_drawing_area_new ();
211
212   g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
213   g_signal_connect (drawing_area, "realize",
214                     G_CALLBACK (on_dwgarea_realize), view);
215   g_signal_connect (drawing_area, "expose_event",
216                     G_CALLBACK (expose_event_callback), view);
217
218   gtk_widget_set_size_request (drawing_area, tw, th);
219   gtk_layout_put (view->output, drawing_area, 0, view->y);
220
221   gtk_widget_show (drawing_area);
222
223   if (view->overview
224       && (!is_text_item (item)
225           || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
226           || !view->in_command))
227     {
228       store = GTK_TREE_STORE (gtk_tree_view_get_model (view->overview));
229
230       ds_init_empty (&title);
231       if (is_text_item (item)
232           && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
233         {
234           gtk_tree_store_append (store, &iter, NULL);
235           view->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
236           view->in_command = true;
237         }
238       else
239         {
240           GtkTreeIter *p = view->in_command ? &view->cur_command : NULL;
241           gtk_tree_store_append (store, &iter, p);
242         }
243
244       ds_clear (&title);
245       if (is_text_item (item))
246         ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
247       else if (is_message_item (item))
248         {
249           const struct message_item *msg_item = to_message_item (item);
250           const struct msg *msg = message_item_get_msg (msg_item);
251           ds_put_format (&title, "%s: %s", _("Message"),
252                          msg_severity_to_string (msg->severity));
253         }
254       else if (is_table_item (item))
255         {
256           const char *caption = table_item_get_caption (to_table_item (item));
257           if (caption != NULL)
258             ds_put_format (&title, "Table: %s", caption);
259           else
260             ds_put_cstr (&title, "Table");
261         }
262       else if (is_chart_item (item))
263         {
264           const char *s = chart_item_get_title (to_chart_item (item));
265           if (s != NULL)
266             ds_put_format (&title, "Chart: %s", s);
267           else
268             ds_put_cstr (&title, "Chart");
269         }
270       gtk_tree_store_set (store, &iter,
271                           COL_TITLE, ds_cstr (&title),
272                           COL_ADDR, item,
273                           COL_Y, view->y,
274                           -1);
275       ds_destroy (&title);
276
277       path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
278       gtk_tree_view_expand_row (view->overview, path, TRUE);
279       gtk_tree_path_free (path);
280     }
281
282   if (view->max_width < tw)
283     view->max_width = tw;
284   view->y += th;
285
286   gtk_layout_set_size (view->output, view->max_width, view->y);
287
288 done:
289   cairo_destroy (cr);
290 }
291
292 static void
293 on_row_activate (GtkTreeView *overview,
294                  GtkTreePath *path,
295                  GtkTreeViewColumn *column,
296                  struct psppire_output_view *view)
297 {
298   GtkTreeModel *model;
299   GtkTreeIter iter;
300   GtkAdjustment *vadj;
301   GValue value = {0};
302   double y, min, max;
303
304   model = gtk_tree_view_get_model (overview);
305   if (!gtk_tree_model_get_iter (model, &iter, path))
306     return;
307
308   gtk_tree_model_get_value (model, &iter, COL_Y, &value);
309   y = g_value_get_long (&value);
310   g_value_unset (&value);
311
312   vadj = gtk_layout_get_vadjustment (view->output);
313   min = vadj->lower;
314   max = vadj->upper - vadj->page_size;
315   if (y < min)
316     y = min;
317   else if (y > max)
318     y = max;
319   gtk_adjustment_set_value (vadj, y);
320 }
321
322 static void
323 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
324 {
325   int i;
326   for (i = 0; i < 5; ++i)
327     {
328       gtk_widget_modify_bg (dest, i, &gtk_widget_get_style (src)->base[i]);
329       gtk_widget_modify_fg (dest, i, &gtk_widget_get_style (src)->text[i]);
330     }
331 }
332
333 /* Copy the base style from the parent widget to the container and all its
334    children.  We do this because the container's primary purpose is to display
335    text.  This way psppire appears to follow the chosen gnome theme. */
336 static void
337 on_style_set (GtkWidget *toplevel, GtkStyle *prev,
338               struct psppire_output_view *view)
339 {
340   copy_base_to_bg (GTK_WIDGET (view->output), toplevel);
341   gtk_container_foreach (GTK_CONTAINER (view->output),
342                          (GtkCallback) copy_base_to_bg, view->output);
343 }
344
345 static void
346 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
347 {
348   copy_base_to_bg (dwg_area, gtk_widget_get_toplevel (dwg_area));
349 }
350
351 enum {
352   SELECT_FMT_NULL,
353   SELECT_FMT_TEXT,
354   SELECT_FMT_UTF8,
355   SELECT_FMT_HTML,
356   SELECT_FMT_ODT
357 };
358
359 /* GNU Hurd doesn't have PATH_MAX.  Use a fallback.
360    Temporary directory names are usually not that long.  */
361 #ifndef PATH_MAX
362 # define PATH_MAX 1024
363 #endif
364
365 static void
366 clipboard_get_cb (GtkClipboard     *clipboard,
367                   GtkSelectionData *selection_data,
368                   guint             info,
369                   gpointer          data)
370 {
371   struct psppire_output_view *view = data;
372
373   gsize length;
374   gchar *text = NULL;
375   struct output_driver *driver = NULL;
376   char dirname[PATH_MAX], *filename;
377   struct string_map options;
378
379   GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
380   GtkTreeModel *model = gtk_tree_view_get_model (view->overview);
381
382   GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
383   GList *n = rows;
384
385   if ( n == NULL)
386     return;
387
388   if (path_search (dirname, sizeof dirname, NULL, NULL, true)
389       || mkdtemp (dirname) == NULL)
390     {
391       msg_error (errno, _("failed to create temporary directory during clipboard operation"));
392       return;
393     }
394   filename = xasprintf ("%s/clip.tmp", dirname);
395
396   string_map_init (&options);
397   string_map_insert (&options, "output-file", filename);
398
399   switch (info)
400     {
401     case SELECT_FMT_UTF8:
402       string_map_insert (&options, "box", "unicode");
403       /* fall-through */
404
405     case SELECT_FMT_TEXT:
406       string_map_insert (&options, "format", "txt");
407       break;
408
409     case SELECT_FMT_HTML:
410       string_map_insert (&options, "format", "html");
411       string_map_insert (&options, "borders", "false");
412       string_map_insert (&options, "css", "false");
413       break;
414
415     case SELECT_FMT_ODT:
416       string_map_insert (&options, "format", "odt");
417       break;
418
419     default:
420       g_warning ("unsupported clip target\n");
421       goto finish;
422       break;
423     }
424
425   driver = output_driver_create (&options);
426   if (driver == NULL)
427     goto finish;
428
429   while (n)
430     {
431       GtkTreePath *path = n->data ;
432       GtkTreeIter iter;
433       struct output_item *item ;
434
435       gtk_tree_model_get_iter (model, &iter, path);
436       gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
437
438       driver->class->submit (driver, item);
439
440       n = n->next;
441     }
442
443   if ( driver->class->flush)
444     driver->class->flush (driver);
445
446
447   /* Some drivers (eg: the odt one) don't write anything until they
448      are closed */
449   output_driver_destroy (driver);
450   driver = NULL;
451
452   if ( g_file_get_contents (filename, &text, &length, NULL) )
453     {
454       gtk_selection_data_set (selection_data, selection_data->target,
455                               8,
456                               (const guchar *) text, length);
457     }
458
459  finish:
460
461   if (driver != NULL)
462     output_driver_destroy (driver);
463
464   g_free (text);
465
466   unlink (filename);
467   free (filename);
468   rmdir (dirname);
469
470   g_list_free (rows);
471 }
472
473 static void
474 clipboard_clear_cb (GtkClipboard *clipboard,
475                     gpointer data)
476 {
477 }
478
479 static const GtkTargetEntry targets[] = {
480
481   { "STRING",        0, SELECT_FMT_TEXT },
482   { "TEXT",          0, SELECT_FMT_TEXT },
483   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
484   { "text/plain",    0, SELECT_FMT_TEXT },
485
486   { "UTF8_STRING",   0, SELECT_FMT_UTF8 },
487   { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
488
489   { "text/html",     0, SELECT_FMT_HTML },
490
491   { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
492 };
493
494 static void
495 on_copy (struct psppire_output_view *view)
496 {
497   GtkWidget *widget = GTK_WIDGET (view->overview);
498   GtkClipboard *cb = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
499
500   if (!gtk_clipboard_set_with_data (cb, targets, G_N_ELEMENTS (targets),
501                                     clipboard_get_cb, clipboard_clear_cb,
502                                     view))
503     clipboard_clear_cb (cb, view);
504 }
505
506 static void
507 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
508 {
509   /* The Copy action is available only if there is something selected */
510   gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
511 }
512
513 static void
514 on_select_all (struct psppire_output_view *view)
515 {
516   GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
517   gtk_tree_view_expand_all (view->overview);
518   gtk_tree_selection_select_all (sel);
519 }
520
521 struct psppire_output_view *
522 psppire_output_view_new (GtkLayout *output, GtkTreeView *overview,
523                          GtkAction *copy_action, GtkAction *select_all_action)
524 {
525   struct psppire_output_view *view;
526   GtkTreeViewColumn *column;
527   GtkCellRenderer *renderer;
528   GtkTreeSelection *sel;
529   GtkTreeModel *model;
530
531   view = xmalloc (sizeof *view);
532   view->xr = NULL;
533   view->font_height = 0;
534   view->output = output;
535   view->max_width = 0;
536   view->y = 0;
537   string_map_init (&view->render_opts);
538   view->overview = overview;
539   memset (&view->cur_command, 0, sizeof view->cur_command);
540   view->in_command = false;
541   view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output));
542   view->items = NULL;
543   view->n_items = view->allocated_items = 0;
544   view->print_settings = NULL;
545   view->print_xrd = NULL;
546   view->print_item = 0;
547   view->print_n_pages = 0;
548   view->paginated = FALSE;
549
550   g_signal_connect (view->toplevel, "style-set", G_CALLBACK (on_style_set), view);
551
552
553   if (overview)
554     {
555       model = GTK_TREE_MODEL (gtk_tree_store_new (
556                                 N_COLS,
557                                 G_TYPE_STRING,  /* COL_TITLE */
558                                 G_TYPE_POINTER, /* COL_ADDR */
559                                 G_TYPE_LONG));  /* COL_Y */
560       gtk_tree_view_set_model (overview, model);
561       g_object_unref (model);
562
563       sel = gtk_tree_view_get_selection (overview);
564       gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
565       g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change),
566                         copy_action);
567
568       column = gtk_tree_view_column_new ();
569       gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column);
570       renderer = gtk_cell_renderer_text_new ();
571       gtk_tree_view_column_pack_start (column, renderer, TRUE);
572       gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
573
574       g_signal_connect (GTK_TREE_VIEW (overview),
575                         "row-activated", G_CALLBACK (on_row_activate), view);
576
577       gtk_action_set_sensitive (copy_action, FALSE);
578       g_signal_connect_swapped (copy_action, "activate",
579                                 G_CALLBACK (on_copy), view);
580       g_signal_connect_swapped (select_all_action, "activate",
581                                 G_CALLBACK (on_select_all), view);
582     }
583
584   return view;
585 }
586
587 void
588 psppire_output_view_destroy (struct psppire_output_view *view)
589 {
590   size_t i;
591
592   if (!view)
593     return;
594
595   g_signal_handlers_disconnect_by_func (view->toplevel,
596                                         G_CALLBACK (on_style_set), view);
597
598   string_map_destroy (&view->render_opts);
599
600   for (i = 0; i < view->n_items; i++)
601     output_item_unref (view->items[i]);
602   free (view->items);
603   view->items = NULL;
604   view->n_items = view->allocated_items = 0;
605
606   if (view->print_settings != NULL)
607     g_object_unref (view->print_settings);
608
609   free (view);
610 }
611 \f
612 /* Export. */
613
614 void
615 psppire_output_view_export (struct psppire_output_view *view,
616                             struct string_map *options)
617 {
618   struct output_driver *driver;
619
620   driver = output_driver_create (options);
621   if (driver)
622     {
623       size_t i;
624
625       for (i = 0; i < view->n_items; i++)
626         driver->class->submit (driver, view->items[i]);
627       output_driver_destroy (driver);
628     }
629 }
630 \f
631 /* Print. */
632
633 static cairo_t *
634 get_cairo_context_from_print_context (GtkPrintContext *context)
635 {
636   cairo_t *cr = gtk_print_context_get_cairo_context (context);
637   
638   /*
639     For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
640     Windows returns 600.
641   */
642   double xres = gtk_print_context_get_dpi_x (context);
643   double yres = gtk_print_context_get_dpi_y (context);
644   
645   /* This means that the cairo context now has its dimensions in Points */
646   cairo_scale (cr, xres / 72.0, yres / 72.0);
647   
648   return cr;
649 }
650
651
652 static void
653 create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view)
654 {
655   struct string_map options;
656   GtkPageSetup *page_setup;
657   double width, height;
658   double left_margin;
659   double right_margin;
660   double top_margin;
661   double bottom_margin;
662
663   page_setup = gtk_print_context_get_page_setup (context);
664   width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
665   height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
666   left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
667   right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
668   top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
669   bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
670
671   string_map_init (&options);
672   string_map_insert_nocopy (&options, xstrdup ("paper-size"),
673                             c_xasprintf("%.2fx%.2fmm", width, height));
674   string_map_insert_nocopy (&options, xstrdup ("left-margin"),
675                             c_xasprintf ("%.2fmm", left_margin));
676   string_map_insert_nocopy (&options, xstrdup ("right-margin"),
677                             c_xasprintf ("%.2fmm", right_margin));
678   string_map_insert_nocopy (&options, xstrdup ("top-margin"),
679                             c_xasprintf ("%.2fmm", top_margin));
680   string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
681                             c_xasprintf ("%.2fmm", bottom_margin));
682
683   view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
684
685   string_map_destroy (&options);
686 }
687
688 static gboolean
689 paginate (GtkPrintOperation *operation,
690           GtkPrintContext   *context,
691           struct psppire_output_view *view)
692 {
693   if (view->paginated)
694     {
695       /* Sometimes GTK+ emits this signal again even after pagination is
696          complete.  Don't let that screw up printing. */
697       return TRUE;
698     }
699   else if ( view->print_item < view->n_items )
700     {
701       xr_driver_output_item (view->print_xrd, view->items[view->print_item++]);
702       while (xr_driver_need_new_page (view->print_xrd))
703         {
704           xr_driver_next_page (view->print_xrd, NULL);
705           view->print_n_pages ++;
706         }
707       return FALSE;
708     }
709   else
710     {
711       gtk_print_operation_set_n_pages (operation, view->print_n_pages);
712
713       /* Re-create the driver to do the real printing. */
714       xr_driver_destroy (view->print_xrd);
715       create_xr_print_driver (context, view);
716       view->print_item = 0;
717       view->paginated = TRUE;
718
719       return TRUE;
720     }
721 }
722
723 static void
724 begin_print (GtkPrintOperation *operation,
725              GtkPrintContext   *context,
726              struct psppire_output_view *view)
727 {
728   create_xr_print_driver (context, view);
729
730   view->print_item = 0;
731   view->print_n_pages = 1;
732   view->paginated = FALSE;
733 }
734
735 static void
736 end_print (GtkPrintOperation *operation,
737            GtkPrintContext   *context,
738            struct psppire_output_view *view)
739 {
740   xr_driver_destroy (view->print_xrd);
741 }
742
743
744 static void
745 draw_page (GtkPrintOperation *operation,
746            GtkPrintContext   *context,
747            gint               page_number,
748            struct psppire_output_view *view)
749 {
750   xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context));
751   while (!xr_driver_need_new_page (view->print_xrd)
752          && view->print_item < view->n_items)
753     xr_driver_output_item (view->print_xrd, view->items [view->print_item++]);
754 }
755
756
757 void
758 psppire_output_view_print (struct psppire_output_view *view,
759                            GtkWindow *parent_window)
760 {
761   GtkPrintOperationResult res;
762
763   GtkPrintOperation *print = gtk_print_operation_new ();
764
765   if (view->print_settings != NULL) 
766     gtk_print_operation_set_print_settings (print, view->print_settings);
767
768   g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), view);
769   g_signal_connect (print, "end_print",   G_CALLBACK (end_print),   view);
770   g_signal_connect (print, "paginate",    G_CALLBACK (paginate),    view);
771   g_signal_connect (print, "draw_page",   G_CALLBACK (draw_page),   view);
772
773   res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
774                                  parent_window, NULL);
775
776   if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
777     {
778       if (view->print_settings != NULL)
779         g_object_unref (view->print_settings);
780       view->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
781     }
782
783   g_object_unref (print);
784 }