NPar two samples dialog and T-Test paired dialogs: Converted to PsppireDialogAction
[pspp] / src / ui / gui / psppire-data-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013  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/gtk.h>
20 #include <stdlib.h>
21
22 #include "data/dataset.h"
23 #include "data/session.h"
24 #include "language/lexer/lexer.h"
25 #include "libpspp/message.h"
26 #include "libpspp/str.h"
27 #include "ui/gui/aggregate-dialog.h"
28 #include "ui/gui/autorecode-dialog.h"
29 #include "ui/gui/builder-wrapper.h"
30 #include "ui/gui/comments-dialog.h"
31 #include "ui/gui/entry-dialog.h"
32 #include "ui/gui/executor.h"
33 #include "ui/gui/help-menu.h"
34 #include "ui/gui/helper.h"
35 #include "ui/gui/helper.h"
36 #include "ui/gui/oneway-anova-dialog.h"
37 #include "ui/gui/psppire-data-window.h"
38 #include "ui/gui/psppire-dialog-action.h"
39 #include "ui/gui/psppire-syntax-window.h"
40 #include "ui/gui/psppire-window.h"
41 #include "ui/gui/psppire.h"
42 #include "ui/gui/recode-dialog.h"
43 #include "ui/gui/select-cases-dialog.h"
44 #include "ui/gui/split-file-dialog.h"
45 #include "ui/gui/text-data-import-dialog.h"
46 #include "ui/gui/weight-cases-dialog.h"
47 #include "ui/syntax-gen.h"
48
49 #include "gl/c-strcase.h"
50 #include "gl/c-strcasestr.h"
51 #include "gl/xvasprintf.h"
52
53 #include <gettext.h>
54 #define _(msgid) gettext (msgid)
55 #define N_(msgid) msgid
56
57 struct session *the_session;
58 struct ll_list all_data_windows = LL_INITIALIZER (all_data_windows);
59
60 static void psppire_data_window_class_init    (PsppireDataWindowClass *class);
61 static void psppire_data_window_init          (PsppireDataWindow      *data_editor);
62
63
64 static void psppire_data_window_iface_init (PsppireWindowIface *iface);
65
66 static void psppire_data_window_dispose (GObject *object);
67 static void psppire_data_window_finalize (GObject *object);
68 static void psppire_data_window_set_property (GObject         *object,
69                                               guint            prop_id,
70                                               const GValue    *value,
71                                               GParamSpec      *pspec);
72 static void psppire_data_window_get_property (GObject         *object,
73                                               guint            prop_id,
74                                               GValue          *value,
75                                               GParamSpec      *pspec);
76
77 static guint psppire_data_window_add_ui (PsppireDataWindow *, GtkUIManager *);
78 static void psppire_data_window_remove_ui (PsppireDataWindow *,
79                                            GtkUIManager *, guint);
80
81 GType
82 psppire_data_window_get_type (void)
83 {
84   static GType psppire_data_window_type = 0;
85
86   if (!psppire_data_window_type)
87     {
88       static const GTypeInfo psppire_data_window_info =
89         {
90           sizeof (PsppireDataWindowClass),
91           NULL,
92           NULL,
93           (GClassInitFunc)psppire_data_window_class_init,
94           (GClassFinalizeFunc) NULL,
95           NULL,
96           sizeof (PsppireDataWindow),
97           0,
98           (GInstanceInitFunc) psppire_data_window_init,
99         };
100
101       static const GInterfaceInfo window_interface_info =
102         {
103           (GInterfaceInitFunc) psppire_data_window_iface_init,
104           NULL,
105           NULL
106         };
107
108       psppire_data_window_type =
109         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireDataWindow",
110                                 &psppire_data_window_info, 0);
111
112
113       g_type_add_interface_static (psppire_data_window_type,
114                                    PSPPIRE_TYPE_WINDOW_MODEL,
115                                    &window_interface_info);
116     }
117
118   return psppire_data_window_type;
119 }
120
121 static GObjectClass *parent_class ;
122
123 enum {
124     PROP_DATASET = 1
125 };
126
127 static void
128 psppire_data_window_class_init (PsppireDataWindowClass *class)
129 {
130   GObjectClass *object_class = G_OBJECT_CLASS (class);
131
132   parent_class = g_type_class_peek_parent (class);
133
134   object_class->dispose = psppire_data_window_dispose;
135   object_class->finalize = psppire_data_window_finalize;
136   object_class->set_property = psppire_data_window_set_property;
137   object_class->get_property = psppire_data_window_get_property;
138
139   g_object_class_install_property (
140     object_class, PROP_DATASET,
141     g_param_spec_pointer ("dataset", "Dataset",
142                           "'struct datset *' represented by the window",
143                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
144 }
145 \f
146 /* Run the EXECUTE command. */
147 static void
148 execute (PsppireDataWindow *dw)
149 {
150   execute_const_syntax_string (dw, "EXECUTE.");
151 }
152
153 static void
154 transformation_change_callback (bool transformations_pending,
155                                 gpointer data)
156 {
157   PsppireDataWindow  *de = PSPPIRE_DATA_WINDOW (data);
158
159   GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
160
161   GtkWidget *menuitem =
162     gtk_ui_manager_get_widget (uim,"/ui/menubar/transform/transform_run-pending");
163
164   GtkWidget *status_label  =
165     get_widget_assert (de->builder, "case-counter-area");
166
167   gtk_widget_set_sensitive (menuitem, transformations_pending);
168
169
170   if ( transformations_pending)
171     gtk_label_set_text (GTK_LABEL (status_label),
172                         _("Transformations Pending"));
173   else
174     gtk_label_set_text (GTK_LABEL (status_label), "");
175 }
176
177 /* Callback for when the dictionary changes its filter variable */
178 static void
179 on_filter_change (GObject *o, gint filter_index, gpointer data)
180 {
181   PsppireDataWindow  *de = PSPPIRE_DATA_WINDOW (data);
182
183   GtkWidget *filter_status_area =
184     get_widget_assert (de->builder, "filter-use-status-area");
185
186   if ( filter_index == -1 )
187     {
188       gtk_label_set_text (GTK_LABEL (filter_status_area), _("Filter off"));
189     }
190   else
191     {
192       PsppireDict *dict = NULL;
193       struct variable *var ;
194       gchar *text ;
195
196       g_object_get (de->data_editor, "dictionary", &dict, NULL);
197
198       var = psppire_dict_get_variable (dict, filter_index);
199
200       text = g_strdup_printf (_("Filter by %s"), var_get_name (var));
201
202       gtk_label_set_text (GTK_LABEL (filter_status_area), text);
203
204       g_free (text);
205     }
206 }
207
208 /* Callback for when the dictionary changes its split variables */
209 static void
210 on_split_change (PsppireDict *dict, gpointer data)
211 {
212   PsppireDataWindow  *de = PSPPIRE_DATA_WINDOW (data);
213
214   size_t n_split_vars = dict_get_split_cnt (dict->dict);
215
216   GtkWidget *split_status_area =
217     get_widget_assert (de->builder, "split-file-status-area");
218
219   if ( n_split_vars == 0 )
220     {
221       gtk_label_set_text (GTK_LABEL (split_status_area), _("No Split"));
222     }
223   else
224     {
225       gint i;
226       GString *text;
227       const struct variable *const * split_vars =
228         dict_get_split_vars (dict->dict);
229
230       text = g_string_new (_("Split by "));
231
232       for (i = 0 ; i < n_split_vars - 1; ++i )
233         {
234           g_string_append_printf (text, "%s, ", var_get_name (split_vars[i]));
235         }
236       g_string_append (text, var_get_name (split_vars[i]));
237
238       gtk_label_set_text (GTK_LABEL (split_status_area), text->str);
239
240       g_string_free (text, TRUE);
241     }
242 }
243
244
245
246
247 /* Callback for when the dictionary changes its weights */
248 static void
249 on_weight_change (GObject *o, gint weight_index, gpointer data)
250 {
251   PsppireDataWindow  *de = PSPPIRE_DATA_WINDOW (data);
252
253   GtkWidget *weight_status_area =
254     get_widget_assert (de->builder, "weight-status-area");
255
256   if ( weight_index == -1 )
257     {
258       gtk_label_set_text (GTK_LABEL (weight_status_area), _("Weights off"));
259     }
260   else
261     {
262       struct variable *var ;
263       PsppireDict *dict = NULL;
264       gchar *text;
265
266       g_object_get (de->data_editor, "dictionary", &dict, NULL);
267
268       var = psppire_dict_get_variable (dict, weight_index);
269
270       text = g_strdup_printf (_("Weight by %s"), var_get_name (var));
271
272       gtk_label_set_text (GTK_LABEL (weight_status_area), text);
273
274       g_free (text);
275     }
276 }
277
278 #if 0
279 static void
280 dump_rm (GtkRecentManager *rm)
281 {
282   GList *items = gtk_recent_manager_get_items (rm);
283
284   GList *i;
285
286   g_print ("Recent Items:\n");
287   for (i = items; i; i = i->next)
288     {
289       GtkRecentInfo *ri = i->data;
290
291       g_print ("Item: %s (Mime: %s) (Desc: %s) (URI: %s)\n",
292                gtk_recent_info_get_short_name (ri),
293                gtk_recent_info_get_mime_type (ri),
294                gtk_recent_info_get_description (ri),
295                gtk_recent_info_get_uri (ri)
296                );
297
298
299       gtk_recent_info_unref (ri);
300     }
301
302   g_list_free (items);
303 }
304 #endif
305
306 static gboolean
307 name_has_por_suffix (const gchar *name)
308 {
309   size_t length = strlen (name);
310   return length > 4 && !c_strcasecmp (&name[length - 4], ".por");
311 }
312
313 static gboolean
314 name_has_sav_suffix (const gchar *name)
315 {
316   size_t length = strlen (name);
317   return length > 4 && !c_strcasecmp (&name[length - 4], ".sav");
318 }
319
320 /* Returns true if NAME has a suffix which might denote a PSPP file */
321 static gboolean
322 name_has_suffix (const gchar *name)
323 {
324   return name_has_por_suffix (name) || name_has_sav_suffix (name);
325 }
326
327 static gboolean
328 load_file (PsppireWindow *de, const gchar *file_name, gpointer syn)
329 {
330   const char *mime_type = NULL;
331   gchar *syntax = NULL;
332   bool ok;
333
334   if (syn == NULL)
335     {
336       gchar *utf8_file_name;
337       struct string filename;
338       ds_init_empty (&filename);
339       
340       utf8_file_name = g_filename_to_utf8 (file_name, -1, NULL, NULL, NULL);
341     
342       syntax_gen_string (&filename, ss_cstr (utf8_file_name));
343       
344       g_free (utf8_file_name);
345       
346       syntax = g_strdup_printf ("GET FILE=%s.", ds_cstr (&filename));
347       ds_destroy (&filename);
348
349     }
350   else
351     {
352       syntax = syn;
353     }
354
355   ok = execute_syntax (PSPPIRE_DATA_WINDOW (de),
356                        lex_reader_for_string (syntax));
357   g_free (syntax);
358
359   if (ok && syn == NULL)
360     {
361       if (name_has_por_suffix (file_name))
362         mime_type = "application/x-spss-por";
363       else if (name_has_sav_suffix (file_name))
364         mime_type = "application/x-spss-sav";
365       
366       add_most_recent (file_name, mime_type);
367     }
368
369   return ok;
370 }
371
372 /* Save DE to file */
373 static void
374 save_file (PsppireWindow *w)
375 {
376   const gchar *file_name = NULL;
377   gchar *utf8_file_name = NULL;
378   GString *fnx;
379   struct string filename ;
380   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (w);
381   gchar *syntax;
382
383   file_name = psppire_window_get_filename (w);
384
385   fnx = g_string_new (file_name);
386
387   if ( ! name_has_suffix (fnx->str))
388     {
389       if ( de->save_as_portable)
390         g_string_append (fnx, ".por");
391       else
392         g_string_append (fnx, ".sav");
393     }
394
395   ds_init_empty (&filename);
396
397   utf8_file_name = g_filename_to_utf8 (fnx->str, -1, NULL, NULL, NULL);
398
399   g_string_free (fnx, TRUE);
400
401   syntax_gen_string (&filename, ss_cstr (utf8_file_name));
402   g_free (utf8_file_name);
403
404   syntax = g_strdup_printf ("%s OUTFILE=%s.",
405                             de->save_as_portable ? "EXPORT" : "SAVE",
406                             ds_cstr (&filename));
407
408   ds_destroy (&filename);
409
410   g_free (execute_syntax_string (de, syntax));
411 }
412
413
414 static void
415 display_dict (PsppireDataWindow *de)
416 {
417   execute_const_syntax_string (de, "DISPLAY DICTIONARY.");
418 }
419
420 static void
421 sysfile_info (PsppireDataWindow *de)
422 {
423   GtkWidget *dialog = psppire_window_file_chooser_dialog (PSPPIRE_WINDOW (de));
424
425   if  ( GTK_RESPONSE_ACCEPT == gtk_dialog_run (GTK_DIALOG (dialog)))
426     {
427       struct string filename;
428       gchar *file_name =
429         gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
430
431       gchar *utf8_file_name = g_filename_to_utf8 (file_name, -1, NULL, NULL,
432                                                   NULL);
433
434       gchar *syntax;
435
436       ds_init_empty (&filename);
437
438       syntax_gen_string (&filename, ss_cstr (utf8_file_name));
439
440       g_free (utf8_file_name);
441
442       syntax = g_strdup_printf ("SYSFILE INFO %s.", ds_cstr (&filename));
443       g_free (execute_syntax_string (de, syntax));
444     }
445
446   gtk_widget_destroy (dialog);
447 }
448
449
450 /* PsppireWindow 'pick_filename' callback: prompt for a filename to save as. */
451 static void
452 data_pick_filename (PsppireWindow *window)
453 {
454   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (window);
455   GtkFileFilter *filter = gtk_file_filter_new ();
456   GtkWidget *button_sys;
457   GtkWidget *dialog =
458     gtk_file_chooser_dialog_new (_("Save"),
459                                  GTK_WINDOW (de),
460                                  GTK_FILE_CHOOSER_ACTION_SAVE,
461                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
462                                  GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
463                                  NULL);
464
465   g_object_set (dialog, "local-only", FALSE, NULL);
466
467   gtk_file_filter_set_name (filter, _("System Files (*.sav)"));
468   gtk_file_filter_add_mime_type (filter, "application/x-spss-sav");
469   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
470
471   filter = gtk_file_filter_new ();
472   gtk_file_filter_set_name (filter, _("Portable Files (*.por) "));
473   gtk_file_filter_add_mime_type (filter, "application/x-spss-por");
474   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
475
476   filter = gtk_file_filter_new ();
477   gtk_file_filter_set_name (filter, _("All Files"));
478   gtk_file_filter_add_pattern (filter, "*");
479   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
480
481   {
482     GtkWidget *button_por;
483     GtkWidget *vbox = gtk_vbox_new (TRUE, 5);
484     button_sys =
485       gtk_radio_button_new_with_label (NULL, _("System File"));
486
487     button_por =
488       gtk_radio_button_new_with_label
489       (gtk_radio_button_get_group (GTK_RADIO_BUTTON(button_sys)),
490        _("Portable File"));
491
492     psppire_box_pack_start_defaults (GTK_BOX (vbox), button_sys);
493     psppire_box_pack_start_defaults (GTK_BOX (vbox), button_por);
494
495     gtk_widget_show_all (vbox);
496
497     gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), vbox);
498   }
499
500   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
501                                                   TRUE);
502
503   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
504     {
505     case GTK_RESPONSE_ACCEPT:
506       {
507         GString *filename =
508           g_string_new
509           (
510            gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog))
511            );
512
513         de->save_as_portable =
514           ! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_sys));
515
516         if ( ! name_has_suffix (filename->str))
517           {
518             if ( de->save_as_portable)
519               g_string_append (filename, ".por");
520             else
521               g_string_append (filename, ".sav");
522           }
523
524         psppire_window_set_filename (PSPPIRE_WINDOW (de), filename->str);
525
526         g_string_free (filename, TRUE);
527       }
528       break;
529     default:
530       break;
531     }
532
533   gtk_widget_destroy (dialog);
534 }
535
536 static bool
537 confirm_delete_dataset (PsppireDataWindow *de,
538                         const char *old_dataset,
539                         const char *new_dataset,
540                         const char *existing_dataset)
541 {
542   GtkWidget *dialog;
543   int result;
544
545   dialog = gtk_message_dialog_new (
546     GTK_WINDOW (de), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s",
547     _("Delete Existing Dataset?"));
548
549   gtk_message_dialog_format_secondary_text (
550     GTK_MESSAGE_DIALOG (dialog),
551     _("Renaming \"%s\" to \"%s\" will destroy the existing "
552       "dataset named \"%s\".  Are you sure that you want to do this?"),
553     old_dataset, new_dataset, existing_dataset);
554
555   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
556                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
557                           GTK_STOCK_DELETE, GTK_RESPONSE_OK,
558                           NULL);
559
560   g_object_set (dialog, "icon-name", "pspp", NULL);
561
562   result = gtk_dialog_run (GTK_DIALOG (dialog));
563
564   gtk_widget_destroy (dialog);
565
566   return result == GTK_RESPONSE_OK;
567 }
568
569 static void
570 on_rename_dataset (PsppireDataWindow *de)
571 {
572   struct dataset *ds = de->dataset;
573   struct session *session = dataset_session (ds);
574   const char *old_name = dataset_name (ds);
575   struct dataset *existing_dataset;
576   char *new_name;
577   char *prompt;
578
579   prompt = xasprintf (_("Please enter a new name for dataset \"%s\":"),
580                       old_name);
581   new_name = entry_dialog_run (GTK_WINDOW (de), _("Rename Dataset"), prompt,
582                                old_name);
583   free (prompt);
584
585   if (new_name == NULL)
586     return;
587
588   existing_dataset = session_lookup_dataset (session, new_name);
589   if (existing_dataset == NULL || existing_dataset == ds
590       || confirm_delete_dataset (de, old_name, new_name,
591                                  dataset_name (existing_dataset)))
592     g_free (execute_syntax_string (de, g_strdup_printf ("DATASET NAME %s.",
593                                                         new_name)));
594
595   free (new_name);
596 }
597
598 static void
599 status_bar_activate (PsppireDataWindow  *de, GtkToggleAction *action)
600 {
601   GtkWidget *statusbar = get_widget_assert (de->builder, "status-bar");
602
603   if ( gtk_toggle_action_get_active (action))
604     gtk_widget_show (statusbar);
605   else
606     gtk_widget_hide (statusbar);
607 }
608
609
610 static void
611 grid_lines_activate (PsppireDataWindow  *de, GtkToggleAction *action)
612 {
613   const gboolean grid_visible = gtk_toggle_action_get_active (action);
614
615   psppire_data_editor_show_grid (de->data_editor, grid_visible);
616 }
617
618 static void
619 data_view_activate (PsppireDataWindow  *de)
620 {
621   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_DATA_VIEW);
622 }
623
624
625 static void
626 variable_view_activate (PsppireDataWindow  *de)
627 {
628   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
629 }
630
631
632 static void
633 fonts_activate (PsppireDataWindow  *de)
634 {
635   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (de));
636   PangoFontDescription *current_font;
637   gchar *font_name;
638   GtkWidget *dialog =
639     gtk_font_selection_dialog_new (_("Font Selection"));
640
641
642   current_font = GTK_WIDGET(de->data_editor)->style->font_desc;
643   font_name = pango_font_description_to_string (current_font);
644
645   gtk_font_selection_dialog_set_font_name (GTK_FONT_SELECTION_DIALOG (dialog), font_name);
646
647   g_free (font_name);
648
649   gtk_window_set_transient_for (GTK_WINDOW (dialog),
650                                 GTK_WINDOW (toplevel));
651
652   if ( GTK_RESPONSE_OK == gtk_dialog_run (GTK_DIALOG (dialog)) )
653     {
654       const gchar *font = gtk_font_selection_dialog_get_font_name
655         (GTK_FONT_SELECTION_DIALOG (dialog));
656
657       PangoFontDescription* font_desc =
658         pango_font_description_from_string (font);
659
660       psppire_data_editor_set_font (de->data_editor, font_desc);
661     }
662
663   gtk_widget_hide (dialog);
664 }
665
666
667
668 /* Callback for the value labels action */
669 static void
670 toggle_value_labels (PsppireDataWindow  *de, GtkToggleAction *ta)
671 {
672   g_object_set (de->data_editor, "value-labels", gtk_toggle_action_get_active (ta), NULL);
673 }
674
675 static void
676 toggle_split_window (PsppireDataWindow  *de, GtkToggleAction *ta)
677 {
678   psppire_data_editor_split_window (de->data_editor,
679                                     gtk_toggle_action_get_active (ta));
680 }
681
682
683 static void
684 file_quit (PsppireDataWindow *de)
685 {
686   /* FIXME: Need to be more intelligent here.
687      Give the user the opportunity to save any unsaved data.
688   */
689   psppire_quit ();
690 }
691
692 static void
693 on_recent_data_select (GtkMenuShell *menushell,
694                        PsppireWindow *window)
695 {
696   gchar *file;
697
698   gchar *uri =
699     gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (menushell));
700
701   file = g_filename_from_uri (uri, NULL, NULL);
702
703   g_free (uri);
704
705   open_data_window (window, file, NULL);
706
707   g_free (file);
708 }
709
710 static char *
711 charset_from_mime_type (const char *mime_type)
712 {
713   const char *charset;
714   struct string s;
715   const char *p;
716
717   if (mime_type == NULL)
718     return NULL;
719
720   charset = c_strcasestr (mime_type, "charset=");
721   if (charset == NULL)
722     return NULL;
723
724   ds_init_empty (&s);
725   p = charset + 8;
726   if (*p == '"')
727     {
728       /* Parse a "quoted-string" as defined by RFC 822. */
729       for (p++; *p != '\0' && *p != '"'; p++)
730         {
731           if (*p != '\\')
732             ds_put_byte (&s, *p);
733           else if (*++p != '\0')
734             ds_put_byte (&s, *p);
735         }
736     }
737   else
738     {
739       /* Parse a "token" as defined by RFC 2045. */
740       while (*p > 32 && *p < 127 && strchr ("()<>@,;:\\\"/[]?=", *p) == NULL)
741         ds_put_byte (&s, *p++);
742     }
743   if (!ds_is_empty (&s))
744     return ds_steal_cstr (&s);
745
746   ds_destroy (&s);
747   return NULL;
748 }
749
750 static void
751 on_recent_files_select (GtkMenuShell *menushell,   gpointer user_data)
752 {
753   GtkRecentInfo *item;
754   char *encoding;
755   GtkWidget *se;
756   gchar *file;
757
758   /* Get the file name and its encoding. */
759   item = gtk_recent_chooser_get_current_item (GTK_RECENT_CHOOSER (menushell));
760   file = g_filename_from_uri (gtk_recent_info_get_uri (item), NULL, NULL);
761   encoding = charset_from_mime_type (gtk_recent_info_get_mime_type (item));
762   gtk_recent_info_unref (item);
763
764   se = psppire_syntax_window_new (encoding);
765
766   free (encoding);
767
768   if ( psppire_window_load (PSPPIRE_WINDOW (se), file, NULL) ) 
769     gtk_widget_show (se);
770   else
771     gtk_widget_destroy (se);
772
773   g_free (file);
774 }
775
776 static void
777 set_unsaved (gpointer w)
778 {
779   psppire_window_set_unsaved (PSPPIRE_WINDOW (w));
780 }
781
782 static void
783 on_switch_page (PsppireDataEditor *de, gpointer p,
784                 gint pagenum, PsppireDataWindow *dw)
785 {
786   GtkWidget *page_menu_item;
787   gboolean is_ds;
788   const char *path;
789
790   is_ds = pagenum == PSPPIRE_DATA_EDITOR_DATA_VIEW;
791   path = (is_ds
792           ? "/ui/menubar/view/view_data"
793           : "/ui/menubar/view/view_variables");
794   page_menu_item = gtk_ui_manager_get_widget (dw->ui_manager, path);
795   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (page_menu_item), TRUE);
796 }
797
798 static void
799 on_ui_manager_changed (PsppireDataEditor *de,
800                        GParamSpec *pspec UNUSED,
801                        PsppireDataWindow *dw)
802 {
803   GtkUIManager *uim = psppire_data_editor_get_ui_manager (de);
804   if (uim == dw->uim)
805     return;
806
807   if (dw->uim)
808     {
809       psppire_data_window_remove_ui (dw, dw->uim, dw->merge_id);
810       g_object_unref (dw->uim);
811       dw->uim = NULL;
812     }
813
814   dw->uim = uim;
815   if (dw->uim)
816     {
817       g_object_ref (dw->uim);
818       dw->merge_id = psppire_data_window_add_ui (dw, dw->uim);
819     }
820 }
821
822 /* Connects the action called ACTION_NAME to HANDLER passing DW as the auxilliary data.
823    Returns a pointer to the action
824 */
825 static GtkAction *
826 connect_action (PsppireDataWindow *dw, const char *action_name, 
827                                     GCallback handler)
828 {
829   GtkAction *action = get_action_assert (dw->builder, action_name);
830
831   g_signal_connect_swapped (action, "activate", handler, dw);
832
833   return action;
834 }
835
836 /* Only a data file with at least one variable can be saved. */
837 static void
838 enable_save (PsppireDataWindow *dw)
839 {
840   gboolean enable = psppire_dict_get_var_cnt (dw->dict) > 0;
841
842   gtk_action_set_sensitive (get_action_assert (dw->builder, "file_save"),
843                             enable);
844   gtk_action_set_sensitive (get_action_assert (dw->builder, "file_save_as"),
845                             enable);
846 }
847
848 /* Initializes as much of a PsppireDataWindow as we can and must before the
849    dataset has been set.
850
851    In particular, the 'menu' member is required in case the "filename" property
852    is set before the "dataset" property: otherwise PsppireWindow will try to
853    modify the menu as part of the "filename" property_set() function and end up
854    with a Gtk-CRITICAL since 'menu' is NULL.  */
855 static void
856 psppire_data_window_init (PsppireDataWindow *de)
857 {
858   de->builder = builder_new ("data-editor.ui");
859
860   de->ui_manager = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
861
862   PSPPIRE_WINDOW (de)->menu =
863     GTK_MENU_SHELL (gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/windows/windows_minimise_all")->parent);
864
865   de->uim = NULL;
866   de->merge_id = 0;
867 }
868
869 static void
870 psppire_data_window_finish_init (PsppireDataWindow *de,
871                                  struct dataset *ds)
872 {
873   static const struct dataset_callbacks cbs =
874     {
875       set_unsaved,                    /* changed */
876       transformation_change_callback, /* transformations_changed */
877     };
878
879   GtkWidget *menubar;
880   GtkWidget *hb ;
881   GtkWidget *sb ;
882
883   GtkWidget *box = gtk_vbox_new (FALSE, 0);
884
885   de->dataset = ds;
886   de->dict = psppire_dict_new_from_dict (dataset_dict (ds));
887   de->data_store = psppire_data_store_new (de->dict);
888   psppire_data_store_set_reader (de->data_store, NULL);
889
890   menubar = get_widget_assert (de->builder, "menubar");
891   hb = get_widget_assert (de->builder, "handlebox1");
892   sb = get_widget_assert (de->builder, "status-bar");
893
894   de->uim = NULL;
895   de->merge_id = 0;
896
897   de->data_editor =
898     PSPPIRE_DATA_EDITOR (psppire_data_editor_new (de->dict, de->data_store));
899   g_signal_connect (de->data_editor, "switch-page",
900                     G_CALLBACK (on_switch_page), de);
901
902   g_signal_connect_swapped (de->data_store, "case-changed",
903                             G_CALLBACK (set_unsaved), de);
904
905   g_signal_connect_swapped (de->data_store, "case-inserted",
906                             G_CALLBACK (set_unsaved), de);
907
908   g_signal_connect_swapped (de->data_store, "cases-deleted",
909                             G_CALLBACK (set_unsaved), de);
910
911   dataset_set_callbacks (de->dataset, &cbs, de);
912
913   connect_help (de->builder);
914
915   gtk_box_pack_start (GTK_BOX (box), menubar, FALSE, TRUE, 0);
916   gtk_box_pack_start (GTK_BOX (box), hb, FALSE, TRUE, 0);
917   gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET (de->data_editor), TRUE, TRUE, 0);
918   gtk_box_pack_start (GTK_BOX (box), sb, FALSE, TRUE, 0);
919
920   gtk_container_add (GTK_CONTAINER (de), box);
921
922   g_signal_connect (de->dict, "weight-changed",
923                     G_CALLBACK (on_weight_change),
924                     de);
925
926   g_signal_connect (de->dict, "filter-changed",
927                     G_CALLBACK (on_filter_change),
928                     de);
929
930   g_signal_connect (de->dict, "split-changed",
931                     G_CALLBACK (on_split_change),
932                     de);
933
934   g_signal_connect_swapped (de->dict, "backend-changed",
935                             G_CALLBACK (enable_save), de);
936   g_signal_connect_swapped (de->dict, "variable-inserted",
937                             G_CALLBACK (enable_save), de);
938   g_signal_connect_swapped (de->dict, "variable-deleted",
939                             G_CALLBACK (enable_save), de);
940   enable_save (de);
941
942   connect_action (de, "file_new_data", G_CALLBACK (create_data_window));
943   connect_action (de, "file_import", G_CALLBACK (text_data_import_assistant));
944   connect_action (de, "file_save", G_CALLBACK (psppire_window_save));
945   connect_action (de, "file_open", G_CALLBACK (psppire_window_open));
946   connect_action (de, "file_save_as", G_CALLBACK (psppire_window_save_as));
947   connect_action (de, "rename_dataset", G_CALLBACK (on_rename_dataset));
948   connect_action (de, "file_information_working-file", G_CALLBACK (display_dict));
949   connect_action (de, "file_information_external-file", G_CALLBACK (sysfile_info));
950
951   g_signal_connect_swapped (get_action_assert (de->builder, "view_value-labels"), "toggled", G_CALLBACK (toggle_value_labels), de);
952
953   connect_action (de, "data_select-cases", G_CALLBACK (select_cases_dialog));
954   connect_action (de, "data_aggregate", G_CALLBACK (aggregate_dialog));
955   connect_action (de, "transform_autorecode", G_CALLBACK (autorecode_dialog));
956   connect_action (de, "data_split-file", G_CALLBACK (split_file_dialog));
957   connect_action (de, "data_weight-cases", G_CALLBACK (weight_cases_dialog));
958   connect_action (de, "oneway-anova", G_CALLBACK (oneway_anova_dialog));
959   connect_action (de, "utilities_comments", G_CALLBACK (comments_dialog));
960   connect_action (de, "transform_recode-same", G_CALLBACK (recode_same_dialog));
961   connect_action (de, "transform_recode-different", G_CALLBACK (recode_different_dialog));
962
963   {
964     GtkWidget *recent_data =
965       gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/file/file_recent-data");
966
967     GtkWidget *recent_files =
968       gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/file/file_recent-files");
969
970
971     GtkWidget *menu_data = gtk_recent_chooser_menu_new_for_manager (
972       gtk_recent_manager_get_default ());
973
974     GtkWidget *menu_files = gtk_recent_chooser_menu_new_for_manager (
975       gtk_recent_manager_get_default ());
976
977     g_object_set (menu_data, "show-tips",  TRUE, NULL);
978     g_object_set (menu_files, "show-tips",  TRUE, NULL);
979
980     {
981       GtkRecentFilter *filter = gtk_recent_filter_new ();
982
983       gtk_recent_filter_add_mime_type (filter, "application/x-spss-sav");
984       gtk_recent_filter_add_mime_type (filter, "application/x-spss-por");
985
986       gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (menu_data), GTK_RECENT_SORT_MRU);
987
988       gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu_data), filter);
989     }
990
991     gtk_menu_item_set_submenu (GTK_MENU_ITEM (recent_data), menu_data);
992
993
994     g_signal_connect (menu_data, "selection-done", G_CALLBACK (on_recent_data_select), de);
995
996     {
997       GtkRecentFilter *filter = gtk_recent_filter_new ();
998
999       gtk_recent_filter_add_pattern (filter, "*.sps");
1000       gtk_recent_filter_add_pattern (filter, "*.SPS");
1001
1002       gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (menu_files), GTK_RECENT_SORT_MRU);
1003
1004       gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu_files), filter);
1005     }
1006
1007     gtk_menu_item_set_submenu (GTK_MENU_ITEM (recent_files), menu_files);
1008
1009     g_signal_connect (menu_files, "selection-done", G_CALLBACK (on_recent_files_select), de);
1010
1011   }
1012
1013   connect_action (de, "file_new_syntax", G_CALLBACK (create_syntax_window));
1014
1015
1016   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
1017   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_DATA_VIEW);
1018
1019   connect_action (de, "view_statusbar", G_CALLBACK (status_bar_activate));
1020
1021   connect_action (de, "view_gridlines", G_CALLBACK (grid_lines_activate));
1022
1023   connect_action (de, "view_data", G_CALLBACK (data_view_activate));
1024
1025   connect_action (de, "view_variables", G_CALLBACK (variable_view_activate));
1026
1027   connect_action (de, "view_fonts", G_CALLBACK (fonts_activate));
1028
1029   connect_action (de, "file_quit", G_CALLBACK (file_quit));
1030
1031   connect_action (de, "transform_run-pending", G_CALLBACK (execute));
1032
1033   connect_action (de, "windows_minimise_all", G_CALLBACK (psppire_window_minimise_all));
1034
1035   g_signal_connect_swapped (get_action_assert (de->builder, "windows_split"), "toggled", G_CALLBACK (toggle_split_window), de);
1036
1037   merge_help_menu (de->ui_manager);
1038
1039   g_signal_connect (de->data_editor, "notify::ui-manager",
1040                     G_CALLBACK (on_ui_manager_changed), de);
1041   on_ui_manager_changed (de->data_editor, NULL, de);
1042
1043   gtk_widget_show (GTK_WIDGET (de->data_editor));
1044   gtk_widget_show (box);
1045
1046   ll_push_head (&all_data_windows, &de->ll);
1047 }
1048
1049 static void
1050 psppire_data_window_dispose (GObject *object)
1051 {
1052   PsppireDataWindow *dw = PSPPIRE_DATA_WINDOW (object);
1053
1054   if (dw->uim)
1055     {
1056       psppire_data_window_remove_ui (dw, dw->uim, dw->merge_id);
1057       g_object_unref (dw->uim);
1058       dw->uim = NULL;
1059     }
1060
1061   if (dw->builder != NULL)
1062     {
1063       g_object_unref (dw->builder);
1064       dw->builder = NULL;
1065     }
1066
1067   if (dw->dict)
1068     {
1069       g_signal_handlers_disconnect_by_func (dw->dict,
1070                                             G_CALLBACK (enable_save), dw);
1071       g_signal_handlers_disconnect_by_func (dw->dict,
1072                                             G_CALLBACK (on_weight_change), dw);
1073       g_signal_handlers_disconnect_by_func (dw->dict,
1074                                             G_CALLBACK (on_filter_change), dw);
1075       g_signal_handlers_disconnect_by_func (dw->dict,
1076                                             G_CALLBACK (on_split_change), dw);
1077
1078       g_object_unref (dw->dict);
1079       dw->dict = NULL;
1080     }
1081
1082   if (dw->data_store)
1083     {
1084       g_object_unref (dw->data_store);
1085       dw->data_store = NULL;
1086     }
1087
1088   if (dw->ll.next != NULL)
1089     {
1090       ll_remove (&dw->ll);
1091       dw->ll.next = NULL;
1092     }
1093
1094   if (G_OBJECT_CLASS (parent_class)->dispose)
1095     G_OBJECT_CLASS (parent_class)->dispose (object);
1096 }
1097
1098 static void
1099 psppire_data_window_finalize (GObject *object)
1100 {
1101   PsppireDataWindow *dw = PSPPIRE_DATA_WINDOW (object);
1102
1103   if (dw->dataset)
1104     {
1105       struct dataset *dataset = dw->dataset;
1106       struct session *session = dataset_session (dataset);
1107
1108       dw->dataset = NULL;
1109
1110       dataset_set_callbacks (dataset, NULL, NULL);
1111       session_set_active_dataset (session, NULL);
1112       dataset_destroy (dataset);
1113     }
1114
1115   if (G_OBJECT_CLASS (parent_class)->finalize)
1116     G_OBJECT_CLASS (parent_class)->finalize (object);
1117 }
1118
1119 static void
1120 psppire_data_window_set_property (GObject         *object,
1121                                   guint            prop_id,
1122                                   const GValue    *value,
1123                                   GParamSpec      *pspec)
1124 {
1125   PsppireDataWindow *window = PSPPIRE_DATA_WINDOW (object);
1126
1127   switch (prop_id)
1128     {
1129     case PROP_DATASET:
1130       psppire_data_window_finish_init (window, g_value_get_pointer (value));
1131       break;
1132     default:
1133       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1134       break;
1135     };
1136 }
1137
1138 static void
1139 psppire_data_window_get_property (GObject         *object,
1140                                   guint            prop_id,
1141                                   GValue          *value,
1142                                   GParamSpec      *pspec)
1143 {
1144   PsppireDataWindow *window = PSPPIRE_DATA_WINDOW (object);
1145
1146   switch (prop_id)
1147     {
1148     case PROP_DATASET:
1149       g_value_set_pointer (value, window->dataset);
1150       break;
1151     default:
1152       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1153       break;
1154     };
1155 }
1156
1157 static guint
1158 psppire_data_window_add_ui (PsppireDataWindow *pdw, GtkUIManager *uim)
1159 {
1160   gchar *ui_string;
1161   guint merge_id;
1162   GList *list;
1163
1164   ui_string = gtk_ui_manager_get_ui (uim);
1165   merge_id = gtk_ui_manager_add_ui_from_string (pdw->ui_manager, ui_string,
1166                                                 -1, NULL);
1167   g_free (ui_string);
1168
1169   g_return_val_if_fail (merge_id != 0, 0);
1170
1171   list = gtk_ui_manager_get_action_groups (uim);
1172   for (; list != NULL; list = list->next)
1173     {
1174       GtkActionGroup *action_group = list->data;
1175       GList *actions = gtk_action_group_list_actions (action_group);
1176       GList *action;
1177
1178       for (action = actions; action != NULL; action = action->next)
1179         {
1180           GtkAction *a = action->data;
1181
1182           if (PSPPIRE_IS_DIALOG_ACTION (a))
1183             g_object_set (a, "manager", pdw->ui_manager, NULL);
1184         }
1185
1186       gtk_ui_manager_insert_action_group (pdw->ui_manager, action_group, 0);
1187     }
1188
1189   gtk_window_add_accel_group (GTK_WINDOW (pdw),
1190                               gtk_ui_manager_get_accel_group (uim));
1191
1192   return merge_id;
1193 }
1194
1195 static void
1196 psppire_data_window_remove_ui (PsppireDataWindow *pdw,
1197                                GtkUIManager *uim, guint merge_id)
1198 {
1199   GList *list;
1200
1201   g_return_if_fail (merge_id != 0);
1202
1203   gtk_ui_manager_remove_ui (pdw->ui_manager, merge_id);
1204
1205   list = gtk_ui_manager_get_action_groups (uim);
1206   for (; list != NULL; list = list->next)
1207     {
1208       GtkActionGroup *action_group = list->data;
1209       gtk_ui_manager_remove_action_group (pdw->ui_manager, action_group);
1210     }
1211
1212   gtk_window_remove_accel_group (GTK_WINDOW (pdw),
1213                                  gtk_ui_manager_get_accel_group (uim));
1214 }
1215
1216 GtkWidget*
1217 psppire_data_window_new (struct dataset *ds)
1218 {
1219   GtkWidget *dw;
1220
1221   if (the_session == NULL)
1222     the_session = session_create (NULL);
1223
1224   if (ds == NULL)
1225     {
1226       char *dataset_name = session_generate_dataset_name (the_session);
1227       ds = dataset_create (the_session, dataset_name);
1228       free (dataset_name);
1229     }
1230   assert (dataset_session (ds) == the_session);
1231
1232   dw = GTK_WIDGET (
1233     g_object_new (
1234       psppire_data_window_get_type (),
1235       "description", _("Data Editor"),
1236       "dataset", ds,
1237       NULL));
1238
1239   if (dataset_name (ds) != NULL)
1240     g_object_set (dw, "id", dataset_name (ds), (void *) NULL);
1241
1242   return dw;
1243 }
1244
1245 bool
1246 psppire_data_window_is_empty (PsppireDataWindow *dw)
1247 {
1248   return psppire_dict_get_var_cnt (dw->dict) == 0;
1249 }
1250
1251 static void
1252 psppire_data_window_iface_init (PsppireWindowIface *iface)
1253 {
1254   iface->save = save_file;
1255   iface->pick_filename = data_pick_filename;
1256   iface->load = load_file;
1257 }
1258 \f
1259 PsppireDataWindow *
1260 psppire_default_data_window (void)
1261 {
1262   if (ll_is_empty (&all_data_windows))
1263     create_data_window ();
1264   return ll_data (ll_head (&all_data_windows), PsppireDataWindow, ll);
1265 }
1266
1267 void
1268 psppire_data_window_set_default (PsppireDataWindow *pdw)
1269 {
1270   ll_remove (&pdw->ll);
1271   ll_push_head (&all_data_windows, &pdw->ll);
1272 }
1273
1274 void
1275 psppire_data_window_undefault (PsppireDataWindow *pdw)
1276 {
1277   ll_remove (&pdw->ll);
1278   ll_push_tail (&all_data_windows, &pdw->ll);
1279 }
1280
1281 PsppireDataWindow *
1282 psppire_data_window_for_dataset (struct dataset *ds)
1283 {
1284   PsppireDataWindow *pdw;
1285
1286   ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows)
1287     if (pdw->dataset == ds)
1288       return pdw;
1289
1290   return NULL;
1291 }
1292
1293 PsppireDataWindow *
1294 psppire_data_window_for_data_store (PsppireDataStore *data_store)
1295 {
1296   PsppireDataWindow *pdw;
1297
1298   ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows)
1299     if (pdw->data_store == data_store)
1300       return pdw;
1301
1302   return NULL;
1303 }
1304
1305 void
1306 create_data_window (void)
1307 {
1308   gtk_widget_show (psppire_data_window_new (NULL));
1309 }
1310
1311 void
1312 open_data_window (PsppireWindow *victim, const char *file_name, gpointer hint)
1313 {
1314   GtkWidget *window;
1315
1316   if (PSPPIRE_IS_DATA_WINDOW (victim)
1317       && psppire_data_window_is_empty (PSPPIRE_DATA_WINDOW (victim)))
1318     {
1319       window = GTK_WIDGET (victim);
1320       gtk_widget_hide (GTK_WIDGET (PSPPIRE_DATA_WINDOW (window)->data_editor));
1321     }
1322   else
1323     window = psppire_data_window_new (NULL);
1324
1325   psppire_window_load (PSPPIRE_WINDOW (window), file_name, hint);
1326   gtk_widget_show_all (window);
1327 }
1328