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