psppire: Add File|Export command to output viewer window.
[pspp-builds.git] / src / ui / gui / psppire-output-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009  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 <gtk/gtksignal.h>
20 #include <gtk/gtkbox.h>
21 #include "helper.h"
22
23 #include <libpspp/cast.h>
24 #include <libpspp/message.h>
25 #include <libpspp/string-map.h>
26 #include <output/cairo.h>
27 #include <output/driver-provider.h>
28 #include <output/output-item.h>
29 #include <output/tab.h>
30 #include <stdlib.h>
31
32 #include "about.h"
33
34 #include "psppire-output-window.h"
35
36
37 #include "xalloc.h"
38
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <unistd.h>
42
43 #include <gettext.h>
44 #define _(msgid) gettext (msgid)
45 #define N_(msgid) msgid
46
47 enum
48   {
49     COL_TITLE,                  /* Table title. */
50     COL_Y,                      /* Y position of top of title. */
51     N_COLS
52   };
53
54 static void psppire_output_window_base_finalize (PsppireOutputWindowClass *, gpointer);
55 static void psppire_output_window_base_init     (PsppireOutputWindowClass *class);
56 static void psppire_output_window_class_init    (PsppireOutputWindowClass *class);
57 static void psppire_output_window_init          (PsppireOutputWindow      *window);
58
59
60 GType
61 psppire_output_window_get_type (void)
62 {
63   static GType psppire_output_window_type = 0;
64
65   if (!psppire_output_window_type)
66     {
67       static const GTypeInfo psppire_output_window_info =
68       {
69         sizeof (PsppireOutputWindowClass),
70         (GBaseInitFunc) psppire_output_window_base_init,
71         (GBaseFinalizeFunc) psppire_output_window_base_finalize,
72         (GClassInitFunc)psppire_output_window_class_init,
73         (GClassFinalizeFunc) NULL,
74         NULL,
75         sizeof (PsppireOutputWindow),
76         0,
77         (GInstanceInitFunc) psppire_output_window_init,
78       };
79
80       psppire_output_window_type =
81         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
82                                 &psppire_output_window_info, 0);
83     }
84
85   return psppire_output_window_type;
86 }
87
88 static GObjectClass *parent_class;
89
90 static void
91 psppire_output_window_finalize (GObject *object)
92 {
93   if (G_OBJECT_CLASS (parent_class)->finalize)
94     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
95 }
96
97
98 static void
99 psppire_output_window_dispose (GObject *obj)
100 {
101   PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
102   size_t i;
103
104   for (i = 0; i < viewer->n_items; i++)
105     output_item_unref (viewer->items[i]);
106   free (viewer->items);
107   viewer->items = NULL;
108   viewer->n_items = viewer->allocated_items = 0;
109
110   /* Chain up to the parent class */
111   G_OBJECT_CLASS (parent_class)->dispose (obj);
112 }
113
114 static void
115 psppire_output_window_class_init (PsppireOutputWindowClass *class)
116 {
117   GObjectClass *object_class = G_OBJECT_CLASS (class);
118
119   parent_class = g_type_class_peek_parent (class);
120   object_class->dispose = psppire_output_window_dispose;
121 }
122
123
124 static void
125 psppire_output_window_base_init (PsppireOutputWindowClass *class)
126 {
127   GObjectClass *object_class = G_OBJECT_CLASS (class);
128
129   object_class->finalize = psppire_output_window_finalize;
130 }
131
132
133
134 static void
135 psppire_output_window_base_finalize (PsppireOutputWindowClass *class,
136                                      gpointer class_data)
137 {
138 }
139 \f
140 /* Output driver class. */
141
142 struct psppire_output_driver
143   {
144     struct output_driver driver;
145     PsppireOutputWindow *viewer;
146     struct xr_driver *xr;
147   };
148
149 static struct output_driver_class psppire_output_class;
150
151 static struct psppire_output_driver *
152 psppire_output_cast (struct output_driver *driver)
153 {
154   assert (driver->class == &psppire_output_class);
155   return UP_CAST (driver, struct psppire_output_driver, driver);
156 }
157
158 static gboolean
159 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
160 {
161   struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
162   cairo_t *cr;
163
164   cr = gdk_cairo_create (widget->window);
165   xr_rendering_draw (r, cr);
166   cairo_destroy (cr);
167
168   return TRUE;
169 }
170
171 static void
172 psppire_output_submit (struct output_driver *this,
173                        const struct output_item *item)
174 {
175   struct psppire_output_driver *pod = psppire_output_cast (this);
176   PsppireOutputWindow *viewer;
177   GtkWidget *drawing_area;
178   struct xr_rendering *r;
179   cairo_t *cr;
180   int tw, th;
181
182   if (pod->viewer == NULL)
183     {
184       pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
185       gtk_widget_show_all (GTK_WIDGET (pod->viewer));
186       pod->viewer->driver = pod;
187     }
188   viewer = pod->viewer;
189
190   cr = gdk_cairo_create (GTK_WIDGET (pod->viewer)->window);
191   if (pod->xr == NULL)
192     pod->xr = xr_create_driver (cr);
193
194   r = xr_rendering_create (pod->xr, item, cr);
195   if (r == NULL)
196     goto done;
197
198   if (viewer->n_items >= viewer->allocated_items)
199     viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items,
200                                 sizeof *viewer->items);
201   viewer->items[viewer->n_items++] = output_item_ref (item);
202
203   xr_rendering_measure (r, &tw, &th);
204
205   drawing_area = gtk_drawing_area_new ();
206   gtk_widget_modify_bg (
207     GTK_WIDGET (drawing_area), GTK_STATE_NORMAL,
208     &gtk_widget_get_style (drawing_area)->base[GTK_STATE_NORMAL]);
209   g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
210   gtk_widget_set_size_request (drawing_area, tw, th);
211   gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y);
212   gtk_widget_show (drawing_area);
213   g_signal_connect (G_OBJECT (drawing_area), "expose_event",
214                      G_CALLBACK (expose_event_callback), NULL);
215
216   if (pod->viewer->max_width < tw)
217     pod->viewer->max_width = tw;
218   pod->viewer->y += th;
219
220   gtk_layout_set_size (pod->viewer->output,
221                        pod->viewer->max_width, pod->viewer->y);
222
223   gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE);
224
225 done:
226   cairo_destroy (cr);
227 }
228
229 static struct output_driver_class psppire_output_class =
230   {
231     "PSPPIRE",                  /* name */
232     NULL,                       /* create */
233     NULL,                       /* destroy */
234     psppire_output_submit,      /* submit */
235     NULL,                       /* flush */
236   };
237
238 void
239 psppire_output_window_setup (void)
240 {
241   struct psppire_output_driver *pod;
242   struct output_driver *d;
243
244   pod = xzalloc (sizeof *pod);
245   d = &pod->driver;
246   output_driver_init (d, &psppire_output_class, "PSPPIRE", 0);
247   output_driver_register (d);
248 }
249 \f
250 int viewer_length = 16;
251 int viewer_width = 59;
252
253 /* Callback for the "delete" action (clicking the x on the top right
254    hand corner of the window) */
255 static gboolean
256 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
257 {
258   PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
259
260   gtk_widget_destroy (GTK_WIDGET (ow));
261
262   ow->driver->viewer = NULL;
263
264   return FALSE;
265 }
266
267
268
269 static void
270 cancel_urgency (GtkWindow *window,  gpointer data)
271 {
272   gtk_window_set_urgency_hint (window, FALSE);
273 }
274
275 static void
276 on_row_activate (GtkTreeView *overview,
277                  GtkTreePath *path,
278                  GtkTreeViewColumn *column,
279                  PsppireOutputWindow *window)
280 {
281   GtkTreeModel *model;
282   GtkTreeIter iter;
283   GtkAdjustment *vadj;
284   GValue value = {0};
285   double y, min, max;
286
287   model = gtk_tree_view_get_model (overview);
288   if (!gtk_tree_model_get_iter (model, &iter, path))
289     return;
290
291   gtk_tree_model_get_value (model, &iter, COL_Y, &value);
292   y = g_value_get_long (&value);
293   g_value_unset (&value);
294
295   vadj = gtk_layout_get_vadjustment (window->output);
296   min = vadj->lower;
297   max = vadj->upper - vadj->page_size;
298   if (y < min)
299     y = min;
300   else if (y > max)
301     y = max;
302   gtk_adjustment_set_value (vadj, y);
303 }
304
305 static GtkFileFilter *
306 add_filter (GtkFileChooser *chooser, const char *name, const char *pattern)
307 {
308   GtkFileFilter *filter = gtk_file_filter_new ();
309   g_object_ref_sink (G_OBJECT (filter));
310   gtk_file_filter_set_name (filter, name);
311   gtk_file_filter_add_pattern (filter, pattern);
312   gtk_file_chooser_add_filter (chooser, filter);
313   return filter;
314 }
315
316 static void
317 export_output (PsppireOutputWindow *window, struct string_map *options,
318                const char *class_name)
319 {
320   struct output_driver *driver;
321   size_t i;
322
323   driver = output_driver_create (class_name, options);
324   if (driver == NULL)
325     return;
326
327   for (i = 0; i < window->n_items; i++)
328     driver->class->submit (driver, window->items[i]);
329   output_driver_destroy (driver);
330 }
331
332 static void
333 psppire_output_window_export (PsppireOutputWindow *window)
334 {
335   gint response;
336
337   GtkFileFilter *pdf_filter;
338   GtkFileFilter *html_filter;
339   GtkFileFilter *odt_filter;
340   GtkFileFilter *txt_filter;
341   GtkFileFilter *ps_filter;
342   GtkFileFilter *csv_filter;
343   GtkFileChooser *chooser;
344   GtkWidget *dialog;
345
346   dialog = gtk_file_chooser_dialog_new (_("Export Output"),
347                                         GTK_WINDOW (window),
348                                         GTK_FILE_CHOOSER_ACTION_SAVE,
349                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
350                                         GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
351                                         NULL);
352   chooser = GTK_FILE_CHOOSER (dialog);
353
354   pdf_filter = add_filter (chooser, _("PDF Files (*.pdf)"), "*.pdf");
355   html_filter = add_filter (chooser, _("HTML Files (*.html)"), "*.html");
356   odt_filter = add_filter (chooser, _("OpenDocument Files (*.odt)"), "*.odt");
357   txt_filter = add_filter (chooser, _("Text Files (*.txt)"), "*.txt");
358   ps_filter = add_filter (chooser, _("PostScript Files (*.ps)"), "*.ps");
359   csv_filter = add_filter (chooser, _("Comma-Separated Value Files (*.csv)"),
360                            "*.csv");
361
362   gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
363   gtk_file_chooser_set_filter (chooser, pdf_filter);
364
365   response = gtk_dialog_run (GTK_DIALOG (dialog));
366
367   if ( response == GTK_RESPONSE_ACCEPT )
368     {
369       char *filename = gtk_file_chooser_get_filename (chooser);
370       GtkFileFilter *filter = gtk_file_chooser_get_filter (chooser);
371       struct string_map options;
372
373       g_return_if_fail (filename);
374       g_return_if_fail (filter);
375
376       string_map_init (&options);
377       string_map_insert (&options, "output-file", filename);
378       if (filter == pdf_filter)
379         {
380           string_map_insert (&options, "output-type", "pdf");
381           export_output (window, &options, "cairo");
382         }
383       else if (filter == html_filter)
384         export_output (window, &options, "html");
385       else if (filter == odt_filter)
386         export_output (window, &options, "odf");
387       else if (filter == txt_filter)
388         {
389           string_map_insert (&options, "headers", "false");
390           string_map_insert (&options, "paginate", "false");
391           string_map_insert (&options, "squeeze", "true");
392           string_map_insert (&options, "emphasis", "none");
393           string_map_insert (&options, "chart-type", "none");
394           string_map_insert (&options, "top-margin", "0");
395           string_map_insert (&options, "bottom-margin", "0");
396           export_output (window, &options, "ascii");
397         }
398       else if (filter == ps_filter)
399         {
400           string_map_insert (&options, "output-type", "ps");
401           export_output (window, &options, "cairo");
402         }
403       else if (filter == csv_filter)
404         export_output (window, &options, "csv");
405       else
406         g_return_if_reached ();
407
408       free (filename);
409     }
410
411   g_object_unref (G_OBJECT (pdf_filter));
412   g_object_unref (G_OBJECT (html_filter));
413   g_object_unref (G_OBJECT (txt_filter));
414   g_object_unref (G_OBJECT (ps_filter));
415   g_object_unref (G_OBJECT (csv_filter));
416
417   gtk_widget_destroy (dialog);
418 }
419
420 static void
421 psppire_output_window_init (PsppireOutputWindow *window)
422 {
423   GtkTreeViewColumn *column;
424   GtkCellRenderer *renderer;
425   GtkBuilder *xml;
426
427   xml = builder_new ("output-viewer.ui");
428
429   gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
430
431   window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
432   window->y = 0;
433
434   window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
435   gtk_tree_view_set_model (window->overview,
436                            GTK_TREE_MODEL (gtk_tree_store_new (
437                                              N_COLS,
438                                              G_TYPE_STRING, /* COL_TITLE */
439                                              G_TYPE_LONG))); /* COL_Y */
440   window->last_table_num = -1;
441
442   window->items = NULL;
443   window->n_items = window->allocated_items = 0;
444
445   column = gtk_tree_view_column_new ();
446   gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
447   renderer = gtk_cell_renderer_text_new ();
448   gtk_tree_view_column_pack_start (column, renderer, TRUE);
449   gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
450
451   g_signal_connect (GTK_TREE_VIEW (window->overview),
452                     "row-activated", G_CALLBACK (on_row_activate), window);
453
454   gtk_widget_modify_bg (GTK_WIDGET (window->output), GTK_STATE_NORMAL,
455                         &gtk_widget_get_style (GTK_WIDGET (window->output))->base[GTK_STATE_NORMAL]);
456
457   connect_help (xml);
458
459   g_signal_connect (window,
460                     "focus-in-event",
461                     G_CALLBACK (cancel_urgency),
462                     NULL);
463
464   g_signal_connect (get_action_assert (xml,"help_about"),
465                     "activate",
466                     G_CALLBACK (about_new),
467                     window);
468
469   g_signal_connect (get_action_assert (xml,"help_reference"),
470                     "activate",
471                     G_CALLBACK (reference_manual),
472                     NULL);
473
474   g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
475                     "activate",
476                     G_CALLBACK (psppire_window_minimise_all),
477                     NULL);
478
479   {
480     GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
481
482     PSPPIRE_WINDOW (window)->menu =
483       GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar1/windows_menuitem/windows_minimise-all")->parent);
484   }
485
486   g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
487                             G_CALLBACK (psppire_output_window_export), window);
488
489   g_object_unref (xml);
490
491   g_signal_connect (window, "delete-event",
492                     G_CALLBACK (on_delete), window);
493 }
494
495
496 GtkWidget*
497 psppire_output_window_new (void)
498 {
499   return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
500                                    "filename", "Output",
501                                    "description", _("Output Viewer"),
502                                    NULL));
503 }