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