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