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