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