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