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