Merge 'psppsheet' into 'master'.
[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_goto_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                  gboolean show_value_labels)
569 {
570   PsppSheetSelection *selection;
571   GtkWidget *ds;
572
573   ds = psppire_data_sheet_new ();
574   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
575   psppire_data_sheet_set_value_labels (PSPPIRE_DATA_SHEET (ds),
576                                        show_value_labels);
577
578   g_signal_connect_swapped (ds, "notify::value-labels",
579                             G_CALLBACK (refresh_entry), de);
580   g_signal_connect (ds, "var-double-clicked",
581                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
582
583   selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
584   g_signal_connect (selection, "changed",
585                     G_CALLBACK (on_data_sheet_selection_changed), de);
586
587   return ds;
588 }
589
590 static GtkWidget *
591 make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
592                        gboolean show_value_labels)
593 {
594   GtkWidget *data_sheet_scroller;
595
596   de->data_sheets[0] = make_data_sheet (de, grid_lines, show_value_labels);
597   de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = NULL;
598
599   /* Put data sheet in scroller. */
600   data_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
601   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_sheet_scroller),
602                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
603   gtk_container_add (GTK_CONTAINER (data_sheet_scroller), de->data_sheets[0]);
604
605   return data_sheet_scroller;
606 }
607
608 static GtkWidget *
609 make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
610                       gboolean show_value_labels)
611 {
612   /* Panes, in the order in which we want to create them. */
613   enum
614     {
615       TL,                       /* top left */
616       TR,                       /* top right */
617       BL,                       /* bottom left */
618       BR                        /* bottom right */
619     };
620
621   PsppSheetView *ds[4];
622   GtkXPaned *xpaned;
623   int i;
624
625   xpaned = GTK_XPANED (gtk_xpaned_new ());
626
627   for (i = 0; i < 4; i++)
628     {
629       GtkAdjustment *hadjust, *vadjust;
630       GtkPolicyType hpolicy, vpolicy;
631       GtkWidget *scroller;
632
633       de->data_sheets[i] = make_data_sheet (de, grid_lines, show_value_labels);
634       ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
635
636       if (i == BL)
637         hadjust = pspp_sheet_view_get_hadjustment (ds[TL]);
638       else if (i == BR)
639         hadjust = pspp_sheet_view_get_hadjustment (ds[TR]);
640       else
641         hadjust = NULL;
642
643       if (i == TR)
644         vadjust = pspp_sheet_view_get_vadjustment (ds[TL]);
645       else if (i == BR)
646         vadjust = pspp_sheet_view_get_vadjustment (ds[BL]);
647       else
648         vadjust = NULL;
649
650       scroller = gtk_scrolled_window_new (hadjust, vadjust);
651       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
652                                            GTK_SHADOW_ETCHED_IN);
653       hpolicy = i == TL || i == TR ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
654       vpolicy = i == TL || i == BL ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
655       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
656                                       hpolicy, vpolicy);
657       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ds[i]));
658
659       switch (i)
660         {
661         case TL:
662           gtk_xpaned_pack_top_left (xpaned, scroller, TRUE, TRUE);
663           break;
664
665         case TR:
666           gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
667           break;
668
669         case BL:
670           gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
671           break;
672
673         case BR:
674           gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
675           break;
676
677         default:
678           g_warn_if_reached ();
679         }
680     }
681
682   /* Bottom sheets don't display variable names. */
683   pspp_sheet_view_set_headers_visible (ds[BL], FALSE);
684   pspp_sheet_view_set_headers_visible (ds[BR], FALSE);
685
686   /* Right sheets don't display case numbers. */
687   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[TR]), FALSE);
688   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[BR]), FALSE);
689
690   g_signal_connect (ds[TL], "notify::fixed-height",
691                     G_CALLBACK (on_data_sheet_fixed_height_notify), de);
692
693   return GTK_WIDGET (xpaned);
694 }
695
696 static void
697 psppire_data_editor_init (PsppireDataEditor *de)
698 {
699   GtkWidget *var_sheet_scroller;
700   GtkWidget *hbox;
701
702   de->font = NULL;
703   de->ui_manager = NULL;
704   de->old_vbox_widget = NULL;
705
706   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
707
708   de->cell_ref_label = gtk_label_new ("");
709   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
710   gtk_misc_set_alignment (GTK_MISC (de->cell_ref_label), 0.0, 0.5);
711
712   de->datum_entry = psppire_value_entry_new ();
713   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
714                     "activate", G_CALLBACK (on_datum_entry_activate), de);
715
716   hbox = gtk_hbox_new (FALSE, 0);
717   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
718   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
719
720   de->split = FALSE;
721   de->datasheet_vbox_widget
722     = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
723
724   de->vbox = gtk_vbox_new (FALSE, 0);
725   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
726   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
727                       TRUE, TRUE, 0);
728
729   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
730                             gtk_label_new_with_mnemonic (_("Data View")));
731
732   gtk_widget_show_all (de->vbox);
733
734   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
735   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
736                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
737   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
738   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
739                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
740   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
741   gtk_widget_show_all (var_sheet_scroller);
742   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
743                             gtk_label_new_with_mnemonic (_("Variable View")));
744
745   g_signal_connect (de->var_sheet, "var-double-clicked",
746                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
747
748   g_object_set (de, "can-focus", FALSE, NULL);
749
750   psppire_data_editor_update_ui_manager (de);
751 }
752
753 GtkWidget*
754 psppire_data_editor_new (PsppireDict *dict,
755                          PsppireDataStore *data_store)
756 {
757   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
758                         "dictionary",  dict,
759                         "data-store",  data_store,
760                         NULL);
761 }
762 \f
763 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
764    sheet(s) and variable sheet. */
765 void
766 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
767 {
768   GtkTreeViewGridLines grid;
769   PsppireDataSheet *data_sheet;
770   int i;
771
772   grid = (grid_visible
773           ? GTK_TREE_VIEW_GRID_LINES_BOTH
774           : GTK_TREE_VIEW_GRID_LINES_NONE);
775
776   FOR_EACH_DATA_SHEET (data_sheet, i, de)
777     pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (data_sheet), grid);
778   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
779 }
780
781
782 static void
783 set_font_recursively (GtkWidget *w, gpointer data)
784 {
785   PangoFontDescription *font_desc = data;
786   GtkRcStyle *style = gtk_widget_get_modifier_style (w);
787
788   pango_font_description_free (style->font_desc);
789   style->font_desc = pango_font_description_copy (font_desc);
790
791   gtk_widget_modify_style (w, style);
792
793   if ( GTK_IS_CONTAINER (w))
794     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
795 }
796
797 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
798 void
799 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
800 {
801   set_font_recursively (GTK_WIDGET (de), font_desc);
802
803   if (de->font)
804     pango_font_description_free (de->font);
805   de->font = pango_font_description_copy (font_desc);
806 }
807
808 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
809    If SPLIT is FALSE, un-splits it into a single pane. */
810 void
811 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
812 {
813   GtkTreeViewGridLines grid_lines;
814   gboolean labels;
815
816   if (split == de->split)
817     return;
818
819
820   grid_lines = pspp_sheet_view_get_grid_lines (
821     PSPP_SHEET_VIEW (de->data_sheets[0]));
822   labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
823                                                   de->data_sheets[0]));
824
825   disconnect_data_sheets (de);
826   if (de->old_vbox_widget)
827     g_object_unref (de->old_vbox_widget);
828   de->old_vbox_widget = de->datasheet_vbox_widget;
829   g_object_ref (de->old_vbox_widget);
830   /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
831         (currently it seems to provoke an error if I do that.  
832         I don't know why. */
833   gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
834
835   if (split)
836     de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
837   else
838     de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
839
840   psppire_data_editor_refresh_model (de);
841
842   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
843                       TRUE, TRUE, 0);
844   gtk_widget_show_all (de->vbox);
845
846   if (de->font)
847     set_font_recursively (GTK_WIDGET (de), de->font);
848
849   de->split = split;
850   g_object_notify (G_OBJECT (de), "split");
851   psppire_data_editor_update_ui_manager (de);
852 }
853
854 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
855    visible and selected in the active view in DE. */
856 void
857 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
858 {
859   PsppireDataSheet *data_sheet;
860
861   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
862     {
863     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
864       data_sheet = psppire_data_editor_get_active_data_sheet (de);
865       psppire_data_sheet_goto_variable (data_sheet, dict_index);
866       break;
867
868     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
869       psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
870                                        dict_index);
871       break;
872     }
873 }
874
875 /* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
876    is the only data sheet.  If DE is in split mode (showing four data sheets),
877    this is the focused data sheet or, if none is focused, the data sheet with
878    selected cells or, if none has selected cells, the upper-left data sheet. */
879 PsppireDataSheet *
880 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
881 {
882   if (de->split)
883     {
884       PsppireDataSheet *data_sheet;
885       GtkWidget *scroller;
886       int i;
887
888       /* If one of the datasheet's scrollers is focused, choose that one. */
889       scroller = gtk_container_get_focus_child (
890         GTK_CONTAINER (de->datasheet_vbox_widget));
891       if (scroller != NULL)
892         return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
893
894       /* Otherwise if there's a nonempty selection in some data sheet, choose
895          that one. */
896       FOR_EACH_DATA_SHEET (data_sheet, i, de)
897         {
898           PsppSheetSelection *selection;
899
900           selection = pspp_sheet_view_get_selection (
901             PSPP_SHEET_VIEW (data_sheet));
902           if (pspp_sheet_selection_count_selected_rows (selection)
903               && pspp_sheet_selection_count_selected_columns (selection))
904             return data_sheet;
905         }
906     }
907
908   return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
909 }
910
911 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
912    manager to display menu items and toolbar items specific to DE's current
913    page and data sheet.
914
915    DE's toplevel widget can watch for changes by connecting to DE's
916    notify::ui-manager signal. */
917 GtkUIManager *
918 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
919 {
920   psppire_data_editor_update_ui_manager (de);
921   return de->ui_manager;
922 }
923
924 static void
925 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
926 {
927   PsppireDataSheet *data_sheet;
928   GtkUIManager *ui_manager;
929
930   ui_manager = NULL;
931
932   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
933     {
934     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
935       data_sheet = psppire_data_editor_get_active_data_sheet (de);
936       if (data_sheet != NULL)
937         ui_manager = psppire_data_sheet_get_ui_manager (data_sheet);
938       else
939         {
940           /* This happens transiently in psppire_data_editor_split_window(). */
941         }
942       break;
943
944     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
945       ui_manager = psppire_var_sheet_get_ui_manager (
946         PSPPIRE_VAR_SHEET (de->var_sheet));
947       break;
948
949     default:
950       /* This happens transiently in psppire_data_editor_init(). */
951       break;
952     }
953
954   if (ui_manager != de->ui_manager)
955     {
956       if (de->ui_manager)
957         g_object_unref (de->ui_manager);
958       if (ui_manager)
959         g_object_ref (ui_manager);
960       de->ui_manager = ui_manager;
961
962       g_object_notify (G_OBJECT (de), "ui-manager");
963     }
964 }