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