Fix memory leak
[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
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   /* Chain up to the parent class */
107   G_OBJECT_CLASS (parent_class)->dispose (obj);
108 }
109
110 enum
111   {
112     PROP_0,
113     PROP_DATA_STORE,
114     PROP_DICTIONARY,
115     PROP_VALUE_LABELS,
116     PROP_SPLIT_WINDOW
117   };
118
119 static void
120 psppire_data_editor_refresh_model (PsppireDataEditor *de)
121 {
122   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
123   PsppireDataSheet *data_sheet;
124   int i;
125
126   FOR_EACH_DATA_SHEET (data_sheet, i, de)
127     psppire_data_sheet_set_data_store (data_sheet, de->data_store);
128   psppire_var_sheet_set_dictionary (var_sheet, de->dict);
129 }
130
131 static void
132 psppire_data_editor_set_property (GObject         *object,
133                                   guint            prop_id,
134                                   const GValue    *value,
135                                   GParamSpec      *pspec)
136 {
137   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
138   PsppireDataSheet *data_sheet;
139   int i;
140
141   switch (prop_id)
142     {
143     case PROP_SPLIT_WINDOW:
144       psppire_data_editor_split_window (de, g_value_get_boolean (value));
145       break;
146     case PROP_DATA_STORE:
147       if ( de->data_store)
148         {
149           g_signal_handlers_disconnect_by_func (de->data_store,
150                                                 G_CALLBACK (refresh_entry),
151                                                 de);
152           g_object_unref (de->data_store);
153         }
154
155       de->data_store = g_value_get_pointer (value);
156       g_object_ref (de->data_store);
157       psppire_data_editor_refresh_model (de);
158
159       g_signal_connect_swapped (de->data_store, "case-changed",
160                                 G_CALLBACK (refresh_entry), de);
161
162       break;
163     case PROP_DICTIONARY:
164       if (de->dict)
165         g_object_unref (de->dict);
166       de->dict = g_value_get_pointer (value);
167       g_object_ref (de->dict);
168
169       psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
170                                         de->dict);
171       break;
172     case PROP_VALUE_LABELS:
173       FOR_EACH_DATA_SHEET (data_sheet, i, de)
174         psppire_data_sheet_set_value_labels (data_sheet,
175                                           g_value_get_boolean (value));
176       break;
177
178     default:
179       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
180       break;
181     };
182 }
183
184 static void
185 psppire_data_editor_get_property (GObject         *object,
186                                   guint            prop_id,
187                                   GValue          *value,
188                                   GParamSpec      *pspec)
189 {
190   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
191
192   switch (prop_id)
193     {
194     case PROP_SPLIT_WINDOW:
195       g_value_set_boolean (value, de->split);
196       break;
197     case PROP_DATA_STORE:
198       g_value_set_pointer (value, de->data_store);
199       break;
200     case PROP_DICTIONARY:
201       g_value_set_pointer (value, de->dict);
202       break;
203     case PROP_VALUE_LABELS:
204       g_value_set_boolean (value,
205                            psppire_data_sheet_get_value_labels (
206                              PSPPIRE_DATA_SHEET (de->data_sheets[0])));
207       break;
208     default:
209       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210       break;
211     }
212 }
213
214 static void
215 psppire_data_editor_switch_page (GtkNotebook     *notebook,
216                                  GtkWidget *w,
217                                  guint            page_num)
218 {
219   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
220
221 }
222
223 static void
224 psppire_data_editor_set_focus_child (GtkContainer *container,
225                                      GtkWidget    *widget)
226 {
227   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
228
229 }
230
231 static void
232 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
233 {
234   GParamSpec *data_store_spec ;
235   GParamSpec *dict_spec ;
236   GParamSpec *value_labels_spec;
237   GParamSpec *split_window_spec;
238
239   GObjectClass *object_class = G_OBJECT_CLASS (klass);
240   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
241   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
242
243   parent_class = g_type_class_peek_parent (klass);
244
245   object_class->dispose = psppire_data_editor_dispose;
246   object_class->set_property = psppire_data_editor_set_property;
247   object_class->get_property = psppire_data_editor_get_property;
248
249   container_class->set_focus_child = psppire_data_editor_set_focus_child;
250
251   notebook_class->switch_page = psppire_data_editor_switch_page;
252
253   data_store_spec =
254     g_param_spec_pointer ("data-store",
255                           "Data Store",
256                           "A pointer to the data store associated with this editor",
257                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
258
259   g_object_class_install_property (object_class,
260                                    PROP_DATA_STORE,
261                                    data_store_spec);
262
263   dict_spec =
264     g_param_spec_pointer ("dictionary",
265                           "Dictionary",
266                           "A pointer to the dictionary 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_DICTIONARY,
271                                    dict_spec);
272
273   value_labels_spec =
274     g_param_spec_boolean ("value-labels",
275                          "Value Labels",
276                          "Whether or not the data sheet should display labels instead of values",
277                           FALSE,
278                          G_PARAM_WRITABLE | G_PARAM_READABLE);
279
280   g_object_class_install_property (object_class,
281                                    PROP_VALUE_LABELS,
282                                    value_labels_spec);
283
284
285   split_window_spec =
286     g_param_spec_boolean ("split",
287                           "Split Window",
288                           "True iff the data sheet is split",
289                           FALSE,
290                           G_PARAM_READABLE | G_PARAM_WRITABLE);
291
292   g_object_class_install_property (object_class,
293                                    PROP_SPLIT_WINDOW,
294                                    split_window_spec);
295
296 }
297
298 static gboolean
299 on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
300                                   gint dict_index,
301                                   PsppireDataEditor *de)
302 {
303   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
304                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
305
306   psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
307                                    dict_index);
308
309   return TRUE;
310 }
311
312 static gboolean
313 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
314                                  PsppireDataEditor *de)
315 {
316   PsppireDataSheet *data_sheet;
317
318   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
319                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
320
321   data_sheet = psppire_data_editor_get_active_data_sheet (de);
322   psppire_data_sheet_goto_variable (data_sheet, dict_index);
323
324   return TRUE;
325 }
326
327 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
328    active cell or cells. */
329 static void
330 refresh_entry (PsppireDataEditor *de)
331 {
332   PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
333   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
334   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
335
336   gchar *ref_cell_text;
337   GList *selected_columns, *iter;
338   struct variable *var;
339   gint n_cases;
340   gint n_vars;
341
342   selected_columns = pspp_sheet_selection_get_selected_columns (selection);
343   n_vars = 0;
344   var = NULL;
345   for (iter = selected_columns; iter != NULL; iter = iter->next)
346     {
347       PsppSheetViewColumn *column = iter->data;
348       struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
349       if (v != NULL)
350         {
351           var = v;
352           n_vars++;
353         }
354     }
355   g_list_free (selected_columns);
356
357   n_cases = pspp_sheet_selection_count_selected_rows (selection);
358   if (n_cases > 0)
359     {
360       /* The final row is selectable but it isn't a case (it's just used to add
361          more cases), so don't count it. */
362       GtkTreePath *path;
363       gint case_count;
364
365       case_count = psppire_data_store_get_case_count (de->data_store);
366       path = gtk_tree_path_new_from_indices (case_count, -1);
367       if (pspp_sheet_selection_path_is_selected (selection, path))
368         n_cases--;
369       gtk_tree_path_free (path);
370     }
371
372   ref_cell_text = NULL;
373   if (n_cases == 1 && n_vars == 1)
374     {
375       PsppireValueEntry *value_entry = PSPPIRE_VALUE_ENTRY (de->datum_entry);
376       struct range_set *selected_rows;
377       gboolean show_value_labels;
378       union value value;
379       int width;
380       gint row;
381
382       selected_rows = pspp_sheet_selection_get_range_set (selection);
383       row = range_set_scan (selected_rows, 0);
384       range_set_destroy (selected_rows);
385
386       ref_cell_text = g_strdup_printf ("%d : %s", row + 1, var_get_name (var));
387
388       show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
389
390       psppire_value_entry_set_variable (value_entry, var);
391       psppire_value_entry_set_show_value_label (value_entry,
392                                                 show_value_labels);
393
394       width = var_get_width (var);
395       value_init (&value, width);
396       datasheet_get_value (de->data_store->datasheet,
397                            row, var_get_case_index (var), &value);
398       psppire_value_entry_set_value (value_entry, &value, width);
399       value_destroy (&value, width);
400
401       gtk_widget_set_sensitive (de->datum_entry, TRUE);
402     }
403   else
404     {
405       if (n_cases == 0 || n_vars == 0)
406         {
407           ref_cell_text = NULL;
408         }
409       else
410         {
411           struct string s;
412
413           /* The glib string library does not understand the ' printf modifier
414              on all platforms, but the "struct string" library does (because
415              Gnulib fixes that problem), so use the latter.  */
416           ds_init_empty (&s);
417           ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
418                          n_cases);
419           ds_put_byte (&s, ' ');
420           ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
421           ds_put_byte (&s, ' ');
422           ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
423                                        n_vars),
424                          n_vars);
425           ref_cell_text = ds_steal_cstr (&s);
426         }
427
428       psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry),
429                                         NULL);
430       gtk_entry_set_text (
431         GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))), "");
432       gtk_widget_set_sensitive (de->datum_entry, FALSE);
433     }
434
435   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
436                        ref_cell_text ? ref_cell_text : "");
437   g_free (ref_cell_text);
438 }
439
440 static void
441 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
442 {
443   PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
444   struct variable *var;
445   union value value;
446   int width;
447   gint row;
448
449   row = psppire_data_sheet_get_current_case (data_sheet);
450   var = psppire_data_sheet_get_current_variable (data_sheet);
451   if (row < 0 || !var)
452     return;
453
454   width = var_get_width (var);
455   value_init (&value, width);
456   if (psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
457                                      &value, width))
458     psppire_data_store_set_value (de->data_store, row, var, &value);
459   value_destroy (&value, width);
460 }
461
462 static void
463 on_data_sheet_selection_changed (PsppSheetSelection *selection,
464                                  PsppireDataEditor *de)
465 {
466   /* In a split view, ensure that only a single data sheet has a nonempty
467      selection.  */
468   if (de->split
469       && pspp_sheet_selection_count_selected_rows (selection)
470       && pspp_sheet_selection_count_selected_columns (selection))
471     {
472       PsppireDataSheet *ds;
473       int i;
474
475       FOR_EACH_DATA_SHEET (ds, i, de)
476         {
477           PsppSheetSelection *s;
478
479           s = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
480           if (s != selection)
481             pspp_sheet_selection_unselect_all (s);
482         }
483     }
484
485   refresh_entry (de);
486 }
487
488 /* Ensures that rows in the right-hand panes in the split view have the same
489    row height as the left-hand panes.  Otherwise, the rows in the right-hand
490    pane tend to be smaller, because the right-hand pane doesn't have buttons
491    for case numbers. */
492 static void
493 on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
494                                    GParamSpec *pspec,
495                                    PsppireDataEditor *de)
496 {
497   enum
498     {
499       TL = GTK_XPANED_TOP_LEFT,
500       TR = GTK_XPANED_TOP_RIGHT,
501       BL = GTK_XPANED_BOTTOM_LEFT,
502       BR = GTK_XPANED_BOTTOM_RIGHT
503     };
504
505   int fixed_height = pspp_sheet_view_get_fixed_height (PSPP_SHEET_VIEW (ds));
506
507   pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[TR]),
508                                     fixed_height);
509   pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[BR]),
510                                     fixed_height);
511 }
512
513 static void
514 disconnect_data_sheets (PsppireDataEditor *de)
515 {
516   PsppireDataSheet *ds;
517   int i;
518
519   FOR_EACH_DATA_SHEET (ds, i, de)
520     {
521       PsppSheetSelection *selection;
522
523       if (ds == NULL)
524         {
525           /* This can only happen if 'dispose' runs more than once. */
526           continue;
527         }
528
529       if (i == GTK_XPANED_TOP_LEFT)
530         g_signal_handlers_disconnect_by_func (
531           ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
532
533       g_signal_handlers_disconnect_by_func (
534         ds, G_CALLBACK (refresh_entry), de);
535       g_signal_handlers_disconnect_by_func (
536         ds, G_CALLBACK (on_data_sheet_var_double_clicked), de);
537
538       selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
539       g_signal_handlers_disconnect_by_func (
540         selection, G_CALLBACK (on_data_sheet_selection_changed), de);
541
542       de->data_sheets[i] = NULL;
543     }
544 }
545
546 static GtkWidget *
547 make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
548                  gboolean show_value_labels)
549 {
550   PsppSheetSelection *selection;
551   GtkWidget *ds;
552
553   ds = psppire_data_sheet_new ();
554   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
555   psppire_data_sheet_set_value_labels (PSPPIRE_DATA_SHEET (ds),
556                                        show_value_labels);
557
558   g_signal_connect_swapped (ds, "notify::value-labels",
559                             G_CALLBACK (refresh_entry), de);
560   g_signal_connect (ds, "var-double-clicked",
561                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
562
563   selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
564   g_signal_connect (selection, "changed",
565                     G_CALLBACK (on_data_sheet_selection_changed), de);
566
567   return ds;
568 }
569
570 static GtkWidget *
571 make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
572                        gboolean show_value_labels)
573 {
574   GtkWidget *data_sheet_scroller;
575
576   de->data_sheets[0] = make_data_sheet (de, grid_lines, show_value_labels);
577   de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = NULL;
578
579   /* Put data sheet in scroller. */
580   data_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
581   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_sheet_scroller),
582                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
583   gtk_container_add (GTK_CONTAINER (data_sheet_scroller), de->data_sheets[0]);
584
585   return data_sheet_scroller;
586 }
587
588 static GtkWidget *
589 make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
590                       gboolean show_value_labels)
591 {
592   /* Panes, in the order in which we want to create them. */
593   enum
594     {
595       TL,                       /* top left */
596       TR,                       /* top right */
597       BL,                       /* bottom left */
598       BR                        /* bottom right */
599     };
600
601   PsppSheetView *ds[4];
602   GtkXPaned *xpaned;
603   int i;
604
605   xpaned = GTK_XPANED (gtk_xpaned_new ());
606
607   for (i = 0; i < 4; i++)
608     {
609       GtkAdjustment *hadjust, *vadjust;
610       GtkPolicyType hpolicy, vpolicy;
611       GtkWidget *scroller;
612
613       de->data_sheets[i] = make_data_sheet (de, grid_lines, show_value_labels);
614       ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
615
616       if (i == BL)
617         hadjust = pspp_sheet_view_get_hadjustment (ds[TL]);
618       else if (i == BR)
619         hadjust = pspp_sheet_view_get_hadjustment (ds[TR]);
620       else
621         hadjust = NULL;
622
623       if (i == TR)
624         vadjust = pspp_sheet_view_get_vadjustment (ds[TL]);
625       else if (i == BR)
626         vadjust = pspp_sheet_view_get_vadjustment (ds[BL]);
627       else
628         vadjust = NULL;
629
630       scroller = gtk_scrolled_window_new (hadjust, vadjust);
631       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
632                                            GTK_SHADOW_ETCHED_IN);
633       hpolicy = i == TL || i == TR ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
634       vpolicy = i == TL || i == BL ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
635       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
636                                       hpolicy, vpolicy);
637       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ds[i]));
638
639       switch (i)
640         {
641         case TL:
642           gtk_xpaned_pack_top_left (xpaned, scroller, TRUE, TRUE);
643           break;
644
645         case TR:
646           gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
647           break;
648
649         case BL:
650           gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
651           break;
652
653         case BR:
654           gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
655           break;
656
657         default:
658           g_warn_if_reached ();
659         }
660     }
661
662   /* Bottom sheets don't display variable names. */
663   pspp_sheet_view_set_headers_visible (ds[BL], FALSE);
664   pspp_sheet_view_set_headers_visible (ds[BR], FALSE);
665
666   /* Right sheets don't display case numbers. */
667   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[TR]), FALSE);
668   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[BR]), FALSE);
669
670   g_signal_connect (ds[TL], "notify::fixed-height",
671                     G_CALLBACK (on_data_sheet_fixed_height_notify), de);
672
673   return GTK_WIDGET (xpaned);
674 }
675
676 static void set_font_recursively (GtkWidget *w, gpointer data);
677
678 static void
679 psppire_data_editor_init (PsppireDataEditor *de)
680 {
681   GtkWidget *var_sheet_scroller;
682   GtkWidget *hbox;
683   gchar *fontname = NULL;
684
685   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (de));
686   gtk_style_context_add_class (context, "psppire-data-editor");
687   
688   de->font = NULL;
689   de->old_vbox_widget = NULL;
690
691   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
692
693   de->cell_ref_label = gtk_label_new ("");
694   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
695   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
696
697   de->datum_entry = psppire_value_entry_new ();
698   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
699                     "activate", G_CALLBACK (on_datum_entry_activate), de);
700
701   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
702   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
703   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
704
705   de->split = FALSE;
706   de->datasheet_vbox_widget
707     = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
708
709   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
710   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
711   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
712                       TRUE, TRUE, 0);
713
714   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
715                             gtk_label_new_with_mnemonic (_("Data View")));
716
717   gtk_widget_show_all (de->vbox);
718
719   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
720   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
721                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
722   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
723   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
724                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
725   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
726   gtk_widget_show_all (var_sheet_scroller);
727   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
728                             gtk_label_new_with_mnemonic (_("Variable View")));
729
730   g_signal_connect (de->var_sheet, "var-double-clicked",
731                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
732
733   g_object_set (de, "can-focus", FALSE, NULL);
734
735   if (psppire_conf_get_string (psppire_conf_new (),
736                            "Data Editor", "font",
737                                 &fontname) )
738     {
739       de->font = pango_font_description_from_string (fontname);
740       g_free (fontname);
741       set_font_recursively (GTK_WIDGET (de), de->font);
742     }
743
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
780   GtkCssProvider *cssp = gtk_css_provider_get_default ();
781
782   gchar *str = pango_font_description_to_string (font_desc);
783   gchar *css = g_strdup_printf (".psppire-data-editor {font: %s }", str);
784   g_free (str);
785                                                                                    
786   if (!gtk_css_provider_load_from_data (cssp, css, -1, NULL))
787     g_warning ("Failed to load font css \"%s\"", css);
788
789   g_free (css);
790
791   if ( GTK_IS_CONTAINER (w))
792     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
793 }
794
795 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
796 void
797 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
798 {
799   gchar *font_name;
800   set_font_recursively (GTK_WIDGET (de), font_desc);
801
802   if (de->font)
803     pango_font_description_free (de->font);
804   de->font = pango_font_description_copy (font_desc);
805   font_name = pango_font_description_to_string (de->font);
806
807   psppire_conf_set_string (psppire_conf_new (),
808                            "Data Editor", "font",
809                            font_name);
810   g_free (font_name);
811 }
812
813 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
814    If SPLIT is FALSE, un-splits it into a single pane. */
815 void
816 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
817 {
818   GtkTreeViewGridLines grid_lines;
819   gboolean labels;
820
821   if (split == de->split)
822     return;
823
824
825   grid_lines = pspp_sheet_view_get_grid_lines (
826     PSPP_SHEET_VIEW (de->data_sheets[0]));
827   labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
828                                                   de->data_sheets[0]));
829
830   disconnect_data_sheets (de);
831   if (de->old_vbox_widget)
832     g_object_unref (de->old_vbox_widget);
833   de->old_vbox_widget = de->datasheet_vbox_widget;
834   g_object_ref (de->old_vbox_widget);
835   /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
836         (currently it seems to provoke an error if I do that.  
837         I don't know why. */
838   gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
839
840   if (split)
841     de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
842   else
843     de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
844
845   psppire_data_editor_refresh_model (de);
846
847   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
848                       TRUE, TRUE, 0);
849   gtk_widget_show_all (de->vbox);
850
851   if (de->font)
852     set_font_recursively (GTK_WIDGET (de), de->font);
853
854   de->split = split;
855   g_object_notify (G_OBJECT (de), "split");
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 }