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