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