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