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