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