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