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