Set the output driver parameters dynamically from the output window style.
[pspp-builds.git] / src / ui / gui / psppire-output-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011  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/helper.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 <gettext.h>
46 #define _(msgid) gettext (msgid)
47 #define N_(msgid) msgid
48
49 enum
50   {
51     COL_TITLE,                  /* Table title. */
52     COL_ADDR,                   /* Pointer to the table */
53     COL_Y,                      /* Y position of top of title. */
54     N_COLS
55   };
56
57 static void psppire_output_window_base_finalize (PsppireOutputWindowClass *, gpointer);
58 static void psppire_output_window_base_init     (PsppireOutputWindowClass *class);
59 static void psppire_output_window_class_init    (PsppireOutputWindowClass *class);
60 static void psppire_output_window_init          (PsppireOutputWindow      *window);
61
62 static void psppire_output_window_style_set (GtkWidget *window, GtkStyle *prev);
63
64
65 GType
66 psppire_output_window_get_type (void)
67 {
68   static GType psppire_output_window_type = 0;
69
70   if (!psppire_output_window_type)
71     {
72       static const GTypeInfo psppire_output_window_info =
73       {
74         sizeof (PsppireOutputWindowClass),
75         (GBaseInitFunc) psppire_output_window_base_init,
76         (GBaseFinalizeFunc) psppire_output_window_base_finalize,
77         (GClassInitFunc)psppire_output_window_class_init,
78         (GClassFinalizeFunc) NULL,
79         NULL,
80         sizeof (PsppireOutputWindow),
81         0,
82         (GInstanceInitFunc) psppire_output_window_init,
83       };
84
85       psppire_output_window_type =
86         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
87                                 &psppire_output_window_info, 0);
88     }
89
90   return psppire_output_window_type;
91 }
92
93 static GObjectClass *parent_class;
94
95 static void
96 psppire_output_window_finalize (GObject *object)
97 {
98   string_map_destroy (&PSPPIRE_OUTPUT_WINDOW(object)->render_opts);
99
100
101   if (G_OBJECT_CLASS (parent_class)->finalize)
102     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
103 }
104
105
106 static void
107 psppire_output_window_dispose (GObject *obj)
108 {
109   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
110   size_t i;
111
112   for (i = 0; i < viewer->n_items; i++)
113     output_item_unref (viewer->items[i]);
114   free (viewer->items);
115   viewer->items = NULL;
116   viewer->n_items = viewer->allocated_items = 0;
117
118   if (viewer->print_settings != NULL)
119     g_object_unref (viewer->print_settings);
120
121   /* Chain up to the parent class */
122   G_OBJECT_CLASS (parent_class)->dispose (obj);
123 }
124
125 static void
126 psppire_output_window_class_init (PsppireOutputWindowClass *class)
127 {
128   GObjectClass *object_class = G_OBJECT_CLASS (class);
129
130   parent_class = g_type_class_peek_parent (class);
131   object_class->dispose = psppire_output_window_dispose;
132   
133   GTK_WIDGET_CLASS (object_class)->style_set = psppire_output_window_style_set;
134 }
135
136
137 static void
138 psppire_output_window_base_init (PsppireOutputWindowClass *class)
139 {
140   GObjectClass *object_class = G_OBJECT_CLASS (class);
141
142   object_class->finalize = psppire_output_window_finalize;
143 }
144
145
146
147 static void
148 psppire_output_window_base_finalize (PsppireOutputWindowClass *class,
149                                      gpointer class_data)
150 {
151 }
152 \f
153 /* Output driver class. */
154
155 struct psppire_output_driver
156   {
157     struct output_driver driver;
158     PsppireOutputWindow *viewer;
159     struct xr_driver *xr;
160     int font_height;
161   };
162
163 static struct output_driver_class psppire_output_class;
164
165 static struct psppire_output_driver *
166 psppire_output_cast (struct output_driver *driver)
167 {
168   assert (driver->class == &psppire_output_class);
169   return UP_CAST (driver, struct psppire_output_driver, driver);
170 }
171
172 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
173
174 static gboolean
175 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
176 {
177   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data);
178   struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
179   cairo_t *cr = gdk_cairo_create (widget->window);
180
181   const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
182
183   struct text_item *text_item;
184   PangoFontDescription *font_desc;
185   char *font_name;
186   int font_width;
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 static void
740 clipboard_get_cb (GtkClipboard     *clipboard,
741                   GtkSelectionData *selection_data,
742                   guint             info,
743                   gpointer          data)
744 {
745   PsppireOutputWindow *window = data;
746
747   gsize length;
748   gchar *text = NULL;
749   struct output_driver *driver = NULL;
750   char dirname[PATH_MAX], *filename;
751   struct string_map options;
752
753   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
754   GtkTreeModel *model = gtk_tree_view_get_model (window->overview);
755
756   GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
757   GList *n = rows;
758
759   if ( n == NULL)
760     return;
761
762   if (path_search (dirname, sizeof dirname, NULL, NULL, true)
763       || mkdtemp (dirname) == NULL)
764     {
765       error (0, errno, _("failed to create temporary directory"));
766       return;
767     }
768   filename = xasprintf ("%s/clip.tmp", dirname);
769
770   string_map_init (&options);
771   string_map_insert (&options, "output-file", filename);
772
773   switch (info)
774     {
775     case SELECT_FMT_UTF8:
776       string_map_insert (&options, "box", "unicode");
777       /* fall-through */
778
779     case SELECT_FMT_TEXT:
780       string_map_insert (&options, "format", "txt");
781       break;
782
783     case SELECT_FMT_HTML:
784       string_map_insert (&options, "format", "html");
785       string_map_insert (&options, "borders", "false");
786       string_map_insert (&options, "css", "false");
787       break;
788
789     case SELECT_FMT_ODT:
790       string_map_insert (&options, "format", "odt");
791       break;
792
793     default:
794       g_warning ("unsupported clip target\n");
795       goto finish;
796       break;
797     }
798
799   driver = output_driver_create (&options);
800   if (driver == NULL)
801     goto finish;
802
803   while (n)
804     {
805       GtkTreePath *path = n->data ; 
806       GtkTreeIter iter;
807       struct output_item *item ;
808
809       gtk_tree_model_get_iter (model, &iter, path);
810       gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
811
812       driver->class->submit (driver, item);
813
814       n = n->next;
815     }
816
817   if ( driver->class->flush)
818     driver->class->flush (driver);
819
820
821   /* Some drivers (eg: the odt one) don't write anything until they
822      are closed */
823   output_driver_destroy (driver);
824   driver = NULL;
825
826   if ( g_file_get_contents (filename, &text, &length, NULL) )
827     {
828       gtk_selection_data_set (selection_data, selection_data->target,
829                               8,
830                               (const guchar *) text, length);
831     }
832
833  finish:
834
835   if (driver != NULL)
836     output_driver_destroy (driver);
837
838   g_free (text);
839
840   unlink (filename);
841   free (filename);
842   rmdir (dirname);
843
844   g_list_free (rows);
845 }
846
847 static void
848 clipboard_clear_cb (GtkClipboard *clipboard,
849                     gpointer data)
850 {
851 }
852
853 static const GtkTargetEntry targets[] = {
854
855   { "STRING",        0, SELECT_FMT_TEXT },
856   { "TEXT",          0, SELECT_FMT_TEXT },
857   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
858   { "text/plain",    0, SELECT_FMT_TEXT },
859
860   { "UTF8_STRING",   0, SELECT_FMT_UTF8 },
861   { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
862
863   { "text/html",     0, SELECT_FMT_HTML },
864
865   { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
866 };
867
868 static void
869 on_copy (PsppireOutputWindow *window)
870 {
871   {
872     GtkClipboard *clipboard =
873       gtk_widget_get_clipboard (GTK_WIDGET (window),
874                                 GDK_SELECTION_CLIPBOARD);
875
876     if (!gtk_clipboard_set_with_data (clipboard, targets,
877                                        G_N_ELEMENTS (targets),
878                                        clipboard_get_cb, clipboard_clear_cb,
879                                       window))
880
881       clipboard_clear_cb (clipboard, window);
882   }
883 }
884
885 static void
886 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
887 {
888   /* The Copy action is available only if there is something selected */
889   gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
890 }
891
892 static void
893 on_select_all (PsppireOutputWindow *window)
894 {
895   GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
896   gtk_tree_view_expand_all (window->overview);
897   gtk_tree_selection_select_all (sel);
898 }
899
900
901 static void
902 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
903 {
904   int i;
905   for (i = 0; i < 5; ++i)
906     {
907       GdkColor *col = &gtk_widget_get_style (src)->base[i];
908       gtk_widget_modify_bg (dest, i, col);
909
910       col = &gtk_widget_get_style (src)->text[i];
911       gtk_widget_modify_fg (dest, i, col);
912     }
913 }
914
915 static void 
916 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
917 {
918   GtkWidget *viewer = GTK_WIDGET (data);
919
920   copy_base_to_bg (dwg_area, viewer);
921 }
922
923
924 static void
925 psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev)
926 {
927   GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output);
928
929   /* Copy the base style from the parent widget to the container and 
930      all its children.
931      We do this, because the container's primary purpose is to 
932      display text.  This way psppire appears to follow the chosen
933      gnome theme.
934    */
935   copy_base_to_bg (op, w);
936   gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg,
937                          PSPPIRE_OUTPUT_WINDOW (w)->output);
938
939     /* Chain up to the parent class */
940   GTK_WIDGET_CLASS (parent_class)->style_set (w, prev);
941 }
942
943 static void
944 psppire_output_window_init (PsppireOutputWindow *window)
945 {
946   GtkTreeViewColumn *column;
947   GtkCellRenderer *renderer;
948   GtkBuilder *xml;
949   GtkAction *copy_action;
950   GtkAction *select_all_action;
951   GtkTreeSelection *sel;
952
953   string_map_init (&window->render_opts);
954
955   xml = builder_new ("output-viewer.ui");
956
957   copy_action = get_action_assert (xml, "edit_copy");
958   select_all_action = get_action_assert (xml, "edit_select-all");
959
960   gtk_action_set_sensitive (copy_action, FALSE);
961
962   g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window);
963
964   g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window);
965
966   gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
967
968   window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
969   window->y = 0;
970
971   window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
972
973   sel = gtk_tree_view_get_selection (window->overview);
974
975   gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
976
977   g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action);
978
979   gtk_tree_view_set_model (window->overview,
980                            GTK_TREE_MODEL (gtk_tree_store_new (
981                                              N_COLS,
982                                              G_TYPE_STRING,  /* COL_TITLE */
983                                              G_TYPE_POINTER, /* COL_ADDR */
984                                              G_TYPE_LONG))); /* COL_Y */
985
986   window->in_command = false;
987
988   window->items = NULL;
989   window->n_items = window->allocated_items = 0;
990
991   column = gtk_tree_view_column_new ();
992   gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
993   renderer = gtk_cell_renderer_text_new ();
994   gtk_tree_view_column_pack_start (column, renderer, TRUE);
995   gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
996
997   g_signal_connect (GTK_TREE_VIEW (window->overview),
998                     "row-activated", G_CALLBACK (on_row_activate), window);
999
1000   connect_help (xml);
1001
1002   g_signal_connect (window,
1003                     "focus-in-event",
1004                     G_CALLBACK (cancel_urgency),
1005                     NULL);
1006
1007   g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
1008                     "activate",
1009                     G_CALLBACK (psppire_window_minimise_all),
1010                     NULL);
1011
1012   {
1013     GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
1014     merge_help_menu (uim);
1015
1016     PSPPIRE_WINDOW (window)->menu =
1017       GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all")->parent);
1018   }
1019
1020   g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
1021                             G_CALLBACK (psppire_output_window_export), window);
1022
1023
1024   g_signal_connect_swapped (get_action_assert (xml, "file_print"), "activate",
1025                             G_CALLBACK (psppire_output_window_print), window);
1026
1027   g_object_unref (xml);
1028
1029   g_signal_connect (window, "delete-event",
1030                     G_CALLBACK (on_delete), window);
1031 }
1032
1033
1034 GtkWidget*
1035 psppire_output_window_new (void)
1036 {
1037   return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
1038                                    /* TRANSLATORS: This will form a filename.  Please avoid whitespace. */
1039                                    "filename", _("Output"),
1040                                    "description", _("Output Viewer"),
1041                                    NULL));
1042 }
1043
1044
1045 \f
1046 static void
1047 create_xr_print_driver (GtkPrintContext *context, PsppireOutputWindow *window)
1048 {
1049   struct string_map options;
1050   GtkPageSetup *page_setup;
1051   double width, height;
1052   double left_margin;
1053   double right_margin;
1054   double top_margin;
1055   double bottom_margin;
1056
1057   page_setup = gtk_print_context_get_page_setup (context);
1058   width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
1059   height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
1060   left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
1061   right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
1062   top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
1063   bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
1064
1065   string_map_init (&options);
1066   string_map_insert_nocopy (&options, xstrdup ("paper-size"),
1067                             xasprintf("%.2fx%.2fmm", width, height));
1068   string_map_insert_nocopy (&options, xstrdup ("left-margin"),
1069                             xasprintf ("%.2fmm", left_margin));
1070   string_map_insert_nocopy (&options, xstrdup ("right-margin"),
1071                             xasprintf ("%.2fmm", right_margin));
1072   string_map_insert_nocopy (&options, xstrdup ("top-margin"),
1073                             xasprintf ("%.2fmm", top_margin));
1074   string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
1075                             xasprintf ("%.2fmm", bottom_margin));
1076
1077   window->print_xrd =
1078     xr_driver_create (gtk_print_context_get_cairo_context (context), &options);
1079
1080   string_map_destroy (&options);
1081 }
1082
1083 static gboolean
1084 paginate (GtkPrintOperation *operation,
1085           GtkPrintContext   *context,
1086           PsppireOutputWindow *window)
1087 {
1088   if (window->paginated)
1089     {
1090       /* Sometimes GTK+ emits this signal again even after pagination is
1091          complete.  Don't let that screw up printing. */
1092       return TRUE;
1093     }
1094   else if ( window->print_item < window->n_items )
1095     {
1096       xr_driver_output_item (window->print_xrd, window->items[window->print_item++]);
1097       while (xr_driver_need_new_page (window->print_xrd))
1098         {
1099           xr_driver_next_page (window->print_xrd, NULL);
1100           window->print_n_pages ++;
1101         }
1102       return FALSE;
1103     }
1104   else
1105     {
1106       gtk_print_operation_set_n_pages (operation, window->print_n_pages);
1107
1108       /* Re-create the driver to do the real printing. */
1109       xr_driver_destroy (window->print_xrd);
1110       create_xr_print_driver (context, window);
1111       window->print_item = 0;
1112       window->paginated = TRUE;
1113
1114       return TRUE;
1115     }
1116 }
1117
1118 static void
1119 begin_print (GtkPrintOperation *operation,
1120              GtkPrintContext   *context,
1121              PsppireOutputWindow *window)
1122 {
1123   create_xr_print_driver (context, window);
1124
1125   window->print_item = 0;
1126   window->print_n_pages = 1;
1127   window->paginated = FALSE;
1128 }
1129
1130 static void
1131 end_print (GtkPrintOperation *operation,
1132            GtkPrintContext   *context,
1133            PsppireOutputWindow *window)
1134 {
1135   xr_driver_destroy (window->print_xrd);
1136 }
1137
1138
1139 static void
1140 draw_page (GtkPrintOperation *operation,
1141            GtkPrintContext   *context,
1142            gint               page_number,
1143            PsppireOutputWindow *window)
1144 {
1145   xr_driver_next_page (window->print_xrd, gtk_print_context_get_cairo_context (context));
1146   while (!xr_driver_need_new_page (window->print_xrd)
1147          && window->print_item < window->n_items)
1148     xr_driver_output_item (window->print_xrd, window->items [window->print_item++]);
1149 }
1150
1151
1152 static void
1153 psppire_output_window_print (PsppireOutputWindow *window)
1154 {
1155   GtkPrintOperationResult res;
1156
1157   GtkPrintOperation *print = gtk_print_operation_new ();
1158
1159   if (window->print_settings != NULL) 
1160     gtk_print_operation_set_print_settings (print, window->print_settings);
1161
1162   g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), window);
1163   g_signal_connect (print, "end_print", G_CALLBACK (end_print),     window);
1164   g_signal_connect (print, "paginate", G_CALLBACK (paginate),       window);
1165   g_signal_connect (print, "draw_page", G_CALLBACK (draw_page),     window);
1166
1167   res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
1168                                  GTK_WINDOW (window), NULL);
1169
1170   if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
1171     {
1172       if (window->print_settings != NULL)
1173         g_object_unref (window->print_settings);
1174       window->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
1175       
1176     }
1177
1178   g_object_unref (print);
1179 }