80c8df7b2c96818ee80803cc9f69f2073ef5dd74
[pspp] / src / ui / gui / psppire-data-editor.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2016,
3    2017 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 #include "ui/gui/psppire-data-editor.h"
21
22 #include <gtk/gtk.h>
23 #include <gtk-contrib/gtkxpaned.h>
24
25 #include "data/datasheet.h"
26 #include "data/value-labels.h"
27 #include "libpspp/range-set.h"
28 #include "libpspp/str.h"
29 #include "ui/gui/helper.h"
30 #include "ui/gui/psppire-data-store.h"
31 #include "ui/gui/psppire-data-window.h"
32 #include "ui/gui/psppire-value-entry.h"
33 #include "ui/gui/psppire-conf.h"
34 #include "ui/gui/psppire-var-sheet-header.h"
35
36 #include "value-variant.h"
37
38
39 #include "ui/gui/efficient-sheet/jmd-sheet.h"
40 #include "ui/gui/efficient-sheet/jmd-sheet-body.h"
41
42 #include <gettext.h>
43 #define _(msgid) gettext (msgid)
44
45
46 static GtkCellRenderer *
47 create_spin_renderer (GType type)
48 {
49   GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
50
51   GtkAdjustment *adj = gtk_adjustment_new (0,
52                                            0, G_MAXDOUBLE,
53                                            1, 1,
54                                            0);
55   g_object_set (r,
56                 "adjustment", adj,
57                 NULL);
58
59   return r;
60 }
61
62 static GtkCellRenderer *
63 create_combo_renderer (GType type)
64 {
65   GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
66
67   GEnumClass *ec = g_type_class_ref (type);
68
69   const GEnumValue *ev ;
70   for (ev = ec->values; ev->value_name; ++ev)
71     {
72       GtkTreeIter iter;
73
74       gtk_list_store_append (list_store, &iter);
75
76       gtk_list_store_set (list_store, &iter,
77                           0, ev->value,
78                           1, gettext (ev->value_nick),
79                           -1);
80     }
81
82   GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
83
84   g_object_set (r,
85                 "model", list_store,
86                 "text-column", 1,
87                 "has-entry", TRUE,
88                 NULL);
89
90   return r;
91 }
92
93 GtkCellRenderer *xx ;
94 GtkCellRenderer *column_width_renderer ;
95 GtkCellRenderer *measure_renderer ;
96 GtkCellRenderer *alignment_renderer ;
97
98
99
100 static GtkCellRenderer *
101 select_renderer_func (gint col, gint row, GType type)
102 {
103   if (!xx)
104     xx = create_spin_renderer (type);
105
106   if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
107     column_width_renderer = create_combo_renderer (type);
108
109   if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
110     measure_renderer = create_combo_renderer (type);
111
112   if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
113     alignment_renderer = create_combo_renderer (type);
114
115   switch  (col)
116     {
117     case DICT_TVM_COL_WIDTH:
118     case DICT_TVM_COL_DECIMAL:
119     case DICT_TVM_COL_COLUMNS:
120       return xx;
121
122     case DICT_TVM_COL_ALIGNMENT:
123       return alignment_renderer;
124
125     case DICT_TVM_COL_MEASURE:
126       return measure_renderer;
127
128     case DICT_TVM_COL_ROLE:
129       return column_width_renderer;
130     }
131
132   return NULL;
133 }
134
135
136 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
137 static void psppire_data_editor_init                (PsppireDataEditor      *de);
138
139 static void disconnect_data_sheets (PsppireDataEditor *);
140 static void refresh_entry (PsppireDataEditor *);
141
142 GType
143 psppire_data_editor_get_type (void)
144 {
145   static GType de_type = 0;
146
147   if (!de_type)
148     {
149       static const GTypeInfo de_info =
150       {
151         sizeof (PsppireDataEditorClass),
152         NULL, /* base_init */
153         NULL, /* base_finalize */
154         (GClassInitFunc) psppire_data_editor_class_init,
155         NULL, /* class_finalize */
156         NULL, /* class_data */
157         sizeof (PsppireDataEditor),
158         0,
159         (GInstanceInitFunc) psppire_data_editor_init,
160       };
161
162       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
163                                         &de_info, 0);
164     }
165
166   return de_type;
167 }
168
169 static GObjectClass * parent_class = NULL;
170
171 static void
172 psppire_data_editor_dispose (GObject *obj)
173 {
174   PsppireDataEditor *de = (PsppireDataEditor *) obj;
175
176   disconnect_data_sheets (de);
177
178   if (de->data_store)
179     {
180       g_object_unref (de->data_store);
181       de->data_store = NULL;
182     }
183
184   if (de->dict)
185     {
186       g_object_unref (de->dict);
187       de->dict = NULL;
188     }
189
190   if (de->font != NULL)
191     {
192       pango_font_description_free (de->font);
193       de->font = NULL;
194     }
195
196   /* Chain up to the parent class */
197   G_OBJECT_CLASS (parent_class)->dispose (obj);
198 }
199
200 enum
201   {
202     PROP_0,
203     PROP_DATA_STORE,
204     PROP_DICTIONARY,
205     PROP_VALUE_LABELS,
206     PROP_SPLIT_WINDOW
207   };
208
209 static void
210 psppire_data_editor_refresh_model (PsppireDataEditor *de)
211 {
212 }
213
214 static void
215 change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
216 {
217   /* Return the IDXth variable */
218   struct variable *var =  psppire_dict_get_variable (dict, row);
219
220   switch (col)
221     {
222     case DICT_TVM_COL_NAME:
223       dict_rename_var (dict->dict, var, g_value_get_string (value));
224       break;
225     case DICT_TVM_COL_LABEL:
226       var_set_label (var, g_value_get_string (value));
227       break;
228     case DICT_TVM_COL_COLUMNS:
229       var_set_display_width (var, g_value_get_int (value));
230       break;
231     case DICT_TVM_COL_MEASURE:
232       var_set_measure (var, g_value_get_enum (value));
233       break;
234     case DICT_TVM_COL_ALIGNMENT:
235       var_set_alignment (var, g_value_get_enum (value));
236       break;
237     case DICT_TVM_COL_ROLE:
238       var_set_role (var, g_value_get_enum (value));
239       break;
240     default:
241       g_message ("Changing col %d of var sheet not yet supported", col);
242       break;
243     }
244 }
245
246 static void
247 change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
248 {
249   const struct variable *var = psppire_dict_get_variable (store->dict, col);
250
251   if (NULL == var)
252     return;
253
254   union value v;
255
256   GVariant *vrnt = g_value_get_variant (value);
257
258   value_variant_get (&v, vrnt);
259
260   psppire_data_store_set_value (store, row, var, &v);
261
262   value_destroy_from_variant (&v, vrnt);
263 }
264
265 static void
266 psppire_data_editor_set_property (GObject         *object,
267                                   guint            prop_id,
268                                   const GValue    *value,
269                                   GParamSpec      *pspec)
270 {
271   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
272
273   switch (prop_id)
274     {
275     case PROP_SPLIT_WINDOW:
276       psppire_data_editor_split_window (de, g_value_get_boolean (value));
277       break;
278     case PROP_DATA_STORE:
279       if ( de->data_store)
280         {
281           g_signal_handlers_disconnect_by_func (de->data_store,
282                                                 G_CALLBACK (refresh_entry),
283                                                 de);
284           g_object_unref (de->data_store);
285         }
286
287       de->data_store = g_value_get_pointer (value);
288       g_object_ref (de->data_store);
289       g_print ("NEW STORE\n");
290
291       g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
292       psppire_data_editor_refresh_model (de);
293
294       g_signal_connect_swapped (de->data_sheet, "value-changed",
295                                 G_CALLBACK (change_data_value), de->data_store);
296
297       g_signal_connect_swapped (de->data_store, "case-changed",
298                                 G_CALLBACK (refresh_entry), de);
299
300       break;
301     case PROP_DICTIONARY:
302       if (de->dict)
303         g_object_unref (de->dict);
304       de->dict = g_value_get_pointer (value);
305       g_object_ref (de->dict);
306
307       g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
308       g_object_set (de->var_sheet, "data-model", de->dict, NULL);
309       g_signal_connect_swapped (de->var_sheet, "value-changed",
310                                 G_CALLBACK (change_var_property), de->dict);
311
312       break;
313     case PROP_VALUE_LABELS:
314       break;
315
316     default:
317       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
318       break;
319     };
320 }
321
322 static void
323 psppire_data_editor_get_property (GObject         *object,
324                                   guint            prop_id,
325                                   GValue          *value,
326                                   GParamSpec      *pspec)
327 {
328   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
329
330   switch (prop_id)
331     {
332     case PROP_SPLIT_WINDOW:
333       g_value_set_boolean (value, de->split);
334       break;
335     case PROP_DATA_STORE:
336       g_value_set_pointer (value, de->data_store);
337       break;
338     case PROP_DICTIONARY:
339       g_value_set_pointer (value, de->dict);
340       break;
341     case PROP_VALUE_LABELS:
342       break;
343     default:
344       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
345       break;
346     }
347 }
348
349 static void
350 psppire_data_editor_switch_page (GtkNotebook     *notebook,
351                                  GtkWidget *w,
352                                  guint            page_num)
353 {
354   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
355
356 }
357
358 static void
359 psppire_data_editor_set_focus_child (GtkContainer *container,
360                                      GtkWidget    *widget)
361 {
362   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
363
364 }
365
366 static void
367 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
368 {
369   GParamSpec *data_store_spec ;
370   GParamSpec *dict_spec ;
371   GParamSpec *value_labels_spec;
372   GParamSpec *split_window_spec;
373
374   GObjectClass *object_class = G_OBJECT_CLASS (klass);
375   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
376   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
377
378   parent_class = g_type_class_peek_parent (klass);
379
380   object_class->dispose = psppire_data_editor_dispose;
381   object_class->set_property = psppire_data_editor_set_property;
382   object_class->get_property = psppire_data_editor_get_property;
383
384   container_class->set_focus_child = psppire_data_editor_set_focus_child;
385
386   notebook_class->switch_page = psppire_data_editor_switch_page;
387
388   data_store_spec =
389     g_param_spec_pointer ("data-store",
390                           "Data Store",
391                           "A pointer to the data store associated with this editor",
392                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
393
394   g_object_class_install_property (object_class,
395                                    PROP_DATA_STORE,
396                                    data_store_spec);
397
398   dict_spec =
399     g_param_spec_pointer ("dictionary",
400                           "Dictionary",
401                           "A pointer to the dictionary associated with this editor",
402                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
403
404   g_object_class_install_property (object_class,
405                                    PROP_DICTIONARY,
406                                    dict_spec);
407
408   value_labels_spec =
409     g_param_spec_boolean ("value-labels",
410                          "Value Labels",
411                          "Whether or not the data sheet should display labels instead of values",
412                           FALSE,
413                          G_PARAM_WRITABLE | G_PARAM_READABLE);
414
415   g_object_class_install_property (object_class,
416                                    PROP_VALUE_LABELS,
417                                    value_labels_spec);
418
419
420   split_window_spec =
421     g_param_spec_boolean ("split",
422                           "Split Window",
423                           "True iff the data sheet is split",
424                           FALSE,
425                           G_PARAM_READABLE | G_PARAM_WRITABLE);
426
427   g_object_class_install_property (object_class,
428                                    PROP_SPLIT_WINDOW,
429                                    split_window_spec);
430
431 }
432
433
434 static void
435 on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
436                                  PsppireDataEditor *de)
437 {
438   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
439                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
440
441   jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
442 }
443
444
445 static void
446 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
447                                  PsppireDataEditor *de)
448 {
449
450   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
451                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
452
453   jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
454 }
455
456
457
458 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
459    active cell or cells. */
460 static void
461 refresh_entry (PsppireDataEditor *de)
462 {
463   union value val;
464   gint row, col;
465   jmd_sheet_get_active_cell (JMD_SHEET (de->data_sheet), &col, &row);
466
467   const struct variable *var = psppire_dict_get_variable (de->dict, col);
468   psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry), var);
469
470   int width = var_get_width (var);
471   if (! psppire_data_store_get_value (PSPPIRE_DATA_STORE (de->data_store),
472                                       row, var, &val))
473     return;
474
475   psppire_value_entry_set_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
476                                  &val, width);
477   value_destroy (&val, width);
478 }
479
480 static void
481 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
482 {
483 }
484
485
486 static void
487 disconnect_data_sheets (PsppireDataEditor *de)
488 {
489 }
490
491 /* Called when the active cell or the selection in the data sheet changes */
492 static void
493 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
494 {
495   gchar *ref_cell_text = NULL;
496
497   gint n_cases = abs (sel->end_y - sel->start_y) + 1;
498   gint n_vars = abs (sel->end_x - sel->start_x) + 1;
499
500   if (n_cases == 1 && n_vars == 1)
501     {
502       /* A single cell is selected */
503       const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
504
505       if (var)
506         ref_cell_text = g_strdup_printf (_("%d : %s"),
507                                          sel->start_y + 1, var_get_name (var));
508     }
509   else
510     {
511       struct string s;
512
513       /* The glib string library does not understand the ' printf modifier
514          on all platforms, but the "struct string" library does (because
515          Gnulib fixes that problem), so use the latter.  */
516       ds_init_empty (&s);
517       ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
518                      n_cases);
519       ds_put_byte (&s, ' ');
520       ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
521       ds_put_byte (&s, ' ');
522       ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
523                                    n_vars),
524                      n_vars);
525       ref_cell_text = ds_steal_cstr (&s);
526     }
527
528   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
529                        ref_cell_text ? ref_cell_text : "");
530
531   g_free (ref_cell_text);
532 }
533
534
535 static void set_font_recursively (GtkWidget *w, gpointer data);
536
537 gchar *myconvfunc (GtkTreeModel *m, gint col, gint row, const GValue *v);
538 void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in, GValue *out);
539
540
541 enum sort_order
542   {
543     SORT_ASCEND,
544     SORT_DESCEND
545   };
546
547 static void
548 do_sort (PsppireDataEditor *de, enum sort_order order)
549 {
550   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
551
552   int n_vars = 0;
553   int i;
554
555   PsppireDataWindow *pdw =
556      psppire_data_window_for_dataset (de->data_store);
557
558   GString *syntax = g_string_new ("SORT CASES BY");
559   for (i = range->start_x ; i <= range->end_x; ++i)
560     {
561       const struct variable *var = psppire_dict_get_variable (de->dict, i);
562       if (var != NULL)
563         {
564           g_string_append_printf (syntax, " %s", var_get_name (var));
565           n_vars++;
566         }
567     }
568   if (n_vars > 0)
569     {
570       if (order == SORT_DESCEND)
571         g_string_append (syntax, " (DOWN)");
572       g_string_append_c (syntax, '.');
573       execute_const_syntax_string (pdw, syntax->str);
574     }
575   g_string_free (syntax, TRUE);
576 }
577
578
579 static void
580 sort_ascending (PsppireDataEditor *de)
581 {
582   do_sort (de, SORT_ASCEND);
583
584   gtk_widget_queue_draw (GTK_WIDGET (de));
585 }
586
587 static void
588 sort_descending (PsppireDataEditor *de)
589 {
590   do_sort (de, SORT_DESCEND);
591
592   gtk_widget_queue_draw (GTK_WIDGET (de));
593 }
594
595 static void
596 delete_cases (PsppireDataEditor *de)
597 {
598   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
599
600   psppire_data_store_delete_cases (de->data_store, range->start_y,
601                                    range->end_y - range->start_y + 1);
602
603   gtk_widget_queue_draw (GTK_WIDGET (de));
604 }
605
606 static void
607 insert_new_case (PsppireDataEditor *de)
608 {
609   gint item = GPOINTER_TO_INT (g_object_get_data
610                                 (G_OBJECT (de->data_sheet_cases_row_popup), "item"));
611
612   psppire_data_store_insert_new_case (de->data_store, item);
613
614   gtk_widget_queue_draw (GTK_WIDGET (de));
615 }
616
617 static void
618 delete_variables (PsppireDataEditor *de)
619 {
620   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
621
622   psppire_dict_delete_variables (de->dict, range->start_x,
623                                  (range->end_x - range->start_x + 1));
624
625   gtk_widget_queue_draw (GTK_WIDGET (de));
626 }
627
628 static void
629 insert_new_variable (PsppireDataEditor *de)
630 {
631   gint item = GPOINTER_TO_INT (g_object_get_data
632                                 (G_OBJECT (de->data_sheet_cases_column_popup),
633                                  "item"));
634
635   const struct variable *v = psppire_dict_insert_variable (de->dict, item, NULL);
636   psppire_data_store_insert_value (de->data_store, var_get_width(v),
637                                    var_get_case_index (v));
638
639   gtk_widget_queue_draw (GTK_WIDGET (de));
640 }
641
642
643 static GtkWidget *
644 create_row_header_popup_menu (PsppireDataEditor *de)
645 {
646   GtkWidget *menu = gtk_menu_new ();
647
648   GtkWidget *item =
649     gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
650
651   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), de);
652   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
653
654   item = gtk_separator_menu_item_new ();
655   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
656
657   de->clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
658   gtk_widget_set_sensitive (de->clear_cases_menu_item, FALSE);
659   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->clear_cases_menu_item);
660   g_signal_connect_swapped (de->clear_cases_menu_item, "activate",
661                             G_CALLBACK (delete_cases), de);
662
663   gtk_widget_show_all (menu);
664   return menu;
665 }
666
667 static GtkWidget *
668 create_column_header_popup_menu (PsppireDataEditor *de)
669 {
670   GtkWidget *menu = gtk_menu_new ();
671
672   GtkWidget *item =
673     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
674   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable), de);
675   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
676
677   item = gtk_separator_menu_item_new ();
678   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
679
680   de->clear_variables_menu_item =
681     gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
682   g_signal_connect_swapped (de->clear_variables_menu_item, "activate",
683                             G_CALLBACK (delete_variables), de);
684   gtk_widget_set_sensitive (de->clear_variables_menu_item, FALSE);
685   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->clear_variables_menu_item);
686
687   item = gtk_separator_menu_item_new ();
688   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
689
690   de->sort_ascending_menu_item =
691     gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
692   g_signal_connect_swapped (de->sort_ascending_menu_item, "activate",
693                             G_CALLBACK (sort_ascending), de);
694   gtk_widget_set_sensitive (de->sort_ascending_menu_item, FALSE);
695   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->sort_ascending_menu_item);
696
697   de->sort_descending_menu_item =
698     gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
699   g_signal_connect_swapped (de->sort_descending_menu_item, "activate",
700                             G_CALLBACK (sort_descending), de);
701   gtk_widget_set_sensitive (de->sort_descending_menu_item, FALSE);
702   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->sort_descending_menu_item);
703
704   gtk_widget_show_all (menu);
705   return menu;
706 }
707
708 static void
709 set_menu_items_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
710 {
711   JmdRange *range = selection;
712   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
713   gint width = gtk_tree_model_get_n_columns (sheet->data_model);
714   gint length = psppire_data_store_get_case_count (de->data_store);
715
716
717   gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
718   gtk_widget_set_sensitive (de->clear_cases_menu_item, whole_row_selected);
719
720
721   gboolean whole_column_selected =
722     (range->start_y == 0 && range->end_y == length - 1);
723   gtk_widget_set_sensitive (de->clear_variables_menu_item, whole_column_selected);
724   gtk_widget_set_sensitive (de->sort_ascending_menu_item, whole_column_selected);
725   gtk_widget_set_sensitive (de->sort_descending_menu_item, whole_column_selected);
726 }
727
728 static void
729 show_cases_row_popup (JmdSheet *sheet, int row, uint button, uint state, gpointer p)
730 {
731   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
732   GListModel *vmodel = NULL;
733   g_object_get (sheet, "vmodel", &vmodel, NULL);
734   if (vmodel == NULL)
735     return;
736
737   guint n_items = g_list_model_get_n_items (vmodel);
738
739   if (row >= n_items)
740     return;
741
742   if (button != 3)
743     return;
744
745   g_object_set_data (G_OBJECT (de->data_sheet_cases_row_popup), "item",
746                      GINT_TO_POINTER (row));
747
748   gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_row_popup), NULL);
749 }
750
751 static void
752 show_cases_column_popup (JmdSheet *sheet, int column, uint button, uint state,
753                          gpointer p)
754 {
755   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
756   GListModel *hmodel = NULL;
757   g_object_get (sheet, "hmodel", &hmodel, NULL);
758   if (hmodel == NULL)
759     return;
760
761   guint n_items = g_list_model_get_n_items (hmodel);
762
763   if (column >= n_items)
764     return;
765
766   if (button != 3)
767     return;
768
769   g_object_set_data (G_OBJECT (de->data_sheet_cases_column_popup), "item",
770                      GINT_TO_POINTER (column));
771
772   gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_column_popup), NULL);
773 }
774
775
776 static void
777 psppire_data_editor_init (PsppireDataEditor *de)
778 {
779   GtkWidget *hbox;
780   gchar *fontname = NULL;
781
782   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (de));
783   gtk_style_context_add_class (context, "psppire-data-editor");
784
785   de->font = NULL;
786   de->old_vbox_widget = NULL;
787
788   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
789
790   de->cell_ref_label = gtk_label_new ("");
791   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
792   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
793
794   de->datum_entry = psppire_value_entry_new ();
795   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
796                     "activate", G_CALLBACK (on_datum_entry_activate), de);
797
798   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
799   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
800   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
801
802   de->split = FALSE;
803   de->data_sheet = jmd_sheet_new ();
804
805   de->data_sheet_cases_column_popup = create_column_header_popup_menu (de);
806   de->data_sheet_cases_row_popup = create_row_header_popup_menu (de);
807   g_signal_connect (de->data_sheet, "row-header-pressed",
808                     G_CALLBACK (show_cases_row_popup), de);
809
810   g_signal_connect (de->data_sheet, "column-header-pressed",
811                     G_CALLBACK (show_cases_column_popup), de);
812
813   g_signal_connect (de->data_sheet, "selection-changed",
814                     G_CALLBACK (set_menu_items_sensitivity), de);
815
816   jmd_sheet_body_set_conversion_func
817     (JMD_SHEET_BODY (JMD_SHEET(de->data_sheet)->selected_body),
818      myconvfunc, myreversefunc);
819
820   GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
821   gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
822   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
823   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
824   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
825
826
827   g_signal_connect_swapped (de->data_sheet, "selection-changed",
828                     G_CALLBACK (on_data_selection_change), de);
829
830   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
831                             gtk_label_new_with_mnemonic (_("Data View")));
832
833   gtk_widget_show_all (de->vbox);
834
835   de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
836
837   PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
838
839   g_object_set (de->var_sheet,
840                 "hmodel", vsh,
841                 "select-renderer-func", select_renderer_func,
842                 NULL);
843
844
845   GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
846   gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
847
848   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
849                             gtk_label_new_with_mnemonic (_("Variable View")));
850
851   gtk_widget_show_all (de->var_sheet);
852
853   g_signal_connect (de->var_sheet, "row-header-double-clicked",
854                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
855
856   g_signal_connect (de->data_sheet, "column-header-double-clicked",
857                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
858
859   g_object_set (de, "can-focus", FALSE, NULL);
860
861   if (psppire_conf_get_string (psppire_conf_new (),
862                            "Data Editor", "font",
863                                 &fontname) )
864     {
865       de->font = pango_font_description_from_string (fontname);
866       g_free (fontname);
867       set_font_recursively (GTK_WIDGET (de), de->font);
868     }
869
870 }
871
872 GtkWidget*
873 psppire_data_editor_new (PsppireDict *dict,
874                          PsppireDataStore *data_store)
875 {
876   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
877                         "dictionary",  dict,
878                         "data-store",  data_store,
879                         NULL);
880 }
881 \f
882 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
883    sheet(s) and variable sheet. */
884 void
885 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
886 {
887   g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
888   g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
889 }
890
891
892 static void
893 set_font_recursively (GtkWidget *w, gpointer data)
894 {
895   PangoFontDescription *font_desc = data;
896
897   GtkStyleContext *style = gtk_widget_get_style_context (w);
898   GtkCssProvider *cssp = gtk_css_provider_new ();
899
900   gchar *str = pango_font_description_to_string (font_desc);
901   gchar *css =
902     g_strdup_printf ("* {font: %s}", str);
903   g_free (str);
904
905   GError *err = NULL;
906   gtk_css_provider_load_from_data (cssp, css, -1, &err);
907   if (err)
908     {
909       g_warning ("Failed to load font css \"%s\": %s", css, err->message);
910       g_error_free (err);
911     }
912   g_free (css);
913
914   gtk_style_context_add_provider (style,
915                                   GTK_STYLE_PROVIDER (cssp),
916                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
917   g_object_unref (cssp);
918
919
920   if ( GTK_IS_CONTAINER (w))
921     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
922 }
923
924 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
925 void
926 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
927 {
928   gchar *font_name;
929   set_font_recursively (GTK_WIDGET (de), font_desc);
930
931   if (de->font)
932     pango_font_description_free (de->font);
933   de->font = pango_font_description_copy (font_desc);
934   font_name = pango_font_description_to_string (de->font);
935
936   psppire_conf_set_string (psppire_conf_new (),
937                            "Data Editor", "font",
938                            font_name);
939   g_free (font_name);
940 }
941
942 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
943    If SPLIT is FALSE, un-splits it into a single pane. */
944 void
945 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
946 {
947   if (split == de->split)
948     return;
949
950   disconnect_data_sheets (de);
951
952   psppire_data_editor_refresh_model (de);
953
954   gtk_widget_show_all (de->vbox);
955
956   if (de->font)
957     set_font_recursively (GTK_WIDGET (de), de->font);
958
959   de->split = split;
960   g_object_notify (G_OBJECT (de), "split");
961 }
962
963 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
964    visible and selected in the active view in DE. */
965 void
966 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
967 {
968 }