INPUT PROGRAM: Use a separate dataset for the input program.
[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/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, gpointer syn)
340 {
341   const char *mime_type = NULL;
342   gchar *syntax = NULL;
343   bool ok;
344
345   if (syn == NULL)
346     {
347       gchar *utf8_file_name;
348       struct string filename;
349       ds_init_empty (&filename);
350       
351       utf8_file_name = g_filename_to_utf8 (file_name, -1, NULL, NULL, NULL);
352     
353       syntax_gen_string (&filename, ss_cstr (utf8_file_name));
354       
355       g_free (utf8_file_name);
356       
357       syntax = g_strdup_printf ("GET FILE=%s.", ds_cstr (&filename));
358       ds_destroy (&filename);
359
360     }
361   else
362     {
363       syntax = syn;
364     }
365
366   ok = execute_syntax (PSPPIRE_DATA_WINDOW (de),
367                        lex_reader_for_string (syntax));
368   g_free (syntax);
369
370   if (ok && syn == NULL)
371     {
372       if (name_has_por_suffix (file_name))
373         mime_type = "application/x-spss-por";
374       else if (name_has_sav_suffix (file_name))
375         mime_type = "application/x-spss-sav";
376       
377       add_most_recent (file_name, mime_type);
378     }
379
380   return ok;
381 }
382
383 /* Save DE to file */
384 static void
385 save_file (PsppireWindow *w)
386 {
387   const gchar *file_name = NULL;
388   gchar *utf8_file_name = NULL;
389   GString *fnx;
390   struct string filename ;
391   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (w);
392   gchar *syntax;
393
394   file_name = psppire_window_get_filename (w);
395
396   fnx = g_string_new (file_name);
397
398   if ( ! name_has_suffix (fnx->str))
399     {
400       if ( de->save_as_portable)
401         g_string_append (fnx, ".por");
402       else
403         g_string_append (fnx, ".sav");
404     }
405
406   ds_init_empty (&filename);
407
408   utf8_file_name = g_filename_to_utf8 (fnx->str, -1, NULL, NULL, NULL);
409
410   g_string_free (fnx, TRUE);
411
412   syntax_gen_string (&filename, ss_cstr (utf8_file_name));
413   g_free (utf8_file_name);
414
415   syntax = g_strdup_printf ("%s OUTFILE=%s.",
416                             de->save_as_portable ? "EXPORT" : "SAVE",
417                             ds_cstr (&filename));
418
419   ds_destroy (&filename);
420
421   g_free (execute_syntax_string (de, syntax));
422 }
423
424
425 static void
426 display_dict (PsppireDataWindow *de)
427 {
428   execute_const_syntax_string (de, "DISPLAY DICTIONARY.");
429 }
430
431 static void
432 sysfile_info (PsppireDataWindow *de)
433 {
434   GtkWidget *dialog = psppire_window_file_chooser_dialog (PSPPIRE_WINDOW (de));
435
436   if  ( GTK_RESPONSE_ACCEPT == gtk_dialog_run (GTK_DIALOG (dialog)))
437     {
438       struct string filename;
439       gchar *file_name =
440         gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
441
442       gchar *utf8_file_name = g_filename_to_utf8 (file_name, -1, NULL, NULL,
443                                                   NULL);
444
445       gchar *syntax;
446
447       ds_init_empty (&filename);
448
449       syntax_gen_string (&filename, ss_cstr (utf8_file_name));
450
451       g_free (utf8_file_name);
452
453       syntax = g_strdup_printf ("SYSFILE INFO %s.", ds_cstr (&filename));
454       g_free (execute_syntax_string (de, syntax));
455     }
456
457   gtk_widget_destroy (dialog);
458 }
459
460
461 /* PsppireWindow 'pick_filename' callback: prompt for a filename to save as. */
462 static void
463 data_pick_filename (PsppireWindow *window)
464 {
465   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (window);
466   GtkFileFilter *filter = gtk_file_filter_new ();
467   GtkWidget *button_sys;
468   GtkWidget *dialog =
469     gtk_file_chooser_dialog_new (_("Save"),
470                                  GTK_WINDOW (de),
471                                  GTK_FILE_CHOOSER_ACTION_SAVE,
472                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
473                                  GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
474                                  NULL);
475
476   g_object_set (dialog, "local-only", FALSE, NULL);
477
478   gtk_file_filter_set_name (filter, _("System Files (*.sav)"));
479   gtk_file_filter_add_mime_type (filter, "application/x-spss-sav");
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, _("Portable Files (*.por) "));
484   gtk_file_filter_add_mime_type (filter, "application/x-spss-por");
485   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
486
487   filter = gtk_file_filter_new ();
488   gtk_file_filter_set_name (filter, _("All Files"));
489   gtk_file_filter_add_pattern (filter, "*");
490   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
491
492   {
493     GtkWidget *button_por;
494     GtkWidget *vbox = gtk_vbox_new (TRUE, 5);
495     button_sys =
496       gtk_radio_button_new_with_label (NULL, _("System File"));
497
498     button_por =
499       gtk_radio_button_new_with_label
500       (gtk_radio_button_get_group (GTK_RADIO_BUTTON(button_sys)),
501        _("Portable File"));
502
503     psppire_box_pack_start_defaults (GTK_BOX (vbox), button_sys);
504     psppire_box_pack_start_defaults (GTK_BOX (vbox), button_por);
505
506     gtk_widget_show_all (vbox);
507
508     gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), vbox);
509   }
510
511   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
512                                                   TRUE);
513
514   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
515     {
516     case GTK_RESPONSE_ACCEPT:
517       {
518         GString *filename =
519           g_string_new
520           (
521            gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog))
522            );
523
524         de->save_as_portable =
525           ! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_sys));
526
527         if ( ! name_has_suffix (filename->str))
528           {
529             if ( de->save_as_portable)
530               g_string_append (filename, ".por");
531             else
532               g_string_append (filename, ".sav");
533           }
534
535         psppire_window_set_filename (PSPPIRE_WINDOW (de), filename->str);
536
537         g_string_free (filename, TRUE);
538       }
539       break;
540     default:
541       break;
542     }
543
544   gtk_widget_destroy (dialog);
545 }
546
547 static bool
548 confirm_delete_dataset (PsppireDataWindow *de,
549                         const char *old_dataset,
550                         const char *new_dataset,
551                         const char *existing_dataset)
552 {
553   GtkWidget *dialog;
554   int result;
555
556   dialog = gtk_message_dialog_new (
557     GTK_WINDOW (de), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s",
558     _("Delete Existing Dataset?"));
559
560   gtk_message_dialog_format_secondary_text (
561     GTK_MESSAGE_DIALOG (dialog),
562     _("Renaming \"%s\" to \"%s\" will destroy the existing "
563       "dataset named \"%s\".  Are you sure that you want to do this?"),
564     old_dataset, new_dataset, existing_dataset);
565
566   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
567                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
568                           GTK_STOCK_DELETE, GTK_RESPONSE_OK,
569                           NULL);
570
571   g_object_set (dialog, "icon-name", "pspp", NULL);
572
573   result = gtk_dialog_run (GTK_DIALOG (dialog));
574
575   gtk_widget_destroy (dialog);
576
577   return result == GTK_RESPONSE_OK;
578 }
579
580 static void
581 on_rename_dataset (PsppireDataWindow *de)
582 {
583   struct dataset *ds = de->dataset;
584   struct session *session = dataset_session (ds);
585   const char *old_name = dataset_name (ds);
586   struct dataset *existing_dataset;
587   char *new_name;
588   char *prompt;
589
590   prompt = xasprintf (_("Please enter a new name for dataset \"%s\":"),
591                       old_name);
592   new_name = entry_dialog_run (GTK_WINDOW (de), _("Rename Dataset"), prompt,
593                                old_name);
594   free (prompt);
595
596   if (new_name == NULL)
597     return;
598
599   existing_dataset = session_lookup_dataset (session, new_name);
600   if (existing_dataset == NULL || existing_dataset == ds
601       || confirm_delete_dataset (de, old_name, new_name,
602                                  dataset_name (existing_dataset)))
603     g_free (execute_syntax_string (de, g_strdup_printf ("DATASET NAME %s.",
604                                                         new_name)));
605
606   free (new_name);
607 }
608
609 static void
610 status_bar_activate (PsppireDataWindow  *de, GtkToggleAction *action)
611 {
612   GtkWidget *statusbar = get_widget_assert (de->builder, "status-bar");
613
614   if ( gtk_toggle_action_get_active (action))
615     gtk_widget_show (statusbar);
616   else
617     gtk_widget_hide (statusbar);
618 }
619
620
621 static void
622 grid_lines_activate (PsppireDataWindow  *de, GtkToggleAction *action)
623 {
624   const gboolean grid_visible = gtk_toggle_action_get_active (action);
625
626   psppire_data_editor_show_grid (de->data_editor, grid_visible);
627 }
628
629 static void
630 data_view_activate (PsppireDataWindow  *de)
631 {
632   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_DATA_VIEW);
633 }
634
635
636 static void
637 variable_view_activate (PsppireDataWindow  *de)
638 {
639   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
640 }
641
642
643 static void
644 fonts_activate (PsppireDataWindow  *de)
645 {
646   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (de));
647   PangoFontDescription *current_font;
648   gchar *font_name;
649   GtkWidget *dialog =
650     gtk_font_selection_dialog_new (_("Font Selection"));
651
652
653   current_font = GTK_WIDGET(de->data_editor)->style->font_desc;
654   font_name = pango_font_description_to_string (current_font);
655
656   gtk_font_selection_dialog_set_font_name (GTK_FONT_SELECTION_DIALOG (dialog), font_name);
657
658   g_free (font_name);
659
660   gtk_window_set_transient_for (GTK_WINDOW (dialog),
661                                 GTK_WINDOW (toplevel));
662
663   if ( GTK_RESPONSE_OK == gtk_dialog_run (GTK_DIALOG (dialog)) )
664     {
665       const gchar *font = gtk_font_selection_dialog_get_font_name
666         (GTK_FONT_SELECTION_DIALOG (dialog));
667
668       PangoFontDescription* font_desc =
669         pango_font_description_from_string (font);
670
671       psppire_data_editor_set_font (de->data_editor, font_desc);
672     }
673
674   gtk_widget_hide (dialog);
675 }
676
677
678
679 /* Callback for the value labels action */
680 static void
681 toggle_value_labels (PsppireDataWindow  *de, GtkToggleAction *ta)
682 {
683   g_object_set (de->data_editor, "value-labels", gtk_toggle_action_get_active (ta), NULL);
684 }
685
686 static void
687 toggle_split_window (PsppireDataWindow  *de, GtkToggleAction *ta)
688 {
689   psppire_data_editor_split_window (de->data_editor,
690                                     gtk_toggle_action_get_active (ta));
691 }
692
693
694 static void
695 file_quit (PsppireDataWindow *de)
696 {
697   /* FIXME: Need to be more intelligent here.
698      Give the user the opportunity to save any unsaved data.
699   */
700   psppire_quit ();
701 }
702
703 static void
704 on_recent_data_select (GtkMenuShell *menushell,
705                        PsppireWindow *window)
706 {
707   gchar *file;
708
709   gchar *uri =
710     gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (menushell));
711
712   file = g_filename_from_uri (uri, NULL, NULL);
713
714   g_free (uri);
715
716   open_data_window (window, file, NULL);
717
718   g_free (file);
719 }
720
721 static char *
722 charset_from_mime_type (const char *mime_type)
723 {
724   const char *charset;
725   struct string s;
726   const char *p;
727
728   if (mime_type == NULL)
729     return NULL;
730
731   charset = c_strcasestr (mime_type, "charset=");
732   if (charset == NULL)
733     return NULL;
734
735   ds_init_empty (&s);
736   p = charset + 8;
737   if (*p == '"')
738     {
739       /* Parse a "quoted-string" as defined by RFC 822. */
740       for (p++; *p != '\0' && *p != '"'; p++)
741         {
742           if (*p != '\\')
743             ds_put_byte (&s, *p);
744           else if (*++p != '\0')
745             ds_put_byte (&s, *p);
746         }
747     }
748   else
749     {
750       /* Parse a "token" as defined by RFC 2045. */
751       while (*p > 32 && *p < 127 && strchr ("()<>@,;:\\\"/[]?=", *p) == NULL)
752         ds_put_byte (&s, *p++);
753     }
754   if (!ds_is_empty (&s))
755     return ds_steal_cstr (&s);
756
757   ds_destroy (&s);
758   return NULL;
759 }
760
761 static void
762 on_recent_files_select (GtkMenuShell *menushell,   gpointer user_data)
763 {
764   GtkRecentInfo *item;
765   char *encoding;
766   GtkWidget *se;
767   gchar *file;
768
769   /* Get the file name and its encoding. */
770   item = gtk_recent_chooser_get_current_item (GTK_RECENT_CHOOSER (menushell));
771   file = g_filename_from_uri (gtk_recent_info_get_uri (item), NULL, NULL);
772   encoding = charset_from_mime_type (gtk_recent_info_get_mime_type (item));
773   gtk_recent_info_unref (item);
774
775   se = psppire_syntax_window_new (encoding);
776
777   free (encoding);
778
779   if ( psppire_window_load (PSPPIRE_WINDOW (se), file, NULL) ) 
780     gtk_widget_show (se);
781   else
782     gtk_widget_destroy (se);
783
784   g_free (file);
785 }
786
787 static void
788 set_unsaved (gpointer w)
789 {
790   psppire_window_set_unsaved (PSPPIRE_WINDOW (w));
791 }
792
793 static void
794 on_switch_page (PsppireDataEditor *de, gpointer p,
795                 gint pagenum, PsppireDataWindow *dw)
796 {
797   GtkWidget *page_menu_item;
798   gboolean is_ds;
799   const char *path;
800
801   is_ds = pagenum == PSPPIRE_DATA_EDITOR_DATA_VIEW;
802   path = (is_ds
803           ? "/ui/menubar/view/view_data"
804           : "/ui/menubar/view/view_variables");
805   page_menu_item = gtk_ui_manager_get_widget (dw->ui_manager, path);
806   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (page_menu_item), TRUE);
807 }
808
809 static void
810 on_ui_manager_changed (PsppireDataEditor *de,
811                        GParamSpec *pspec UNUSED,
812                        PsppireDataWindow *dw)
813 {
814   GtkUIManager *uim = psppire_data_editor_get_ui_manager (de);
815   if (uim == dw->uim)
816     return;
817
818   if (dw->uim)
819     {
820       psppire_data_window_remove_ui (dw, dw->uim, dw->merge_id);
821       g_object_unref (dw->uim);
822       dw->uim = NULL;
823     }
824
825   dw->uim = uim;
826   if (dw->uim)
827     {
828       g_object_ref (dw->uim);
829       dw->merge_id = psppire_data_window_add_ui (dw, dw->uim);
830     }
831 }
832
833 /* Connects the action called ACTION_NAME to HANDLER passing DW as the auxilliary data.
834    Returns a pointer to the action
835 */
836 static GtkAction *
837 connect_action (PsppireDataWindow *dw, const char *action_name, 
838                                     GCallback handler)
839 {
840   GtkAction *action = get_action_assert (dw->builder, action_name);
841
842   g_signal_connect_swapped (action, "activate", handler, dw);
843
844   return action;
845 }
846
847 /* Only a data file with at least one variable can be saved. */
848 static void
849 enable_save (PsppireDataWindow *dw)
850 {
851   gboolean enable = psppire_dict_get_var_cnt (dw->dict) > 0;
852
853   gtk_action_set_sensitive (get_action_assert (dw->builder, "file_save"),
854                             enable);
855   gtk_action_set_sensitive (get_action_assert (dw->builder, "file_save_as"),
856                             enable);
857 }
858
859 /* Initializes as much of a PsppireDataWindow as we can and must before the
860    dataset has been set.
861
862    In particular, the 'menu' member is required in case the "filename" property
863    is set before the "dataset" property: otherwise PsppireWindow will try to
864    modify the menu as part of the "filename" property_set() function and end up
865    with a Gtk-CRITICAL since 'menu' is NULL.  */
866 static void
867 psppire_data_window_init (PsppireDataWindow *de)
868 {
869   de->builder = builder_new ("data-editor.ui");
870
871   de->ui_manager = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
872
873   PSPPIRE_WINDOW (de)->menu =
874     GTK_MENU_SHELL (gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/windows/windows_minimise_all")->parent);
875
876   de->uim = NULL;
877   de->merge_id = 0;
878 }
879
880 static void
881 psppire_data_window_finish_init (PsppireDataWindow *de,
882                                  struct dataset *ds)
883 {
884   static const struct dataset_callbacks cbs =
885     {
886       set_unsaved,                    /* changed */
887       transformation_change_callback, /* transformations_changed */
888     };
889
890   GtkWidget *menubar;
891   GtkWidget *hb ;
892   GtkWidget *sb ;
893
894   GtkWidget *box = gtk_vbox_new (FALSE, 0);
895
896   de->dataset = ds;
897   de->dict = psppire_dict_new_from_dict (dataset_dict (ds));
898   de->data_store = psppire_data_store_new (de->dict);
899   psppire_data_store_set_reader (de->data_store, NULL);
900
901   menubar = get_widget_assert (de->builder, "menubar");
902   hb = get_widget_assert (de->builder, "handlebox1");
903   sb = get_widget_assert (de->builder, "status-bar");
904
905   de->uim = NULL;
906   de->merge_id = 0;
907
908   de->data_editor =
909     PSPPIRE_DATA_EDITOR (psppire_data_editor_new (de->dict, de->data_store));
910   g_signal_connect (de->data_editor, "switch-page",
911                     G_CALLBACK (on_switch_page), de);
912
913   g_signal_connect_swapped (de->data_store, "case-changed",
914                             G_CALLBACK (set_unsaved), de);
915
916   g_signal_connect_swapped (de->data_store, "case-inserted",
917                             G_CALLBACK (set_unsaved), de);
918
919   g_signal_connect_swapped (de->data_store, "cases-deleted",
920                             G_CALLBACK (set_unsaved), de);
921
922   dataset_set_callbacks (de->dataset, &cbs, de);
923
924   connect_help (de->builder);
925
926   gtk_box_pack_start (GTK_BOX (box), menubar, FALSE, TRUE, 0);
927   gtk_box_pack_start (GTK_BOX (box), hb, FALSE, TRUE, 0);
928   gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET (de->data_editor), TRUE, TRUE, 0);
929   gtk_box_pack_start (GTK_BOX (box), sb, FALSE, TRUE, 0);
930
931   gtk_container_add (GTK_CONTAINER (de), box);
932
933   g_signal_connect (de->dict, "weight-changed",
934                     G_CALLBACK (on_weight_change),
935                     de);
936
937   g_signal_connect (de->dict, "filter-changed",
938                     G_CALLBACK (on_filter_change),
939                     de);
940
941   g_signal_connect (de->dict, "split-changed",
942                     G_CALLBACK (on_split_change),
943                     de);
944
945   g_signal_connect_swapped (de->dict, "backend-changed",
946                             G_CALLBACK (enable_save), de);
947   g_signal_connect_swapped (de->dict, "variable-inserted",
948                             G_CALLBACK (enable_save), de);
949   g_signal_connect_swapped (de->dict, "variable-deleted",
950                             G_CALLBACK (enable_save), de);
951   enable_save (de);
952
953   connect_action (de, "file_new_data", G_CALLBACK (create_data_window));
954
955   connect_action (de, "file_import", G_CALLBACK (text_data_import_assistant));
956
957   connect_action (de, "file_save", G_CALLBACK (psppire_window_save));
958  
959   connect_action (de, "file_open", G_CALLBACK (psppire_window_open));
960
961   connect_action (de, "file_save_as", G_CALLBACK (psppire_window_save_as));
962
963   connect_action (de, "rename_dataset", G_CALLBACK (on_rename_dataset));
964
965   connect_action (de, "file_information_working-file", G_CALLBACK (display_dict));
966
967   connect_action (de, "file_information_external-file", G_CALLBACK (sysfile_info));
968
969   g_signal_connect_swapped (get_action_assert (de->builder, "view_value-labels"), "toggled", G_CALLBACK (toggle_value_labels), de);
970
971   connect_action (de, "data_transpose", G_CALLBACK (transpose_dialog));
972   connect_action (de, "data_select-cases", G_CALLBACK (select_cases_dialog));
973   connect_action (de, "data_aggregate", G_CALLBACK (aggregate_dialog));
974   connect_action (de, "transform_compute", G_CALLBACK (compute_dialog));
975   connect_action (de, "transform_autorecode", G_CALLBACK (autorecode_dialog));
976   connect_action (de, "data_split-file", G_CALLBACK (split_file_dialog));
977   connect_action (de, "data_weight-cases", G_CALLBACK (weight_cases_dialog));
978   connect_action (de, "oneway-anova", G_CALLBACK (oneway_anova_dialog));
979   connect_action (de, "paired-t-test", G_CALLBACK (t_test_paired_samples_dialog));
980   connect_action (de, "one-sample-t-test", G_CALLBACK (t_test_one_sample_dialog));
981   connect_action (de, "utilities_comments", G_CALLBACK (comments_dialog));
982   connect_action (de, "transform_count", G_CALLBACK (count_dialog));
983   connect_action (de, "transform_recode-same", G_CALLBACK (recode_same_dialog));
984   connect_action (de, "transform_recode-different", G_CALLBACK (recode_different_dialog));
985   connect_action (de, "univariate", G_CALLBACK (univariate_dialog));
986   connect_action (de, "chi-square", G_CALLBACK (chisquare_dialog));
987   connect_action (de, "runs", G_CALLBACK (runs_dialog));
988   connect_action (de, "ks-one-sample", G_CALLBACK (ks_one_sample_dialog));
989   connect_action (de, "k-related-samples", G_CALLBACK (k_related_dialog));
990   connect_action (de, "two-related-samples", G_CALLBACK (two_related_dialog));
991
992   {
993     GtkWidget *recent_data =
994       gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/file/file_recent-data");
995
996     GtkWidget *recent_files =
997       gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/file/file_recent-files");
998
999
1000     GtkWidget *menu_data = gtk_recent_chooser_menu_new_for_manager (
1001       gtk_recent_manager_get_default ());
1002
1003     GtkWidget *menu_files = gtk_recent_chooser_menu_new_for_manager (
1004       gtk_recent_manager_get_default ());
1005
1006     g_object_set (menu_data, "show-tips",  TRUE, NULL);
1007     g_object_set (menu_files, "show-tips",  TRUE, NULL);
1008
1009     {
1010       GtkRecentFilter *filter = gtk_recent_filter_new ();
1011
1012       gtk_recent_filter_add_mime_type (filter, "application/x-spss-sav");
1013       gtk_recent_filter_add_mime_type (filter, "application/x-spss-por");
1014
1015       gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (menu_data), GTK_RECENT_SORT_MRU);
1016
1017       gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu_data), filter);
1018     }
1019
1020     gtk_menu_item_set_submenu (GTK_MENU_ITEM (recent_data), menu_data);
1021
1022
1023     g_signal_connect (menu_data, "selection-done", G_CALLBACK (on_recent_data_select), de);
1024
1025     {
1026       GtkRecentFilter *filter = gtk_recent_filter_new ();
1027
1028       gtk_recent_filter_add_pattern (filter, "*.sps");
1029       gtk_recent_filter_add_pattern (filter, "*.SPS");
1030
1031       gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (menu_files), GTK_RECENT_SORT_MRU);
1032
1033       gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu_files), filter);
1034     }
1035
1036     gtk_menu_item_set_submenu (GTK_MENU_ITEM (recent_files), menu_files);
1037
1038     g_signal_connect (menu_files, "selection-done", G_CALLBACK (on_recent_files_select), de);
1039
1040   }
1041
1042   connect_action (de, "file_new_syntax", G_CALLBACK (create_syntax_window));
1043
1044
1045   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
1046   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_DATA_VIEW);
1047
1048   connect_action (de, "view_statusbar", G_CALLBACK (status_bar_activate));
1049
1050   connect_action (de, "view_gridlines", G_CALLBACK (grid_lines_activate));
1051
1052   connect_action (de, "view_data", G_CALLBACK (data_view_activate));
1053
1054   connect_action (de, "view_variables", G_CALLBACK (variable_view_activate));
1055
1056   connect_action (de, "view_fonts", G_CALLBACK (fonts_activate));
1057
1058   connect_action (de, "file_quit", G_CALLBACK (file_quit));
1059
1060   connect_action (de, "transform_run-pending", G_CALLBACK (execute));
1061
1062   connect_action (de, "windows_minimise_all", G_CALLBACK (psppire_window_minimise_all));
1063
1064   g_signal_connect_swapped (get_action_assert (de->builder, "windows_split"), "toggled", G_CALLBACK (toggle_split_window), de);
1065
1066   merge_help_menu (de->ui_manager);
1067
1068   g_signal_connect (de->data_editor, "notify::ui-manager",
1069                     G_CALLBACK (on_ui_manager_changed), de);
1070   on_ui_manager_changed (de->data_editor, NULL, de);
1071
1072   gtk_widget_show (GTK_WIDGET (de->data_editor));
1073   gtk_widget_show (box);
1074
1075   ll_push_head (&all_data_windows, &de->ll);
1076 }
1077
1078 static void
1079 psppire_data_window_dispose (GObject *object)
1080 {
1081   PsppireDataWindow *dw = PSPPIRE_DATA_WINDOW (object);
1082
1083   if (dw->uim)
1084     {
1085       psppire_data_window_remove_ui (dw, dw->uim, dw->merge_id);
1086       g_object_unref (dw->uim);
1087       dw->uim = NULL;
1088     }
1089
1090   if (dw->builder != NULL)
1091     {
1092       g_object_unref (dw->builder);
1093       dw->builder = NULL;
1094     }
1095
1096   if (dw->dict)
1097     {
1098       g_object_unref (dw->dict);
1099       dw->dict = NULL;
1100     }
1101
1102   if (dw->data_store)
1103     {
1104       g_object_unref (dw->data_store);
1105       dw->data_store = NULL;
1106     }
1107
1108   if (dw->ll.next != NULL)
1109     {
1110       ll_remove (&dw->ll);
1111       dw->ll.next = NULL;
1112     }
1113
1114   if (G_OBJECT_CLASS (parent_class)->dispose)
1115     G_OBJECT_CLASS (parent_class)->dispose (object);
1116 }
1117
1118 static void
1119 psppire_data_window_finalize (GObject *object)
1120 {
1121   PsppireDataWindow *dw = PSPPIRE_DATA_WINDOW (object);
1122
1123   if (dw->dataset)
1124     {
1125       struct dataset *dataset = dw->dataset;
1126       struct session *session = dataset_session (dataset);
1127
1128       dw->dataset = NULL;
1129
1130       dataset_set_callbacks (dataset, NULL, NULL);
1131       session_set_active_dataset (session, NULL);
1132       dataset_destroy (dataset);
1133     }
1134
1135   if (G_OBJECT_CLASS (parent_class)->finalize)
1136     G_OBJECT_CLASS (parent_class)->finalize (object);
1137 }
1138
1139 static void
1140 psppire_data_window_set_property (GObject         *object,
1141                                   guint            prop_id,
1142                                   const GValue    *value,
1143                                   GParamSpec      *pspec)
1144 {
1145   PsppireDataWindow *window = PSPPIRE_DATA_WINDOW (object);
1146
1147   switch (prop_id)
1148     {
1149     case PROP_DATASET:
1150       psppire_data_window_finish_init (window, g_value_get_pointer (value));
1151       break;
1152     default:
1153       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1154       break;
1155     };
1156 }
1157
1158 static void
1159 psppire_data_window_get_property (GObject         *object,
1160                                   guint            prop_id,
1161                                   GValue          *value,
1162                                   GParamSpec      *pspec)
1163 {
1164   PsppireDataWindow *window = PSPPIRE_DATA_WINDOW (object);
1165
1166   switch (prop_id)
1167     {
1168     case PROP_DATASET:
1169       g_value_set_pointer (value, window->dataset);
1170       break;
1171     default:
1172       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1173       break;
1174     };
1175 }
1176
1177 static guint
1178 psppire_data_window_add_ui (PsppireDataWindow *pdw, GtkUIManager *uim)
1179 {
1180   gchar *ui_string;
1181   guint merge_id;
1182   GList *list;
1183
1184   ui_string = gtk_ui_manager_get_ui (uim);
1185   merge_id = gtk_ui_manager_add_ui_from_string (pdw->ui_manager, ui_string,
1186                                                 -1, NULL);
1187   g_free (ui_string);
1188
1189   g_return_val_if_fail (merge_id != 0, 0);
1190
1191   list = gtk_ui_manager_get_action_groups (uim);
1192   for (; list != NULL; list = list->next)
1193     {
1194       GtkActionGroup *action_group = list->data;
1195       GList *actions = gtk_action_group_list_actions (action_group);
1196       GList *action;
1197
1198       for (action = actions; action != NULL; action = action->next)
1199         {
1200           GtkAction *a = action->data;
1201
1202           if (PSPPIRE_IS_DIALOG_ACTION (a))
1203             g_object_set (a, "manager", pdw->ui_manager, NULL);
1204         }
1205
1206       gtk_ui_manager_insert_action_group (pdw->ui_manager, action_group, 0);
1207     }
1208
1209   gtk_window_add_accel_group (GTK_WINDOW (pdw),
1210                               gtk_ui_manager_get_accel_group (uim));
1211
1212   return merge_id;
1213 }
1214
1215 static void
1216 psppire_data_window_remove_ui (PsppireDataWindow *pdw,
1217                                GtkUIManager *uim, guint merge_id)
1218 {
1219   GList *list;
1220
1221   g_return_if_fail (merge_id != 0);
1222
1223   gtk_ui_manager_remove_ui (pdw->ui_manager, merge_id);
1224
1225   list = gtk_ui_manager_get_action_groups (uim);
1226   for (; list != NULL; list = list->next)
1227     {
1228       GtkActionGroup *action_group = list->data;
1229       gtk_ui_manager_remove_action_group (pdw->ui_manager, action_group);
1230     }
1231
1232   gtk_window_remove_accel_group (GTK_WINDOW (pdw),
1233                                  gtk_ui_manager_get_accel_group (uim));
1234 }
1235
1236 GtkWidget*
1237 psppire_data_window_new (struct dataset *ds)
1238 {
1239   GtkWidget *dw;
1240
1241   if (the_session == NULL)
1242     the_session = session_create (NULL);
1243
1244   if (ds == NULL)
1245     {
1246       char *dataset_name = session_generate_dataset_name (the_session);
1247       ds = dataset_create (the_session, dataset_name);
1248       free (dataset_name);
1249     }
1250   assert (dataset_session (ds) == the_session);
1251
1252   dw = GTK_WIDGET (
1253     g_object_new (
1254       psppire_data_window_get_type (),
1255       "description", _("Data Editor"),
1256       "dataset", ds,
1257       NULL));
1258
1259   if (dataset_name (ds) != NULL)
1260     g_object_set (dw, "id", dataset_name (ds), (void *) NULL);
1261
1262   return dw;
1263 }
1264
1265 bool
1266 psppire_data_window_is_empty (PsppireDataWindow *dw)
1267 {
1268   return psppire_dict_get_var_cnt (dw->dict) == 0;
1269 }
1270
1271 static void
1272 psppire_data_window_iface_init (PsppireWindowIface *iface)
1273 {
1274   iface->save = save_file;
1275   iface->pick_filename = data_pick_filename;
1276   iface->load = load_file;
1277 }
1278 \f
1279 PsppireDataWindow *
1280 psppire_default_data_window (void)
1281 {
1282   if (ll_is_empty (&all_data_windows))
1283     create_data_window ();
1284   return ll_data (ll_head (&all_data_windows), PsppireDataWindow, ll);
1285 }
1286
1287 void
1288 psppire_data_window_set_default (PsppireDataWindow *pdw)
1289 {
1290   ll_remove (&pdw->ll);
1291   ll_push_head (&all_data_windows, &pdw->ll);
1292 }
1293
1294 void
1295 psppire_data_window_undefault (PsppireDataWindow *pdw)
1296 {
1297   ll_remove (&pdw->ll);
1298   ll_push_tail (&all_data_windows, &pdw->ll);
1299 }
1300
1301 PsppireDataWindow *
1302 psppire_data_window_for_dataset (struct dataset *ds)
1303 {
1304   PsppireDataWindow *pdw;
1305
1306   ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows)
1307     if (pdw->dataset == ds)
1308       return pdw;
1309
1310   return NULL;
1311 }
1312
1313 PsppireDataWindow *
1314 psppire_data_window_for_data_store (PsppireDataStore *data_store)
1315 {
1316   PsppireDataWindow *pdw;
1317
1318   ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows)
1319     if (pdw->data_store == data_store)
1320       return pdw;
1321
1322   return NULL;
1323 }
1324
1325 void
1326 create_data_window (void)
1327 {
1328   gtk_widget_show (psppire_data_window_new (NULL));
1329 }
1330
1331 void
1332 open_data_window (PsppireWindow *victim, const char *file_name, gpointer hint)
1333 {
1334   GtkWidget *window;
1335
1336   if (PSPPIRE_IS_DATA_WINDOW (victim)
1337       && psppire_data_window_is_empty (PSPPIRE_DATA_WINDOW (victim)))
1338     {
1339       window = GTK_WIDGET (victim);
1340       gtk_widget_hide (GTK_WIDGET (PSPPIRE_DATA_WINDOW (window)->data_editor));
1341     }
1342   else
1343     window = psppire_data_window_new (NULL);
1344
1345   psppire_window_load (PSPPIRE_WINDOW (window), file_name, hint);
1346   gtk_widget_show_all (window);
1347 }
1348