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