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