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