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