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