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