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