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