4bad5b5efbaa6079ed348b2dc1e88c25f6c13429
[pspp] / src / ui / gui / psppire-output-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <errno.h>
20 #include <gtk/gtk.h>
21 #include <stdlib.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "libpspp/cast.h"
27 #include "libpspp/message.h"
28 #include "libpspp/string-map.h"
29 #include "output/cairo.h"
30 #include "output/chart-item.h"
31 #include "output/driver-provider.h"
32 #include "output/message-item.h"
33 #include "output/output-item.h"
34 #include "output/tab.h"
35 #include "output/table-item.h"
36 #include "output/text-item.h"
37 #include "ui/gui/help-menu.h"
38 #include "ui/gui/builder-wrapper.h"
39 #include "ui/gui/psppire-output-window.h"
40
41 #include "gl/error.h"
42 #include "gl/tmpdir.h"
43 #include "gl/xalloc.h"
44 #include "gl/c-xvasprintf.h"
45
46 #include "helper.h"
47
48 #include <gettext.h>
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
51
52 enum
53   {
54     COL_TITLE,                  /* Table title. */
55     COL_ADDR,                   /* Pointer to the table */
56     COL_Y,                      /* Y position of top of title. */
57     N_COLS
58   };
59
60 static void psppire_output_window_class_init    (PsppireOutputWindowClass *class);
61 static void psppire_output_window_init          (PsppireOutputWindow      *window);
62
63 static void psppire_output_window_style_set (GtkWidget *window, GtkStyle *prev);
64
65
66 GType
67 psppire_output_window_get_type (void)
68 {
69   static GType psppire_output_window_type = 0;
70
71   if (!psppire_output_window_type)
72     {
73       static const GTypeInfo psppire_output_window_info =
74       {
75         sizeof (PsppireOutputWindowClass),
76         (GBaseInitFunc) NULL,
77         (GBaseFinalizeFunc) NULL,
78         (GClassInitFunc)psppire_output_window_class_init,
79         (GClassFinalizeFunc) NULL,
80         NULL,
81         sizeof (PsppireOutputWindow),
82         0,
83         (GInstanceInitFunc) psppire_output_window_init,
84       };
85
86       psppire_output_window_type =
87         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
88                                 &psppire_output_window_info, 0);
89     }
90
91   return psppire_output_window_type;
92 }
93
94 static GObjectClass *parent_class;
95
96 static void
97 psppire_output_window_finalize (GObject *object)
98 {
99   string_map_destroy (&PSPPIRE_OUTPUT_WINDOW(object)->render_opts);
100
101
102   if (G_OBJECT_CLASS (parent_class)->finalize)
103     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
104 }
105
106
107 static void
108 psppire_output_window_dispose (GObject *obj)
109 {
110   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
111   size_t i;
112
113   if (viewer->dispose_has_run) 
114     return;
115
116   viewer->dispose_has_run = TRUE;
117   for (i = 0; i < viewer->n_items; i++)
118     output_item_unref (viewer->items[i]);
119   free (viewer->items);
120   viewer->items = NULL;
121   viewer->n_items = viewer->allocated_items = 0;
122
123   if (viewer->print_settings != NULL)
124     g_object_unref (viewer->print_settings);
125
126   /* Chain up to the parent class */
127   G_OBJECT_CLASS (parent_class)->dispose (obj);
128 }
129
130 static void
131 psppire_output_window_class_init (PsppireOutputWindowClass *class)
132 {
133   GObjectClass *object_class = G_OBJECT_CLASS (class);
134
135   parent_class = g_type_class_peek_parent (class);
136   object_class->dispose = psppire_output_window_dispose;
137   
138   GTK_WIDGET_CLASS (object_class)->style_set = psppire_output_window_style_set;
139   object_class->finalize = psppire_output_window_finalize;
140 }
141
142
143 \f
144 /* Output driver class. */
145
146 struct psppire_output_driver
147   {
148     struct output_driver driver;
149     PsppireOutputWindow *viewer;
150     struct xr_driver *xr;
151     int font_height;
152   };
153
154 static struct output_driver_class psppire_output_class;
155
156 static struct psppire_output_driver *
157 psppire_output_cast (struct output_driver *driver)
158 {
159   assert (driver->class == &psppire_output_class);
160   return UP_CAST (driver, struct psppire_output_driver, driver);
161 }
162
163 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
164
165 static gboolean
166 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
167 {
168   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data);
169   struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
170   cairo_t *cr = gdk_cairo_create (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       gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
657       gchar *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           /* If the "Infer file type from extension" option was chosen,
665              search for the respective type in the list.
666              (It's a O(n) search, but fortunately n is small). */
667           gint i;
668           for (i = 1 ; i < N_EXTENSIONS ; ++i)
669             {
670               if ( g_str_has_suffix (filename, ft[i].ext))
671                 {
672                   file_type = i;
673                   break;
674                 }
675             }
676         }
677       else if (! g_str_has_suffix (filename, ft[file_type].ext))
678         {
679           /* If an explicit document format was chosen, and if the chosen
680              filename does not already have that particular "extension",
681              then append it.
682            */
683
684           gchar *of = filename;
685           filename = g_strconcat (filename, ft[file_type].ext, NULL);
686           g_free (of);
687         }
688       
689       string_map_init (&options);
690       string_map_insert (&options, "output-file", filename);
691
692       switch (file_type)
693         {
694         case FT_PDF:
695           export_output (window, &options, "pdf");
696           break;
697         case FT_HTML:
698           export_output (window, &options, "html");
699           break;
700         case FT_ODT:
701           export_output (window, &options, "odt");
702           break;
703         case FT_PS:
704           export_output (window, &options, "ps");
705           break;
706         case FT_CSV:
707           export_output (window, &options, "csv");
708           break;
709
710         case FT_TXT:
711           string_map_insert (&options, "headers", "false");
712           string_map_insert (&options, "paginate", "false");
713           string_map_insert (&options, "squeeze", "true");
714           string_map_insert (&options, "emphasis", "none");
715           string_map_insert (&options, "charts", "none");
716           string_map_insert (&options, "top-margin", "0");
717           string_map_insert (&options, "bottom-margin", "0");
718           export_output (window, &options, "txt");
719           break;
720         default:
721           g_assert_not_reached ();
722         }
723
724       string_map_destroy (&options);
725
726       free (filename);
727     }
728
729   gtk_widget_destroy (dialog);
730 }
731
732
733 enum {
734   SELECT_FMT_NULL,
735   SELECT_FMT_TEXT,
736   SELECT_FMT_UTF8,
737   SELECT_FMT_HTML,
738   SELECT_FMT_ODT
739 };
740
741 /* GNU Hurd doesn't have PATH_MAX.  Use a fallback.
742    Temporary directory names are usually not that long.  */
743 #ifndef PATH_MAX
744 # define PATH_MAX 1024
745 #endif
746
747 static void
748 clipboard_get_cb (GtkClipboard     *clipboard,
749                   GtkSelectionData *selection_data,
750                   guint             info,
751                   gpointer          data)
752 {
753   PsppireOutputWindow *window = data;
754
755   gsize length;
756   gchar *text = NULL;
757   struct output_driver *driver = NULL;
758   char dirname[PATH_MAX], *filename;
759   struct string_map options;
760
761   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
762   GtkTreeModel *model = gtk_tree_view_get_model (window->overview);
763
764   GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
765   GList *n = rows;
766
767   if ( n == NULL)
768     return;
769
770   if (path_search (dirname, sizeof dirname, NULL, NULL, true)
771       || mkdtemp (dirname) == NULL)
772     {
773       error (0, errno, _("failed to create temporary directory"));
774       return;
775     }
776   filename = xasprintf ("%s/clip.tmp", dirname);
777
778   string_map_init (&options);
779   string_map_insert (&options, "output-file", filename);
780
781   switch (info)
782     {
783     case SELECT_FMT_UTF8:
784       string_map_insert (&options, "box", "unicode");
785       /* fall-through */
786
787     case SELECT_FMT_TEXT:
788       string_map_insert (&options, "format", "txt");
789       break;
790
791     case SELECT_FMT_HTML:
792       string_map_insert (&options, "format", "html");
793       string_map_insert (&options, "borders", "false");
794       string_map_insert (&options, "css", "false");
795       break;
796
797     case SELECT_FMT_ODT:
798       string_map_insert (&options, "format", "odt");
799       break;
800
801     default:
802       g_warning ("unsupported clip target\n");
803       goto finish;
804       break;
805     }
806
807   driver = output_driver_create (&options);
808   if (driver == NULL)
809     goto finish;
810
811   while (n)
812     {
813       GtkTreePath *path = n->data ; 
814       GtkTreeIter iter;
815       struct output_item *item ;
816
817       gtk_tree_model_get_iter (model, &iter, path);
818       gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
819
820       driver->class->submit (driver, item);
821
822       n = n->next;
823     }
824
825   if ( driver->class->flush)
826     driver->class->flush (driver);
827
828
829   /* Some drivers (eg: the odt one) don't write anything until they
830      are closed */
831   output_driver_destroy (driver);
832   driver = NULL;
833
834   if ( g_file_get_contents (filename, &text, &length, NULL) )
835     {
836       gtk_selection_data_set (selection_data, selection_data->target,
837                               8,
838                               (const guchar *) text, length);
839     }
840
841  finish:
842
843   if (driver != NULL)
844     output_driver_destroy (driver);
845
846   g_free (text);
847
848   unlink (filename);
849   free (filename);
850   rmdir (dirname);
851
852   g_list_free (rows);
853 }
854
855 static void
856 clipboard_clear_cb (GtkClipboard *clipboard,
857                     gpointer data)
858 {
859 }
860
861 static const GtkTargetEntry targets[] = {
862
863   { "STRING",        0, SELECT_FMT_TEXT },
864   { "TEXT",          0, SELECT_FMT_TEXT },
865   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
866   { "text/plain",    0, SELECT_FMT_TEXT },
867
868   { "UTF8_STRING",   0, SELECT_FMT_UTF8 },
869   { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
870
871   { "text/html",     0, SELECT_FMT_HTML },
872
873   { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
874 };
875
876 static void
877 on_copy (PsppireOutputWindow *window)
878 {
879   {
880     GtkClipboard *clipboard =
881       gtk_widget_get_clipboard (GTK_WIDGET (window),
882                                 GDK_SELECTION_CLIPBOARD);
883
884     if (!gtk_clipboard_set_with_data (clipboard, targets,
885                                        G_N_ELEMENTS (targets),
886                                        clipboard_get_cb, clipboard_clear_cb,
887                                       window))
888
889       clipboard_clear_cb (clipboard, window);
890   }
891 }
892
893 static void
894 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
895 {
896   /* The Copy action is available only if there is something selected */
897   gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
898 }
899
900 static void
901 on_select_all (PsppireOutputWindow *window)
902 {
903   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
904   gtk_tree_view_expand_all (window->overview);
905   gtk_tree_selection_select_all (sel);
906 }
907
908
909 static void
910 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
911 {
912   int i;
913   for (i = 0; i < 5; ++i)
914     {
915       GdkColor *col = &gtk_widget_get_style (src)->base[i];
916       gtk_widget_modify_bg (dest, i, col);
917
918       col = &gtk_widget_get_style (src)->text[i];
919       gtk_widget_modify_fg (dest, i, col);
920     }
921 }
922
923 static void 
924 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
925 {
926   GtkWidget *viewer = GTK_WIDGET (data);
927
928   copy_base_to_bg (dwg_area, viewer);
929 }
930
931
932 static void
933 psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev)
934 {
935   GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output);
936
937   /* Copy the base style from the parent widget to the container and 
938      all its children.
939      We do this, because the container's primary purpose is to 
940      display text.  This way psppire appears to follow the chosen
941      gnome theme.
942    */
943   copy_base_to_bg (op, w);
944   gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg,
945                          PSPPIRE_OUTPUT_WINDOW (w)->output);
946
947     /* Chain up to the parent class */
948   GTK_WIDGET_CLASS (parent_class)->style_set (w, prev);
949 }
950
951 static void
952 psppire_output_window_init (PsppireOutputWindow *window)
953 {
954   GtkTreeViewColumn *column;
955   GtkCellRenderer *renderer;
956   GtkBuilder *xml;
957   GtkAction *copy_action;
958   GtkAction *select_all_action;
959   GtkTreeSelection *sel;
960   GtkTreeModel *model;
961
962   string_map_init (&window->render_opts);
963
964   xml = builder_new ("output-viewer.ui");
965
966   copy_action = get_action_assert (xml, "edit_copy");
967   select_all_action = get_action_assert (xml, "edit_select-all");
968
969   gtk_action_set_sensitive (copy_action, FALSE);
970
971   g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window);
972
973   g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window);
974
975   gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
976
977   window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
978   window->y = 0;
979   window->print_settings = NULL;
980   window->dispose_has_run = FALSE;
981
982   window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
983
984   sel = gtk_tree_view_get_selection (window->overview);
985
986   gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
987
988   g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action);
989
990   model = GTK_TREE_MODEL (gtk_tree_store_new (
991                                              N_COLS,
992                                              G_TYPE_STRING,  /* COL_TITLE */
993                                              G_TYPE_POINTER, /* COL_ADDR */
994                                              G_TYPE_LONG));  /* COL_Y */
995   gtk_tree_view_set_model (window->overview, model);
996   g_object_unref (model);
997
998   window->in_command = false;
999
1000   window->items = NULL;
1001   window->n_items = window->allocated_items = 0;
1002
1003   column = gtk_tree_view_column_new ();
1004   gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
1005   renderer = gtk_cell_renderer_text_new ();
1006   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1007   gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
1008
1009   g_signal_connect (GTK_TREE_VIEW (window->overview),
1010                     "row-activated", G_CALLBACK (on_row_activate), window);
1011
1012   connect_help (xml);
1013
1014   g_signal_connect (window,
1015                     "focus-in-event",
1016                     G_CALLBACK (cancel_urgency),
1017                     NULL);
1018
1019   g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
1020                     "activate",
1021                     G_CALLBACK (psppire_window_minimise_all),
1022                     NULL);
1023
1024   {
1025     GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
1026     merge_help_menu (uim);
1027
1028     PSPPIRE_WINDOW (window)->menu =
1029       GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all")->parent);
1030   }
1031
1032   g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
1033                             G_CALLBACK (psppire_output_window_export), window);
1034
1035
1036   g_signal_connect_swapped (get_action_assert (xml, "file_print"), "activate",
1037                             G_CALLBACK (psppire_output_window_print), window);
1038
1039   g_object_unref (xml);
1040
1041   g_signal_connect (window, "delete-event",
1042                     G_CALLBACK (on_delete), window);
1043 }
1044
1045
1046 GtkWidget*
1047 psppire_output_window_new (void)
1048 {
1049   return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
1050                                    /* TRANSLATORS: This will form a filename.  Please avoid whitespace. */
1051                                    "filename", _("Output"),
1052                                    "description", _("Output Viewer"),
1053                                    NULL));
1054 }
1055
1056 \f
1057
1058 static cairo_t *
1059 get_cairo_context_from_print_context (GtkPrintContext *context)
1060 {
1061   cairo_t *cr = gtk_print_context_get_cairo_context (context);
1062   
1063   /*
1064     For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
1065     Windows returns 600.
1066   */
1067   double xres = gtk_print_context_get_dpi_x (context);
1068   double yres = gtk_print_context_get_dpi_y (context);
1069   
1070   /* This means that the cairo context now has its dimensions in Points */
1071   cairo_scale (cr, xres / 72.0, yres / 72.0);
1072   
1073   return cr;
1074 }
1075
1076
1077 static void
1078 create_xr_print_driver (GtkPrintContext *context, PsppireOutputWindow *window)
1079 {
1080   struct string_map options;
1081   GtkPageSetup *page_setup;
1082   double width, height;
1083   double left_margin;
1084   double right_margin;
1085   double top_margin;
1086   double bottom_margin;
1087
1088   page_setup = gtk_print_context_get_page_setup (context);
1089   width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
1090   height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
1091   left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
1092   right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
1093   top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
1094   bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
1095
1096   string_map_init (&options);
1097   string_map_insert_nocopy (&options, xstrdup ("paper-size"),
1098                             c_xasprintf("%.2fx%.2fmm", width, height));
1099   string_map_insert_nocopy (&options, xstrdup ("left-margin"),
1100                             c_xasprintf ("%.2fmm", left_margin));
1101   string_map_insert_nocopy (&options, xstrdup ("right-margin"),
1102                             c_xasprintf ("%.2fmm", right_margin));
1103   string_map_insert_nocopy (&options, xstrdup ("top-margin"),
1104                             c_xasprintf ("%.2fmm", top_margin));
1105   string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
1106                             c_xasprintf ("%.2fmm", bottom_margin));
1107
1108   window->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
1109
1110   string_map_destroy (&options);
1111 }
1112
1113 static gboolean
1114 paginate (GtkPrintOperation *operation,
1115           GtkPrintContext   *context,
1116           PsppireOutputWindow *window)
1117 {
1118   if (window->paginated)
1119     {
1120       /* Sometimes GTK+ emits this signal again even after pagination is
1121          complete.  Don't let that screw up printing. */
1122       return TRUE;
1123     }
1124   else if ( window->print_item < window->n_items )
1125     {
1126       xr_driver_output_item (window->print_xrd, window->items[window->print_item++]);
1127       while (xr_driver_need_new_page (window->print_xrd))
1128         {
1129           xr_driver_next_page (window->print_xrd, NULL);
1130           window->print_n_pages ++;
1131         }
1132       return FALSE;
1133     }
1134   else
1135     {
1136       gtk_print_operation_set_n_pages (operation, window->print_n_pages);
1137
1138       /* Re-create the driver to do the real printing. */
1139       xr_driver_destroy (window->print_xrd);
1140       create_xr_print_driver (context, window);
1141       window->print_item = 0;
1142       window->paginated = TRUE;
1143
1144       return TRUE;
1145     }
1146 }
1147
1148 static void
1149 begin_print (GtkPrintOperation *operation,
1150              GtkPrintContext   *context,
1151              PsppireOutputWindow *window)
1152 {
1153   create_xr_print_driver (context, window);
1154
1155   window->print_item = 0;
1156   window->print_n_pages = 1;
1157   window->paginated = FALSE;
1158 }
1159
1160 static void
1161 end_print (GtkPrintOperation *operation,
1162            GtkPrintContext   *context,
1163            PsppireOutputWindow *window)
1164 {
1165   xr_driver_destroy (window->print_xrd);
1166 }
1167
1168
1169 static void
1170 draw_page (GtkPrintOperation *operation,
1171            GtkPrintContext   *context,
1172            gint               page_number,
1173            PsppireOutputWindow *window)
1174 {
1175   xr_driver_next_page (window->print_xrd, get_cairo_context_from_print_context (context));
1176   while (!xr_driver_need_new_page (window->print_xrd)
1177          && window->print_item < window->n_items)
1178     xr_driver_output_item (window->print_xrd, window->items [window->print_item++]);
1179 }
1180
1181
1182 static void
1183 psppire_output_window_print (PsppireOutputWindow *window)
1184 {
1185   GtkPrintOperationResult res;
1186
1187   GtkPrintOperation *print = gtk_print_operation_new ();
1188
1189   if (window->print_settings != NULL) 
1190     gtk_print_operation_set_print_settings (print, window->print_settings);
1191
1192   g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), window);
1193   g_signal_connect (print, "end_print",   G_CALLBACK (end_print),   window);
1194   g_signal_connect (print, "paginate",    G_CALLBACK (paginate),    window);
1195   g_signal_connect (print, "draw_page",   G_CALLBACK (draw_page),   window);
1196
1197   res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
1198                                  GTK_WINDOW (window), NULL);
1199
1200   if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
1201     {
1202       if (window->print_settings != NULL)
1203         g_object_unref (window->print_settings);
1204       window->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
1205     }
1206
1207   g_object_unref (print);
1208 }