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