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