Add support for reading SPSS/PC+ system files.
[pspp] / src / ui / gui / psppire-data-editor.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
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
19 #include "ui/gui/psppire-data-editor.h"
20
21 #include <gtk/gtk.h>
22 #include <gtk-contrib/gtkxpaned.h>
23
24 #include "data/datasheet.h"
25 #include "data/value-labels.h"
26 #include "libpspp/range-set.h"
27 #include "libpspp/str.h"
28 #include "ui/gui/helper.h"
29 #include "ui/gui/pspp-sheet-selection.h"
30 #include "ui/gui/psppire-data-sheet.h"
31 #include "ui/gui/psppire-data-store.h"
32 #include "ui/gui/psppire-value-entry.h"
33 #include "ui/gui/psppire-var-sheet.h"
34 #include "ui/gui/psppire.h"
35 #include "ui/gui/psppire-conf.h"
36
37 #include <gettext.h>
38 #define _(msgid) gettext (msgid)
39
40 #define FOR_EACH_DATA_SHEET(DATA_SHEET, IDX, DATA_EDITOR)       \
41   for ((IDX) = 0;                                               \
42        (IDX) < 4                                                \
43          && ((DATA_SHEET) = PSPPIRE_DATA_SHEET (                \
44                (DATA_EDITOR)->data_sheets[IDX])) != NULL;       \
45        (IDX)++)
46
47 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
48 static void psppire_data_editor_init                (PsppireDataEditor      *de);
49
50 static void disconnect_data_sheets (PsppireDataEditor *);
51 static void refresh_entry (PsppireDataEditor *);
52 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
53
54 GType
55 psppire_data_editor_get_type (void)
56 {
57   static GType de_type = 0;
58
59   if (!de_type)
60     {
61       static const GTypeInfo de_info =
62       {
63         sizeof (PsppireDataEditorClass),
64         NULL, /* base_init */
65         NULL, /* base_finalize */
66         (GClassInitFunc) psppire_data_editor_class_init,
67         NULL, /* class_finalize */
68         NULL, /* class_data */
69         sizeof (PsppireDataEditor),
70         0,
71         (GInstanceInitFunc) psppire_data_editor_init,
72       };
73
74       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
75                                         &de_info, 0);
76     }
77
78   return de_type;
79 }
80
81 static GObjectClass * parent_class = NULL;
82
83 static void
84 psppire_data_editor_dispose (GObject *obj)
85 {
86   PsppireDataEditor *de = (PsppireDataEditor *) obj;
87
88   disconnect_data_sheets (de);
89
90   if (de->data_store)
91     {
92       g_object_unref (de->data_store);
93       de->data_store = NULL;
94     }
95
96   if (de->dict)
97     {
98       g_object_unref (de->dict);
99       de->dict = NULL;
100     }
101
102   if (de->font != NULL)
103     {
104       pango_font_description_free (de->font);
105       de->font = NULL;
106     }
107
108   if (de->ui_manager)
109     {
110       g_object_unref (de->ui_manager);
111       de->ui_manager = NULL;
112     }
113
114   /* Chain up to the parent class */
115   G_OBJECT_CLASS (parent_class)->dispose (obj);
116 }
117
118 enum
119   {
120     PROP_0,
121     PROP_DATA_STORE,
122     PROP_DICTIONARY,
123     PROP_VALUE_LABELS,
124     PROP_SPLIT_WINDOW,
125     PROP_UI_MANAGER
126   };
127
128 static void
129 psppire_data_editor_refresh_model (PsppireDataEditor *de)
130 {
131   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
132   PsppireDataSheet *data_sheet;
133   int i;
134
135   FOR_EACH_DATA_SHEET (data_sheet, i, de)
136     psppire_data_sheet_set_data_store (data_sheet, de->data_store);
137   psppire_var_sheet_set_dictionary (var_sheet, de->dict);
138 }
139
140 static void
141 psppire_data_editor_set_property (GObject         *object,
142                                   guint            prop_id,
143                                   const GValue    *value,
144                                   GParamSpec      *pspec)
145 {
146   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
147   PsppireDataSheet *data_sheet;
148   int i;
149
150   switch (prop_id)
151     {
152     case PROP_SPLIT_WINDOW:
153       psppire_data_editor_split_window (de, g_value_get_boolean (value));
154       break;
155     case PROP_DATA_STORE:
156       if ( de->data_store)
157         {
158           g_signal_handlers_disconnect_by_func (de->data_store,
159                                                 G_CALLBACK (refresh_entry),
160                                                 de);
161           g_object_unref (de->data_store);
162         }
163
164       de->data_store = g_value_get_pointer (value);
165       g_object_ref (de->data_store);
166       psppire_data_editor_refresh_model (de);
167
168       g_signal_connect_swapped (de->data_store, "case-changed",
169                                 G_CALLBACK (refresh_entry), de);
170
171       break;
172     case PROP_DICTIONARY:
173       if (de->dict)
174         g_object_unref (de->dict);
175       de->dict = g_value_get_pointer (value);
176       g_object_ref (de->dict);
177
178       psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
179                                         de->dict);
180       break;
181     case PROP_VALUE_LABELS:
182       FOR_EACH_DATA_SHEET (data_sheet, i, de)
183         psppire_data_sheet_set_value_labels (data_sheet,
184                                           g_value_get_boolean (value));
185       break;
186     case PROP_UI_MANAGER:
187     default:
188       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
189       break;
190     };
191 }
192
193 static void
194 psppire_data_editor_get_property (GObject         *object,
195                                   guint            prop_id,
196                                   GValue          *value,
197                                   GParamSpec      *pspec)
198 {
199   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
200
201   switch (prop_id)
202     {
203     case PROP_SPLIT_WINDOW:
204       g_value_set_boolean (value, de->split);
205       break;
206     case PROP_DATA_STORE:
207       g_value_set_pointer (value, de->data_store);
208       break;
209     case PROP_DICTIONARY:
210       g_value_set_pointer (value, de->dict);
211       break;
212     case PROP_VALUE_LABELS:
213       g_value_set_boolean (value,
214                            psppire_data_sheet_get_value_labels (
215                              PSPPIRE_DATA_SHEET (de->data_sheets[0])));
216       break;
217     case PROP_UI_MANAGER:
218       g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
219       break;
220     default:
221       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
222       break;
223     }
224 }
225
226 static void
227 psppire_data_editor_switch_page (GtkNotebook     *notebook,
228 #if GTK_DISABLE_DEPRECATED && GTK_CHECK_VERSION(2,20,0)
229                                  gpointer page,
230 #else
231                                  GtkNotebookPage *page,
232 #endif
233                                  guint            page_num)
234 {
235   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, page, page_num);
236   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
237 }
238
239 static void
240 psppire_data_editor_set_focus_child (GtkContainer *container,
241                                      GtkWidget    *widget)
242 {
243   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
244   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
245 }
246
247 static void
248 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
249 {
250   GParamSpec *data_store_spec ;
251   GParamSpec *dict_spec ;
252   GParamSpec *value_labels_spec;
253   GParamSpec *split_window_spec;
254   GParamSpec *ui_manager_spec;
255   GObjectClass *object_class = G_OBJECT_CLASS (klass);
256   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
257   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
258
259   parent_class = g_type_class_peek_parent (klass);
260
261   object_class->dispose = psppire_data_editor_dispose;
262   object_class->set_property = psppire_data_editor_set_property;
263   object_class->get_property = psppire_data_editor_get_property;
264
265   container_class->set_focus_child = psppire_data_editor_set_focus_child;
266
267   notebook_class->switch_page = psppire_data_editor_switch_page;
268
269   data_store_spec =
270     g_param_spec_pointer ("data-store",
271                           "Data Store",
272                           "A pointer to the data store associated with this editor",
273                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
274
275   g_object_class_install_property (object_class,
276                                    PROP_DATA_STORE,
277                                    data_store_spec);
278
279   dict_spec =
280     g_param_spec_pointer ("dictionary",
281                           "Dictionary",
282                           "A pointer to the dictionary associated with this editor",
283                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
284
285   g_object_class_install_property (object_class,
286                                    PROP_DICTIONARY,
287                                    dict_spec);
288
289   value_labels_spec =
290     g_param_spec_boolean ("value-labels",
291                          "Value Labels",
292                          "Whether or not the data sheet should display labels instead of values",
293                           FALSE,
294                          G_PARAM_WRITABLE | G_PARAM_READABLE);
295
296   g_object_class_install_property (object_class,
297                                    PROP_VALUE_LABELS,
298                                    value_labels_spec);
299
300
301   split_window_spec =
302     g_param_spec_boolean ("split",
303                           "Split Window",
304                           "True iff the data sheet is split",
305                           FALSE,
306                           G_PARAM_READABLE | G_PARAM_WRITABLE);
307
308   g_object_class_install_property (object_class,
309                                    PROP_SPLIT_WINDOW,
310                                    split_window_spec);
311
312   ui_manager_spec =
313     g_param_spec_object ("ui-manager",
314                          "UI Manager",
315                          "UI manager for the active notebook tab.  The client should merge this UI manager with the active UI manager to obtain menu items and tool bar items specific to the active notebook tab.",
316                          GTK_TYPE_UI_MANAGER,
317                          G_PARAM_READABLE);
318   g_object_class_install_property (object_class,
319                                    PROP_UI_MANAGER,
320                                    ui_manager_spec);
321 }
322
323 static gboolean
324 on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
325                                   gint dict_index,
326                                   PsppireDataEditor *de)
327 {
328   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
329                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
330
331   psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
332                                    dict_index);
333
334   return TRUE;
335 }
336
337 static gboolean
338 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
339                                  PsppireDataEditor *de)
340 {
341   PsppireDataSheet *data_sheet;
342
343   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
344                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
345
346   data_sheet = psppire_data_editor_get_active_data_sheet (de);
347   psppire_data_sheet_goto_variable (data_sheet, dict_index);
348
349   return TRUE;
350 }
351
352 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
353    active cell or cells. */
354 static void
355 refresh_entry (PsppireDataEditor *de)
356 {
357   PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
358   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
359   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
360
361   gchar *ref_cell_text;
362   GList *selected_columns, *iter;
363   struct variable *var;
364   gint n_cases;
365   gint n_vars;
366
367   selected_columns = pspp_sheet_selection_get_selected_columns (selection);
368   n_vars = 0;
369   var = NULL;
370   for (iter = selected_columns; iter != NULL; iter = iter->next)
371     {
372       PsppSheetViewColumn *column = iter->data;
373       struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
374       if (v != NULL)
375         {
376           var = v;
377           n_vars++;
378         }
379     }
380   g_list_free (selected_columns);
381
382   n_cases = pspp_sheet_selection_count_selected_rows (selection);
383   if (n_cases > 0)
384     {
385       /* The final row is selectable but it isn't a case (it's just used to add
386          more cases), so don't count it. */
387       GtkTreePath *path;
388       gint case_count;
389
390       case_count = psppire_data_store_get_case_count (de->data_store);
391       path = gtk_tree_path_new_from_indices (case_count, -1);
392       if (pspp_sheet_selection_path_is_selected (selection, path))
393         n_cases--;
394       gtk_tree_path_free (path);
395     }
396
397   ref_cell_text = NULL;
398   if (n_cases == 1 && n_vars == 1)
399     {
400       PsppireValueEntry *value_entry = PSPPIRE_VALUE_ENTRY (de->datum_entry);
401       struct range_set *selected_rows;
402       gboolean show_value_labels;
403       union value value;
404       int width;
405       gint row;
406
407       selected_rows = pspp_sheet_selection_get_range_set (selection);
408       row = range_set_scan (selected_rows, 0);
409       range_set_destroy (selected_rows);
410
411       ref_cell_text = g_strdup_printf ("%d : %s", row + 1, var_get_name (var));
412
413       show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
414
415       psppire_value_entry_set_variable (value_entry, var);
416       psppire_value_entry_set_show_value_label (value_entry,
417                                                 show_value_labels);
418
419       width = var_get_width (var);
420       value_init (&value, width);
421       datasheet_get_value (de->data_store->datasheet,
422                            row, var_get_case_index (var), &value);
423       psppire_value_entry_set_value (value_entry, &value, width);
424       value_destroy (&value, width);
425
426       gtk_widget_set_sensitive (de->datum_entry, TRUE);
427     }
428   else
429     {
430       if (n_cases == 0 || n_vars == 0)
431         {
432           ref_cell_text = NULL;
433         }
434       else
435         {
436           struct string s;
437
438           /* The glib string library does not understand the ' printf modifier
439              on all platforms, but the "struct string" library does (because
440              Gnulib fixes that problem), so use the latter.  */
441           ds_init_empty (&s);
442           ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
443                          n_cases);
444           ds_put_byte (&s, ' ');
445           ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
446           ds_put_byte (&s, ' ');
447           ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
448                                        n_vars),
449                          n_vars);
450           ref_cell_text = ds_steal_cstr (&s);
451         }
452
453       psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry),
454                                         NULL);
455       gtk_entry_set_text (
456         GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))), "");
457       gtk_widget_set_sensitive (de->datum_entry, FALSE);
458     }
459
460   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
461                        ref_cell_text ? ref_cell_text : "");
462   g_free (ref_cell_text);
463 }
464
465 static void
466 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
467 {
468   PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
469   struct variable *var;
470   union value value;
471   int width;
472   gint row;
473
474   row = psppire_data_sheet_get_current_case (data_sheet);
475   var = psppire_data_sheet_get_current_variable (data_sheet);
476   if (row < 0 || !var)
477     return;
478
479   width = var_get_width (var);
480   value_init (&value, width);
481   if (psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
482                                      &value, width))
483     psppire_data_store_set_value (de->data_store, row, var, &value);
484   value_destroy (&value, width);
485 }
486
487 static void
488 on_data_sheet_selection_changed (PsppSheetSelection *selection,
489                                  PsppireDataEditor *de)
490 {
491   /* In a split view, ensure that only a single data sheet has a nonempty
492      selection.  */
493   if (de->split
494       && pspp_sheet_selection_count_selected_rows (selection)
495       && pspp_sheet_selection_count_selected_columns (selection))
496     {
497       PsppireDataSheet *ds;
498       int i;
499
500       FOR_EACH_DATA_SHEET (ds, i, de)
501         {
502           PsppSheetSelection *s;
503
504           s = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
505           if (s != selection)
506             pspp_sheet_selection_unselect_all (s);
507         }
508     }
509
510   refresh_entry (de);
511 }
512
513 /* Ensures that rows in the right-hand panes in the split view have the same
514    row height as the left-hand panes.  Otherwise, the rows in the right-hand
515    pane tend to be smaller, because the right-hand pane doesn't have buttons
516    for case numbers. */
517 static void
518 on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
519                                    GParamSpec *pspec,
520                                    PsppireDataEditor *de)
521 {
522   enum
523     {
524       TL = GTK_XPANED_TOP_LEFT,
525       TR = GTK_XPANED_TOP_RIGHT,
526       BL = GTK_XPANED_BOTTOM_LEFT,
527       BR = GTK_XPANED_BOTTOM_RIGHT
528     };
529
530   int fixed_height = pspp_sheet_view_get_fixed_height (PSPP_SHEET_VIEW (ds));
531
532   pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[TR]),
533                                     fixed_height);
534   pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[BR]),
535                                     fixed_height);
536 }
537
538 static void
539 disconnect_data_sheets (PsppireDataEditor *de)
540 {
541   PsppireDataSheet *ds;
542   int i;
543
544   FOR_EACH_DATA_SHEET (ds, i, de)
545     {
546       PsppSheetSelection *selection;
547
548       if (ds == NULL)
549         {
550           /* This can only happen if 'dispose' runs more than once. */
551           continue;
552         }
553
554       if (i == GTK_XPANED_TOP_LEFT)
555         g_signal_handlers_disconnect_by_func (
556           ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
557
558       g_signal_handlers_disconnect_by_func (
559         ds, G_CALLBACK (refresh_entry), de);
560       g_signal_handlers_disconnect_by_func (
561         ds, G_CALLBACK (on_data_sheet_var_double_clicked), de);
562
563       selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
564       g_signal_handlers_disconnect_by_func (
565         selection, G_CALLBACK (on_data_sheet_selection_changed), de);
566
567       de->data_sheets[i] = NULL;
568     }
569 }
570
571 static GtkWidget *
572 make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
573                  gboolean show_value_labels)
574 {
575   PsppSheetSelection *selection;
576   GtkWidget *ds;
577
578   ds = psppire_data_sheet_new ();
579   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
580   psppire_data_sheet_set_value_labels (PSPPIRE_DATA_SHEET (ds),
581                                        show_value_labels);
582
583   g_signal_connect_swapped (ds, "notify::value-labels",
584                             G_CALLBACK (refresh_entry), de);
585   g_signal_connect (ds, "var-double-clicked",
586                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
587
588   selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
589   g_signal_connect (selection, "changed",
590                     G_CALLBACK (on_data_sheet_selection_changed), de);
591
592   return ds;
593 }
594
595 static GtkWidget *
596 make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
597                        gboolean show_value_labels)
598 {
599   GtkWidget *data_sheet_scroller;
600
601   de->data_sheets[0] = make_data_sheet (de, grid_lines, show_value_labels);
602   de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = NULL;
603
604   /* Put data sheet in scroller. */
605   data_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
606   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_sheet_scroller),
607                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
608   gtk_container_add (GTK_CONTAINER (data_sheet_scroller), de->data_sheets[0]);
609
610   return data_sheet_scroller;
611 }
612
613 static GtkWidget *
614 make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
615                       gboolean show_value_labels)
616 {
617   /* Panes, in the order in which we want to create them. */
618   enum
619     {
620       TL,                       /* top left */
621       TR,                       /* top right */
622       BL,                       /* bottom left */
623       BR                        /* bottom right */
624     };
625
626   PsppSheetView *ds[4];
627   GtkXPaned *xpaned;
628   int i;
629
630   xpaned = GTK_XPANED (gtk_xpaned_new ());
631
632   for (i = 0; i < 4; i++)
633     {
634       GtkAdjustment *hadjust, *vadjust;
635       GtkPolicyType hpolicy, vpolicy;
636       GtkWidget *scroller;
637
638       de->data_sheets[i] = make_data_sheet (de, grid_lines, show_value_labels);
639       ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
640
641       if (i == BL)
642         hadjust = pspp_sheet_view_get_hadjustment (ds[TL]);
643       else if (i == BR)
644         hadjust = pspp_sheet_view_get_hadjustment (ds[TR]);
645       else
646         hadjust = NULL;
647
648       if (i == TR)
649         vadjust = pspp_sheet_view_get_vadjustment (ds[TL]);
650       else if (i == BR)
651         vadjust = pspp_sheet_view_get_vadjustment (ds[BL]);
652       else
653         vadjust = NULL;
654
655       scroller = gtk_scrolled_window_new (hadjust, vadjust);
656       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
657                                            GTK_SHADOW_ETCHED_IN);
658       hpolicy = i == TL || i == TR ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
659       vpolicy = i == TL || i == BL ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
660       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
661                                       hpolicy, vpolicy);
662       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ds[i]));
663
664       switch (i)
665         {
666         case TL:
667           gtk_xpaned_pack_top_left (xpaned, scroller, TRUE, TRUE);
668           break;
669
670         case TR:
671           gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
672           break;
673
674         case BL:
675           gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
676           break;
677
678         case BR:
679           gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
680           break;
681
682         default:
683           g_warn_if_reached ();
684         }
685     }
686
687   /* Bottom sheets don't display variable names. */
688   pspp_sheet_view_set_headers_visible (ds[BL], FALSE);
689   pspp_sheet_view_set_headers_visible (ds[BR], FALSE);
690
691   /* Right sheets don't display case numbers. */
692   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[TR]), FALSE);
693   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[BR]), FALSE);
694
695   g_signal_connect (ds[TL], "notify::fixed-height",
696                     G_CALLBACK (on_data_sheet_fixed_height_notify), de);
697
698   return GTK_WIDGET (xpaned);
699 }
700
701 static void set_font_recursively (GtkWidget *w, gpointer data);
702
703 static void
704 psppire_data_editor_init (PsppireDataEditor *de)
705 {
706   GtkWidget *var_sheet_scroller;
707   GtkWidget *hbox;
708   gchar *fontname = NULL;
709
710   de->font = NULL;
711   de->ui_manager = NULL;
712   de->old_vbox_widget = NULL;
713
714   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
715
716   de->cell_ref_label = gtk_label_new ("");
717   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
718   gtk_misc_set_alignment (GTK_MISC (de->cell_ref_label), 0.0, 0.5);
719
720   de->datum_entry = psppire_value_entry_new ();
721   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
722                     "activate", G_CALLBACK (on_datum_entry_activate), de);
723
724   hbox = gtk_hbox_new (FALSE, 0);
725   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
726   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
727
728   de->split = FALSE;
729   de->datasheet_vbox_widget
730     = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
731
732   de->vbox = gtk_vbox_new (FALSE, 0);
733   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
734   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
735                       TRUE, TRUE, 0);
736
737   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
738                             gtk_label_new_with_mnemonic (_("Data View")));
739
740   gtk_widget_show_all (de->vbox);
741
742   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
743   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
744                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
745   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
746   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
747                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
748   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
749   gtk_widget_show_all (var_sheet_scroller);
750   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
751                             gtk_label_new_with_mnemonic (_("Variable View")));
752
753   g_signal_connect (de->var_sheet, "var-double-clicked",
754                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
755
756   g_object_set (de, "can-focus", FALSE, NULL);
757
758   if (psppire_conf_get_string (psppire_conf_new (),
759                            "Data Editor", "font",
760                                 &fontname) )
761     {
762       de->font = pango_font_description_from_string (fontname);
763       g_free (fontname);
764       set_font_recursively (GTK_WIDGET (de), de->font);
765     }
766
767   psppire_data_editor_update_ui_manager (de);
768 }
769
770 GtkWidget*
771 psppire_data_editor_new (PsppireDict *dict,
772                          PsppireDataStore *data_store)
773 {
774   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
775                         "dictionary",  dict,
776                         "data-store",  data_store,
777                         NULL);
778 }
779 \f
780 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
781    sheet(s) and variable sheet. */
782 void
783 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
784 {
785   GtkTreeViewGridLines grid;
786   PsppireDataSheet *data_sheet;
787   int i;
788
789   grid = (grid_visible
790           ? GTK_TREE_VIEW_GRID_LINES_BOTH
791           : GTK_TREE_VIEW_GRID_LINES_NONE);
792
793   FOR_EACH_DATA_SHEET (data_sheet, i, de)
794     pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (data_sheet), grid);
795   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
796 }
797
798
799 static void
800 set_font_recursively (GtkWidget *w, gpointer data)
801 {
802   PangoFontDescription *font_desc = data;
803   GtkRcStyle *style = gtk_widget_get_modifier_style (w);
804
805   pango_font_description_free (style->font_desc);
806   style->font_desc = pango_font_description_copy (font_desc);
807
808   gtk_widget_modify_style (w, style);
809
810   if ( GTK_IS_CONTAINER (w))
811     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
812 }
813
814 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
815 void
816 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
817 {
818   gchar *font_name;
819   set_font_recursively (GTK_WIDGET (de), font_desc);
820
821   if (de->font)
822     pango_font_description_free (de->font);
823   de->font = pango_font_description_copy (font_desc);
824   font_name = pango_font_description_to_string (de->font);
825
826   psppire_conf_set_string (psppire_conf_new (),
827                            "Data Editor", "font",
828                            font_name);
829
830 }
831
832 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
833    If SPLIT is FALSE, un-splits it into a single pane. */
834 void
835 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
836 {
837   GtkTreeViewGridLines grid_lines;
838   gboolean labels;
839
840   if (split == de->split)
841     return;
842
843
844   grid_lines = pspp_sheet_view_get_grid_lines (
845     PSPP_SHEET_VIEW (de->data_sheets[0]));
846   labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
847                                                   de->data_sheets[0]));
848
849   disconnect_data_sheets (de);
850   if (de->old_vbox_widget)
851     g_object_unref (de->old_vbox_widget);
852   de->old_vbox_widget = de->datasheet_vbox_widget;
853   g_object_ref (de->old_vbox_widget);
854   /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
855         (currently it seems to provoke an error if I do that.  
856         I don't know why. */
857   gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
858
859   if (split)
860     de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
861   else
862     de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
863
864   psppire_data_editor_refresh_model (de);
865
866   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
867                       TRUE, TRUE, 0);
868   gtk_widget_show_all (de->vbox);
869
870   if (de->font)
871     set_font_recursively (GTK_WIDGET (de), de->font);
872
873   de->split = split;
874   g_object_notify (G_OBJECT (de), "split");
875   psppire_data_editor_update_ui_manager (de);
876 }
877
878 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
879    visible and selected in the active view in DE. */
880 void
881 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
882 {
883   PsppireDataSheet *data_sheet;
884
885   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
886     {
887     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
888       data_sheet = psppire_data_editor_get_active_data_sheet (de);
889       psppire_data_sheet_goto_variable (data_sheet, dict_index);
890       break;
891
892     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
893       psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
894                                        dict_index);
895       break;
896     }
897 }
898
899 /* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
900    is the only data sheet.  If DE is in split mode (showing four data sheets),
901    this is the focused data sheet or, if none is focused, the data sheet with
902    selected cells or, if none has selected cells, the upper-left data sheet. */
903 PsppireDataSheet *
904 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
905 {
906   if (de->split)
907     {
908       PsppireDataSheet *data_sheet;
909       GtkWidget *scroller;
910       int i;
911
912       /* If one of the datasheet's scrollers is focused, choose that one. */
913       scroller = gtk_container_get_focus_child (
914         GTK_CONTAINER (de->datasheet_vbox_widget));
915       if (scroller != NULL)
916         return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
917
918       /* Otherwise if there's a nonempty selection in some data sheet, choose
919          that one. */
920       FOR_EACH_DATA_SHEET (data_sheet, i, de)
921         {
922           PsppSheetSelection *selection;
923
924           selection = pspp_sheet_view_get_selection (
925             PSPP_SHEET_VIEW (data_sheet));
926           if (pspp_sheet_selection_count_selected_rows (selection)
927               && pspp_sheet_selection_count_selected_columns (selection))
928             return data_sheet;
929         }
930     }
931
932   return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
933 }
934
935 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
936    manager to display menu items and toolbar items specific to DE's current
937    page and data sheet.
938
939    DE's toplevel widget can watch for changes by connecting to DE's
940    notify::ui-manager signal. */
941 GtkUIManager *
942 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
943 {
944   psppire_data_editor_update_ui_manager (de);
945   return de->ui_manager;
946 }
947
948 static void
949 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
950 {
951   PsppireDataSheet *data_sheet;
952   GtkUIManager *ui_manager;
953
954   ui_manager = NULL;
955
956   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
957     {
958     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
959       data_sheet = psppire_data_editor_get_active_data_sheet (de);
960       if (data_sheet != NULL)
961         ui_manager = psppire_data_sheet_get_ui_manager (data_sheet);
962       else
963         {
964           /* This happens transiently in psppire_data_editor_split_window(). */
965         }
966       break;
967
968     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
969       ui_manager = psppire_var_sheet_get_ui_manager (
970         PSPPIRE_VAR_SHEET (de->var_sheet));
971       break;
972
973     default:
974       /* This happens transiently in psppire_data_editor_init(). */
975       break;
976     }
977
978   if (ui_manager != de->ui_manager)
979     {
980       if (de->ui_manager)
981         g_object_unref (de->ui_manager);
982       if (ui_manager)
983         g_object_ref (ui_manager);
984       de->ui_manager = ui_manager;
985
986       g_object_notify (G_OBJECT (de), "ui-manager");
987     }
988 }