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