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