PsppireOutputWindow: Draw the entire page instead of just the exposed area.
[pspp] / src / ui / gui / psppire-output-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013  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 <errno.h>
20 #include <gtk/gtk.h>
21 #include <stdlib.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "libpspp/cast.h"
27 #include "libpspp/message.h"
28 #include "libpspp/string-map.h"
29 #include "output/cairo.h"
30 #include "output/chart-item.h"
31 #include "output/driver-provider.h"
32 #include "output/message-item.h"
33 #include "output/output-item.h"
34 #include "output/tab.h"
35 #include "output/table-item.h"
36 #include "output/text-item.h"
37 #include "ui/gui/help-menu.h"
38 #include "ui/gui/builder-wrapper.h"
39 #include "ui/gui/psppire-output-window.h"
40
41 #include "gl/error.h"
42 #include "gl/tmpdir.h"
43 #include "gl/xalloc.h"
44 #include "gl/c-xvasprintf.h"
45
46 #include "helper.h"
47
48 #include <gettext.h>
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
51
52 enum
53   {
54     COL_TITLE,                  /* Table title. */
55     COL_ADDR,                   /* Pointer to the table */
56     COL_Y,                      /* Y position of top of title. */
57     N_COLS
58   };
59
60 static void psppire_output_window_class_init    (PsppireOutputWindowClass *class);
61 static void psppire_output_window_init          (PsppireOutputWindow      *window);
62
63 static void psppire_output_window_style_set (GtkWidget *window, GtkStyle *prev);
64
65
66 GType
67 psppire_output_window_get_type (void)
68 {
69   static GType psppire_output_window_type = 0;
70
71   if (!psppire_output_window_type)
72     {
73       static const GTypeInfo psppire_output_window_info =
74       {
75         sizeof (PsppireOutputWindowClass),
76         (GBaseInitFunc) NULL,
77         (GBaseFinalizeFunc) NULL,
78         (GClassInitFunc)psppire_output_window_class_init,
79         (GClassFinalizeFunc) NULL,
80         NULL,
81         sizeof (PsppireOutputWindow),
82         0,
83         (GInstanceInitFunc) psppire_output_window_init,
84       };
85
86       psppire_output_window_type =
87         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
88                                 &psppire_output_window_info, 0);
89     }
90
91   return psppire_output_window_type;
92 }
93
94 static GObjectClass *parent_class;
95
96 static void
97 psppire_output_window_finalize (GObject *object)
98 {
99   string_map_destroy (&PSPPIRE_OUTPUT_WINDOW(object)->render_opts);
100
101
102   if (G_OBJECT_CLASS (parent_class)->finalize)
103     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
104 }
105
106
107 static void
108 psppire_output_window_dispose (GObject *obj)
109 {
110   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
111   size_t i;
112
113   if (viewer->dispose_has_run) 
114     return;
115
116   viewer->dispose_has_run = TRUE;
117   for (i = 0; i < viewer->n_items; i++)
118     output_item_unref (viewer->items[i]);
119   free (viewer->items);
120   viewer->items = NULL;
121   viewer->n_items = viewer->allocated_items = 0;
122
123   if (viewer->print_settings != NULL)
124     g_object_unref (viewer->print_settings);
125
126   /* Chain up to the parent class */
127   G_OBJECT_CLASS (parent_class)->dispose (obj);
128 }
129
130 static void
131 psppire_output_window_class_init (PsppireOutputWindowClass *class)
132 {
133   GObjectClass *object_class = G_OBJECT_CLASS (class);
134
135   parent_class = g_type_class_peek_parent (class);
136   object_class->dispose = psppire_output_window_dispose;
137   
138   GTK_WIDGET_CLASS (object_class)->style_set = psppire_output_window_style_set;
139   object_class->finalize = psppire_output_window_finalize;
140 }
141
142
143 \f
144 /* Output driver class. */
145
146 struct psppire_output_driver
147   {
148     struct output_driver driver;
149     PsppireOutputWindow *viewer;
150     struct xr_driver *xr;
151     int font_height;
152   };
153
154 static struct output_driver_class psppire_output_class;
155
156 static struct psppire_output_driver *
157 psppire_output_cast (struct output_driver *driver)
158 {
159   assert (driver->class == &psppire_output_class);
160   return UP_CAST (driver, struct psppire_output_driver, driver);
161 }
162
163 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
164
165 static gboolean
166 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
167 {
168   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data);
169   struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
170   cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
171
172   const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
173
174   PangoFontDescription *font_desc;
175   char *font_name;
176   
177   gchar *fgc =
178     gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (widget))]);
179
180   string_map_replace (&viewer->render_opts, "foreground-color", fgc);
181
182   free (fgc);
183
184   /* Use GTK+ default font as proportional font. */
185   font_name = pango_font_description_to_string (style->font_desc);
186   string_map_replace (&viewer->render_opts, "prop-font", font_name);
187   g_free (font_name);
188
189   /* Derived emphasized font from proportional font. */
190   font_desc = pango_font_description_copy (style->font_desc);
191   pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
192   font_name = pango_font_description_to_string (font_desc);
193   string_map_replace (&viewer->render_opts, "emph-font", font_name);
194   g_free (font_name);
195   pango_font_description_free (font_desc);
196
197   xr_rendering_apply_options (r, &viewer->render_opts);
198
199   xr_rendering_draw_all (r, cr);
200
201   cairo_destroy (cr);
202
203   return TRUE;
204 }
205
206
207 static void
208 psppire_output_submit (struct output_driver *this,
209                        const struct output_item *item)
210 {
211   struct psppire_output_driver *pod = psppire_output_cast (this);
212   PsppireOutputWindow *viewer;
213   GtkWidget *drawing_area;
214   struct xr_rendering *r;
215   struct string title;
216   GtkTreeStore *store;
217   GtkTreePath *path;
218   GtkTreeIter iter;
219   cairo_t *cr;
220   int tw, th;
221
222   if (pod->viewer == NULL)
223     {
224       pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
225       gtk_widget_show_all (GTK_WIDGET (pod->viewer));
226       pod->viewer->driver = pod;
227     }
228   viewer = pod->viewer;
229
230   if (viewer->n_items >= viewer->allocated_items)
231     viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items,
232                                 sizeof *viewer->items);
233   viewer->items[viewer->n_items++] = output_item_ref (item);
234
235   if (is_text_item (item))
236     {
237       const struct text_item *text_item = to_text_item (item);
238       enum text_item_type type = text_item_get_type (text_item);
239       const char *text = text_item_get_text (text_item);
240
241       if (type == TEXT_ITEM_COMMAND_CLOSE)
242         {
243           viewer->in_command = false;
244           return;
245         }
246       else if (text[0] == '\0')
247         return;
248     }
249
250   cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (pod->viewer)));
251   if (pod->xr == NULL)
252     {
253       const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
254       struct text_item *text_item;
255       PangoFontDescription *font_desc;
256       char *font_name;
257       int font_width;
258       
259       /* Set the widget's text color as the foreground color for the output driver */
260       gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (viewer))]);
261
262       string_map_insert (&pod->viewer->render_opts, "foreground-color", fgc);
263       g_free (fgc);
264
265       /* Use GTK+ default font as proportional font. */
266       font_name = pango_font_description_to_string (style->font_desc);
267       string_map_insert (&pod->viewer->render_opts, "prop-font", font_name);
268       g_free (font_name);
269
270       /* Derived emphasized font from proportional font. */
271       font_desc = pango_font_description_copy (style->font_desc);
272       pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
273       font_name = pango_font_description_to_string (font_desc);
274       string_map_insert (&pod->viewer->render_opts, "emph-font", font_name);
275       g_free (font_name);
276       pango_font_description_free (font_desc);
277
278       /* Pretend that the "page" has a reasonable width and a very big length,
279          so that most tables can be conveniently viewed on-screen with vertical
280          scrolling only.  (The length should not be increased very much because
281          it is already close enough to INT_MAX when expressed as thousands of a
282          point.) */
283       string_map_insert (&pod->viewer->render_opts, "paper-size", "300x200000mm");
284       string_map_insert (&pod->viewer->render_opts, "left-margin", "0");
285       string_map_insert (&pod->viewer->render_opts, "right-margin", "0");
286       string_map_insert (&pod->viewer->render_opts, "top-margin", "0");
287       string_map_insert (&pod->viewer->render_opts, "bottom-margin", "0");
288
289       pod->xr = xr_driver_create (cr, &pod->viewer->render_opts);
290
291
292       text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
293       r = xr_rendering_create (pod->xr, text_item_super (text_item), cr);
294       xr_rendering_measure (r, &font_width, &pod->font_height);
295       /* xr_rendering_destroy (r); */
296       text_item_unref (text_item);
297     }
298   else
299     pod->viewer->y += pod->font_height / 2;
300
301   r = xr_rendering_create (pod->xr, item, cr);
302   if (r == NULL)
303     goto done;
304
305   xr_rendering_measure (r, &tw, &th);
306
307   drawing_area = gtk_drawing_area_new ();
308
309   g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
310   g_signal_connect (drawing_area, "realize",
311                      G_CALLBACK (on_dwgarea_realize), pod->viewer);
312
313   g_signal_connect (drawing_area, "expose_event",
314                      G_CALLBACK (expose_event_callback), pod->viewer);
315
316   gtk_widget_set_size_request (drawing_area, tw, th);
317   gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y);
318
319   gtk_widget_show (drawing_area);
320
321   if (!is_text_item (item)
322       || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
323       || !viewer->in_command)
324     {
325       store = GTK_TREE_STORE (gtk_tree_view_get_model (viewer->overview));
326
327       ds_init_empty (&title);
328       if (is_text_item (item)
329           && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
330         {
331           gtk_tree_store_append (store, &iter, NULL);
332           viewer->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
333           viewer->in_command = true;
334         }
335       else
336         {
337           GtkTreeIter *p = viewer->in_command ? &viewer->cur_command : NULL;
338           gtk_tree_store_append (store, &iter, p);
339         }
340
341       ds_clear (&title);
342       if (is_text_item (item))
343         ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
344       else if (is_message_item (item))
345         {
346           const struct message_item *msg_item = to_message_item (item);
347           const struct msg *msg = message_item_get_msg (msg_item);
348           ds_put_format (&title, "%s: %s", _("Message"),
349                          msg_severity_to_string (msg->severity));
350         }
351       else if (is_table_item (item))
352         {
353           const char *caption = table_item_get_caption (to_table_item (item));
354           if (caption != NULL)
355             ds_put_format (&title, "Table: %s", caption);
356           else
357             ds_put_cstr (&title, "Table");
358         }
359       else if (is_chart_item (item))
360         {
361           const char *s = chart_item_get_title (to_chart_item (item));
362           if (s != NULL)
363             ds_put_format (&title, "Chart: %s", s);
364           else
365             ds_put_cstr (&title, "Chart");
366         }
367       gtk_tree_store_set (store, &iter,
368                           COL_TITLE, ds_cstr (&title),
369                           COL_ADDR, item, 
370                           COL_Y, viewer->y,
371                           -1);
372       ds_destroy (&title);
373
374       path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
375       gtk_tree_view_expand_row (viewer->overview, path, TRUE);
376       gtk_tree_path_free (path);
377     }
378
379   if (pod->viewer->max_width < tw)
380     pod->viewer->max_width = tw;
381   pod->viewer->y += th;
382
383   gtk_layout_set_size (pod->viewer->output,
384                        pod->viewer->max_width, pod->viewer->y);
385
386   gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE);
387
388 done:
389   cairo_destroy (cr);
390 }
391
392 static struct output_driver_class psppire_output_class =
393   {
394     "PSPPIRE",                  /* name */
395     NULL,                       /* destroy */
396     psppire_output_submit,      /* submit */
397     NULL,                       /* flush */
398   };
399
400 void
401 psppire_output_window_setup (void)
402 {
403   struct psppire_output_driver *pod;
404   struct output_driver *d;
405
406   pod = xzalloc (sizeof *pod);
407   d = &pod->driver;
408   output_driver_init (d, &psppire_output_class, "PSPPIRE",
409                       SETTINGS_DEVICE_UNFILTERED);
410   output_driver_register (d);
411 }
412
413 \f
414
415 /* Callback for the "delete" action (clicking the x on the top right
416    hand corner of the window) */
417 static gboolean
418 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
419 {
420   PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
421
422   gtk_widget_destroy (GTK_WIDGET (ow));
423
424   ow->driver->viewer = NULL;
425
426   return FALSE;
427 }
428
429
430
431 static void
432 cancel_urgency (GtkWindow *window,  gpointer data)
433 {
434   gtk_window_set_urgency_hint (window, FALSE);
435 }
436
437 static void
438 on_row_activate (GtkTreeView *overview,
439                  GtkTreePath *path,
440                  GtkTreeViewColumn *column,
441                  PsppireOutputWindow *window)
442 {
443   GtkTreeModel *model;
444   GtkTreeIter iter;
445   GtkAdjustment *vadj;
446   GValue value = {0};
447   double y, min, max;
448
449   model = gtk_tree_view_get_model (overview);
450   if (!gtk_tree_model_get_iter (model, &iter, path))
451     return;
452
453   gtk_tree_model_get_value (model, &iter, COL_Y, &value);
454   y = g_value_get_long (&value);
455   g_value_unset (&value);
456
457   vadj = gtk_layout_get_vadjustment (window->output);
458   min = gtk_adjustment_get_lower (vadj);
459   max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj);
460   if (y < min)
461     y = min;
462   else if (y > max)
463     y = max;
464   gtk_adjustment_set_value (vadj, y);
465 }
466
467 static void psppire_output_window_print (PsppireOutputWindow *window);
468
469
470 static void
471 export_output (PsppireOutputWindow *window, struct string_map *options,
472                const char *format)
473 {
474   struct output_driver *driver;
475   size_t i;
476
477   string_map_insert (options, "format", format);
478   driver = output_driver_create (options);
479   if (driver == NULL)
480     return;
481
482   for (i = 0; i < window->n_items; i++)
483     driver->class->submit (driver, window->items[i]);
484   output_driver_destroy (driver);
485 }
486
487
488 struct file_types
489 {
490   const gchar *label;
491   const gchar *ext;
492 };
493
494 enum 
495   {
496     FT_AUTO = 0,
497     FT_PDF,
498     FT_HTML,
499     FT_ODT,
500     FT_TXT,
501     FT_PS,
502     FT_CSV,
503     n_FT
504   };
505
506 #define N_EXTENSIONS (n_FT - 1)
507
508 struct file_types ft[n_FT] = {
509   {N_("Infer file type from extension"),  NULL},
510   {N_("PDF (*.pdf)"),                     ".pdf"},
511   {N_("HTML (*.html)"),                   ".html"},
512   {N_("OpenDocument (*.odt)"),            ".odt"},
513   {N_("Text (*.txt)"),                    ".txt"},
514   {N_("PostScript (*.ps)"),               ".ps"},
515   {N_("Comma-Separated Values (*.csv)"),  ".csv"}
516 };
517
518
519 static void
520 on_combo_change (GtkFileChooser *chooser)
521 {
522   gboolean sensitive = FALSE;
523   GtkWidget *combo = gtk_file_chooser_get_extra_widget (chooser);
524
525   int x = 0; 
526   gchar *fn = gtk_file_chooser_get_filename (chooser);
527
528   if (combo &&  gtk_widget_get_realized (combo))
529     x = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
530
531   if (fn == NULL)
532     {
533       sensitive = FALSE;
534     }
535   else
536     {
537       gint i;
538       if ( x != 0 )
539         sensitive = TRUE;
540
541       for (i = 1 ; i < N_EXTENSIONS ; ++i)
542         {
543           if ( g_str_has_suffix (fn, ft[i].ext))
544             {
545               sensitive = TRUE;
546               break;
547             }
548         }
549     }
550
551   g_free (fn);
552
553   gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, sensitive);
554 }
555
556
557 static void
558 on_file_chooser_change (GObject *w, GParamSpec *pspec, gpointer data)
559 {
560
561   GtkFileChooser *chooser = data;
562   const gchar *name = g_param_spec_get_name (pspec);
563
564   if ( ! gtk_widget_get_realized (GTK_WIDGET (chooser)))
565     return;
566
567   /* Ignore this one.  It causes recursion. */
568   if ( 0 == strcmp ("tooltip-text", name))
569     return;
570
571   on_combo_change (chooser);
572 }
573
574
575 /* Recursively descend all the children of W, connecting
576    to their "notify" signal */
577 static void
578 iterate_widgets (GtkWidget *w, gpointer data)
579 {
580   if ( GTK_IS_CONTAINER (w))
581     gtk_container_forall (GTK_CONTAINER (w), iterate_widgets, data);
582   else
583     g_signal_connect (w, "notify",  G_CALLBACK (on_file_chooser_change), data);
584 }
585
586
587
588 static GtkListStore *
589 create_file_type_list (void)
590 {
591   int i;
592   GtkTreeIter iter;
593   GtkListStore *list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
594   
595   for (i = 0 ; i < n_FT ; ++i)
596     {
597       gtk_list_store_append (list, &iter);
598       gtk_list_store_set (list, &iter,
599                           0,  gettext (ft[i].label),
600                           1,  ft[i].ext,
601                           -1);
602     }
603   
604   return list;
605 }
606
607 static void
608 psppire_output_window_export (PsppireOutputWindow *window)
609 {
610   gint response;
611   GtkWidget *combo;
612   GtkListStore *list;
613
614   GtkFileChooser *chooser;
615   
616   GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Export Output"),
617                                         GTK_WINDOW (window),
618                                         GTK_FILE_CHOOSER_ACTION_SAVE,
619                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
620                                         GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
621                                         NULL);
622
623   g_object_set (dialog, "local-only", FALSE, NULL);
624
625   chooser = GTK_FILE_CHOOSER (dialog);
626
627   list = create_file_type_list ();
628
629   combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list));
630
631
632   {
633     /* Create text cell renderer */
634     GtkCellRenderer *cell = gtk_cell_renderer_text_new();
635     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE );
636
637     gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell,  "text", 0);
638   }
639
640   g_signal_connect_swapped (combo, "changed", G_CALLBACK (on_combo_change), chooser);
641
642   gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
643
644   gtk_file_chooser_set_extra_widget (chooser, combo);
645
646   /* This kludge is necessary because there is no signal to tell us
647      when the candidate filename of a GtkFileChooser has changed */
648   gtk_container_forall (GTK_CONTAINER (dialog), iterate_widgets, dialog);
649
650
651   gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
652
653   response = gtk_dialog_run (GTK_DIALOG (dialog));
654
655   if ( response == GTK_RESPONSE_ACCEPT )
656     {
657       gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
658       gchar *filename = gtk_file_chooser_get_filename (chooser);
659       struct string_map options;
660
661       g_return_if_fail (filename);
662
663       if (file_type == FT_AUTO)
664         {
665           /* If the "Infer file type from extension" option was chosen,
666              search for the respective type in the list.
667              (It's a O(n) search, but fortunately n is small). */
668           gint i;
669           for (i = 1 ; i < N_EXTENSIONS ; ++i)
670             {
671               if ( g_str_has_suffix (filename, ft[i].ext))
672                 {
673                   file_type = i;
674                   break;
675                 }
676             }
677         }
678       else if (! g_str_has_suffix (filename, ft[file_type].ext))
679         {
680           /* If an explicit document format was chosen, and if the chosen
681              filename does not already have that particular "extension",
682              then append it.
683            */
684
685           gchar *of = filename;
686           filename = g_strconcat (filename, ft[file_type].ext, NULL);
687           g_free (of);
688         }
689       
690       string_map_init (&options);
691       string_map_insert (&options, "output-file", filename);
692
693       switch (file_type)
694         {
695         case FT_PDF:
696           export_output (window, &options, "pdf");
697           break;
698         case FT_HTML:
699           export_output (window, &options, "html");
700           break;
701         case FT_ODT:
702           export_output (window, &options, "odt");
703           break;
704         case FT_PS:
705           export_output (window, &options, "ps");
706           break;
707         case FT_CSV:
708           export_output (window, &options, "csv");
709           break;
710
711         case FT_TXT:
712           string_map_insert (&options, "headers", "false");
713           string_map_insert (&options, "paginate", "false");
714           string_map_insert (&options, "squeeze", "true");
715           string_map_insert (&options, "emphasis", "none");
716           string_map_insert (&options, "charts", "none");
717           string_map_insert (&options, "top-margin", "0");
718           string_map_insert (&options, "bottom-margin", "0");
719           export_output (window, &options, "txt");
720           break;
721         default:
722           g_assert_not_reached ();
723         }
724
725       string_map_destroy (&options);
726
727       free (filename);
728     }
729
730   gtk_widget_destroy (dialog);
731 }
732
733
734 enum {
735   SELECT_FMT_NULL,
736   SELECT_FMT_TEXT,
737   SELECT_FMT_UTF8,
738   SELECT_FMT_HTML,
739   SELECT_FMT_ODT
740 };
741
742 /* GNU Hurd doesn't have PATH_MAX.  Use a fallback.
743    Temporary directory names are usually not that long.  */
744 #ifndef PATH_MAX
745 # define PATH_MAX 1024
746 #endif
747
748 static void
749 clipboard_get_cb (GtkClipboard     *clipboard,
750                   GtkSelectionData *selection_data,
751                   guint             info,
752                   gpointer          data)
753 {
754   PsppireOutputWindow *window = data;
755
756   gsize length;
757   gchar *text = NULL;
758   struct output_driver *driver = NULL;
759   char dirname[PATH_MAX], *filename;
760   struct string_map options;
761
762   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
763   GtkTreeModel *model = gtk_tree_view_get_model (window->overview);
764
765   GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
766   GList *n = rows;
767
768   if ( n == NULL)
769     return;
770
771   if (path_search (dirname, sizeof dirname, NULL, NULL, true)
772       || mkdtemp (dirname) == NULL)
773     {
774       error (0, errno, _("failed to create temporary directory"));
775       return;
776     }
777   filename = xasprintf ("%s/clip.tmp", dirname);
778
779   string_map_init (&options);
780   string_map_insert (&options, "output-file", filename);
781
782   switch (info)
783     {
784     case SELECT_FMT_UTF8:
785       string_map_insert (&options, "box", "unicode");
786       /* fall-through */
787
788     case SELECT_FMT_TEXT:
789       string_map_insert (&options, "format", "txt");
790       break;
791
792     case SELECT_FMT_HTML:
793       string_map_insert (&options, "format", "html");
794       string_map_insert (&options, "borders", "false");
795       string_map_insert (&options, "css", "false");
796       break;
797
798     case SELECT_FMT_ODT:
799       string_map_insert (&options, "format", "odt");
800       break;
801
802     default:
803       g_warning ("unsupported clip target\n");
804       goto finish;
805       break;
806     }
807
808   driver = output_driver_create (&options);
809   if (driver == NULL)
810     goto finish;
811
812   while (n)
813     {
814       GtkTreePath *path = n->data ; 
815       GtkTreeIter iter;
816       struct output_item *item ;
817
818       gtk_tree_model_get_iter (model, &iter, path);
819       gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
820
821       driver->class->submit (driver, item);
822
823       n = n->next;
824     }
825
826   if ( driver->class->flush)
827     driver->class->flush (driver);
828
829
830   /* Some drivers (eg: the odt one) don't write anything until they
831      are closed */
832   output_driver_destroy (driver);
833   driver = NULL;
834
835   if ( g_file_get_contents (filename, &text, &length, NULL) )
836     {
837       gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
838                               8,
839                               (const guchar *) text, length);
840     }
841
842  finish:
843
844   if (driver != NULL)
845     output_driver_destroy (driver);
846
847   g_free (text);
848
849   unlink (filename);
850   free (filename);
851   rmdir (dirname);
852
853   g_list_free (rows);
854 }
855
856 static void
857 clipboard_clear_cb (GtkClipboard *clipboard,
858                     gpointer data)
859 {
860 }
861
862 static const GtkTargetEntry targets[] = {
863
864   { "STRING",        0, SELECT_FMT_TEXT },
865   { "TEXT",          0, SELECT_FMT_TEXT },
866   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
867   { "text/plain",    0, SELECT_FMT_TEXT },
868
869   { "UTF8_STRING",   0, SELECT_FMT_UTF8 },
870   { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
871
872   { "text/html",     0, SELECT_FMT_HTML },
873
874   { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
875 };
876
877 static void
878 on_copy (PsppireOutputWindow *window)
879 {
880   {
881     GtkClipboard *clipboard =
882       gtk_widget_get_clipboard (GTK_WIDGET (window),
883                                 GDK_SELECTION_CLIPBOARD);
884
885     if (!gtk_clipboard_set_with_data (clipboard, targets,
886                                        G_N_ELEMENTS (targets),
887                                        clipboard_get_cb, clipboard_clear_cb,
888                                       window))
889
890       clipboard_clear_cb (clipboard, window);
891   }
892 }
893
894 static void
895 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
896 {
897   /* The Copy action is available only if there is something selected */
898   gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
899 }
900
901 static void
902 on_select_all (PsppireOutputWindow *window)
903 {
904   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
905   gtk_tree_view_expand_all (window->overview);
906   gtk_tree_selection_select_all (sel);
907 }
908
909
910 static void
911 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
912 {
913   int i;
914   for (i = 0; i < 5; ++i)
915     {
916       GdkColor *col = &gtk_widget_get_style (src)->base[i];
917       gtk_widget_modify_bg (dest, i, col);
918
919       col = &gtk_widget_get_style (src)->text[i];
920       gtk_widget_modify_fg (dest, i, col);
921     }
922 }
923
924 static void 
925 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
926 {
927   GtkWidget *viewer = GTK_WIDGET (data);
928
929   copy_base_to_bg (dwg_area, viewer);
930 }
931
932
933 static void
934 psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev)
935 {
936   GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output);
937
938   /* Copy the base style from the parent widget to the container and 
939      all its children.
940      We do this, because the container's primary purpose is to 
941      display text.  This way psppire appears to follow the chosen
942      gnome theme.
943    */
944   copy_base_to_bg (op, w);
945   gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg,
946                          PSPPIRE_OUTPUT_WINDOW (w)->output);
947
948     /* Chain up to the parent class */
949   GTK_WIDGET_CLASS (parent_class)->style_set (w, prev);
950 }
951
952 static void
953 psppire_output_window_init (PsppireOutputWindow *window)
954 {
955   GtkTreeViewColumn *column;
956   GtkCellRenderer *renderer;
957   GtkBuilder *xml;
958   GtkAction *copy_action;
959   GtkAction *select_all_action;
960   GtkTreeSelection *sel;
961   GtkTreeModel *model;
962
963   string_map_init (&window->render_opts);
964
965   xml = builder_new ("output-viewer.ui");
966
967   copy_action = get_action_assert (xml, "edit_copy");
968   select_all_action = get_action_assert (xml, "edit_select-all");
969
970   gtk_action_set_sensitive (copy_action, FALSE);
971
972   g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window);
973
974   g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window);
975
976   gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
977
978   window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
979   window->y = 0;
980   window->print_settings = NULL;
981   window->dispose_has_run = FALSE;
982
983   window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
984
985   sel = gtk_tree_view_get_selection (window->overview);
986
987   gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
988
989   g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action);
990
991   model = GTK_TREE_MODEL (gtk_tree_store_new (
992                                              N_COLS,
993                                              G_TYPE_STRING,  /* COL_TITLE */
994                                              G_TYPE_POINTER, /* COL_ADDR */
995                                              G_TYPE_LONG));  /* COL_Y */
996   gtk_tree_view_set_model (window->overview, model);
997   g_object_unref (model);
998
999   window->in_command = false;
1000
1001   window->items = NULL;
1002   window->n_items = window->allocated_items = 0;
1003
1004   column = gtk_tree_view_column_new ();
1005   gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
1006   renderer = gtk_cell_renderer_text_new ();
1007   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1008   gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
1009
1010   g_signal_connect (GTK_TREE_VIEW (window->overview),
1011                     "row-activated", G_CALLBACK (on_row_activate), window);
1012
1013   connect_help (xml);
1014
1015   g_signal_connect (window,
1016                     "focus-in-event",
1017                     G_CALLBACK (cancel_urgency),
1018                     NULL);
1019
1020   g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
1021                     "activate",
1022                     G_CALLBACK (psppire_window_minimise_all),
1023                     NULL);
1024
1025   {
1026     GtkWidget *w;
1027     GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
1028     merge_help_menu (uim);
1029
1030     w = gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all");
1031
1032     PSPPIRE_WINDOW (window)->menu =
1033       GTK_MENU_SHELL (gtk_widget_get_parent (w));
1034   }
1035
1036   g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
1037                             G_CALLBACK (psppire_output_window_export), window);
1038
1039
1040   g_signal_connect_swapped (get_action_assert (xml, "file_print"), "activate",
1041                             G_CALLBACK (psppire_output_window_print), window);
1042
1043   g_object_unref (xml);
1044
1045   g_signal_connect (window, "delete-event",
1046                     G_CALLBACK (on_delete), window);
1047 }
1048
1049
1050 GtkWidget*
1051 psppire_output_window_new (void)
1052 {
1053   return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
1054                                    /* TRANSLATORS: This will form a filename.  Please avoid whitespace. */
1055                                    "filename", _("Output"),
1056                                    "description", _("Output Viewer"),
1057                                    NULL));
1058 }
1059
1060 \f
1061
1062 static cairo_t *
1063 get_cairo_context_from_print_context (GtkPrintContext *context)
1064 {
1065   cairo_t *cr = gtk_print_context_get_cairo_context (context);
1066   
1067   /*
1068     For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
1069     Windows returns 600.
1070   */
1071   double xres = gtk_print_context_get_dpi_x (context);
1072   double yres = gtk_print_context_get_dpi_y (context);
1073   
1074   /* This means that the cairo context now has its dimensions in Points */
1075   cairo_scale (cr, xres / 72.0, yres / 72.0);
1076   
1077   return cr;
1078 }
1079
1080
1081 static void
1082 create_xr_print_driver (GtkPrintContext *context, PsppireOutputWindow *window)
1083 {
1084   struct string_map options;
1085   GtkPageSetup *page_setup;
1086   double width, height;
1087   double left_margin;
1088   double right_margin;
1089   double top_margin;
1090   double bottom_margin;
1091
1092   page_setup = gtk_print_context_get_page_setup (context);
1093   width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
1094   height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
1095   left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
1096   right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
1097   top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
1098   bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
1099
1100   string_map_init (&options);
1101   string_map_insert_nocopy (&options, xstrdup ("paper-size"),
1102                             c_xasprintf("%.2fx%.2fmm", width, height));
1103   string_map_insert_nocopy (&options, xstrdup ("left-margin"),
1104                             c_xasprintf ("%.2fmm", left_margin));
1105   string_map_insert_nocopy (&options, xstrdup ("right-margin"),
1106                             c_xasprintf ("%.2fmm", right_margin));
1107   string_map_insert_nocopy (&options, xstrdup ("top-margin"),
1108                             c_xasprintf ("%.2fmm", top_margin));
1109   string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
1110                             c_xasprintf ("%.2fmm", bottom_margin));
1111
1112   window->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
1113
1114   string_map_destroy (&options);
1115 }
1116
1117 static gboolean
1118 paginate (GtkPrintOperation *operation,
1119           GtkPrintContext   *context,
1120           PsppireOutputWindow *window)
1121 {
1122   if (window->paginated)
1123     {
1124       /* Sometimes GTK+ emits this signal again even after pagination is
1125          complete.  Don't let that screw up printing. */
1126       return TRUE;
1127     }
1128   else if ( window->print_item < window->n_items )
1129     {
1130       xr_driver_output_item (window->print_xrd, window->items[window->print_item++]);
1131       while (xr_driver_need_new_page (window->print_xrd))
1132         {
1133           xr_driver_next_page (window->print_xrd, NULL);
1134           window->print_n_pages ++;
1135         }
1136       return FALSE;
1137     }
1138   else
1139     {
1140       gtk_print_operation_set_n_pages (operation, window->print_n_pages);
1141
1142       /* Re-create the driver to do the real printing. */
1143       xr_driver_destroy (window->print_xrd);
1144       create_xr_print_driver (context, window);
1145       window->print_item = 0;
1146       window->paginated = TRUE;
1147
1148       return TRUE;
1149     }
1150 }
1151
1152 static void
1153 begin_print (GtkPrintOperation *operation,
1154              GtkPrintContext   *context,
1155              PsppireOutputWindow *window)
1156 {
1157   create_xr_print_driver (context, window);
1158
1159   window->print_item = 0;
1160   window->print_n_pages = 1;
1161   window->paginated = FALSE;
1162 }
1163
1164 static void
1165 end_print (GtkPrintOperation *operation,
1166            GtkPrintContext   *context,
1167            PsppireOutputWindow *window)
1168 {
1169   xr_driver_destroy (window->print_xrd);
1170 }
1171
1172
1173 static void
1174 draw_page (GtkPrintOperation *operation,
1175            GtkPrintContext   *context,
1176            gint               page_number,
1177            PsppireOutputWindow *window)
1178 {
1179   xr_driver_next_page (window->print_xrd, get_cairo_context_from_print_context (context));
1180   while (!xr_driver_need_new_page (window->print_xrd)
1181          && window->print_item < window->n_items)
1182     xr_driver_output_item (window->print_xrd, window->items [window->print_item++]);
1183 }
1184
1185
1186 static void
1187 psppire_output_window_print (PsppireOutputWindow *window)
1188 {
1189   GtkPrintOperationResult res;
1190
1191   GtkPrintOperation *print = gtk_print_operation_new ();
1192
1193   if (window->print_settings != NULL) 
1194     gtk_print_operation_set_print_settings (print, window->print_settings);
1195
1196   g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), window);
1197   g_signal_connect (print, "end_print",   G_CALLBACK (end_print),   window);
1198   g_signal_connect (print, "paginate",    G_CALLBACK (paginate),    window);
1199   g_signal_connect (print, "draw_page",   G_CALLBACK (draw_page),   window);
1200
1201   res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
1202                                  GTK_WINDOW (window), NULL);
1203
1204   if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
1205     {
1206       if (window->print_settings != NULL)
1207         g_object_unref (window->print_settings);
1208       window->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
1209     }
1210
1211   g_object_unref (print);
1212 }