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