18fd5e8e8d81d294f0557ecb8de1b6cb2b852ed0
[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-conf.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                                  GtkWidget *w,
228                                  guint            page_num)
229 {
230   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, 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 set_font_recursively (GtkWidget *w, gpointer data);
697
698 static void
699 psppire_data_editor_init (PsppireDataEditor *de)
700 {
701   GtkWidget *var_sheet_scroller;
702   GtkWidget *hbox;
703   gchar *fontname = NULL;
704
705   de->font = NULL;
706   de->ui_manager = NULL;
707   de->old_vbox_widget = NULL;
708
709   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
710
711   de->cell_ref_label = gtk_label_new ("");
712   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
713   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
714
715   de->datum_entry = psppire_value_entry_new ();
716   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
717                     "activate", G_CALLBACK (on_datum_entry_activate), de);
718
719   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
720   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
721   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
722
723   de->split = FALSE;
724   de->datasheet_vbox_widget
725     = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
726
727   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
728   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
729   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
730                       TRUE, TRUE, 0);
731
732   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
733                             gtk_label_new_with_mnemonic (_("Data View")));
734
735   gtk_widget_show_all (de->vbox);
736
737   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
738   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
739                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
740   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
741   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
742                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
743   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
744   gtk_widget_show_all (var_sheet_scroller);
745   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
746                             gtk_label_new_with_mnemonic (_("Variable View")));
747
748   g_signal_connect (de->var_sheet, "var-double-clicked",
749                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
750
751   g_object_set (de, "can-focus", FALSE, NULL);
752
753   if (psppire_conf_get_string (psppire_conf_new (),
754                            "Data Editor", "font",
755                                 &fontname) )
756     {
757       de->font = pango_font_description_from_string (fontname);
758       g_free (fontname);
759       set_font_recursively (GTK_WIDGET (de), de->font);
760     }
761
762   psppire_data_editor_update_ui_manager (de);
763 }
764
765 GtkWidget*
766 psppire_data_editor_new (PsppireDict *dict,
767                          PsppireDataStore *data_store)
768 {
769   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
770                         "dictionary",  dict,
771                         "data-store",  data_store,
772                         NULL);
773 }
774 \f
775 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
776    sheet(s) and variable sheet. */
777 void
778 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
779 {
780   GtkTreeViewGridLines grid;
781   PsppireDataSheet *data_sheet;
782   int i;
783
784   grid = (grid_visible
785           ? GTK_TREE_VIEW_GRID_LINES_BOTH
786           : GTK_TREE_VIEW_GRID_LINES_NONE);
787
788   FOR_EACH_DATA_SHEET (data_sheet, i, de)
789     pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (data_sheet), grid);
790   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
791 }
792
793
794 static void
795 set_font_recursively (GtkWidget *w, gpointer data)
796 {
797   PangoFontDescription *font_desc = data;
798
799   gtk_widget_override_font (w, font_desc);
800
801   if ( GTK_IS_CONTAINER (w))
802     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
803 }
804
805 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
806 void
807 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
808 {
809   gchar *font_name;
810   set_font_recursively (GTK_WIDGET (de), font_desc);
811
812   if (de->font)
813     pango_font_description_free (de->font);
814   de->font = pango_font_description_copy (font_desc);
815   font_name = pango_font_description_to_string (de->font);
816
817   psppire_conf_set_string (psppire_conf_new (),
818                            "Data Editor", "font",
819                            font_name);
820
821 }
822
823 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
824    If SPLIT is FALSE, un-splits it into a single pane. */
825 void
826 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
827 {
828   GtkTreeViewGridLines grid_lines;
829   gboolean labels;
830
831   if (split == de->split)
832     return;
833
834
835   grid_lines = pspp_sheet_view_get_grid_lines (
836     PSPP_SHEET_VIEW (de->data_sheets[0]));
837   labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
838                                                   de->data_sheets[0]));
839
840   disconnect_data_sheets (de);
841   if (de->old_vbox_widget)
842     g_object_unref (de->old_vbox_widget);
843   de->old_vbox_widget = de->datasheet_vbox_widget;
844   g_object_ref (de->old_vbox_widget);
845   /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
846         (currently it seems to provoke an error if I do that.  
847         I don't know why. */
848   gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
849
850   if (split)
851     de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
852   else
853     de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
854
855   psppire_data_editor_refresh_model (de);
856
857   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
858                       TRUE, TRUE, 0);
859   gtk_widget_show_all (de->vbox);
860
861   if (de->font)
862     set_font_recursively (GTK_WIDGET (de), de->font);
863
864   de->split = split;
865   g_object_notify (G_OBJECT (de), "split");
866   psppire_data_editor_update_ui_manager (de);
867 }
868
869 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
870    visible and selected in the active view in DE. */
871 void
872 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
873 {
874   PsppireDataSheet *data_sheet;
875
876   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
877     {
878     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
879       data_sheet = psppire_data_editor_get_active_data_sheet (de);
880       psppire_data_sheet_goto_variable (data_sheet, dict_index);
881       break;
882
883     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
884       psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
885                                        dict_index);
886       break;
887     }
888 }
889
890 /* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
891    is the only data sheet.  If DE is in split mode (showing four data sheets),
892    this is the focused data sheet or, if none is focused, the data sheet with
893    selected cells or, if none has selected cells, the upper-left data sheet. */
894 PsppireDataSheet *
895 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
896 {
897   if (de->split)
898     {
899       PsppireDataSheet *data_sheet;
900       GtkWidget *scroller;
901       int i;
902
903       /* If one of the datasheet's scrollers is focused, choose that one. */
904       scroller = gtk_container_get_focus_child (
905         GTK_CONTAINER (de->datasheet_vbox_widget));
906       if (scroller != NULL)
907         return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
908
909       /* Otherwise if there's a nonempty selection in some data sheet, choose
910          that one. */
911       FOR_EACH_DATA_SHEET (data_sheet, i, de)
912         {
913           PsppSheetSelection *selection;
914
915           selection = pspp_sheet_view_get_selection (
916             PSPP_SHEET_VIEW (data_sheet));
917           if (pspp_sheet_selection_count_selected_rows (selection)
918               && pspp_sheet_selection_count_selected_columns (selection))
919             return data_sheet;
920         }
921     }
922
923   return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
924 }
925
926 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
927    manager to display menu items and toolbar items specific to DE's current
928    page and data sheet.
929
930    DE's toplevel widget can watch for changes by connecting to DE's
931    notify::ui-manager signal. */
932 GtkUIManager *
933 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
934 {
935   psppire_data_editor_update_ui_manager (de);
936   return de->ui_manager;
937 }
938
939 static void
940 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
941 {
942   PsppireDataSheet *data_sheet;
943   GtkUIManager *ui_manager;
944
945   ui_manager = NULL;
946
947   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
948     {
949     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
950       data_sheet = psppire_data_editor_get_active_data_sheet (de);
951       if (data_sheet != NULL)
952         ui_manager = psppire_data_sheet_get_ui_manager (data_sheet);
953       else
954         {
955           /* This happens transiently in psppire_data_editor_split_window(). */
956         }
957       break;
958
959     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
960       ui_manager = psppire_var_sheet_get_ui_manager (
961         PSPPIRE_VAR_SHEET (de->var_sheet));
962       break;
963
964     default:
965       /* This happens transiently in psppire_data_editor_init(). */
966       break;
967     }
968
969   if (ui_manager != de->ui_manager)
970     {
971       if (de->ui_manager)
972         g_object_unref (de->ui_manager);
973       if (ui_manager)
974         g_object_ref (ui_manager);
975       de->ui_manager = ui_manager;
976
977       g_object_notify (G_OBJECT (de), "ui-manager");
978     }
979 }