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