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