Merge remote-tracking branch 'origin/master' into sheet
[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/psppire-data-store.h"
30 #include "ui/gui/psppire-value-entry.h"
31 #include "ui/gui/psppire-conf.h"
32 #include "ui/gui/psppire-var-sheet-header.h"
33
34 #include "value-variant.h"
35
36
37 #include "ui/gui/efficient-sheet/jmd-sheet.h"
38 #include "ui/gui/efficient-sheet/jmd-sheet-body.h"
39
40 #include <gettext.h>
41 #define _(msgid) gettext (msgid)
42
43
44 static GtkCellRenderer *
45 create_spin_renderer (GType type)
46 {
47   GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
48       
49   GtkAdjustment *adj = gtk_adjustment_new (0,
50                                            0, G_MAXDOUBLE,
51                                            1, 1,
52                                            0);
53   g_object_set (r,
54                 "adjustment", adj,
55                 NULL);
56   
57   return r;
58 }
59
60 static GtkCellRenderer *
61 create_combo_renderer (GType type)
62 {
63   GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
64   
65   GEnumClass *ec = g_type_class_ref (type);
66   
67   const GEnumValue *ev ;
68   for (ev = ec->values; ev->value_name; ++ev)
69     {
70       GtkTreeIter iter;
71
72       gtk_list_store_append (list_store, &iter);
73
74       gtk_list_store_set (list_store, &iter,
75                           0, ev->value,
76                           1, gettext (ev->value_nick),
77                           -1);
78     }
79
80   GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
81
82   g_object_set (r,
83                 "model", list_store,
84                 "text-column", 1,
85                 "has-entry", TRUE,
86                 NULL);
87
88   return r;
89 }
90
91 GtkCellRenderer *xx ;
92 GtkCellRenderer *column_width_renderer ;
93 GtkCellRenderer *measure_renderer ;
94 GtkCellRenderer *alignment_renderer ;
95
96
97
98 static GtkCellRenderer *
99 select_renderer_func (gint col, gint row, GType type)
100 {
101   if (!xx)
102     xx = create_spin_renderer (type);
103
104   if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
105     column_width_renderer = create_combo_renderer (type);
106
107   if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
108     measure_renderer = create_combo_renderer (type);
109
110   if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
111     alignment_renderer = create_combo_renderer (type);
112
113   switch  (col)
114     {
115     case DICT_TVM_COL_WIDTH:
116     case DICT_TVM_COL_DECIMAL:
117     case DICT_TVM_COL_COLUMNS:
118       return xx;
119       
120     case DICT_TVM_COL_ALIGNMENT:
121       return alignment_renderer;
122
123     case DICT_TVM_COL_MEASURE:
124       return measure_renderer;
125       
126     case DICT_TVM_COL_ROLE:
127       return column_width_renderer;
128     }
129   
130   return NULL;
131 }
132
133
134 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
135 static void psppire_data_editor_init                (PsppireDataEditor      *de);
136
137 static void disconnect_data_sheets (PsppireDataEditor *);
138 static void refresh_entry (PsppireDataEditor *);
139
140 GType
141 psppire_data_editor_get_type (void)
142 {
143   static GType de_type = 0;
144
145   if (!de_type)
146     {
147       static const GTypeInfo de_info =
148       {
149         sizeof (PsppireDataEditorClass),
150         NULL, /* base_init */
151         NULL, /* base_finalize */
152         (GClassInitFunc) psppire_data_editor_class_init,
153         NULL, /* class_finalize */
154         NULL, /* class_data */
155         sizeof (PsppireDataEditor),
156         0,
157         (GInstanceInitFunc) psppire_data_editor_init,
158       };
159
160       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
161                                         &de_info, 0);
162     }
163
164   return de_type;
165 }
166
167 static GObjectClass * parent_class = NULL;
168
169 static void
170 psppire_data_editor_dispose (GObject *obj)
171 {
172   PsppireDataEditor *de = (PsppireDataEditor *) obj;
173
174   disconnect_data_sheets (de);
175
176   if (de->data_store)
177     {
178       g_object_unref (de->data_store);
179       de->data_store = NULL;
180     }
181
182   if (de->dict)
183     {
184       g_object_unref (de->dict);
185       de->dict = NULL;
186     }
187
188   if (de->font != NULL)
189     {
190       pango_font_description_free (de->font);
191       de->font = NULL;
192     }
193
194   /* Chain up to the parent class */
195   G_OBJECT_CLASS (parent_class)->dispose (obj);
196 }
197
198 enum
199   {
200     PROP_0,
201     PROP_DATA_STORE,
202     PROP_DICTIONARY,
203     PROP_VALUE_LABELS,
204     PROP_SPLIT_WINDOW
205   };
206
207 static void
208 psppire_data_editor_refresh_model (PsppireDataEditor *de)
209 {
210 }
211
212 static void
213 change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
214 {
215   /* Return the IDXth variable */
216   struct variable *var =  psppire_dict_get_variable (dict, row);
217
218   switch (col)
219     {
220     case DICT_TVM_COL_NAME:
221       dict_rename_var (dict->dict, var, g_value_get_string (value));
222       break;
223     case DICT_TVM_COL_LABEL:
224       var_set_label (var, g_value_get_string (value));
225       break;
226     case DICT_TVM_COL_COLUMNS:
227       var_set_display_width (var, g_value_get_int (value));
228       break;
229     case DICT_TVM_COL_MEASURE:
230       var_set_measure (var, g_value_get_enum (value));
231       break;
232     case DICT_TVM_COL_ALIGNMENT:
233       var_set_alignment (var, g_value_get_enum (value));
234       break;
235     case DICT_TVM_COL_ROLE:
236       var_set_role (var, g_value_get_enum (value));
237       break;
238     default:
239       g_message ("Changing col %d of var sheet not yet supported", col);
240       break;
241     }
242 }
243
244 static void
245 change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
246 {
247   const struct variable *var = psppire_dict_get_variable (store->dict, col);
248
249   if (NULL == var)
250     return;
251
252   union value v;
253
254   GVariant *vrnt = g_value_get_variant (value);
255   
256   value_variant_get (&v, vrnt);
257   
258   psppire_data_store_set_value (store, row, var, &v);
259
260   value_destroy_from_variant (&v, vrnt);
261 }
262
263 static void
264 psppire_data_editor_set_property (GObject         *object,
265                                   guint            prop_id,
266                                   const GValue    *value,
267                                   GParamSpec      *pspec)
268 {
269   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
270
271   switch (prop_id)
272     {
273     case PROP_SPLIT_WINDOW:
274       psppire_data_editor_split_window (de, g_value_get_boolean (value));
275       break;
276     case PROP_DATA_STORE:
277       if ( de->data_store)
278         {
279           g_signal_handlers_disconnect_by_func (de->data_store,
280                                                 G_CALLBACK (refresh_entry),
281                                                 de);
282           g_object_unref (de->data_store);
283         }
284
285       de->data_store = g_value_get_pointer (value);
286       g_object_ref (de->data_store);
287       g_print ("NEW STORE\n");
288
289       g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
290       psppire_data_editor_refresh_model (de);
291
292       g_signal_connect_swapped (de->data_sheet, "value-changed",
293                                 G_CALLBACK (change_data_value), de->data_store);
294       
295       g_signal_connect_swapped (de->data_store, "case-changed",
296                                 G_CALLBACK (refresh_entry), de);
297
298       break;
299     case PROP_DICTIONARY:
300       if (de->dict)
301         g_object_unref (de->dict);
302       de->dict = g_value_get_pointer (value);
303       g_object_ref (de->dict);
304
305       g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
306       g_object_set (de->var_sheet, "data-model", de->dict, NULL);
307       g_signal_connect_swapped (de->var_sheet, "value-changed",
308                                 G_CALLBACK (change_var_property), de->dict);
309
310       break;
311     case PROP_VALUE_LABELS:
312       break;
313
314     default:
315       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
316       break;
317     };
318 }
319
320 static void
321 psppire_data_editor_get_property (GObject         *object,
322                                   guint            prop_id,
323                                   GValue          *value,
324                                   GParamSpec      *pspec)
325 {
326   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
327
328   switch (prop_id)
329     {
330     case PROP_SPLIT_WINDOW:
331       g_value_set_boolean (value, de->split);
332       break;
333     case PROP_DATA_STORE:
334       g_value_set_pointer (value, de->data_store);
335       break;
336     case PROP_DICTIONARY:
337       g_value_set_pointer (value, de->dict);
338       break;
339     case PROP_VALUE_LABELS:
340       break;
341     default:
342       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
343       break;
344     }
345 }
346
347 static void
348 psppire_data_editor_switch_page (GtkNotebook     *notebook,
349                                  GtkWidget *w,
350                                  guint            page_num)
351 {
352   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
353
354 }
355
356 static void
357 psppire_data_editor_set_focus_child (GtkContainer *container,
358                                      GtkWidget    *widget)
359 {
360   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
361
362 }
363
364 static void
365 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
366 {
367   GParamSpec *data_store_spec ;
368   GParamSpec *dict_spec ;
369   GParamSpec *value_labels_spec;
370   GParamSpec *split_window_spec;
371
372   GObjectClass *object_class = G_OBJECT_CLASS (klass);
373   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
374   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
375
376   parent_class = g_type_class_peek_parent (klass);
377
378   object_class->dispose = psppire_data_editor_dispose;
379   object_class->set_property = psppire_data_editor_set_property;
380   object_class->get_property = psppire_data_editor_get_property;
381
382   container_class->set_focus_child = psppire_data_editor_set_focus_child;
383
384   notebook_class->switch_page = psppire_data_editor_switch_page;
385
386   data_store_spec =
387     g_param_spec_pointer ("data-store",
388                           "Data Store",
389                           "A pointer to the data store associated with this editor",
390                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
391
392   g_object_class_install_property (object_class,
393                                    PROP_DATA_STORE,
394                                    data_store_spec);
395
396   dict_spec =
397     g_param_spec_pointer ("dictionary",
398                           "Dictionary",
399                           "A pointer to the dictionary associated with this editor",
400                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
401
402   g_object_class_install_property (object_class,
403                                    PROP_DICTIONARY,
404                                    dict_spec);
405
406   value_labels_spec =
407     g_param_spec_boolean ("value-labels",
408                          "Value Labels",
409                          "Whether or not the data sheet should display labels instead of values",
410                           FALSE,
411                          G_PARAM_WRITABLE | G_PARAM_READABLE);
412
413   g_object_class_install_property (object_class,
414                                    PROP_VALUE_LABELS,
415                                    value_labels_spec);
416
417
418   split_window_spec =
419     g_param_spec_boolean ("split",
420                           "Split Window",
421                           "True iff the data sheet is split",
422                           FALSE,
423                           G_PARAM_READABLE | G_PARAM_WRITABLE);
424
425   g_object_class_install_property (object_class,
426                                    PROP_SPLIT_WINDOW,
427                                    split_window_spec);
428
429 }
430
431
432 static void
433 on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
434                                  PsppireDataEditor *de)
435 {
436   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
437                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
438
439   jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
440 }
441
442
443 static void
444 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
445                                  PsppireDataEditor *de)
446 {
447   
448   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
449                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
450
451   jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
452 }
453
454
455
456 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
457    active cell or cells. */
458 static void
459 refresh_entry (PsppireDataEditor *de)
460 {
461   g_print ("%s\n", __FUNCTION__);
462 }
463
464 static void
465 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
466 {
467 }
468
469
470 static void
471 disconnect_data_sheets (PsppireDataEditor *de)
472 {
473 }
474
475 /* Called when the active cell or the selection in the data sheet changes */
476 static void
477 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
478 {
479   gchar *ref_cell_text = NULL;
480
481   gint n_cases = abs (sel->end_y - sel->start_y) + 1;
482   gint n_vars = abs (sel->end_x - sel->start_x) + 1;
483
484   if (n_cases == 1 && n_vars == 1)
485     {
486       /* A single cell is selected */
487       const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
488
489       if (var)
490         ref_cell_text = g_strdup_printf (_("%d : %s"),
491                                          sel->start_y + 1, var_get_name (var));
492     }
493   else
494     {
495       struct string s;
496
497       /* The glib string library does not understand the ' printf modifier
498          on all platforms, but the "struct string" library does (because
499          Gnulib fixes that problem), so use the latter.  */
500       ds_init_empty (&s);
501       ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
502                      n_cases);
503       ds_put_byte (&s, ' ');
504       ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
505       ds_put_byte (&s, ' ');
506       ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
507                                    n_vars),
508                      n_vars);
509       ref_cell_text = ds_steal_cstr (&s);
510     }
511   
512   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
513                        ref_cell_text ? ref_cell_text : "");
514   
515   g_free (ref_cell_text);
516 }
517
518
519 static void set_font_recursively (GtkWidget *w, gpointer data);
520
521 gchar *myconvfunc (GtkTreeModel *m, gint col, gint row, const GValue *v);
522 void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in, GValue *out);
523
524
525 static void
526 psppire_data_editor_init (PsppireDataEditor *de)
527 {
528   GtkWidget *hbox;
529   gchar *fontname = NULL;
530
531   de->font = NULL;
532   de->old_vbox_widget = NULL;
533
534   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
535
536   de->cell_ref_label = gtk_label_new ("");
537   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
538   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
539
540   de->datum_entry = psppire_value_entry_new ();
541   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
542                     "activate", G_CALLBACK (on_datum_entry_activate), de);
543
544   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
545   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
546   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
547
548   de->split = FALSE;
549   de->data_sheet = jmd_sheet_new ();
550   jmd_sheet_body_set_conversion_func
551     (JMD_SHEET_BODY (JMD_SHEET(de->data_sheet)->selected_body),
552      myconvfunc, myreversefunc);
553                                       
554   GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
555   gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
556   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
557   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
558   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
559
560
561   g_signal_connect_swapped (de->data_sheet, "selection-changed",
562                     G_CALLBACK (on_data_selection_change), de);
563   
564   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
565                             gtk_label_new_with_mnemonic (_("Data View")));
566
567   gtk_widget_show_all (de->vbox);
568
569   de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
570
571   PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
572   
573   g_object_set (de->var_sheet,
574                 "hmodel", vsh,
575                 "select-renderer-func", select_renderer_func,
576                 NULL);
577
578   
579   GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
580   gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
581   
582   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
583                             gtk_label_new_with_mnemonic (_("Variable View")));
584
585   gtk_widget_show_all (de->var_sheet);
586   
587   g_signal_connect (de->var_sheet, "row-header-double-clicked",
588                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
589
590   g_signal_connect (de->data_sheet, "column-header-double-clicked",
591                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
592
593   g_object_set (de, "can-focus", FALSE, NULL);
594
595   if (psppire_conf_get_string (psppire_conf_new (),
596                            "Data Editor", "font",
597                                 &fontname) )
598     {
599       de->font = pango_font_description_from_string (fontname);
600       g_free (fontname);
601       set_font_recursively (GTK_WIDGET (de), de->font);
602     }
603
604 }
605
606 GtkWidget*
607 psppire_data_editor_new (PsppireDict *dict,
608                          PsppireDataStore *data_store)
609 {
610   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
611                         "dictionary",  dict,
612                         "data-store",  data_store,
613                         NULL);
614 }
615 \f
616 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
617    sheet(s) and variable sheet. */
618 void
619 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
620 {
621   g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
622   g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
623 }
624
625
626 static void
627 set_font_recursively (GtkWidget *w, gpointer data)
628 {
629   PangoFontDescription *font_desc = data;
630
631   gtk_widget_override_font (w, font_desc);
632
633   if ( GTK_IS_CONTAINER (w))
634     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
635 }
636
637 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
638 void
639 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
640 {
641   gchar *font_name;
642   set_font_recursively (GTK_WIDGET (de), font_desc);
643
644   if (de->font)
645     pango_font_description_free (de->font);
646   de->font = pango_font_description_copy (font_desc);
647   font_name = pango_font_description_to_string (de->font);
648
649   psppire_conf_set_string (psppire_conf_new (),
650                            "Data Editor", "font",
651                            font_name);
652
653 }
654
655 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
656    If SPLIT is FALSE, un-splits it into a single pane. */
657 void
658 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
659 {
660   if (split == de->split)
661     return;
662
663   disconnect_data_sheets (de);
664
665   psppire_data_editor_refresh_model (de);
666
667   gtk_widget_show_all (de->vbox);
668
669   if (de->font)
670     set_font_recursively (GTK_WIDGET (de), de->font);
671
672   de->split = split;
673   g_object_notify (G_OBJECT (de), "split");
674 }
675
676 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
677    visible and selected in the active view in DE. */
678 void
679 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
680 {
681 }
682
683 #if SHEET_MERGE
684 /* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
685    is the only data sheet.  If DE is in split mode (showing four data sheets),
686    this is the focused data sheet or, if none is focused, the data sheet with
687    selected cells or, if none has selected cells, the upper-left data sheet. */
688 PsppireDataSheet *
689 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
690 {
691   if (de->split)
692     {
693       PsppireDataSheet *data_sheet;
694       GtkWidget *scroller;
695       int i;
696
697       /* If one of the datasheet's scrollers is focused, choose that one. */
698       scroller = gtk_container_get_focus_child (
699         GTK_CONTAINER (de->datasheet_vbox_widget));
700       if (scroller != NULL)
701         return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
702
703       /* Otherwise if there's a nonempty selection in some data sheet, choose
704          that one. */
705       FOR_EACH_DATA_SHEET (data_sheet, i, de)
706         {
707           PsppSheetSelection *selection;
708
709           selection = pspp_sheet_view_get_selection (
710             PSPP_SHEET_VIEW (data_sheet));
711           if (pspp_sheet_selection_count_selected_rows (selection)
712               && pspp_sheet_selection_count_selected_columns (selection))
713             return data_sheet;
714         }
715     }
716
717   return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
718 }
719 #endif