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