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