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