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