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