f1848621b45e68c6af4a670fc82cce0f2e417483
[pspp-builds.git] / src / ui / gui / data-editor.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2006, 2007  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 #include <stdlib.h>
19 #include <gettext.h>
20
21 #include <glade/glade.h>
22 #include <gtk/gtk.h>
23
24 #include "window-manager.h"
25 #include <gtksheet/gtksheet.h>
26
27 #include "helper.h"
28 #include "about.h"
29 #include <data/procedure.h>
30 #include "psppire-dialog.h"
31 #include "psppire-selector.h"
32 #include "weight-cases-dialog.h"
33 #include "split-file-dialog.h"
34 #include "transpose-dialog.h"
35 #include "sort-cases-dialog.h"
36 #include "select-cases-dialog.h"
37 #include "compute-dialog.h"
38 #include "goto-case-dialog.h"
39 #include "find-dialog.h"
40 #include "rank-dialog.h"
41 #include "recode-dialog.h"
42 #include "comments-dialog.h"
43 #include "variable-info-dialog.h"
44 #include "descriptives-dialog.h"
45 #include "dict-display.h"
46 #include "clipboard.h"
47
48
49 #include "oneway-anova-dialog.h"
50 #include "t-test-independent-samples-dialog.h"
51
52 #define _(msgid) gettext (msgid)
53 #define N_(msgid) msgid
54
55 #include "data-editor.h"
56 #include "syntax-editor.h"
57 #include <language/syntax-string-source.h>
58 #include <language/command.h>
59 #include <libpspp/syntax-gen.h>
60 #include "window-manager.h"
61
62 #include "psppire-data-store.h"
63 #include "psppire-var-store.h"
64
65 static void on_edit_copy (GtkMenuItem *, gpointer);
66 static void on_edit_cut (GtkMenuItem *, gpointer);
67 static void on_edit_paste (GtkAction *a, gpointer data);
68
69
70 static void create_data_sheet_variable_popup_menu (struct data_editor *);
71 static void create_data_sheet_cases_popup_menu (struct data_editor *);
72
73 static void popup_variable_menu (GtkSheet *, gint,
74                                  GdkEventButton *, gpointer data);
75
76 static void popup_cases_menu (GtkSheet *, gint,
77                                  GdkEventButton *, gpointer data);
78
79 /* Update the data_ref_entry with the reference of the active cell */
80 static gint update_data_ref_entry (const GtkSheet *sheet,
81                                    gint row, gint col, gpointer data);
82
83 static void register_data_editor_actions (struct data_editor *de);
84
85 static void insert_variable (GtkAction *, gpointer data);
86 static void insert_case (GtkAction *a, gpointer data);
87 static void delete_cases (GtkAction *a, gpointer data);
88 static void delete_variables (GtkAction *a, gpointer data);
89
90 static void toggle_value_labels (GtkToggleAction *a, gpointer data);
91
92 /* Switch between the VAR SHEET and the DATA SHEET */
93
94 static gboolean click2column (GtkWidget *w, gint col, gpointer data);
95 static gboolean click2row (GtkWidget *w, gint row, gpointer data);
96
97
98 /* Callback for when the dictionary changes properties*/
99 static void on_weight_change (GObject *, gint, gpointer);
100 static void on_filter_change (GObject *, gint, gpointer);
101 static void on_split_change (PsppireDict *, gpointer);
102
103 static void on_switch_sheet (GtkNotebook *notebook,
104                             GtkNotebookPage *page,
105                             guint page_num,
106                             gpointer user_data);
107
108 static void status_bar_activate (GtkCheckMenuItem *, gpointer);
109
110 static void grid_lines_activate (GtkCheckMenuItem *, gpointer);
111
112 static void data_sheet_activate (GtkCheckMenuItem *, gpointer);
113
114 static void variable_sheet_activate (GtkCheckMenuItem *, gpointer );
115
116 static void fonts_activate (GtkMenuItem *, gpointer);
117
118 static void file_quit (GtkCheckMenuItem *, gpointer );
119
120 static void
121 enable_delete_cases (GtkWidget *w, gint var, gpointer data)
122 {
123   struct data_editor *de = data;
124
125   gtk_action_set_visible (de->delete_cases, var != -1);
126 }
127
128
129 static void
130 enable_delete_variables (GtkWidget *w, gint var, gpointer data)
131 {
132   struct data_editor *de = data;
133
134   gtk_action_set_visible (de->delete_variables, var != -1);
135 }
136
137
138
139 /* Run the EXECUTE command. */
140 static void
141 execute (GtkMenuItem *mi, gpointer data)
142 {
143   struct getl_interface *sss = create_syntax_string_source ("EXECUTE.");
144
145   execute_syntax (sss);
146 }
147
148 static void
149 transformation_change_callback (bool transformations_pending,
150                                 gpointer data)
151 {
152   struct data_editor *de = data;
153   GtkWidget *menuitem =
154     get_widget_assert (de->xml, "transform_run-pending");
155   GtkWidget *status_label  =
156     get_widget_assert (de->xml, "case-counter-area");
157
158   gtk_widget_set_sensitive (menuitem, transformations_pending);
159
160
161   if ( transformations_pending)
162     gtk_label_set_text (GTK_LABEL (status_label),
163                         _("Transformations Pending"));
164   else
165     gtk_label_set_text (GTK_LABEL (status_label), "");
166 }
167
168
169 static void open_data_file (const gchar *, struct data_editor *);
170
171
172 /* Puts FILE_NAME into the recent list.
173    If it's already in the list, it moves it to the top
174 */
175 static void
176 add_most_recent (const char *file_name)
177 {
178 #if RECENT_LISTS_AVAILABLE
179
180   GtkRecentManager *manager = gtk_recent_manager_get_default();
181   gchar *uri = g_filename_to_uri (file_name, NULL, NULL);
182
183   gtk_recent_manager_remove_item (manager, uri, NULL);
184
185   if ( ! gtk_recent_manager_add_item (manager, uri))
186     g_warning ("Could not add item %s to recent list\n",uri);
187
188   g_free (uri);
189 #endif
190 }
191
192
193
194 #if RECENT_LISTS_AVAILABLE
195
196 static void
197 on_recent_data_select (GtkMenuShell *menushell,   gpointer user_data)
198 {
199   gchar *file;
200   struct data_editor *de = user_data;
201
202   gchar *uri =
203     gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (menushell));
204
205   file = g_filename_from_uri (uri, NULL, NULL);
206
207   g_free (uri);
208
209   open_data_file (file, de);
210
211   g_free (file);
212 }
213
214 static void
215 on_recent_files_select (GtkMenuShell *menushell,   gpointer user_data)
216 {
217   gchar *file;
218
219   struct syntax_editor *se ;
220
221   gchar *uri =
222     gtk_recent_chooser_get_current_uri (GTK_RECENT_CHOOSER (menushell));
223
224   file = g_filename_from_uri (uri, NULL, NULL);
225
226   g_free (uri);
227
228   se = (struct syntax_editor *)
229     window_create (WINDOW_SYNTAX, file);
230
231   load_editor_from_file (se, file, NULL);
232
233   g_free (file);
234 }
235
236 #endif
237
238 static void
239 datum_entry_activate (GtkEntry *entry, gpointer data)
240 {
241   gint row, column;
242   GtkSheet *data_sheet = GTK_SHEET (data);
243   PsppireDataStore *store = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
244
245   const char *text = gtk_entry_get_text (entry);
246
247   gtk_sheet_get_active_cell (data_sheet, &row, &column);
248
249   if ( row == -1 || column == -1)
250     return;
251
252   psppire_data_store_set_string (store, text, row, column);
253 }
254
255
256 /* Update the Edit->Paste menuitem
257    If PAGE is not -1 , then it should be set to the current page of
258    the data editors notebook widget.
259    If -1, then it'll be queried.
260 */
261 static void
262 update_paste_menuitem (struct data_editor *de, gint page)
263 {
264   GtkWidget * edit_paste = get_widget_assert (de->xml, "edit_paste");
265   GtkWidget *notebook = get_widget_assert (de->xml, "notebook");
266   GtkSheet * data_sheet ;
267   gint row, column;
268
269   if ( page < 0 )
270     page = gtk_notebook_get_current_page (GTK_NOTEBOOK(notebook));
271
272
273   if ( PAGE_VAR_SHEET == page )
274     {
275       /* We don't yet support pasting to the var sheet */
276       gtk_widget_set_sensitive (edit_paste, FALSE);
277       return;
278     }
279
280   data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
281
282   gtk_sheet_get_active_cell (data_sheet, &row, &column);
283
284   if ( row < 0 || column < 0 )
285       gtk_widget_set_sensitive (edit_paste, FALSE);
286   else
287       gtk_widget_set_sensitive (edit_paste, TRUE);
288 }
289
290 /* Update the Edit->Cut and Edit->Copy menuitems
291    If PAGE is not -1 , then it should be set to the current page of
292    the data editors notebook widget.
293    If -1, then it'll be queried.
294 */
295 static void
296 update_cut_copy_menuitem (struct data_editor *de, gint page)
297 {
298   GtkWidget * edit_copy = get_widget_assert (de->xml, "edit_copy");
299   GtkWidget * edit_cut = get_widget_assert (de->xml, "edit_cut");
300   GtkWidget *notebook = get_widget_assert (de->xml, "notebook");
301   GtkSheet * data_sheet ;
302   gint row, column;
303
304   if ( page < 0 )
305     page = gtk_notebook_get_current_page (GTK_NOTEBOOK(notebook));
306
307
308   if ( PAGE_VAR_SHEET == page )
309     {
310       /* We don't yet support copying from the var sheet */
311       gtk_widget_set_sensitive (edit_copy, FALSE);
312       gtk_widget_set_sensitive (edit_cut, FALSE);
313       return;
314     }
315
316   data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
317
318   gtk_sheet_get_active_cell (data_sheet, &row, &column);
319
320   if ( row < 0 || column < 0 )
321     {
322       gtk_widget_set_sensitive (edit_copy, FALSE);
323       gtk_widget_set_sensitive (edit_cut, FALSE);
324       return;
325     }
326
327   gtk_widget_set_sensitive (edit_copy, TRUE);
328   gtk_widget_set_sensitive (edit_cut, TRUE);
329 }
330
331
332 /* Callback for when the datasheet's active cell becomes active/inactive */
333 static gboolean
334 on_data_sheet_activate_change (GtkSheet *sheet,
335                                gint row, gint column, gpointer data)
336 {
337   struct data_editor *de = data;
338
339   update_paste_menuitem (de, -1);
340   update_cut_copy_menuitem (de, -1);
341
342   return TRUE;
343 }
344
345 extern struct dataset *the_dataset;
346
347 /*
348   Create a new data editor.
349 */
350 struct data_editor *
351 new_data_editor (void)
352 {
353   struct data_editor *de ;
354   struct editor_window *e;
355   GtkSheet *var_sheet ;
356   GtkSheet *data_sheet ;
357   PsppireVarStore *vs;
358   GtkWidget *datum_entry;
359
360   de = g_malloc0 (sizeof (*de));
361
362   e = (struct editor_window *) de;
363
364   de->xml = XML_NEW ("data-editor.glade");
365
366
367   dataset_add_transform_change_callback (the_dataset,
368                                          transformation_change_callback,
369                                          de);
370
371   var_sheet = GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
372   data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
373
374
375   g_signal_connect (G_OBJECT (data_sheet), "activate",
376                     G_CALLBACK (on_data_sheet_activate_change), de);
377
378   g_signal_connect (G_OBJECT (data_sheet), "deactivate",
379                     G_CALLBACK (on_data_sheet_activate_change), de);
380
381
382   vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
383
384   g_assert(vs); /* Traps a possible bug in win32 build */
385
386   g_signal_connect (G_OBJECT (data_sheet), "activate",
387                     G_CALLBACK (update_data_ref_entry),
388                     de->xml);
389
390   datum_entry = get_widget_assert (de->xml, "datum_entry");
391
392   g_signal_connect (G_OBJECT (datum_entry), "activate",
393                     G_CALLBACK (datum_entry_activate),
394                     data_sheet);
395
396   g_signal_connect (vs->dict, "weight-changed",
397                     G_CALLBACK (on_weight_change),
398                     de);
399
400   g_signal_connect (vs->dict, "filter-changed",
401                     G_CALLBACK (on_filter_change),
402                     de);
403
404   g_signal_connect (vs->dict, "split-changed",
405                     G_CALLBACK (on_split_change),
406                     de);
407
408   connect_help (de->xml);
409
410
411
412   g_signal_connect (get_widget_assert (de->xml, "edit_copy"),
413                     "activate",
414                     G_CALLBACK (on_edit_copy), de);
415
416   g_signal_connect (get_widget_assert (de->xml, "edit_cut"),
417                     "activate",
418                     G_CALLBACK (on_edit_cut), de);
419
420
421   register_data_editor_actions (de);
422
423   de->toggle_value_labels =
424     gtk_toggle_action_new ("toggle-value-labels",
425                            _("Labels"),
426                            _("Show/hide value labels"),
427                            "pspp-value-labels");
428
429   g_signal_connect (de->toggle_value_labels, "activate",
430                     G_CALLBACK (toggle_value_labels), de);
431
432
433   gtk_action_connect_proxy (GTK_ACTION (de->toggle_value_labels),
434                             get_widget_assert (de->xml,
435                                                "togglebutton-value-labels"));
436
437
438   gtk_action_connect_proxy (GTK_ACTION (de->toggle_value_labels),
439                             get_widget_assert (de->xml,
440                                                "view_value-labels"));
441
442   de->delete_cases =
443     gtk_action_new ("clear-cases",
444                     _("Clear"),
445                     _("Delete the cases at the selected position(s)"),
446                     "pspp-clear-cases");
447
448   g_signal_connect (de->delete_cases, "activate",
449                     G_CALLBACK (delete_cases), de);
450
451   gtk_action_connect_proxy (de->delete_cases,
452                             get_widget_assert (de->xml, "edit_clear-cases"));
453
454   g_signal_connect (get_widget_assert (de->xml, "edit_paste"), "activate",
455                     G_CALLBACK (on_edit_paste), de);
456
457   gtk_action_set_visible (de->delete_cases, FALSE);
458
459   de->delete_variables =
460     gtk_action_new ("clear-variables",
461                     _("Clear"),
462                     _("Delete the variables at the selected position(s)"),
463                     "pspp-clear-variables");
464
465   g_signal_connect (de->delete_variables, "activate",
466                     G_CALLBACK (delete_variables), de);
467
468   gtk_action_connect_proxy (de->delete_variables,
469                             get_widget_assert (de->xml, "edit_clear-variables")
470                             );
471
472   gtk_action_set_visible (de->delete_variables, FALSE);
473
474   de->insert_variable =
475     gtk_action_new ("insert-variable",
476                     _("Insert Variable"),
477                     _("Create a new variable at the current position"),
478                     "pspp-insert-variable");
479
480   g_signal_connect (de->insert_variable, "activate",
481                     G_CALLBACK (insert_variable), de);
482
483
484   gtk_action_connect_proxy (de->insert_variable,
485                             get_widget_assert (de->xml, "button-insert-variable")
486                             );
487
488   gtk_action_connect_proxy (de->insert_variable,
489                             get_widget_assert (de->xml, "edit_insert-variable")
490                             );
491
492
493   de->insert_case =
494     gtk_action_new ("insert-case",
495                     _("Insert Case"),
496                     _("Create a new case at the current position"),
497                     "pspp-insert-case");
498
499   g_signal_connect (de->insert_case, "activate",
500                     G_CALLBACK (insert_case), de);
501
502
503   gtk_action_connect_proxy (de->insert_case,
504                             get_widget_assert (de->xml, "button-insert-case")
505                             );
506
507
508   gtk_action_connect_proxy (de->insert_case,
509                             get_widget_assert (de->xml, "edit_insert-case")
510                             );
511
512
513
514   de->invoke_goto_dialog =
515     gtk_action_new ("goto-case-dialog",
516                     _("Goto Case"),
517                     _("Jump to a Case in the Data Sheet"),
518                     "gtk-jump-to");
519
520
521   gtk_action_connect_proxy (de->invoke_goto_dialog,
522                             get_widget_assert (de->xml, "button-goto-case")
523                             );
524
525   gtk_action_connect_proxy (de->invoke_goto_dialog,
526                             get_widget_assert (de->xml, "edit_goto-case")
527                             );
528
529
530   g_signal_connect (de->invoke_goto_dialog, "activate",
531                     G_CALLBACK (goto_case_dialog), de);
532
533
534   de->invoke_weight_cases_dialog =
535     gtk_action_new ("weight-cases-dialog",
536                     _("Weights"),
537                     _("Weight cases by variable"),
538                     "pspp-weight-cases");
539
540   g_signal_connect (de->invoke_weight_cases_dialog, "activate",
541                     G_CALLBACK (weight_cases_dialog), de);
542
543
544   de->invoke_transpose_dialog =
545     gtk_action_new ("transpose-dialog",
546                     _("Transpose"),
547                     _("Transpose the cases with the variables"),
548                     NULL);
549
550
551   g_signal_connect (de->invoke_transpose_dialog, "activate",
552                     G_CALLBACK (transpose_dialog), de);
553
554
555
556   de->invoke_split_file_dialog =
557     gtk_action_new ("split-file-dialog",
558                     _("Split"),
559                     _("Split the active file"),
560                     "pspp-split-file");
561
562   g_signal_connect (de->invoke_split_file_dialog, "activate",
563                     G_CALLBACK (split_file_dialog), de);
564
565
566
567   de->invoke_sort_cases_dialog =
568     gtk_action_new ("sort-cases-dialog",
569                     _("Sort"),
570                     _("Sort cases in the active file"),
571                     "pspp-sort-cases");
572
573   g_signal_connect (de->invoke_sort_cases_dialog, "activate",
574                     G_CALLBACK (sort_cases_dialog), de);
575
576   de->invoke_select_cases_dialog =
577     gtk_action_new ("select-cases-dialog",
578                     _("Select Cases"),
579                     _("Select cases from the active file"),
580                     "pspp-select-cases");
581
582   g_signal_connect (de->invoke_select_cases_dialog, "activate",
583                     G_CALLBACK (select_cases_dialog), de);
584
585
586   de->invoke_compute_dialog =
587     gtk_action_new ("compute-dialog",
588                     _("Compute"),
589                     _("Compute new values for a variable"),
590                     "pspp-compute");
591
592   g_signal_connect (de->invoke_compute_dialog, "activate",
593                     G_CALLBACK (compute_dialog), de);
594
595   de->invoke_oneway_anova_dialog =
596     gtk_action_new ("oneway-anova",
597                     _("Oneway _ANOVA"),
598                     _("Perform one way analysis of variance"),
599                     NULL);
600
601   g_signal_connect (de->invoke_oneway_anova_dialog, "activate",
602                     G_CALLBACK (oneway_anova_dialog), de);
603
604   de->invoke_t_test_independent_samples_dialog =
605     gtk_action_new ("t-test-independent-samples",
606                     _("_Independent Samples T Test"),
607                     _("Calculate T Test for samples from independent groups"),
608                     NULL);
609
610   g_signal_connect (de->invoke_t_test_independent_samples_dialog, "activate",
611                     G_CALLBACK (t_test_independent_samples_dialog), de);
612
613
614   de->invoke_comments_dialog =
615     gtk_action_new ("commments-dialog",
616                     _("Data File Comments"),
617                     _("Commentary text for the data file"),
618                     NULL);
619
620   g_signal_connect (de->invoke_comments_dialog, "activate",
621                     G_CALLBACK (comments_dialog), de);
622
623   de->invoke_find_dialog  =
624     gtk_action_new ("find-dialog",
625                     _("Find"),
626                     _("Find Case"),
627                     "gtk-find");
628
629   g_signal_connect (de->invoke_find_dialog, "activate",
630                     G_CALLBACK (find_dialog), de);
631
632
633   de->invoke_rank_dialog  =
634     gtk_action_new ("rank-dialog",
635                     _("Ran_k Cases"),
636                     _("Rank Cases"),
637                     "pspp-rank-cases");
638
639   g_signal_connect (de->invoke_rank_dialog, "activate",
640                     G_CALLBACK (rank_dialog), de);
641
642
643   de->invoke_recode_same_dialog  =
644     gtk_action_new ("recode-same-dialog",
645                     _("Recode into _Same Variables"),
646                     _("Recode values into the same Variables"),
647                     "pspp-recode-same");
648
649   g_signal_connect (de->invoke_recode_same_dialog, "activate",
650                     G_CALLBACK (recode_same_dialog), de);
651
652
653   de->invoke_recode_different_dialog  =
654     gtk_action_new ("recode-different-dialog",
655                     _("Recode into _Different Variables"),
656                     _("Recode values into different Variables"),
657                     "pspp-recode-different");
658
659   g_signal_connect (de->invoke_recode_different_dialog, "activate",
660                     G_CALLBACK (recode_different_dialog), de);
661
662
663   de->invoke_variable_info_dialog  =
664     gtk_action_new ("variable-info-dialog",
665                     _("Variables"),
666                     _("Jump to Variable"),
667                     "pspp-goto-variable");
668
669   g_signal_connect (de->invoke_variable_info_dialog, "activate",
670                     G_CALLBACK (variable_info_dialog), de);
671
672   de->invoke_descriptives_dialog =
673     gtk_action_new ("descriptives-dialog",
674                     _("_Descriptives"),
675                     _("Calculate descriptive statistics (mean, variance, ...)"),
676                     "pspp-descriptives");
677
678   g_signal_connect (de->invoke_descriptives_dialog, "activate",
679                     G_CALLBACK (descriptives_dialog), de);
680
681   e->window = GTK_WINDOW (get_widget_assert (de->xml, "data_editor"));
682
683   g_signal_connect_swapped (get_widget_assert (de->xml,"file_new_data"),
684                             "activate",
685                             G_CALLBACK (gtk_action_activate),
686                             de->action_data_new);
687
688   g_signal_connect_swapped (get_widget_assert (de->xml,"file_open_data"),
689                             "activate",
690                             G_CALLBACK (gtk_action_activate),
691                             de->action_data_open);
692
693
694 #if RECENT_LISTS_AVAILABLE
695   {
696     GtkRecentManager *rm = gtk_recent_manager_get_default ();
697     GtkWidget *recent_data = get_widget_assert (de->xml, "file_recent-data");
698     GtkWidget *recent_files = get_widget_assert (de->xml, "file_recent-files");
699     GtkWidget *recent_separator = get_widget_assert (de->xml, "file_separator1");
700
701     GtkWidget *menu = gtk_recent_chooser_menu_new_for_manager (rm);
702
703     GtkRecentFilter *filter = gtk_recent_filter_new ();
704
705     gtk_widget_show (recent_data);
706     gtk_widget_show (recent_files);
707     gtk_widget_show (recent_separator);
708
709     gtk_recent_filter_add_pattern (filter, "*.sav");
710     gtk_recent_filter_add_pattern (filter, "*.SAV");
711
712     gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu), filter);
713
714     gtk_widget_set_sensitive (recent_data, TRUE);
715     g_signal_connect (menu, "selection-done",
716                       G_CALLBACK (on_recent_data_select), de);
717
718     gtk_menu_item_set_submenu (GTK_MENU_ITEM (recent_data), menu);
719
720
721     filter = gtk_recent_filter_new ();
722     menu = gtk_recent_chooser_menu_new_for_manager (rm);
723
724     gtk_recent_filter_add_pattern (filter, "*.sps");
725     gtk_recent_filter_add_pattern (filter, "*.SPS");
726
727     gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu), filter);
728
729     gtk_widget_set_sensitive (recent_files, TRUE);
730     g_signal_connect (menu, "selection-done",
731                       G_CALLBACK (on_recent_files_select), de);
732
733     gtk_menu_item_set_submenu (GTK_MENU_ITEM (recent_files), menu);
734   }
735 #endif
736
737   g_signal_connect (get_widget_assert (de->xml,"file_new_syntax"),
738                     "activate",
739                     G_CALLBACK (new_syntax_window),
740                     e->window);
741
742   g_signal_connect (get_widget_assert (de->xml,"file_open_syntax"),
743                     "activate",
744                     G_CALLBACK (open_syntax_window),
745                     e->window);
746
747   g_signal_connect_swapped (get_widget_assert (de->xml,"file_save"),
748                             "activate",
749                             G_CALLBACK (gtk_action_activate),
750                             de->action_data_save);
751
752   g_signal_connect_swapped (get_widget_assert (de->xml,"file_save_as"),
753                             "activate",
754                             G_CALLBACK (gtk_action_activate),
755                             de->action_data_save_as);
756
757   gtk_action_connect_proxy (de->invoke_find_dialog,
758                             get_widget_assert (de->xml, "edit_find")
759                             );
760
761   gtk_action_connect_proxy (de->invoke_find_dialog,
762                             get_widget_assert (de->xml, "button-find")
763                             );
764
765   gtk_action_connect_proxy (de->invoke_rank_dialog,
766                             get_widget_assert (de->xml, "transform_rank")
767                             );
768
769   gtk_action_connect_proxy (de->invoke_recode_same_dialog,
770                             get_widget_assert (de->xml,
771                                                "transform_recode-same")
772                             );
773
774   gtk_action_connect_proxy (de->invoke_recode_different_dialog,
775                             get_widget_assert (de->xml,
776                                                "transform_recode-different")
777                             );
778
779   gtk_action_connect_proxy (de->invoke_weight_cases_dialog,
780                             get_widget_assert (de->xml, "data_weight-cases")
781                             );
782
783   gtk_action_connect_proxy (de->invoke_transpose_dialog,
784                             get_widget_assert (de->xml, "data_transpose")
785                             );
786
787   gtk_action_connect_proxy (de->invoke_split_file_dialog,
788                             get_widget_assert (de->xml, "data_split-file")
789                             );
790
791   gtk_action_connect_proxy (de->invoke_sort_cases_dialog,
792                             get_widget_assert (de->xml, "data_sort-cases")
793                             );
794
795   gtk_action_connect_proxy (de->invoke_select_cases_dialog,
796                             get_widget_assert (de->xml, "data_select-cases")
797                             );
798
799   gtk_action_connect_proxy (de->invoke_compute_dialog,
800                             get_widget_assert (de->xml, "transform_compute")
801                             );
802
803   gtk_action_connect_proxy (de->invoke_t_test_independent_samples_dialog,
804                             get_widget_assert (de->xml,
805                                                "indep-t-test")
806                             );
807
808
809   gtk_action_connect_proxy (de->invoke_oneway_anova_dialog,
810                             get_widget_assert (de->xml,
811                                                "oneway-anova")
812                             );
813
814
815   gtk_action_connect_proxy (de->invoke_comments_dialog,
816                             get_widget_assert (de->xml, "utilities_comments")
817                             );
818
819   gtk_action_connect_proxy (de->invoke_variable_info_dialog,
820                             get_widget_assert (de->xml, "utilities_variables")
821                             );
822
823   gtk_action_connect_proxy (de->invoke_descriptives_dialog,
824                             get_widget_assert (de->xml, "analyze_descriptives")
825                             );
826
827   g_signal_connect (get_widget_assert (de->xml,"help_about"),
828                     "activate",
829                     G_CALLBACK (about_new),
830                     e->window);
831
832
833   g_signal_connect (get_widget_assert (de->xml,"help_reference"),
834                     "activate",
835                     G_CALLBACK (reference_manual),
836                     e->window);
837
838   g_signal_connect (data_sheet,
839                     "double-click-column",
840                     G_CALLBACK (click2column),
841                     de);
842
843   g_signal_connect (data_sheet,
844                     "select-column",
845                     G_CALLBACK (enable_delete_variables),
846                     de);
847
848   g_signal_connect (data_sheet,
849                     "select-row",
850                     G_CALLBACK (enable_delete_cases),
851                     de);
852
853
854   g_signal_connect (var_sheet,
855                     "double-click-row",
856                     GTK_SIGNAL_FUNC (click2row),
857                     de);
858
859   g_signal_connect_after (var_sheet,
860                     "select-row",
861                     G_CALLBACK (enable_delete_variables),
862                     de);
863
864   g_signal_connect (get_widget_assert (de->xml, "notebook"),
865                     "switch-page",
866                     G_CALLBACK (on_switch_sheet), de);
867
868
869   g_signal_connect (get_widget_assert (de->xml, "view_statusbar"),
870                     "activate",
871                     G_CALLBACK (status_bar_activate), de);
872
873
874   g_signal_connect (get_widget_assert (de->xml, "view_gridlines"),
875                     "activate",
876                     G_CALLBACK (grid_lines_activate), de);
877
878
879
880   g_signal_connect (get_widget_assert (de->xml, "view_data"),
881                     "activate",
882                     G_CALLBACK (data_sheet_activate), de);
883
884   g_signal_connect (get_widget_assert (de->xml, "view_variables"),
885                     "activate",
886                     G_CALLBACK (variable_sheet_activate), de);
887
888
889
890   g_signal_connect (get_widget_assert (de->xml, "view_fonts"),
891                     "activate",
892                     G_CALLBACK (fonts_activate), de);
893
894
895
896
897   gtk_action_connect_proxy (de->action_data_open,
898                             get_widget_assert (de->xml, "button-open")
899                             );
900
901   gtk_action_connect_proxy (de->action_data_save,
902                             get_widget_assert (de->xml, "button-save")
903                             );
904
905   gtk_action_connect_proxy (de->invoke_variable_info_dialog,
906                             get_widget_assert (de->xml, "button-goto-variable")
907                             );
908
909   gtk_action_connect_proxy (de->invoke_weight_cases_dialog,
910                             get_widget_assert (de->xml, "button-weight-cases")
911                             );
912
913   gtk_action_connect_proxy (de->invoke_split_file_dialog,
914                             get_widget_assert (de->xml, "button-split-file")
915                             );
916
917   gtk_action_connect_proxy (de->invoke_select_cases_dialog,
918                             get_widget_assert (de->xml, "button-select-cases")
919                             );
920
921
922   g_signal_connect (get_widget_assert (de->xml, "file_quit"),
923                     "activate",
924                     G_CALLBACK (file_quit), de);
925
926   g_signal_connect (get_widget_assert (de->xml, "transform_run-pending"),
927                     "activate",
928                     G_CALLBACK (execute), de);
929
930
931   g_signal_connect (get_widget_assert (de->xml, "windows_minimise_all"),
932                     "activate",
933                     G_CALLBACK (minimise_all_windows), NULL);
934
935
936   create_data_sheet_variable_popup_menu (de);
937   create_data_sheet_cases_popup_menu (de);
938
939   g_signal_connect (G_OBJECT (data_sheet), "button-event-column",
940                     G_CALLBACK (popup_variable_menu), de);
941
942   g_signal_connect (G_OBJECT (data_sheet), "button-event-row",
943                     G_CALLBACK (popup_cases_menu), de);
944
945   return de;
946 }
947
948
949 /* Callback which occurs when the var sheet's row title
950    button is double clicked */
951 static gboolean
952 click2row (GtkWidget *w, gint row, gpointer data)
953 {
954   struct data_editor *de = data;
955   GtkSheetRange visible_range;
956
957   gint current_row, current_column;
958
959   GtkWidget *data_sheet  = get_widget_assert (de->xml, "data_sheet");
960
961   data_editor_select_sheet (de, PAGE_DATA_SHEET);
962
963   gtk_sheet_get_active_cell (GTK_SHEET (data_sheet),
964                              &current_row, &current_column);
965
966   gtk_sheet_set_active_cell (GTK_SHEET (data_sheet), current_row, row);
967
968   gtk_sheet_get_visible_range (GTK_SHEET (data_sheet), &visible_range);
969
970   if ( row < visible_range.col0 || row > visible_range.coli)
971     {
972       gtk_sheet_moveto (GTK_SHEET (data_sheet),
973                         current_row, row, 0, 0);
974     }
975
976   return FALSE;
977 }
978
979
980 /* Callback which occurs when the data sheet's column title
981    is double clicked */
982 static gboolean
983 click2column (GtkWidget *w, gint col, gpointer data)
984 {
985   struct data_editor *de = data;
986
987   gint current_row, current_column;
988
989   GtkWidget *var_sheet  = get_widget_assert (de->xml, "variable_sheet");
990
991   data_editor_select_sheet (de, PAGE_VAR_SHEET);
992
993   gtk_sheet_get_active_cell (GTK_SHEET (var_sheet),
994                              &current_row, &current_column);
995
996   gtk_sheet_set_active_cell (GTK_SHEET (var_sheet), col, current_column);
997
998   return FALSE;
999 }
1000
1001
1002 void
1003 new_data_window (GtkMenuItem *menuitem, gpointer parent)
1004 {
1005   window_create (WINDOW_DATA, NULL);
1006 }
1007
1008 /* Callback for when the datasheet/varsheet is selected */
1009 static void
1010 on_switch_sheet (GtkNotebook *notebook,
1011                 GtkNotebookPage *page,
1012                 guint page_num,
1013                 gpointer user_data)
1014 {
1015   struct data_editor *de = user_data;
1016
1017   GtkWidget *view_data = get_widget_assert (de->xml, "view_data");
1018   GtkWidget *view_variables = get_widget_assert (de->xml, "view_variables");
1019
1020   switch (page_num)
1021     {
1022     case PAGE_VAR_SHEET:
1023       gtk_widget_hide (view_variables);
1024       gtk_widget_show (view_data);
1025       gtk_action_set_sensitive (de->insert_variable, TRUE);
1026       gtk_action_set_sensitive (de->insert_case, FALSE);
1027       gtk_action_set_sensitive (de->invoke_goto_dialog, FALSE);
1028       break;
1029     case PAGE_DATA_SHEET:
1030       gtk_widget_show (view_variables);
1031       gtk_widget_show (view_data);
1032       gtk_action_set_sensitive (de->invoke_goto_dialog, TRUE);
1033       gtk_action_set_sensitive (de->insert_case, TRUE);
1034       break;
1035     default:
1036       g_assert_not_reached ();
1037       break;
1038     }
1039
1040   update_paste_menuitem (de, page_num);
1041   update_cut_copy_menuitem (de, page_num);
1042 }
1043
1044
1045 void
1046 data_editor_select_sheet (struct data_editor *de, gint page)
1047 {
1048   gtk_notebook_set_current_page
1049    (
1050     GTK_NOTEBOOK (get_widget_assert (de->xml,"notebook")), page
1051     );
1052 }
1053
1054
1055
1056 static void
1057 status_bar_activate (GtkCheckMenuItem *menuitem, gpointer data)
1058 {
1059   struct data_editor *de = data;
1060   GtkWidget *statusbar = get_widget_assert (de->xml, "status-bar");
1061
1062   if ( gtk_check_menu_item_get_active (menuitem) )
1063     gtk_widget_show (statusbar);
1064   else
1065     gtk_widget_hide (statusbar);
1066 }
1067
1068
1069 static void
1070 grid_lines_activate (GtkCheckMenuItem *menuitem, gpointer data)
1071 {
1072   struct data_editor *de = data;
1073   const bool grid_visible = gtk_check_menu_item_get_active (menuitem);
1074
1075   gtk_sheet_show_grid (GTK_SHEET (get_widget_assert (de->xml,
1076                                                      "variable_sheet")),
1077                        grid_visible);
1078
1079   gtk_sheet_show_grid (GTK_SHEET (get_widget_assert (de->xml, "data_sheet")),
1080                        grid_visible);
1081 }
1082
1083
1084
1085 static void
1086 data_sheet_activate (GtkCheckMenuItem *menuitem, gpointer data)
1087 {
1088   struct data_editor *de = data;
1089
1090   data_editor_select_sheet (de, PAGE_DATA_SHEET);
1091 }
1092
1093
1094 static void
1095 variable_sheet_activate (GtkCheckMenuItem *menuitem, gpointer data)
1096 {
1097   struct data_editor *de = data;
1098
1099   data_editor_select_sheet (de, PAGE_VAR_SHEET);
1100 }
1101
1102
1103 static void
1104 fonts_activate (GtkMenuItem *menuitem, gpointer data)
1105 {
1106   struct data_editor *de = data;
1107   GtkWidget *dialog =
1108     gtk_font_selection_dialog_new (_("Font Selection"));
1109
1110   gtk_window_set_transient_for (GTK_WINDOW (dialog),
1111                                 GTK_WINDOW (get_widget_assert (de->xml,
1112                                                                "data_editor")));
1113   if ( GTK_RESPONSE_OK == gtk_dialog_run (GTK_DIALOG (dialog)) )
1114     {
1115       GtkSheet *data_sheet =
1116         GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
1117
1118       GtkSheet *var_sheet =
1119         GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
1120
1121       PsppireDataStore *ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
1122       PsppireVarStore *vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
1123
1124       const gchar *font = gtk_font_selection_dialog_get_font_name
1125         (GTK_FONT_SELECTION_DIALOG (dialog));
1126
1127       PangoFontDescription* font_desc =
1128         pango_font_description_from_string (font);
1129
1130       psppire_var_store_set_font (vs, font_desc);
1131       psppire_data_store_set_font (ds, font_desc);
1132     }
1133
1134   gtk_widget_hide (dialog);
1135 }
1136
1137
1138
1139 /* Callback for the value labels action */
1140 static void
1141 toggle_value_labels (GtkToggleAction *ta, gpointer data)
1142 {
1143   struct data_editor *de = data;
1144
1145   GtkSheet *data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
1146
1147   PsppireDataStore *ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
1148
1149
1150   psppire_data_store_show_labels (ds,
1151                                   gtk_toggle_action_get_active (ta));
1152 }
1153
1154
1155 static void
1156 file_quit (GtkCheckMenuItem *menuitem, gpointer data)
1157 {
1158   /* FIXME: Need to be more intelligent here.
1159      Give the user the opportunity to save any unsaved data.
1160   */
1161   gtk_main_quit ();
1162 }
1163
1164 static void
1165 delete_cases (GtkAction *action, gpointer data)
1166 {
1167   struct data_editor *de = data;
1168   GtkSheet *data_sheet =
1169     GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
1170
1171   GtkSheetRange range;
1172
1173   PsppireDataStore *data_store = PSPPIRE_DATA_STORE
1174     (gtk_sheet_get_model (data_sheet) );
1175
1176
1177   /* This shouldn't be able to happen, because the action
1178      should be disabled */
1179   g_return_if_fail (gtk_sheet_get_state (data_sheet)
1180                     ==  GTK_SHEET_ROW_SELECTED );
1181
1182   gtk_sheet_get_selected_range (data_sheet, &range);
1183
1184   gtk_sheet_unselect_range (data_sheet);
1185
1186   psppire_data_store_delete_cases (data_store, range.row0,
1187                                    1 + range.rowi - range.row0);
1188
1189 }
1190
1191 static void
1192 delete_variables (GtkAction *a, gpointer data)
1193 {
1194   struct data_editor *de = data;
1195   GtkSheetRange range;
1196
1197   GtkNotebook *notebook = GTK_NOTEBOOK (get_widget_assert (de->xml,
1198                                                            "notebook"));
1199
1200   const gint page = gtk_notebook_get_current_page (notebook);
1201
1202   GtkSheet *sheet = GTK_SHEET (get_widget_assert (de->xml,
1203                                                   (page == PAGE_VAR_SHEET) ?
1204                                                   "variable_sheet" :
1205                                                   "data_sheet"));
1206
1207
1208   gtk_sheet_get_selected_range (sheet, &range);
1209
1210   switch ( page )
1211     {
1212     case PAGE_VAR_SHEET:
1213       {
1214         PsppireVarStore *vs =
1215           PSPPIRE_VAR_STORE (gtk_sheet_get_model (sheet));
1216
1217         psppire_dict_delete_variables (vs->dict,
1218                                        range.row0,
1219                                        1 +
1220                                        range.rowi -
1221                                        range.row0 );
1222       }
1223       break;
1224     case PAGE_DATA_SHEET:
1225       {
1226         PsppireDataStore *ds =
1227           PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
1228
1229         psppire_dict_delete_variables (ds->dict,
1230                                        range.col0,
1231                                        1 +
1232                                        range.coli -
1233                                        range.col0 );
1234       }
1235       break;
1236     };
1237
1238   gtk_sheet_unselect_range (sheet);
1239 }
1240
1241 static void
1242 insert_case (GtkAction *action, gpointer data)
1243 {
1244   gint current_row ;
1245   struct data_editor *de = data;
1246
1247   GtkSheet *data_sheet =
1248     GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
1249
1250   PsppireDataStore *ds = PSPPIRE_DATA_STORE
1251     (gtk_sheet_get_model (data_sheet) );
1252
1253
1254   gtk_sheet_get_active_cell (data_sheet, &current_row, NULL);
1255
1256   if (current_row < 0) current_row = 0;
1257
1258   psppire_data_store_insert_new_case (ds, current_row);
1259 }
1260
1261 /* Insert a new variable before the current row in the variable sheet,
1262    or before the current column in the data sheet, whichever is selected */
1263 static void
1264 insert_variable (GtkAction *action, gpointer data)
1265 {
1266   struct data_editor *de = data;
1267   gint posn = -1;
1268
1269   GtkWidget *notebook = get_widget_assert (de->xml, "notebook");
1270
1271   GtkSheet *var_sheet =
1272     GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
1273
1274   PsppireVarStore *vs = PSPPIRE_VAR_STORE
1275     (gtk_sheet_get_model (var_sheet) );
1276
1277   switch ( gtk_notebook_get_current_page ( GTK_NOTEBOOK (notebook)) )
1278     {
1279     case PAGE_VAR_SHEET:
1280       posn = var_sheet->active_cell.row;
1281       break;
1282     case PAGE_DATA_SHEET:
1283       {
1284         GtkSheet *data_sheet =
1285           GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
1286
1287         if ( data_sheet->state == GTK_SHEET_COLUMN_SELECTED )
1288           posn = data_sheet->range.col0;
1289         else
1290           posn = data_sheet->active_cell.col;
1291       }
1292       break;
1293     default:
1294       g_assert_not_reached ();
1295     }
1296
1297   if ( posn == -1 ) posn = 0;
1298
1299   psppire_dict_insert_variable (vs->dict, posn, NULL);
1300 }
1301
1302 /* Callback for when the dictionary changes its split variables */
1303 static void
1304 on_split_change (PsppireDict *dict, gpointer data)
1305 {
1306   struct data_editor *de = data;
1307
1308   size_t n_split_vars = dict_get_split_cnt (dict->dict);
1309
1310   GtkWidget *split_status_area =
1311     get_widget_assert (de->xml, "split-file-status-area");
1312
1313   if ( n_split_vars == 0 )
1314     {
1315       gtk_label_set_text (GTK_LABEL (split_status_area), _("No Split"));
1316     }
1317   else
1318     {
1319       gint i;
1320       GString *text;
1321       const struct variable *const * split_vars =
1322         dict_get_split_vars (dict->dict);
1323
1324       text = g_string_new (_("Split by "));
1325
1326       for (i = 0 ; i < n_split_vars - 1; ++i )
1327         {
1328           g_string_append_printf (text, "%s, ", var_get_name (split_vars[i]));
1329         }
1330       g_string_append (text, var_get_name (split_vars[i]));
1331
1332       gtk_label_set_text (GTK_LABEL (split_status_area), text->str);
1333
1334       g_string_free (text, TRUE);
1335     }
1336 }
1337
1338
1339 /* Callback for when the dictionary changes its filter variable */
1340 static void
1341 on_filter_change (GObject *o, gint filter_index, gpointer data)
1342 {
1343   struct data_editor *de = data;
1344   GtkWidget *filter_status_area =
1345     get_widget_assert (de->xml, "filter-use-status-area");
1346
1347   if ( filter_index == -1 )
1348     {
1349       gtk_label_set_text (GTK_LABEL (filter_status_area), _("Filter off"));
1350     }
1351   else
1352     {
1353       GtkSheet *var_sheet =
1354         GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
1355
1356       PsppireVarStore *vs = PSPPIRE_VAR_STORE
1357         (gtk_sheet_get_model (var_sheet) );
1358
1359       struct variable *var = psppire_dict_get_variable (vs->dict,
1360                                                         filter_index);
1361
1362       gchar *text = g_strdup_printf (_("Filter by %s"), var_get_name (var));
1363
1364       gtk_label_set_text (GTK_LABEL (filter_status_area), text);
1365
1366       g_free (text);
1367     }
1368 }
1369
1370 /* Callback for when the dictionary changes its weights */
1371 static void
1372 on_weight_change (GObject *o, gint weight_index, gpointer data)
1373 {
1374   struct data_editor *de = data;
1375   GtkWidget *weight_status_area =
1376     get_widget_assert (de->xml, "weight-status-area");
1377
1378   if ( weight_index == -1 )
1379     {
1380       gtk_label_set_text (GTK_LABEL (weight_status_area), _("Weights off"));
1381     }
1382   else
1383     {
1384       GtkSheet *var_sheet =
1385         GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
1386
1387       PsppireVarStore *vs = PSPPIRE_VAR_STORE
1388         (gtk_sheet_get_model (var_sheet) );
1389
1390       struct variable *var = psppire_dict_get_variable (vs->dict,
1391                                                         weight_index);
1392
1393       gchar *text = g_strdup_printf (_("Weight by %s"), var_get_name (var));
1394
1395       gtk_label_set_text (GTK_LABEL (weight_status_area), text);
1396
1397       g_free (text);
1398     }
1399 }
1400
1401
1402
1403 \f
1404 static void data_save_as_dialog (GtkAction *, struct data_editor *de);
1405 static void new_file (GtkAction *, struct editor_window *de);
1406 static void open_data_dialog (GtkAction *, struct data_editor *de);
1407 static void data_save (GtkAction *action, struct data_editor *e);
1408
1409
1410 /* Create the GtkActions and connect to their signals */
1411 static void
1412 register_data_editor_actions (struct data_editor *de)
1413 {
1414   de->action_data_open =
1415     gtk_action_new ("data-open-dialog",
1416                     _("Open"),
1417                     _("Open a data file"),
1418                     "gtk-open");
1419
1420   g_signal_connect (de->action_data_open, "activate",
1421                     G_CALLBACK (open_data_dialog), de);
1422
1423
1424   de->action_data_save = gtk_action_new ("data-save",
1425                                             _("Save"),
1426                                             _("Save data to file"),
1427                                             "gtk-save");
1428
1429   g_signal_connect (de->action_data_save, "activate",
1430                     G_CALLBACK (data_save), de);
1431
1432
1433
1434   de->action_data_save_as = gtk_action_new ("data-save-as-dialog",
1435                                             _("Save As"),
1436                                             _("Save data to file"),
1437                                             "gtk-save");
1438
1439   g_signal_connect (de->action_data_save_as, "activate",
1440                     G_CALLBACK (data_save_as_dialog), de);
1441
1442   de->action_data_new =
1443     gtk_action_new ("data-new",
1444                     _("New"),
1445                     _("New data file"),
1446                     NULL);
1447
1448   g_signal_connect (de->action_data_new, "activate",
1449                     G_CALLBACK (new_file), de);
1450 }
1451
1452 /* Returns true if NAME has a suffix which might denote a PSPP file */
1453 static gboolean
1454 name_has_suffix (const gchar *name)
1455 {
1456   if ( g_str_has_suffix (name, ".sav"))
1457     return TRUE;
1458   if ( g_str_has_suffix (name, ".SAV"))
1459     return TRUE;
1460   if ( g_str_has_suffix (name, ".por"))
1461     return TRUE;
1462   if ( g_str_has_suffix (name, ".POR"))
1463     return TRUE;
1464
1465   return FALSE;
1466 }
1467
1468 /* Append SUFFIX to the filename of DE */
1469 static void
1470 append_filename_suffix (struct data_editor *de, const gchar *suffix)
1471 {
1472   if ( ! name_has_suffix (de->file_name))
1473     {
1474       gchar *s = de->file_name;
1475       de->file_name = g_strconcat (de->file_name, suffix, NULL);
1476       g_free (s);
1477     }
1478 }
1479
1480 /* Save DE to file */
1481 static void
1482 save_file (struct data_editor *de)
1483 {
1484   struct getl_interface *sss;
1485   struct string file_name ;
1486
1487   g_assert (de->file_name);
1488
1489   ds_init_cstr (&file_name, de->file_name);
1490   gen_quoted_string (&file_name);
1491
1492   if ( de->save_as_portable )
1493     {
1494       append_filename_suffix (de, ".por");
1495       sss = create_syntax_string_source ("EXPORT OUTFILE=%s.",
1496                                          ds_cstr (&file_name));
1497     }
1498   else
1499     {
1500       append_filename_suffix (de, ".sav");
1501       sss = create_syntax_string_source ("SAVE OUTFILE=%s.",
1502                                          ds_cstr (&file_name));
1503     }
1504
1505   ds_destroy (&file_name);
1506
1507   execute_syntax (sss);
1508 }
1509
1510
1511 /* Callback for data_save action.
1512    If there's an existing file name, then just save,
1513    otherwise prompt for a file name, then save */
1514 static void
1515 data_save (GtkAction *action, struct data_editor *de)
1516 {
1517   if ( de->file_name)
1518     save_file (de);
1519   else
1520     data_save_as_dialog (action, de);
1521 }
1522
1523
1524 /* Callback for data_save_as action. Prompt for a filename and save */
1525 static void
1526 data_save_as_dialog (GtkAction *action, struct data_editor *de)
1527 {
1528   struct editor_window *e = (struct editor_window *) de;
1529
1530   GtkWidget *button_sys;
1531   GtkWidget *dialog =
1532     gtk_file_chooser_dialog_new (_("Save"),
1533                                  GTK_WINDOW (e->window),
1534                                  GTK_FILE_CHOOSER_ACTION_SAVE,
1535                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1536                                  GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1537                                  NULL);
1538
1539   GtkFileFilter *filter = gtk_file_filter_new ();
1540   gtk_file_filter_set_name (filter, _("System Files (*.sav)"));
1541   gtk_file_filter_add_pattern (filter, "*.sav");
1542   gtk_file_filter_add_pattern (filter, "*.SAV");
1543   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
1544
1545   filter = gtk_file_filter_new ();
1546   gtk_file_filter_set_name (filter, _("Portable Files (*.por) "));
1547   gtk_file_filter_add_pattern (filter, "*.por");
1548   gtk_file_filter_add_pattern (filter, "*.POR");
1549   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
1550
1551   filter = gtk_file_filter_new ();
1552   gtk_file_filter_set_name (filter, _("All Files"));
1553   gtk_file_filter_add_pattern (filter, "*");
1554   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
1555
1556   {
1557     GtkWidget *button_por;
1558     GtkWidget *vbox = gtk_vbox_new (TRUE, 5);
1559     button_sys =
1560       gtk_radio_button_new_with_label (NULL, _("System File"));
1561
1562     button_por =
1563       gtk_radio_button_new_with_label
1564       (gtk_radio_button_get_group (GTK_RADIO_BUTTON(button_sys)),
1565        _("Portable File"));
1566
1567     gtk_box_pack_start_defaults (GTK_BOX (vbox), button_sys);
1568     gtk_box_pack_start_defaults (GTK_BOX (vbox), button_por);
1569
1570     gtk_widget_show_all (vbox);
1571
1572     gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), vbox);
1573   }
1574
1575   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
1576     {
1577     case GTK_RESPONSE_ACCEPT:
1578       {
1579         g_free (de->file_name);
1580
1581         de->file_name =
1582           gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1583
1584         de->save_as_portable =
1585           ! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_sys));
1586
1587         save_file (de);
1588
1589         window_set_name_from_filename (e, de->file_name);
1590       }
1591       break;
1592     default:
1593       break;
1594     }
1595
1596   gtk_widget_destroy (dialog);
1597 }
1598
1599
1600 /* Callback for data_new action.
1601    Performs the NEW FILE command */
1602 static void
1603 new_file (GtkAction *action, struct editor_window *e)
1604 {
1605   struct data_editor *de = (struct data_editor *) e;
1606
1607   struct getl_interface *sss =
1608     create_syntax_string_source ("NEW FILE.");
1609
1610   execute_syntax (sss);
1611
1612   g_free (de->file_name);
1613   de->file_name = NULL;
1614
1615   default_window_name (e);
1616 }
1617
1618
1619 static void
1620 open_data_file (const gchar *file_name, struct data_editor *de)
1621 {
1622   struct getl_interface *sss;
1623   struct string filename;
1624
1625   ds_init_cstr (&filename, file_name);
1626
1627   gen_quoted_string (&filename);
1628
1629   sss = create_syntax_string_source ("GET FILE=%s.",
1630                                      ds_cstr (&filename));
1631   ds_destroy (&filename);
1632
1633   if (execute_syntax (sss) )
1634   {
1635     window_set_name_from_filename ((struct editor_window *) de, file_name);
1636     add_most_recent (file_name);
1637   }
1638 }
1639
1640
1641
1642 /* Callback for the data_open action.
1643    Prompts for a filename and opens it */
1644 static void
1645 open_data_dialog (GtkAction *action, struct data_editor *de)
1646 {
1647   struct editor_window *e = (struct editor_window *) de;
1648
1649   GtkWidget *dialog =
1650     gtk_file_chooser_dialog_new (_("Open"),
1651                                  GTK_WINDOW (e->window),
1652                                  GTK_FILE_CHOOSER_ACTION_OPEN,
1653                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1654                                  GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1655                                  NULL);
1656
1657   GtkFileFilter *filter = gtk_file_filter_new ();
1658   gtk_file_filter_set_name (filter, _("System Files (*.sav)"));
1659   gtk_file_filter_add_pattern (filter, "*.sav");
1660   gtk_file_filter_add_pattern (filter, "*.SAV");
1661   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
1662
1663   filter = gtk_file_filter_new ();
1664   gtk_file_filter_set_name (filter, _("Portable Files (*.por) "));
1665   gtk_file_filter_add_pattern (filter, "*.por");
1666   gtk_file_filter_add_pattern (filter, "*.POR");
1667   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
1668
1669   filter = gtk_file_filter_new ();
1670   gtk_file_filter_set_name (filter, _("All Files"));
1671   gtk_file_filter_add_pattern (filter, "*");
1672   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
1673
1674
1675   if ( de->file_name)
1676     {
1677       gchar *dir_name = g_path_get_dirname (de->file_name);
1678       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
1679                                            dir_name);
1680       free (dir_name);
1681     }
1682
1683   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
1684     {
1685     case GTK_RESPONSE_ACCEPT:
1686       {
1687         g_free (de->file_name);
1688         de->file_name =
1689           gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1690
1691         open_data_file (de->file_name, de);
1692       }
1693       break;
1694     default:
1695       break;
1696     }
1697
1698   gtk_widget_destroy (dialog);
1699 }
1700
1701
1702
1703 /* Update the data_ref_entry with the reference of the active cell */
1704 static gint
1705 update_data_ref_entry (const GtkSheet *sheet, gint row, gint col, gpointer data)
1706 {
1707   GladeXML *data_editor_xml = data;
1708
1709   PsppireDataStore *data_store =
1710     PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
1711
1712   g_return_val_if_fail (data_editor_xml, FALSE);
1713
1714   if (data_store)
1715     {
1716       const struct variable *var =
1717         psppire_dict_get_variable (data_store->dict, col);
1718
1719       /* The entry where the reference to the current cell is displayed */
1720       GtkEntry *cell_ref_entry =
1721         GTK_ENTRY (get_widget_assert (data_editor_xml,
1722                                       "cell_ref_entry"));
1723       GtkEntry *datum_entry =
1724         GTK_ENTRY (get_widget_assert (data_editor_xml,
1725                                       "datum_entry"));
1726
1727       if ( var )
1728         {
1729           gchar *text = g_strdup_printf ("%d: %s", row + FIRST_CASE_NUMBER,
1730                                          var_get_name (var));
1731
1732           gchar *s = pspp_locale_to_utf8 (text, -1, 0);
1733
1734           g_free (text);
1735
1736           gtk_entry_set_text (cell_ref_entry, s);
1737
1738           g_free (s);
1739         }
1740       else
1741         gtk_entry_set_text (cell_ref_entry, "");
1742
1743
1744       if ( var )
1745         {
1746           gchar *text =
1747             psppire_data_store_get_string (data_store, row,
1748                                            var_get_dict_index(var));
1749           g_strchug (text);
1750
1751           gtk_entry_set_text (datum_entry, text);
1752
1753           free (text);
1754         }
1755       else
1756         gtk_entry_set_text (datum_entry, "");
1757     }
1758
1759   return FALSE;
1760 }
1761
1762
1763
1764
1765
1766 static void
1767 do_sort (PsppireDataStore *ds, int var, gboolean descend)
1768 {
1769   GString *string = g_string_new ("SORT CASES BY ");
1770
1771   const struct variable *v =
1772     psppire_dict_get_variable (ds->dict, var);
1773
1774   g_string_append_printf (string, "%s", var_get_name (v));
1775
1776   if ( descend )
1777     g_string_append (string, " (D)");
1778
1779   g_string_append (string, ".");
1780
1781   execute_syntax (create_syntax_string_source (string->str));
1782
1783   g_string_free (string, TRUE);
1784 }
1785
1786
1787 static void
1788 sort_up (GtkMenuItem *item, gpointer data)
1789 {
1790   GtkSheet *sheet  = data;
1791   GtkSheetRange range;
1792   gtk_sheet_get_selected_range (sheet, &range);
1793
1794   do_sort (PSPPIRE_DATA_STORE (gtk_sheet_get_model(sheet)),
1795            range.col0, FALSE);
1796
1797 }
1798
1799 static void
1800 sort_down (GtkMenuItem *item, gpointer data)
1801 {
1802   GtkSheet *sheet  = data;
1803   GtkSheetRange range;
1804   gtk_sheet_get_selected_range (sheet, &range);
1805
1806   do_sort (PSPPIRE_DATA_STORE (gtk_sheet_get_model(sheet)),
1807            range.col0, TRUE);
1808 }
1809
1810
1811
1812
1813 static void
1814 create_data_sheet_variable_popup_menu (struct data_editor *de)
1815 {
1816   GtkSheet *sheet  = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
1817   GtkWidget *menu = gtk_menu_new ();
1818
1819   GtkWidget *sort_ascending =
1820     gtk_menu_item_new_with_label (_("Sort Ascending"));
1821
1822   GtkWidget *sort_descending =
1823     gtk_menu_item_new_with_label (_("Sort Descending"));
1824
1825
1826   GtkWidget *insert_variable =
1827     gtk_menu_item_new_with_label (_("Insert Variable"));
1828
1829   GtkWidget *clear_variable =
1830     gtk_menu_item_new_with_label (_("Clear"));
1831
1832
1833   gtk_action_connect_proxy (de->insert_variable,
1834                             insert_variable );
1835
1836
1837   gtk_action_connect_proxy (de->delete_variables,
1838                             clear_variable );
1839
1840
1841   gtk_menu_shell_append (GTK_MENU_SHELL (menu), insert_variable);
1842
1843
1844   gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1845                          gtk_separator_menu_item_new ());
1846
1847
1848   gtk_menu_shell_append (GTK_MENU_SHELL (menu), clear_variable);
1849
1850
1851   gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1852                          gtk_separator_menu_item_new ());
1853
1854
1855   g_signal_connect (G_OBJECT (sort_ascending), "activate",
1856                     G_CALLBACK (sort_up), sheet);
1857
1858   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sort_ascending);
1859
1860
1861   g_signal_connect (G_OBJECT (sort_descending), "activate",
1862                     G_CALLBACK (sort_down), sheet);
1863
1864
1865   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sort_descending);
1866
1867   gtk_widget_show_all (menu);
1868
1869
1870   de->data_sheet_variable_popup_menu = GTK_MENU(menu);
1871 }
1872
1873
1874 static void
1875 create_data_sheet_cases_popup_menu (struct data_editor *de)
1876 {
1877   GtkWidget *menu = gtk_menu_new ();
1878
1879   GtkWidget *insert_case =
1880     gtk_menu_item_new_with_label (_("Insert Case"));
1881
1882   GtkWidget *delete_case =
1883     gtk_menu_item_new_with_label (_("Clear"));
1884
1885
1886   gtk_action_connect_proxy (de->insert_case,
1887                             insert_case);
1888
1889
1890   gtk_action_connect_proxy (de->delete_cases,
1891                             delete_case);
1892
1893
1894   gtk_menu_shell_append (GTK_MENU_SHELL (menu), insert_case);
1895
1896
1897   gtk_menu_shell_append (GTK_MENU_SHELL (menu),
1898                          gtk_separator_menu_item_new ());
1899
1900
1901   gtk_menu_shell_append (GTK_MENU_SHELL (menu), delete_case);
1902
1903
1904   gtk_widget_show_all (menu);
1905
1906
1907   de->data_sheet_cases_popup_menu = GTK_MENU (menu);
1908 }
1909
1910
1911 static void
1912 popup_variable_menu (GtkSheet *sheet, gint column,
1913                      GdkEventButton *event, gpointer data)
1914 {
1915   struct data_editor *de = data;
1916
1917   PsppireDataStore *data_store =
1918     PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
1919
1920   const struct variable *v =
1921     psppire_dict_get_variable (data_store->dict, column);
1922
1923   if ( v && event->button == 3)
1924     {
1925
1926       gtk_sheet_select_column (sheet, column);
1927
1928       gtk_menu_popup (GTK_MENU (de->data_sheet_variable_popup_menu),
1929                       NULL, NULL, NULL, NULL,
1930                       event->button, event->time);
1931     }
1932 }
1933
1934
1935 static void
1936 popup_cases_menu (GtkSheet *sheet, gint row,
1937                   GdkEventButton *event, gpointer data)
1938 {
1939   struct data_editor *de = data;
1940
1941   PsppireDataStore *data_store =
1942     PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
1943
1944   if ( row <= psppire_data_store_get_case_count (data_store) &&
1945        event->button == 3)
1946     {
1947       gtk_sheet_select_row (sheet, row);
1948
1949       gtk_menu_popup (GTK_MENU (de->data_sheet_cases_popup_menu),
1950                       NULL, NULL, NULL, NULL,
1951                       event->button, event->time);
1952     }
1953 }
1954
1955
1956 static void
1957 on_edit_paste (GtkAction *a, gpointer data)
1958 {
1959   GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1960
1961   gtk_clipboard_request_contents (clipboard,
1962                                   gdk_atom_intern ("UTF8_STRING", TRUE),
1963                                   data_sheet_contents_received_callback,
1964                                   data);
1965 }
1966
1967
1968 static void
1969 on_edit_copy (GtkMenuItem *m, gpointer data)
1970 {
1971   struct data_editor *de = data;
1972
1973   GtkSheet *data_sheet = GTK_SHEET (get_widget_assert (de->xml,
1974                                                        "data_sheet"));
1975
1976   data_sheet_set_clip (data_sheet);
1977 }
1978
1979
1980
1981 static void
1982 on_edit_cut (GtkMenuItem *m, gpointer data)
1983 {
1984   struct data_editor *de = data;
1985   gint max_rows, max_columns;
1986   gint r;
1987   GtkSheetRange range;
1988   PsppireDataStore *ds;
1989   GtkSheet *data_sheet = GTK_SHEET (get_widget_assert (de->xml,
1990                                                        "data_sheet"));
1991
1992   data_sheet_set_clip (data_sheet);
1993
1994
1995   /* Now blank all the cells */
1996   gtk_sheet_get_selected_range (data_sheet, &range);
1997
1998   ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
1999
2000
2001    /* If nothing selected, then use active cell */
2002   if ( range.row0 < 0 || range.col0 < 0 )
2003     {
2004       gint row, col;
2005       gtk_sheet_get_active_cell (data_sheet, &row, &col);
2006
2007       range.row0 = range.rowi = row;
2008       range.col0 = range.coli = col;
2009     }
2010
2011   /* The sheet range can include cells that do not include data.
2012      Exclude them from the range. */
2013   max_rows = psppire_data_store_get_case_count (ds);
2014   if (range.rowi >= max_rows)
2015     {
2016       if (max_rows == 0)
2017         return;
2018       range.rowi = max_rows - 1;
2019     }
2020
2021   max_columns = dict_get_var_cnt (ds->dict->dict);
2022   if (range.coli >= max_columns)
2023     {
2024       if (max_columns == 0)
2025         return;
2026       range.coli = max_columns - 1;
2027     }
2028
2029   g_return_if_fail (range.rowi >= range.row0);
2030   g_return_if_fail (range.row0 >= 0);
2031   g_return_if_fail (range.coli >= range.col0);
2032   g_return_if_fail (range.col0 >= 0);
2033
2034
2035   for (r = range.row0; r <= range.rowi ; ++r )
2036     {
2037       gint c;
2038
2039       for (c = range.col0 ; c <= range.coli; ++c)
2040         {
2041           psppire_data_store_set_string (ds, "", r, c);
2042         }
2043     }
2044
2045   /* and remove the selection */
2046   gtk_sheet_unselect_range (data_sheet);
2047 }
2048
2049