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