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