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