Added psppire-dialog and psppire-buttonbox widgets.
[pspp-builds.git] / src / ui / gui / data-editor.c
1 /*
2     PSPPIRE --- A Graphical User Interface for PSPP
3     Copyright (C) 2006, 2007  Free Software Foundation
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18     02110-1301, USA. */
19
20 #include <config.h>
21 #include <stdlib.h>
22 #include <gettext.h>
23
24 #include <glade/glade.h>
25 #include <gtk/gtk.h>
26
27 #include "window-manager.h"
28 #include <gtksheet/gtksheet.h>
29
30 #include "helper.h"
31 #include "about.h"
32 #include "psppire-dialog.h"
33 #include "psppire-var-select.h"
34
35 #define _(msgid) gettext (msgid)
36 #define N_(msgid) msgid
37
38 #include "data-editor.h"
39 #include "syntax-editor.h"
40 #include "window-manager.h"
41
42 #include "psppire-data-store.h"
43 #include "psppire-var-store.h"
44
45 #include "weight-cases-dialog.h"
46
47
48 static void insert_variable (GtkCheckMenuItem *m, gpointer data);
49
50
51 /* Switch between the VAR SHEET and the DATA SHEET */
52 enum {PAGE_DATA_SHEET = 0, PAGE_VAR_SHEET};
53
54 static gboolean click2column (GtkWidget *w, gint col, gpointer data);
55
56 static gboolean click2row (GtkWidget *w, gint row, gpointer data);
57
58
59 static void select_sheet (struct data_editor *de, guint page_num);
60
61
62 /* Callback for when the dictionary changes its weights */
63 static void on_weight_change (GObject *, gint, gpointer);
64
65 static void data_var_select (GtkNotebook *notebook,
66                             GtkNotebookPage *page,
67                             guint page_num,
68                             gpointer user_data);
69
70 static void status_bar_activate (GtkCheckMenuItem *, gpointer);
71
72 static void grid_lines_activate (GtkCheckMenuItem *, gpointer);
73
74 static void data_sheet_activate (GtkCheckMenuItem *, gpointer);
75
76 static void variable_sheet_activate (GtkCheckMenuItem *, gpointer );
77
78 static void fonts_activate (GtkMenuItem *, gpointer);
79
80 static void value_labels_activate (GtkCheckMenuItem *, gpointer);
81 static void value_labels_toggled (GtkToggleToolButton *, gpointer);
82
83
84 static void file_quit (GtkCheckMenuItem *, gpointer );
85
86 static void on_clear_activate (GtkMenuItem *, gpointer);
87
88 static void
89 enable_edit_clear (GtkWidget *w, gint row, gpointer data)
90 {
91   struct data_editor *de = data;
92
93   GtkWidget *menuitem = get_widget_assert (de->xml, "edit_clear");
94
95   gtk_widget_set_sensitive (menuitem, TRUE);
96 }
97
98 static gboolean
99 disable_edit_clear (GtkWidget *w, gint x, gint y, gpointer data)
100 {
101   struct data_editor *de = data;
102
103   GtkWidget *menuitem = get_widget_assert (de->xml, "edit_clear");
104
105   gtk_widget_set_sensitive (menuitem, FALSE);
106
107   return FALSE;
108 }
109
110 static void weight_cases_dialog (GObject *o, gpointer data);
111
112
113 /*
114   Create a new data editor.
115 */
116 struct data_editor *
117 new_data_editor (void)
118 {
119   struct data_editor *de ;
120   struct editor_window *e;
121   GtkSheet *var_sheet ;
122   PsppireVarStore *vs;
123
124   de = g_malloc (sizeof (*de));
125
126   e = (struct editor_window *) de;
127
128   de->xml = glade_xml_new (PKGDATADIR "/data-editor.glade", NULL, NULL);
129
130
131   var_sheet = GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
132
133   vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
134
135   g_signal_connect (vs->dict, "weight-changed",
136                     G_CALLBACK (on_weight_change),
137                     de);
138
139   connect_help (de->xml);
140
141   de->invoke_weight_cases_dialog =
142     gtk_action_new ("weight-cases-dialog",
143                     _("Weights"),
144                     _("Weight cases by variable"),
145                     "pspp-weight-cases");
146
147
148   g_signal_connect (de->invoke_weight_cases_dialog, "activate",
149                     G_CALLBACK (weight_cases_dialog), de);
150
151   e->window = GTK_WINDOW (get_widget_assert (de->xml, "data_editor"));
152
153   g_signal_connect (get_widget_assert (de->xml,"file_new_data"),
154                     "activate",
155                     G_CALLBACK (new_data_window),
156                     e->window);
157
158   g_signal_connect (get_widget_assert (de->xml,"file_open_data"),
159                     "activate",
160                     G_CALLBACK (open_data_window),
161                     e->window);
162
163   g_signal_connect (get_widget_assert (de->xml,"file_new_syntax"),
164                     "activate",
165                     G_CALLBACK (new_syntax_window),
166                     e->window);
167
168   g_signal_connect (get_widget_assert (de->xml,"file_open_syntax"),
169                     "activate",
170                     G_CALLBACK (open_syntax_window),
171                     e->window);
172
173
174   g_signal_connect (get_widget_assert (de->xml,"edit_clear"),
175                     "activate",
176                     G_CALLBACK (on_clear_activate),
177                     de);
178
179
180   g_signal_connect (get_widget_assert (de->xml,"data_insert-variable"),
181                     "activate",
182                     G_CALLBACK (insert_variable),
183                     de);
184
185   gtk_action_connect_proxy (de->invoke_weight_cases_dialog,
186                             get_widget_assert (de->xml, "data_weight-cases")
187                             );
188
189   /* 
190   g_signal_connect (get_widget_assert (de->xml,"data_weight-cases"),
191                     "activate",
192                     G_CALLBACK (weight_cases_dialog),
193                     de);
194   */
195
196
197   g_signal_connect (get_widget_assert (de->xml,"help_about"),
198                     "activate",
199                     G_CALLBACK (about_new),
200                     e->window);
201
202
203   g_signal_connect (get_widget_assert (de->xml,"help_reference"),
204                     "activate",
205                     G_CALLBACK (reference_manual),
206                     e->window);
207
208
209
210   g_signal_connect (get_widget_assert (de->xml,"data_sheet"),
211                     "double-click-column",
212                     G_CALLBACK (click2column),
213                     de);
214
215
216   g_signal_connect (get_widget_assert (de->xml, "variable_sheet"),
217                     "double-click-row",
218                     GTK_SIGNAL_FUNC (click2row),
219                     de);
220
221
222   g_signal_connect (get_widget_assert (de->xml, "variable_sheet"),
223                     "select-row",
224                     GTK_SIGNAL_FUNC (enable_edit_clear),
225                     de);
226
227   g_signal_connect (get_widget_assert (de->xml, "variable_sheet"),
228                     "activate",
229                     GTK_SIGNAL_FUNC (disable_edit_clear),
230                     de);
231
232
233   g_signal_connect (get_widget_assert (de->xml, "notebook"),
234                     "switch-page",
235                     G_CALLBACK (data_var_select), de);
236
237
238
239   g_signal_connect (get_widget_assert (de->xml, "view_statusbar"),
240                     "activate",
241                     G_CALLBACK (status_bar_activate), de);
242
243
244   g_signal_connect (get_widget_assert (de->xml, "view_gridlines"),
245                     "activate",
246                     G_CALLBACK (grid_lines_activate), de);
247
248
249
250   g_signal_connect (get_widget_assert (de->xml, "view_data"),
251                     "activate",
252                     G_CALLBACK (data_sheet_activate), de);
253
254   g_signal_connect (get_widget_assert (de->xml, "view_variables"),
255                     "activate",
256                     G_CALLBACK (variable_sheet_activate), de);
257
258
259
260   g_signal_connect (get_widget_assert (de->xml, "view_fonts"),
261                     "activate",
262                     G_CALLBACK (fonts_activate), de);
263
264
265
266   g_signal_connect (get_widget_assert (de->xml, "view_valuelabels"),
267                     "activate",
268                     G_CALLBACK (value_labels_activate), de);
269
270
271   g_signal_connect (get_widget_assert (de->xml, "togglebutton-value-labels"),
272                     "toggled",
273                     G_CALLBACK (value_labels_toggled), de);
274
275
276   gtk_action_connect_proxy (de->invoke_weight_cases_dialog,
277                             get_widget_assert (de->xml, "button-weight-cases")
278                             );
279
280   g_signal_connect (get_widget_assert (de->xml, "file_quit"),
281                     "activate",
282                     G_CALLBACK (file_quit), de);
283
284
285   g_signal_connect (get_widget_assert (de->xml, "windows_minimise_all"),
286                     "activate",
287                     G_CALLBACK (minimise_all_windows), NULL);
288
289
290
291   select_sheet (de, PAGE_DATA_SHEET);
292
293   return de;
294 }
295
296
297 /* Callback which occurs when the var sheet's row title
298    button is double clicked */
299 static gboolean
300 click2row (GtkWidget *w, gint row, gpointer data)
301 {
302   struct data_editor *de = data;
303
304   gint current_row, current_column;
305
306   GtkWidget *data_sheet  = get_widget_assert (de->xml, "data_sheet");
307
308   data_editor_select_sheet (de, PAGE_DATA_SHEET);
309
310   gtk_sheet_get_active_cell (GTK_SHEET (data_sheet),
311                              &current_row, &current_column);
312
313   gtk_sheet_set_active_cell (GTK_SHEET (data_sheet), current_row, row);
314
315   return FALSE;
316 }
317
318
319 /* Callback which occurs when the data sheet's column title
320    is double clicked */
321 static gboolean
322 click2column (GtkWidget *w, gint col, gpointer data)
323 {
324   struct data_editor *de = data;
325
326   gint current_row, current_column;
327
328   GtkWidget *var_sheet  = get_widget_assert (de->xml, "variable_sheet");
329
330   data_editor_select_sheet (de, PAGE_VAR_SHEET);
331
332   gtk_sheet_get_active_cell (GTK_SHEET (var_sheet),
333                              &current_row, &current_column);
334
335   gtk_sheet_set_active_cell (GTK_SHEET (var_sheet), col, current_column);
336
337   return FALSE;
338 }
339
340
341
342
343 void
344 new_data_window (GtkMenuItem *menuitem, gpointer parent)
345 {
346   window_create (WINDOW_DATA, NULL);
347 }
348
349
350 static void
351 select_sheet (struct data_editor *de, guint page_num)
352 {
353   GtkWidget *insert_variable = get_widget_assert (de->xml, "data_insert-variable");
354   GtkWidget *insert_cases = get_widget_assert (de->xml, "insert-cases");
355
356   GtkWidget *view_data = get_widget_assert (de->xml, "view_data");
357   GtkWidget *view_variables = get_widget_assert (de->xml, "view_variables");
358
359   switch (page_num)
360     {
361     case PAGE_VAR_SHEET:
362       gtk_widget_hide (view_variables);
363       gtk_widget_show (view_data);
364       gtk_widget_set_sensitive (insert_variable, TRUE);
365       gtk_widget_set_sensitive (insert_cases, FALSE);
366       break;
367     case PAGE_DATA_SHEET:
368       gtk_widget_show (view_variables);
369       gtk_widget_hide (view_data);
370 #if 0
371       gtk_widget_set_sensitive (insert_cases, TRUE);
372 #endif
373       break;
374     default:
375       g_assert_not_reached ();
376       break;
377     }
378 }
379
380
381 static void
382 data_var_select (GtkNotebook *notebook,
383                 GtkNotebookPage *page,
384                 guint page_num,
385                 gpointer user_data)
386 {
387   struct data_editor *de = user_data;
388
389   select_sheet (de, page_num);
390 }
391
392
393
394
395 void
396 data_editor_select_sheet (struct data_editor *de, gint page)
397 {
398   gtk_notebook_set_current_page
399    (
400     GTK_NOTEBOOK (get_widget_assert (de->xml,"notebook")), page
401     );
402 }
403
404
405 void
406 open_data_window (GtkMenuItem *menuitem, gpointer parent)
407 {
408   bool finished = FALSE;
409
410   GtkWidget *dialog;
411
412   GtkFileFilter *filter ;
413
414   dialog = gtk_file_chooser_dialog_new (_("Open"),
415                                         GTK_WINDOW (parent),
416                                         GTK_FILE_CHOOSER_ACTION_OPEN,
417                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
418                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
419                                         NULL);
420
421   filter = gtk_file_filter_new ();
422   gtk_file_filter_set_name (filter, _("System Files (*.sav)"));
423   gtk_file_filter_add_pattern (filter, "*.sav");
424   gtk_file_filter_add_pattern (filter, "*.SAV");
425   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
426
427   filter = gtk_file_filter_new ();
428   gtk_file_filter_set_name (filter, _("Portable Files (*.por) "));
429   gtk_file_filter_add_pattern (filter, "*.por");
430   gtk_file_filter_add_pattern (filter, "*.POR");
431   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
432
433   filter = gtk_file_filter_new ();
434   gtk_file_filter_set_name (filter, _("All Files"));
435   gtk_file_filter_add_pattern (filter, "*");
436   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
437
438   do {
439
440     if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
441       {
442         gchar *file_name =
443           gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
444
445         g_free (file_name);
446       }
447     else
448       finished = TRUE;
449
450   } while ( ! finished ) ;
451
452   gtk_widget_destroy (dialog);
453 }
454
455
456
457
458 static void
459 status_bar_activate (GtkCheckMenuItem *menuitem, gpointer data)
460 {
461   struct data_editor *de = data;
462   GtkWidget *statusbar = get_widget_assert (de->xml, "status-bar");
463
464   if ( gtk_check_menu_item_get_active (menuitem) )
465     gtk_widget_show (statusbar);
466   else
467     gtk_widget_hide (statusbar);
468 }
469
470
471 static void
472 grid_lines_activate (GtkCheckMenuItem *menuitem, gpointer data)
473 {
474   struct data_editor *de = data;
475   const bool grid_visible = gtk_check_menu_item_get_active (menuitem);
476
477   gtk_sheet_show_grid (GTK_SHEET (get_widget_assert (de->xml,
478                                                      "variable_sheet")),
479                        grid_visible);
480
481   gtk_sheet_show_grid (GTK_SHEET (get_widget_assert (de->xml, "data_sheet")),
482                        grid_visible);
483 }
484
485
486
487 static void
488 data_sheet_activate (GtkCheckMenuItem *menuitem, gpointer data)
489 {
490   struct data_editor *de = data;
491
492   data_editor_select_sheet (de, PAGE_DATA_SHEET);
493 }
494
495
496 static void
497 variable_sheet_activate (GtkCheckMenuItem *menuitem, gpointer data)
498 {
499   struct data_editor *de = data;
500
501   data_editor_select_sheet (de, PAGE_VAR_SHEET);
502 }
503
504
505 static void
506 fonts_activate (GtkMenuItem *menuitem, gpointer data)
507 {
508   struct data_editor *de = data;
509   GtkWidget *dialog =
510     gtk_font_selection_dialog_new (_("Font Selection"));
511
512   gtk_window_set_transient_for (GTK_WINDOW (dialog),
513                                 GTK_WINDOW (get_widget_assert (de->xml,
514                                                                "data_editor")));
515   if ( GTK_RESPONSE_OK == gtk_dialog_run (GTK_DIALOG (dialog)) )
516     {
517       GtkSheet *data_sheet =
518         GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
519
520       GtkSheet *var_sheet =
521         GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
522
523       PsppireDataStore *ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
524       PsppireVarStore *vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
525
526       const gchar *font = gtk_font_selection_dialog_get_font_name
527         (GTK_FONT_SELECTION_DIALOG (dialog));
528
529       PangoFontDescription* font_desc =
530         pango_font_description_from_string (font);
531
532       psppire_var_store_set_font (vs, font_desc);
533       psppire_data_store_set_font (ds, font_desc);
534     }
535
536   gtk_widget_hide (dialog);
537 }
538
539
540 /* The next two callbacks are mutually co-operative */
541
542 /* Callback for the value labels menu item */
543 static void
544 value_labels_activate (GtkCheckMenuItem *menuitem, gpointer data)
545 {
546   struct data_editor *de = data;
547
548   GtkSheet *data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
549
550   GtkToggleToolButton *tb =
551     GTK_TOGGLE_TOOL_BUTTON (get_widget_assert (de->xml,
552                                                "togglebutton-value-labels"));
553
554   PsppireDataStore *ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
555
556   gboolean show_value_labels = gtk_check_menu_item_get_active (menuitem);
557
558   gtk_toggle_tool_button_set_active (tb, show_value_labels);
559
560   psppire_data_store_show_labels (ds, show_value_labels);
561 }
562
563
564 /* Callback for the value labels tooglebutton */
565 static void
566 value_labels_toggled (GtkToggleToolButton *toggle_tool_button,
567                       gpointer data)
568 {
569   struct data_editor *de = data;
570
571   GtkSheet *data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
572
573   GtkCheckMenuItem *item =
574     GTK_CHECK_MENU_ITEM (get_widget_assert (de->xml, "view_valuelabels"));
575
576   PsppireDataStore *ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
577
578   gboolean show_value_labels =
579     gtk_toggle_tool_button_get_active (toggle_tool_button);
580
581   gtk_check_menu_item_set_active (item, show_value_labels);
582
583   psppire_data_store_show_labels (ds, show_value_labels);
584 }
585
586
587 static void
588 file_quit (GtkCheckMenuItem *menuitem, gpointer data)
589 {
590   /* FIXME: Need to be more intelligent here.
591      Give the user the opportunity to save any unsaved data.
592   */
593   gtk_main_quit ();
594 }
595
596
597
598 /* Callback for when the Clear item in the edit menu is activated */
599 static void
600 on_clear_activate (GtkMenuItem *menuitem, gpointer data)
601 {
602   struct data_editor *de = data;
603
604   GtkNotebook *notebook = GTK_NOTEBOOK (get_widget_assert (de->xml,
605                                                            "notebook"));
606
607   switch ( gtk_notebook_get_current_page (notebook) )
608     {
609     case PAGE_VAR_SHEET:
610       {
611         GtkSheet *var_sheet =
612           GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
613
614         PsppireVarStore *vs = PSPPIRE_VAR_STORE
615           (gtk_sheet_get_model (var_sheet) );
616
617         /* This shouldn't be able to happen, because the menuitem
618            should be disabled */
619         g_return_if_fail (var_sheet->state  ==  GTK_SHEET_ROW_SELECTED );
620
621         psppire_dict_delete_variables (vs->dict,
622                                        var_sheet->range.row0,
623                                        1 +
624                                        var_sheet->range.rowi -
625                                        var_sheet->range.row0 );
626       }
627       break;
628       case PAGE_DATA_SHEET:
629         break;
630       default:
631         g_assert_not_reached ();
632     }
633 }
634
635
636 /* Insert a new variable before the current row in the variable sheet,
637    or before the current column in the data sheet, whichever is selected */
638 static void
639 insert_variable (GtkCheckMenuItem *m, gpointer data)
640 {
641   struct data_editor *de = data;
642   gint posn;
643
644   GtkWidget *notebook = get_widget_assert (de->xml, "notebook");
645
646   GtkSheet *var_sheet =
647     GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
648
649   PsppireVarStore *vs = PSPPIRE_VAR_STORE
650     (gtk_sheet_get_model (var_sheet) );
651
652   switch ( gtk_notebook_get_current_page ( GTK_NOTEBOOK (notebook)) )
653     {
654     case PAGE_VAR_SHEET:
655       posn = var_sheet->active_cell.row;
656       break;
657     case PAGE_DATA_SHEET:
658       {
659         GtkSheet *data_sheet =
660           GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
661
662         if ( data_sheet->state == GTK_SHEET_COLUMN_SELECTED )
663           posn = data_sheet->range.col0;
664         else
665           posn = data_sheet->active_cell.col;
666       }
667       break;
668     default:
669       g_assert_not_reached ();
670     }
671
672   psppire_dict_insert_variable (vs->dict, posn, NULL);
673 }
674
675
676 /* Callback for when the dictionary changes its weights */
677 static void
678 on_weight_change (GObject *o, gint weight_index, gpointer data)
679 {
680   struct data_editor *de = data;
681   GtkWidget *weight_status_area =
682     get_widget_assert (de->xml, "weight-status-area");
683
684   if ( weight_index == -1 )
685     {
686       gtk_label_set_text (GTK_LABEL (weight_status_area), _("Weights off"));
687     }
688   else
689     {
690       GtkSheet *var_sheet =
691         GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
692
693       PsppireVarStore *vs = PSPPIRE_VAR_STORE
694         (gtk_sheet_get_model (var_sheet) );
695
696       struct variable *var = psppire_dict_get_variable (vs->dict,
697                                                         weight_index);
698
699       gchar *text = g_strdup_printf (_("Weight by %s"), var_get_name (var));
700
701       gtk_label_set_text (GTK_LABEL (weight_status_area), text);
702
703       g_free (text);
704     }
705 }
706
707
708 static void
709 weight_cases_dialog (GObject *o, gpointer data)
710 {
711   gint response;
712   struct data_editor *de = data;
713   GtkSheet *var_sheet =
714     GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
715
716
717   GladeXML *xml = glade_xml_new (PKGDATADIR "/psppire.glade",
718                                  "weight-cases-dialog", NULL);
719
720
721   GtkWidget *treeview =  get_widget_assert (xml, "treeview");
722   GtkWidget *entry =  get_widget_assert (xml, "entry1");
723
724
725   PsppireVarStore *vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
726
727   PsppireVarSelect *select = psppire_var_select_new (treeview,
728                                                      entry, vs->dict);
729
730
731   PsppireDialog *dialog = create_weight_dialog (select, xml);
732
733   response = psppire_dialog_run (dialog);
734
735   g_object_unref (xml);
736
737   if (response == GTK_RESPONSE_OK)
738     {
739       const GList *list = psppire_var_select_get_variables (select);
740
741       g_assert ( g_list_length (list) <= 1 );
742
743       if ( list == NULL)
744         psppire_dict_set_weight_variable (select->dict, NULL);
745       else
746         {
747           struct variable *var = list->data;
748
749           psppire_dict_set_weight_variable (select->dict, var);
750         }
751     }
752 }
753
754