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