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