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