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