Merge remote-tracking branch 'origin/master' into gtk3
[pspp] / src / ui / gui / psppire-output-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <errno.h>
20 #include <gtk/gtk.h>
21 #include <stdlib.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "libpspp/cast.h"
27 #include "libpspp/message.h"
28 #include "libpspp/string-map.h"
29 #include "output/cairo.h"
30 #include "output/chart-item.h"
31 #include "output/driver-provider.h"
32 #include "output/message-item.h"
33 #include "output/output-item.h"
34 #include "output/tab.h"
35 #include "output/table-item.h"
36 #include "output/text-item.h"
37 #include "ui/gui/help-menu.h"
38 #include "ui/gui/builder-wrapper.h"
39 #include "ui/gui/psppire-output-window.h"
40
41 #include "gl/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 draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data)
167 {
168   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data);
169   struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
170   const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
171
172   PangoFontDescription *font_desc;
173   char *font_name;
174   
175   gchar *fgc =
176     gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (widget))]);
177
178   string_map_replace (&viewer->render_opts, "foreground-color", fgc);
179
180   free (fgc);
181
182   /* Use GTK+ default font as proportional font. */
183   font_name = pango_font_description_to_string (style->font_desc);
184   string_map_replace (&viewer->render_opts, "prop-font", font_name);
185   g_free (font_name);
186
187   /* Derived emphasized font from proportional font. */
188   font_desc = pango_font_description_copy (style->font_desc);
189   pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
190   font_name = pango_font_description_to_string (font_desc);
191   string_map_replace (&viewer->render_opts, "emph-font", font_name);
192   g_free (font_name);
193   pango_font_description_free (font_desc);
194
195   xr_rendering_apply_options (r, &viewer->render_opts);
196   xr_rendering_draw_all (r, cr);
197
198   return TRUE;
199 }
200
201
202 static void
203 psppire_output_submit (struct output_driver *this,
204                        const struct output_item *item)
205 {
206   struct psppire_output_driver *pod = psppire_output_cast (this);
207   PsppireOutputWindow *viewer;
208   GtkWidget *drawing_area;
209   struct xr_rendering *r;
210   struct string title;
211   GtkTreeStore *store;
212   GtkTreePath *path;
213   GtkTreeIter iter;
214   cairo_t *cr;
215   int tw, th;
216
217   if (pod->viewer == NULL)
218     {
219       pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
220       gtk_widget_show_all (GTK_WIDGET (pod->viewer));
221       pod->viewer->driver = pod;
222     }
223   viewer = pod->viewer;
224
225   if (viewer->n_items >= viewer->allocated_items)
226     viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items,
227                                 sizeof *viewer->items);
228   viewer->items[viewer->n_items++] = output_item_ref (item);
229
230   if (is_text_item (item))
231     {
232       const struct text_item *text_item = to_text_item (item);
233       enum text_item_type type = text_item_get_type (text_item);
234       const char *text = text_item_get_text (text_item);
235
236       if (type == TEXT_ITEM_COMMAND_CLOSE)
237         {
238           viewer->in_command = false;
239           return;
240         }
241       else if (text[0] == '\0')
242         return;
243     }
244
245   cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (pod->viewer)));
246   if (pod->xr == NULL)
247     {
248       const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
249       struct text_item *text_item;
250       PangoFontDescription *font_desc;
251       char *font_name;
252       int font_width;
253       
254       /* Set the widget's text color as the foreground color for the output driver */
255       gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (viewer))]);
256
257       string_map_insert (&pod->viewer->render_opts, "foreground-color", fgc);
258       g_free (fgc);
259
260       /* Use GTK+ default font as proportional font. */
261       font_name = pango_font_description_to_string (style->font_desc);
262       string_map_insert (&pod->viewer->render_opts, "prop-font", font_name);
263       g_free (font_name);
264
265       /* Derived emphasized font from proportional font. */
266       font_desc = pango_font_description_copy (style->font_desc);
267       pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
268       font_name = pango_font_description_to_string (font_desc);
269       string_map_insert (&pod->viewer->render_opts, "emph-font", font_name);
270       g_free (font_name);
271       pango_font_description_free (font_desc);
272
273       /* Pretend that the "page" has a reasonable width and a very big length,
274          so that most tables can be conveniently viewed on-screen with vertical
275          scrolling only.  (The length should not be increased very much because
276          it is already close enough to INT_MAX when expressed as thousands of a
277          point.) */
278       string_map_insert (&pod->viewer->render_opts, "paper-size", "300x200000mm");
279       string_map_insert (&pod->viewer->render_opts, "left-margin", "0");
280       string_map_insert (&pod->viewer->render_opts, "right-margin", "0");
281       string_map_insert (&pod->viewer->render_opts, "top-margin", "0");
282       string_map_insert (&pod->viewer->render_opts, "bottom-margin", "0");
283
284       pod->xr = xr_driver_create (cr, &pod->viewer->render_opts);
285
286
287       text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
288       r = xr_rendering_create (pod->xr, text_item_super (text_item), cr);
289       xr_rendering_measure (r, &font_width, &pod->font_height);
290       /* xr_rendering_destroy (r); */
291       text_item_unref (text_item);
292     }
293   else
294     pod->viewer->y += pod->font_height / 2;
295
296   r = xr_rendering_create (pod->xr, item, cr);
297   if (r == NULL)
298     goto done;
299
300   xr_rendering_measure (r, &tw, &th);
301
302   drawing_area = gtk_drawing_area_new ();
303
304   g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
305   g_signal_connect (drawing_area, "realize",
306                      G_CALLBACK (on_dwgarea_realize), pod->viewer);
307
308   g_signal_connect (drawing_area, "draw",
309                      G_CALLBACK (draw_callback), pod->viewer);
310
311   gtk_widget_set_size_request (drawing_area, tw, th);
312   gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y);
313
314   gtk_widget_show (drawing_area);
315
316   if (!is_text_item (item)
317       || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
318       || !viewer->in_command)
319     {
320       store = GTK_TREE_STORE (gtk_tree_view_get_model (viewer->overview));
321
322       ds_init_empty (&title);
323       if (is_text_item (item)
324           && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
325         {
326           gtk_tree_store_append (store, &iter, NULL);
327           viewer->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
328           viewer->in_command = true;
329         }
330       else
331         {
332           GtkTreeIter *p = viewer->in_command ? &viewer->cur_command : NULL;
333           gtk_tree_store_append (store, &iter, p);
334         }
335
336       ds_clear (&title);
337       if (is_text_item (item))
338         ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
339       else if (is_message_item (item))
340         {
341           const struct message_item *msg_item = to_message_item (item);
342           const struct msg *msg = message_item_get_msg (msg_item);
343           ds_put_format (&title, "%s: %s", _("Message"),
344                          msg_severity_to_string (msg->severity));
345         }
346       else if (is_table_item (item))
347         {
348           const char *caption = table_item_get_caption (to_table_item (item));
349           if (caption != NULL)
350             ds_put_format (&title, "Table: %s", caption);
351           else
352             ds_put_cstr (&title, "Table");
353         }
354       else if (is_chart_item (item))
355         {
356           const char *s = chart_item_get_title (to_chart_item (item));
357           if (s != NULL)
358             ds_put_format (&title, "Chart: %s", s);
359           else
360             ds_put_cstr (&title, "Chart");
361         }
362       gtk_tree_store_set (store, &iter,
363                           COL_TITLE, ds_cstr (&title),
364                           COL_ADDR, item, 
365                           COL_Y, viewer->y,
366                           -1);
367       ds_destroy (&title);
368
369       path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
370       gtk_tree_view_expand_row (viewer->overview, path, TRUE);
371       gtk_tree_path_free (path);
372     }
373
374   if (pod->viewer->max_width < tw)
375     pod->viewer->max_width = tw;
376   pod->viewer->y += th;
377
378   gtk_layout_set_size (pod->viewer->output,
379                        pod->viewer->max_width, pod->viewer->y);
380
381   gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE);
382
383 done:
384   cairo_destroy (cr);
385 }
386
387 static struct output_driver_class psppire_output_class =
388   {
389     "PSPPIRE",                  /* name */
390     NULL,                       /* destroy */
391     psppire_output_submit,      /* submit */
392     NULL,                       /* flush */
393   };
394
395 void
396 psppire_output_window_setup (void)
397 {
398   struct psppire_output_driver *pod;
399   struct output_driver *d;
400
401   pod = xzalloc (sizeof *pod);
402   d = &pod->driver;
403   output_driver_init (d, &psppire_output_class, "PSPPIRE",
404                       SETTINGS_DEVICE_UNFILTERED);
405   output_driver_register (d);
406 }
407
408 \f
409
410 /* Callback for the "delete" action (clicking the x on the top right
411    hand corner of the window) */
412 static gboolean
413 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
414 {
415   PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
416
417   gtk_widget_destroy (GTK_WIDGET (ow));
418
419   ow->driver->viewer = NULL;
420
421   return FALSE;
422 }
423
424
425
426 static void
427 cancel_urgency (GtkWindow *window,  gpointer data)
428 {
429   gtk_window_set_urgency_hint (window, FALSE);
430 }
431
432 static void
433 on_row_activate (GtkTreeView *overview,
434                  GtkTreePath *path,
435                  GtkTreeViewColumn *column,
436                  PsppireOutputWindow *window)
437 {
438   GtkTreeModel *model;
439   GtkTreeIter iter;
440   GtkAdjustment *vadj;
441   GValue value = {0};
442   double y, min, max;
443
444   model = gtk_tree_view_get_model (overview);
445   if (!gtk_tree_model_get_iter (model, &iter, path))
446     return;
447
448   gtk_tree_model_get_value (model, &iter, COL_Y, &value);
449   y = g_value_get_long (&value);
450   g_value_unset (&value);
451
452   vadj = gtk_layout_get_vadjustment (window->output);
453   min = gtk_adjustment_get_lower (vadj);
454   max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj);
455   if (y < min)
456     y = min;
457   else if (y > max)
458     y = max;
459   gtk_adjustment_set_value (vadj, y);
460 }
461
462 static void psppire_output_window_print (PsppireOutputWindow *window);
463
464
465 static void
466 export_output (PsppireOutputWindow *window, struct string_map *options,
467                const char *format)
468 {
469   struct output_driver *driver;
470   size_t i;
471
472   string_map_insert (options, "format", format);
473   driver = output_driver_create (options);
474   if (driver == NULL)
475     return;
476
477   for (i = 0; i < window->n_items; i++)
478     driver->class->submit (driver, window->items[i]);
479   output_driver_destroy (driver);
480 }
481
482
483 struct file_types
484 {
485   const gchar *label;
486   const gchar *ext;
487 };
488
489 enum 
490   {
491     FT_AUTO = 0,
492     FT_PDF,
493     FT_HTML,
494     FT_ODT,
495     FT_TXT,
496     FT_PS,
497     FT_CSV,
498     n_FT
499   };
500
501 #define N_EXTENSIONS (n_FT - 1)
502
503 struct file_types ft[n_FT] = {
504   {N_("Infer file type from extension"),  NULL},
505   {N_("PDF (*.pdf)"),                     ".pdf"},
506   {N_("HTML (*.html)"),                   ".html"},
507   {N_("OpenDocument (*.odt)"),            ".odt"},
508   {N_("Text (*.txt)"),                    ".txt"},
509   {N_("PostScript (*.ps)"),               ".ps"},
510   {N_("Comma-Separated Values (*.csv)"),  ".csv"}
511 };
512
513
514 static void
515 on_combo_change (GtkFileChooser *chooser)
516 {
517   gboolean sensitive = FALSE;
518   GtkWidget *combo = gtk_file_chooser_get_extra_widget (chooser);
519
520   int x = 0; 
521   gchar *fn = gtk_file_chooser_get_filename (chooser);
522
523   if (combo &&  gtk_widget_get_realized (combo))
524     x = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
525
526   if (fn == NULL)
527     {
528       sensitive = FALSE;
529     }
530   else
531     {
532       gint i;
533       if ( x != 0 )
534         sensitive = TRUE;
535
536       for (i = 1 ; i < N_EXTENSIONS ; ++i)
537         {
538           if ( g_str_has_suffix (fn, ft[i].ext))
539             {
540               sensitive = TRUE;
541               break;
542             }
543         }
544     }
545
546   g_free (fn);
547
548   gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, sensitive);
549 }
550
551
552 static void
553 on_file_chooser_change (GObject *w, GParamSpec *pspec, gpointer data)
554 {
555
556   GtkFileChooser *chooser = data;
557   const gchar *name = g_param_spec_get_name (pspec);
558
559   if ( ! gtk_widget_get_realized (GTK_WIDGET (chooser)))
560     return;
561
562   /* Ignore this one.  It causes recursion. */
563   if ( 0 == strcmp ("tooltip-text", name))
564     return;
565
566   on_combo_change (chooser);
567 }
568
569
570 /* Recursively descend all the children of W, connecting
571    to their "notify" signal */
572 static void
573 iterate_widgets (GtkWidget *w, gpointer data)
574 {
575   if ( GTK_IS_CONTAINER (w))
576     gtk_container_forall (GTK_CONTAINER (w), iterate_widgets, data);
577   else
578     g_signal_connect (w, "notify",  G_CALLBACK (on_file_chooser_change), data);
579 }
580
581
582
583 static GtkListStore *
584 create_file_type_list (void)
585 {
586   int i;
587   GtkTreeIter iter;
588   GtkListStore *list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
589   
590   for (i = 0 ; i < n_FT ; ++i)
591     {
592       gtk_list_store_append (list, &iter);
593       gtk_list_store_set (list, &iter,
594                           0,  gettext (ft[i].label),
595                           1,  ft[i].ext,
596                           -1);
597     }
598   
599   return list;
600 }
601
602 static void
603 psppire_output_window_export (PsppireOutputWindow *window)
604 {
605   gint response;
606   GtkWidget *combo;
607   GtkListStore *list;
608
609   GtkFileChooser *chooser;
610   
611   GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Export Output"),
612                                         GTK_WINDOW (window),
613                                         GTK_FILE_CHOOSER_ACTION_SAVE,
614                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
615                                         GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
616                                         NULL);
617
618   g_object_set (dialog, "local-only", FALSE, NULL);
619
620   chooser = GTK_FILE_CHOOSER (dialog);
621
622   list = create_file_type_list ();
623
624   combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list));
625
626
627   {
628     /* Create text cell renderer */
629     GtkCellRenderer *cell = gtk_cell_renderer_text_new();
630     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE );
631
632     gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell,  "text", 0);
633   }
634
635   g_signal_connect_swapped (combo, "changed", G_CALLBACK (on_combo_change), chooser);
636
637   gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
638
639   gtk_file_chooser_set_extra_widget (chooser, combo);
640
641   /* This kludge is necessary because there is no signal to tell us
642      when the candidate filename of a GtkFileChooser has changed */
643   gtk_container_forall (GTK_CONTAINER (dialog), iterate_widgets, dialog);
644
645
646   gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
647
648   response = gtk_dialog_run (GTK_DIALOG (dialog));
649
650   if ( response == GTK_RESPONSE_ACCEPT )
651     {
652       gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
653       gchar *filename = gtk_file_chooser_get_filename (chooser);
654       struct string_map options;
655
656       g_return_if_fail (filename);
657
658       if (file_type == FT_AUTO)
659         {
660           /* If the "Infer file type from extension" option was chosen,
661              search for the respective type in the list.
662              (It's a O(n) search, but fortunately n is small). */
663           gint i;
664           for (i = 1 ; i < N_EXTENSIONS ; ++i)
665             {
666               if ( g_str_has_suffix (filename, ft[i].ext))
667                 {
668                   file_type = i;
669                   break;
670                 }
671             }
672         }
673       else if (! g_str_has_suffix (filename, ft[file_type].ext))
674         {
675           /* If an explicit document format was chosen, and if the chosen
676              filename does not already have that particular "extension",
677              then append it.
678            */
679
680           gchar *of = filename;
681           filename = g_strconcat (filename, ft[file_type].ext, NULL);
682           g_free (of);
683         }
684       
685       string_map_init (&options);
686       string_map_insert (&options, "output-file", filename);
687
688       switch (file_type)
689         {
690         case FT_PDF:
691           export_output (window, &options, "pdf");
692           break;
693         case FT_HTML:
694           export_output (window, &options, "html");
695           break;
696         case FT_ODT:
697           export_output (window, &options, "odt");
698           break;
699         case FT_PS:
700           export_output (window, &options, "ps");
701           break;
702         case FT_CSV:
703           export_output (window, &options, "csv");
704           break;
705
706         case FT_TXT:
707           string_map_insert (&options, "headers", "false");
708           string_map_insert (&options, "paginate", "false");
709           string_map_insert (&options, "squeeze", "true");
710           string_map_insert (&options, "emphasis", "none");
711           string_map_insert (&options, "charts", "none");
712           string_map_insert (&options, "top-margin", "0");
713           string_map_insert (&options, "bottom-margin", "0");
714           export_output (window, &options, "txt");
715           break;
716         default:
717           g_assert_not_reached ();
718         }
719
720       string_map_destroy (&options);
721
722       free (filename);
723     }
724
725   gtk_widget_destroy (dialog);
726 }
727
728
729 enum {
730   SELECT_FMT_NULL,
731   SELECT_FMT_TEXT,
732   SELECT_FMT_UTF8,
733   SELECT_FMT_HTML,
734   SELECT_FMT_ODT
735 };
736
737 /* GNU Hurd doesn't have PATH_MAX.  Use a fallback.
738    Temporary directory names are usually not that long.  */
739 #ifndef PATH_MAX
740 # define PATH_MAX 1024
741 #endif
742
743 static void
744 clipboard_get_cb (GtkClipboard     *clipboard,
745                   GtkSelectionData *selection_data,
746                   guint             info,
747                   gpointer          data)
748 {
749   PsppireOutputWindow *window = data;
750
751   gsize length;
752   gchar *text = NULL;
753   struct output_driver *driver = NULL;
754   char dirname[PATH_MAX], *filename;
755   struct string_map options;
756
757   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
758   GtkTreeModel *model = gtk_tree_view_get_model (window->overview);
759
760   GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
761   GList *n = rows;
762
763   if ( n == NULL)
764     return;
765
766   if (path_search (dirname, sizeof dirname, NULL, NULL, true)
767       || mkdtemp (dirname) == NULL)
768     {
769       error (0, errno, _("failed to create temporary directory"));
770       return;
771     }
772   filename = xasprintf ("%s/clip.tmp", dirname);
773
774   string_map_init (&options);
775   string_map_insert (&options, "output-file", filename);
776
777   switch (info)
778     {
779     case SELECT_FMT_UTF8:
780       string_map_insert (&options, "box", "unicode");
781       /* fall-through */
782
783     case SELECT_FMT_TEXT:
784       string_map_insert (&options, "format", "txt");
785       break;
786
787     case SELECT_FMT_HTML:
788       string_map_insert (&options, "format", "html");
789       string_map_insert (&options, "borders", "false");
790       string_map_insert (&options, "css", "false");
791       break;
792
793     case SELECT_FMT_ODT:
794       string_map_insert (&options, "format", "odt");
795       break;
796
797     default:
798       g_warning ("unsupported clip target\n");
799       goto finish;
800       break;
801     }
802
803   driver = output_driver_create (&options);
804   if (driver == NULL)
805     goto finish;
806
807   while (n)
808     {
809       GtkTreePath *path = n->data ; 
810       GtkTreeIter iter;
811       struct output_item *item ;
812
813       gtk_tree_model_get_iter (model, &iter, path);
814       gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
815
816       driver->class->submit (driver, item);
817
818       n = n->next;
819     }
820
821   if ( driver->class->flush)
822     driver->class->flush (driver);
823
824
825   /* Some drivers (eg: the odt one) don't write anything until they
826      are closed */
827   output_driver_destroy (driver);
828   driver = NULL;
829
830   if ( g_file_get_contents (filename, &text, &length, NULL) )
831     {
832       gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
833                               8,
834                               (const guchar *) text, length);
835     }
836
837  finish:
838
839   if (driver != NULL)
840     output_driver_destroy (driver);
841
842   g_free (text);
843
844   unlink (filename);
845   free (filename);
846   rmdir (dirname);
847
848   g_list_free (rows);
849 }
850
851 static void
852 clipboard_clear_cb (GtkClipboard *clipboard,
853                     gpointer data)
854 {
855 }
856
857 static const GtkTargetEntry targets[] = {
858
859   { "STRING",        0, SELECT_FMT_TEXT },
860   { "TEXT",          0, SELECT_FMT_TEXT },
861   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
862   { "text/plain",    0, SELECT_FMT_TEXT },
863
864   { "UTF8_STRING",   0, SELECT_FMT_UTF8 },
865   { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
866
867   { "text/html",     0, SELECT_FMT_HTML },
868
869   { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
870 };
871
872 static void
873 on_copy (PsppireOutputWindow *window)
874 {
875   {
876     GtkClipboard *clipboard =
877       gtk_widget_get_clipboard (GTK_WIDGET (window),
878                                 GDK_SELECTION_CLIPBOARD);
879
880     if (!gtk_clipboard_set_with_data (clipboard, targets,
881                                        G_N_ELEMENTS (targets),
882                                        clipboard_get_cb, clipboard_clear_cb,
883                                       window))
884
885       clipboard_clear_cb (clipboard, window);
886   }
887 }
888
889 static void
890 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
891 {
892   /* The Copy action is available only if there is something selected */
893   gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
894 }
895
896 static void
897 on_select_all (PsppireOutputWindow *window)
898 {
899   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
900   gtk_tree_view_expand_all (window->overview);
901   gtk_tree_selection_select_all (sel);
902 }
903
904
905 static void
906 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
907 {
908   int i;
909   for (i = 0; i < 5; ++i)
910     {
911       GdkColor *col = &gtk_widget_get_style (src)->base[i];
912       gtk_widget_modify_bg (dest, i, col);
913
914       col = &gtk_widget_get_style (src)->text[i];
915       gtk_widget_modify_fg (dest, i, col);
916     }
917 }
918
919 static void 
920 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
921 {
922   GtkWidget *viewer = GTK_WIDGET (data);
923
924   copy_base_to_bg (dwg_area, viewer);
925 }
926
927
928 static void
929 psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev)
930 {
931   GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output);
932
933   /* Copy the base style from the parent widget to the container and 
934      all its children.
935      We do this, because the container's primary purpose is to 
936      display text.  This way psppire appears to follow the chosen
937      gnome theme.
938    */
939   copy_base_to_bg (op, w);
940   gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg,
941                          PSPPIRE_OUTPUT_WINDOW (w)->output);
942
943     /* Chain up to the parent class */
944   GTK_WIDGET_CLASS (parent_class)->style_set (w, prev);
945 }
946
947 static void
948 psppire_output_window_init (PsppireOutputWindow *window)
949 {
950   GtkTreeViewColumn *column;
951   GtkCellRenderer *renderer;
952   GtkBuilder *xml;
953   GtkAction *copy_action;
954   GtkAction *select_all_action;
955   GtkTreeSelection *sel;
956   GtkTreeModel *model;
957
958   string_map_init (&window->render_opts);
959
960   xml = builder_new ("output-viewer.ui");
961
962   copy_action = get_action_assert (xml, "edit_copy");
963   select_all_action = get_action_assert (xml, "edit_select-all");
964
965   gtk_action_set_sensitive (copy_action, FALSE);
966
967   g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window);
968
969   g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window);
970
971   gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
972
973   window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
974   window->y = 0;
975   window->print_settings = NULL;
976   window->dispose_has_run = FALSE;
977
978   window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
979
980   sel = gtk_tree_view_get_selection (window->overview);
981
982   gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
983
984   g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action);
985
986   model = GTK_TREE_MODEL (gtk_tree_store_new (
987                                              N_COLS,
988                                              G_TYPE_STRING,  /* COL_TITLE */
989                                              G_TYPE_POINTER, /* COL_ADDR */
990                                              G_TYPE_LONG));  /* COL_Y */
991   gtk_tree_view_set_model (window->overview, model);
992   g_object_unref (model);
993
994   window->in_command = false;
995
996   window->items = NULL;
997   window->n_items = window->allocated_items = 0;
998
999   column = gtk_tree_view_column_new ();
1000   gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
1001   renderer = gtk_cell_renderer_text_new ();
1002   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1003   gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
1004
1005   g_signal_connect (GTK_TREE_VIEW (window->overview),
1006                     "row-activated", G_CALLBACK (on_row_activate), window);
1007
1008   connect_help (xml);
1009
1010   g_signal_connect (window,
1011                     "focus-in-event",
1012                     G_CALLBACK (cancel_urgency),
1013                     NULL);
1014
1015   g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
1016                     "activate",
1017                     G_CALLBACK (psppire_window_minimise_all),
1018                     NULL);
1019
1020   {
1021     GtkWidget *w;
1022     GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
1023     merge_help_menu (uim);
1024
1025     w = gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all");
1026
1027     PSPPIRE_WINDOW (window)->menu =
1028       GTK_MENU_SHELL (gtk_widget_get_parent (w));
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 }