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