795804a82278d60fbf8810a0135ad010a72f7f00
[pspp] / src / ui / gui / psppire-output-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014  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 "ui/gui/psppire-output-window.h"
20
21 #include <errno.h>
22 #include <gtk/gtk.h>
23 #include <stdlib.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27
28 #include "libpspp/cast.h"
29 #include "libpspp/message.h"
30 #include "libpspp/string-map.h"
31 #include "output/chart-item.h"
32 #include "output/driver-provider.h"
33 #include "output/message-item.h"
34 #include "output/output-item.h"
35 #include "output/tab.h"
36 #include "output/table-item.h"
37 #include "output/text-item.h"
38 #include "ui/gui/help-menu.h"
39 #include "ui/gui/builder-wrapper.h"
40 #include "ui/gui/psppire-output-view.h"
41
42 #include "gl/xalloc.h"
43
44 #include "helper.h"
45
46 #include <gettext.h>
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
49
50 static void psppire_output_window_class_init    (PsppireOutputWindowClass *class);
51 static void psppire_output_window_init          (PsppireOutputWindow      *window);
52
53 GType
54 psppire_output_window_get_type (void)
55 {
56   static GType psppire_output_window_type = 0;
57
58   if (!psppire_output_window_type)
59     {
60       static const GTypeInfo psppire_output_window_info =
61       {
62         sizeof (PsppireOutputWindowClass),
63         (GBaseInitFunc) NULL,
64         (GBaseFinalizeFunc) NULL,
65         (GClassInitFunc)psppire_output_window_class_init,
66         (GClassFinalizeFunc) NULL,
67         NULL,
68         sizeof (PsppireOutputWindow),
69         0,
70         (GInstanceInitFunc) psppire_output_window_init,
71       };
72
73       psppire_output_window_type =
74         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
75                                 &psppire_output_window_info, 0);
76     }
77
78   return psppire_output_window_type;
79 }
80
81 static GObjectClass *parent_class;
82
83 static void
84 psppire_output_window_finalize (GObject *object)
85 {
86   if (G_OBJECT_CLASS (parent_class)->finalize)
87     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
88 }
89
90
91 static void
92 psppire_output_window_dispose (GObject *obj)
93 {
94   PsppireOutputWindow *window = PSPPIRE_OUTPUT_WINDOW (obj);
95
96   if (window->dispose_has_run) 
97     return;
98
99   window->dispose_has_run = TRUE;
100   psppire_output_view_destroy (window->view);
101   window->view = NULL;
102
103   /* Chain up to the parent class */
104   G_OBJECT_CLASS (parent_class)->dispose (obj);
105 }
106
107 static void
108 psppire_output_window_class_init (PsppireOutputWindowClass *class)
109 {
110   GObjectClass *object_class = G_OBJECT_CLASS (class);
111
112   parent_class = g_type_class_peek_parent (class);
113   object_class->dispose = psppire_output_window_dispose;
114   
115   object_class->finalize = psppire_output_window_finalize;
116 }
117 \f
118 /* Output driver class. */
119
120 struct psppire_output_driver
121   {
122     struct output_driver driver;
123     PsppireOutputWindow *window;
124   };
125
126 static struct output_driver_class psppire_output_class;
127
128 static struct psppire_output_driver *
129 psppire_output_cast (struct output_driver *driver)
130 {
131   assert (driver->class == &psppire_output_class);
132   return UP_CAST (driver, struct psppire_output_driver, driver);
133 }
134
135 static void
136 psppire_output_submit (struct output_driver *this,
137                        const struct output_item *item)
138 {
139   struct psppire_output_driver *pod = psppire_output_cast (this);
140   PsppireOutputWindow *window;
141   bool new;
142
143   new = pod->window == NULL;
144   if (new)
145     {
146       pod->window = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
147       pod->window->driver = pod;
148     }
149   window = pod->window;
150
151   psppire_output_view_put (window->view, item);
152
153   if (new)
154     {
155       /* We could have called this earlier in the previous "if (new)" block,
156          but doing it here finds, in a plain GTK+ environment, a bug that
157          otherwise only showed up on an Ubuntu Unity desktop.  See bug
158          #43362. */
159       gtk_widget_show_all (GTK_WIDGET (pod->window));
160     }
161
162   gtk_window_set_urgency_hint (GTK_WINDOW (pod->window), TRUE);
163 }
164
165 static struct output_driver_class psppire_output_class =
166   {
167     "PSPPIRE",                  /* name */
168     NULL,                       /* destroy */
169     psppire_output_submit,      /* submit */
170     NULL,                       /* flush */
171   };
172
173 void
174 psppire_output_window_setup (void)
175 {
176   struct psppire_output_driver *pod;
177   struct output_driver *d;
178
179   pod = xzalloc (sizeof *pod);
180   d = &pod->driver;
181   output_driver_init (d, &psppire_output_class, "PSPPIRE",
182                       SETTINGS_DEVICE_UNFILTERED);
183   output_driver_register (d);
184 }
185
186 \f
187
188 /* Callback for the "delete" action (clicking the x on the top right
189    hand corner of the window) */
190 static gboolean
191 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
192 {
193   PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
194
195   gtk_widget_destroy (GTK_WIDGET (ow));
196
197   ow->driver->window = NULL;
198
199   return FALSE;
200 }
201
202
203
204 static void
205 cancel_urgency (GtkWindow *window,  gpointer data)
206 {
207   gtk_window_set_urgency_hint (window, FALSE);
208 }
209
210 static void psppire_output_window_print (PsppireOutputWindow *window);
211
212
213 static void
214 export_output (PsppireOutputWindow *window, struct string_map *options,
215                const char *format)
216 {
217   string_map_insert (options, "format", format);
218   psppire_output_view_export (window->view, options);
219 }
220
221
222 struct file_types
223 {
224   const gchar *label;
225   const gchar *ext;
226 };
227
228 enum 
229   {
230     FT_AUTO = 0,
231     FT_PDF,
232     FT_HTML,
233     FT_ODT,
234     FT_TXT,
235     FT_ASCII,
236     FT_PS,
237     FT_CSV,
238     n_FT
239   };
240
241 #define N_EXTENSIONS (n_FT - 1)
242
243 struct file_types ft[n_FT] = {
244   {N_("Infer file type from extension"),  NULL},
245   {N_("PDF (*.pdf)"),                     ".pdf"},
246   {N_("HTML (*.html)"),                   ".html"},
247   {N_("OpenDocument (*.odt)"),            ".odt"},
248   {N_("Text (*.txt)"),                    ".txt"},
249   {N_("Text [plain] (*.txt)"),            ".txt"},
250   {N_("PostScript (*.ps)"),               ".ps"},
251   {N_("Comma-Separated Values (*.csv)"),  ".csv"}
252 };
253
254
255 static void
256 on_combo_change (GtkFileChooser *chooser)
257 {
258   gboolean sensitive = FALSE;
259   GtkWidget *combo = gtk_file_chooser_get_extra_widget (chooser);
260
261   int x = 0; 
262   gchar *fn = gtk_file_chooser_get_filename (chooser);
263
264   if (combo &&  gtk_widget_get_realized (combo))
265     x = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
266
267   if (fn == NULL)
268     {
269       sensitive = FALSE;
270     }
271   else
272     {
273       gint i;
274       if ( x != 0 )
275         sensitive = TRUE;
276
277       for (i = 1 ; i < N_EXTENSIONS ; ++i)
278         {
279           if ( g_str_has_suffix (fn, ft[i].ext))
280             {
281               sensitive = TRUE;
282               break;
283             }
284         }
285     }
286
287   g_free (fn);
288
289   gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, sensitive);
290 }
291
292
293 static void
294 on_file_chooser_change (GObject *w, GParamSpec *pspec, gpointer data)
295 {
296
297   GtkFileChooser *chooser = data;
298   const gchar *name = g_param_spec_get_name (pspec);
299
300   if ( ! gtk_widget_get_realized (GTK_WIDGET (chooser)))
301     return;
302
303   /* Ignore this one.  It causes recursion. */
304   if ( 0 == strcmp ("tooltip-text", name))
305     return;
306
307   on_combo_change (chooser);
308 }
309
310
311 /* Recursively descend all the children of W, connecting
312    to their "notify" signal */
313 static void
314 iterate_widgets (GtkWidget *w, gpointer data)
315 {
316   if ( GTK_IS_CONTAINER (w))
317     gtk_container_forall (GTK_CONTAINER (w), iterate_widgets, data);
318   else
319     g_signal_connect (w, "notify",  G_CALLBACK (on_file_chooser_change), data);
320 }
321
322
323
324 static GtkListStore *
325 create_file_type_list (void)
326 {
327   int i;
328   GtkTreeIter iter;
329   GtkListStore *list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
330   
331   for (i = 0 ; i < n_FT ; ++i)
332     {
333       gtk_list_store_append (list, &iter);
334       gtk_list_store_set (list, &iter,
335                           0,  gettext (ft[i].label),
336                           1,  ft[i].ext,
337                           -1);
338     }
339   
340   return list;
341 }
342
343 static void
344 psppire_output_window_export (PsppireOutputWindow *window)
345 {
346   gint response;
347   GtkWidget *combo;
348   GtkListStore *list;
349
350   GtkFileChooser *chooser;
351   
352   GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Export Output"),
353                                         GTK_WINDOW (window),
354                                         GTK_FILE_CHOOSER_ACTION_SAVE,
355                                         _("Cancel"), GTK_RESPONSE_CANCEL,
356                                         _("Save"),   GTK_RESPONSE_ACCEPT,
357                                         NULL);
358
359   g_object_set (dialog, "local-only", FALSE, NULL);
360
361   chooser = GTK_FILE_CHOOSER (dialog);
362
363   list = create_file_type_list ();
364
365   combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list));
366
367
368   {
369     /* Create text cell renderer */
370     GtkCellRenderer *cell = gtk_cell_renderer_text_new();
371     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE );
372
373     gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell,  "text", 0);
374   }
375
376   g_signal_connect_swapped (combo, "changed", G_CALLBACK (on_combo_change), chooser);
377
378   gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
379
380   gtk_file_chooser_set_extra_widget (chooser, combo);
381
382   /* This kludge is necessary because there is no signal to tell us
383      when the candidate filename of a GtkFileChooser has changed */
384   gtk_container_forall (GTK_CONTAINER (dialog), iterate_widgets, dialog);
385
386
387   gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
388
389   response = gtk_dialog_run (GTK_DIALOG (dialog));
390
391   if ( response == GTK_RESPONSE_ACCEPT )
392     {
393       gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
394       gchar *filename = gtk_file_chooser_get_filename (chooser);
395       struct string_map options;
396
397       g_return_if_fail (filename);
398
399       if (file_type == FT_AUTO)
400         {
401           /* If the "Infer file type from extension" option was chosen,
402              search for the respective type in the list.
403              (It's a O(n) search, but fortunately n is small). */
404           gint i;
405           for (i = 1 ; i < N_EXTENSIONS ; ++i)
406             {
407               if ( g_str_has_suffix (filename, ft[i].ext))
408                 {
409                   file_type = i;
410                   break;
411                 }
412             }
413         }
414       else if (! g_str_has_suffix (filename, ft[file_type].ext))
415         {
416           /* If an explicit document format was chosen, and if the chosen
417              filename does not already have that particular "extension",
418              then append it.
419            */
420
421           gchar *of = filename;
422           filename = g_strconcat (filename, ft[file_type].ext, NULL);
423           g_free (of);
424         }
425       
426       string_map_init (&options);
427       string_map_insert (&options, "output-file", filename);
428
429       switch (file_type)
430         {
431         case FT_PDF:
432           export_output (window, &options, "pdf");
433           break;
434         case FT_HTML:
435           export_output (window, &options, "html");
436           break;
437         case FT_ODT:
438           export_output (window, &options, "odt");
439           break;
440         case FT_PS:
441           export_output (window, &options, "ps");
442           break;
443         case FT_CSV:
444           export_output (window, &options, "csv");
445           break;
446
447         case FT_TXT:
448           string_map_insert (&options, "box", "unicode");
449           /* Fall through */
450
451         case FT_ASCII:
452           string_map_insert (&options, "headers", "false");
453           string_map_insert (&options, "paginate", "false");
454           string_map_insert (&options, "squeeze", "true");
455           string_map_insert (&options, "emphasis", "none");
456           string_map_insert (&options, "charts", "none");
457           string_map_insert (&options, "top-margin", "0");
458           string_map_insert (&options, "bottom-margin", "0");
459           export_output (window, &options, "txt");
460           break;
461         default:
462           g_assert_not_reached ();
463         }
464
465       string_map_destroy (&options);
466
467       free (filename);
468     }
469
470   gtk_widget_destroy (dialog);
471 }
472
473 static void
474 psppire_output_window_init (PsppireOutputWindow *window)
475 {
476   GtkBuilder *xml = builder_new ("output-window.ui");
477
478   GtkWidget *box = get_widget_assert (xml, "box1");
479   gtk_container_add (GTK_CONTAINER (window), box);
480
481   window->dispose_has_run = FALSE;
482
483   window->view = psppire_output_view_new (
484     GTK_LAYOUT (get_widget_assert (xml, "output")),
485     GTK_TREE_VIEW (get_widget_assert (xml, "overview")),
486     get_action_assert (xml, "edit_copy"),
487     get_action_assert (xml, "edit_select-all"));
488
489
490   connect_help (xml);
491
492   g_signal_connect (window,
493                     "focus-in-event",
494                     G_CALLBACK (cancel_urgency),
495                     NULL);
496
497   g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
498                     "activate",
499                     G_CALLBACK (psppire_window_minimise_all),
500                     NULL);
501
502   {
503     GtkWidget *w;
504     GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
505
506     GtkWidget *menubar = get_widget_assert (xml, "menubar");
507
508     gtk_menu_shell_append (GTK_MENU_SHELL (menubar),  create_help_menu (GTK_WINDOW (window)));
509
510     w = gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all");
511
512     PSPPIRE_WINDOW (window)->menu =
513       GTK_MENU_SHELL (gtk_widget_get_parent (w));
514   }
515
516   g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
517                             G_CALLBACK (psppire_output_window_export), window);
518
519
520   g_signal_connect_swapped (get_action_assert (xml, "file_print"), "activate",
521                             G_CALLBACK (psppire_output_window_print), window);
522
523   g_object_unref (xml);
524
525   g_signal_connect (window, "delete-event",
526                     G_CALLBACK (on_delete), window);
527 }
528
529
530 GtkWidget*
531 psppire_output_window_new (void)
532 {
533   return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
534                                    /* TRANSLATORS: This will form a filename.  Please avoid whitespace. */
535                                    "filename", _("Output"),
536                                    "description", _("Output Viewer"),
537                                    NULL));
538 }
539
540 static void
541 psppire_output_window_print (PsppireOutputWindow *window)
542 {
543   psppire_output_view_print (window->view, GTK_WINDOW (window));
544 }