Move paste related code from PsppireDataWindow to PsppireDataEditor
[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   gint row, col;
502   if (jmd_sheet_get_active_cell (JMD_SHEET (de->data_sheet), &col, &row))
503     {
504       union value val;
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
519 static void
520 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
521 {
522 }
523
524
525 /* Called when the active cell or the selection in the data sheet changes */
526 static void
527 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
528 {
529   gchar *ref_cell_text = NULL;
530
531   gint n_cases = abs (sel->end_y - sel->start_y) + 1;
532   gint n_vars = abs (sel->end_x - sel->start_x) + 1;
533
534   if (n_cases == 1 && n_vars == 1)
535     {
536       /* A single cell is selected */
537       const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
538
539       if (var)
540         ref_cell_text = g_strdup_printf (_("%d : %s"),
541                                          sel->start_y + 1, var_get_name (var));
542     }
543   else
544     {
545       struct string s;
546
547       /* The glib string library does not understand the ' printf modifier
548          on all platforms, but the "struct string" library does (because
549          Gnulib fixes that problem), so use the latter.  */
550       ds_init_empty (&s);
551       ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
552                      n_cases);
553       ds_put_byte (&s, ' ');
554       ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
555       ds_put_byte (&s, ' ');
556       ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
557                                    n_vars),
558                      n_vars);
559       ref_cell_text = ds_steal_cstr (&s);
560     }
561
562   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
563                        ref_cell_text ? ref_cell_text : "");
564
565   g_free (ref_cell_text);
566 }
567
568
569 static void set_font_recursively (GtkWidget *w, gpointer data);
570
571 void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in,
572                     GValue *out);
573
574
575 enum sort_order
576   {
577     SORT_ASCEND,
578     SORT_DESCEND
579   };
580
581 static void
582 do_sort (PsppireDataEditor *de, enum sort_order order)
583 {
584   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
585
586   int n_vars = 0;
587   int i;
588
589   PsppireDataWindow *pdw =
590      psppire_data_window_for_data_store (de->data_store);
591
592   GString *syntax = g_string_new ("SORT CASES BY");
593   for (i = range->start_x ; i <= range->end_x; ++i)
594     {
595       const struct variable *var = psppire_dict_get_variable (de->dict, i);
596       if (var != NULL)
597         {
598           g_string_append_printf (syntax, " %s", var_get_name (var));
599           n_vars++;
600         }
601     }
602   if (n_vars > 0)
603     {
604       if (order == SORT_DESCEND)
605         g_string_append (syntax, " (DOWN)");
606       g_string_append_c (syntax, '.');
607       execute_const_syntax_string (pdw, syntax->str);
608     }
609   g_string_free (syntax, TRUE);
610 }
611
612
613 static void
614 sort_ascending (PsppireDataEditor *de)
615 {
616   do_sort (de, SORT_ASCEND);
617
618   gtk_widget_queue_draw (GTK_WIDGET (de));
619 }
620
621 static void
622 sort_descending (PsppireDataEditor *de)
623 {
624   do_sort (de, SORT_DESCEND);
625
626   gtk_widget_queue_draw (GTK_WIDGET (de));
627 }
628
629 static void
630 delete_cases (PsppireDataEditor *de)
631 {
632   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
633
634   psppire_data_store_delete_cases (de->data_store, range->start_y,
635                                    range->end_y - range->start_y + 1);
636
637   gtk_widget_queue_draw (GTK_WIDGET (de));
638 }
639
640 static void
641 insert_new_case (PsppireDataEditor *de)
642 {
643   gint posn = GPOINTER_TO_INT (g_object_get_data
644                                 (G_OBJECT (de->data_sheet_cases_row_popup), "item"));
645
646   psppire_data_editor_insert_new_case_at_posn (de, posn);
647 }
648
649 void
650 psppire_data_editor_data_delete_variables (PsppireDataEditor *de)
651 {
652   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
653
654   psppire_dict_delete_variables (de->dict, range->start_x,
655                                  (range->end_x - range->start_x + 1));
656
657   gtk_widget_queue_draw (GTK_WIDGET (de->data_sheet));
658 }
659
660 void
661 psppire_data_editor_var_delete_variables (PsppireDataEditor *de)
662 {
663   JmdRange *range = JMD_SHEET(de->var_sheet)->selection;
664
665   psppire_dict_delete_variables (de->dict, range->start_y,
666                                  (range->end_y - range->start_y + 1));
667
668   gtk_widget_queue_draw (GTK_WIDGET (de->var_sheet));
669 }
670
671 void
672 psppire_data_editor_insert_new_case_at_posn  (PsppireDataEditor *de, gint posn)
673 {
674   psppire_data_store_insert_new_case (de->data_store, posn);
675
676   gtk_widget_queue_draw (GTK_WIDGET (de->data_sheet));
677 }
678
679 void
680 psppire_data_editor_insert_new_variable_at_posn (PsppireDataEditor *de, gint posn)
681 {
682   const struct variable *v = psppire_dict_insert_variable (de->dict, posn, NULL);
683   psppire_data_store_insert_value (de->data_store, var_get_width(v),
684                                    var_get_case_index (v));
685
686   gtk_widget_queue_draw (GTK_WIDGET (de));
687 }
688
689 static void
690 insert_new_variable_data (PsppireDataEditor *de)
691 {
692   gint posn = GPOINTER_TO_INT (g_object_get_data
693                                 (G_OBJECT (de->data_sheet_cases_column_popup),
694                                  "item"));
695
696   psppire_data_editor_insert_new_variable_at_posn (de, posn);
697 }
698
699 static void
700 insert_new_variable_var (PsppireDataEditor *de)
701 {
702   gint item = GPOINTER_TO_INT (g_object_get_data
703                                 (G_OBJECT (de->var_sheet_row_popup),
704                                  "item"));
705
706   const struct variable *v = psppire_dict_insert_variable (de->dict, item, NULL);
707   psppire_data_store_insert_value (de->data_store, var_get_width(v),
708                                    var_get_case_index (v));
709
710   gtk_widget_queue_draw (GTK_WIDGET (de));
711 }
712
713
714 static GtkWidget *
715 create_var_row_header_popup_menu (PsppireDataEditor *de)
716 {
717   GtkWidget *menu = gtk_menu_new ();
718
719   GtkWidget *item =
720     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
721   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
722                             de);
723   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
724
725   item = gtk_separator_menu_item_new ();
726   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
727
728   de->var_clear_variables_menu_item =
729     gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
730   g_signal_connect_swapped (de->var_clear_variables_menu_item, "activate",
731                             G_CALLBACK (psppire_data_editor_var_delete_variables),
732                             de);
733   gtk_widget_set_sensitive (de->var_clear_variables_menu_item, FALSE);
734   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->var_clear_variables_menu_item);
735
736   gtk_widget_show_all (menu);
737   return menu;
738 }
739
740 static GtkWidget *
741 create_data_row_header_popup_menu (PsppireDataEditor *de)
742 {
743   GtkWidget *menu = gtk_menu_new ();
744
745   GtkWidget *item =
746     gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
747
748   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), de);
749   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
750
751   item = gtk_separator_menu_item_new ();
752   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
753
754   de->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
755   gtk_widget_set_sensitive (de->data_clear_cases_menu_item, FALSE);
756   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_clear_cases_menu_item);
757   g_signal_connect_swapped (de->data_clear_cases_menu_item, "activate",
758                             G_CALLBACK (delete_cases), de);
759
760   gtk_widget_show_all (menu);
761   return menu;
762 }
763
764 static GtkWidget *
765 create_data_column_header_popup_menu (PsppireDataEditor *de)
766 {
767   GtkWidget *menu = gtk_menu_new ();
768
769   GtkWidget *item =
770     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
771   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_data),
772                             de);
773   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
774
775   item = gtk_separator_menu_item_new ();
776   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
777
778   de->data_clear_variables_menu_item =
779     gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
780   g_signal_connect_swapped (de->data_clear_variables_menu_item, "activate",
781                             G_CALLBACK (psppire_data_editor_data_delete_variables),
782                             de);
783   gtk_widget_set_sensitive (de->data_clear_variables_menu_item, FALSE);
784   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_clear_variables_menu_item);
785
786   item = gtk_separator_menu_item_new ();
787   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
788
789   de->data_sort_ascending_menu_item =
790     gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
791   g_signal_connect_swapped (de->data_sort_ascending_menu_item, "activate",
792                             G_CALLBACK (sort_ascending), de);
793   gtk_widget_set_sensitive (de->data_sort_ascending_menu_item, FALSE);
794   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_sort_ascending_menu_item);
795
796   de->data_sort_descending_menu_item =
797     gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
798   g_signal_connect_swapped (de->data_sort_descending_menu_item, "activate",
799                             G_CALLBACK (sort_descending), de);
800   gtk_widget_set_sensitive (de->data_sort_descending_menu_item, FALSE);
801   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_sort_descending_menu_item);
802
803   gtk_widget_show_all (menu);
804   return menu;
805 }
806
807 static void
808 set_var_popup_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
809 {
810
811   JmdRange *range = selection;
812   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
813   gint width = gtk_tree_model_get_n_columns (sheet->data_model);
814
815   gboolean whole_row_selected = (range->start_x == 0 &&
816                                  range->end_x == width - 1 - 1);
817   /*  PsppireDict has an "extra" column: TVM_COL_VAR   ^^^ */
818   gtk_widget_set_sensitive (de->var_clear_variables_menu_item, whole_row_selected);
819 }
820
821 static void
822 set_menu_items_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
823 {
824   JmdRange *range = selection;
825   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
826   gint width = gtk_tree_model_get_n_columns (sheet->data_model);
827   gint length = psppire_data_store_get_case_count (de->data_store);
828
829
830   gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
831   gtk_widget_set_sensitive (de->data_clear_cases_menu_item, whole_row_selected);
832
833
834   gboolean whole_column_selected =
835     (range->start_y == 0 && range->end_y == length - 1);
836   gtk_widget_set_sensitive (de->data_clear_variables_menu_item,
837                             whole_column_selected);
838   gtk_widget_set_sensitive (de->data_sort_ascending_menu_item,
839                             whole_column_selected);
840   gtk_widget_set_sensitive (de->data_sort_descending_menu_item,
841                             whole_column_selected);
842 }
843
844 static void
845 show_variables_row_popup (JmdSheet *sheet, int row, uint button,
846                           uint state, gpointer p)
847 {
848   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
849   GListModel *vmodel = NULL;
850   g_object_get (sheet, "vmodel", &vmodel, NULL);
851   if (vmodel == NULL)
852     return;
853
854   guint n_items = g_list_model_get_n_items (vmodel);
855
856   if (row >= n_items)
857     return;
858
859   if (button != 3)
860     return;
861
862   g_object_set_data (G_OBJECT (de->var_sheet_row_popup), "item",
863                      GINT_TO_POINTER (row));
864
865   gtk_menu_popup_at_pointer (GTK_MENU (de->var_sheet_row_popup), NULL);
866 }
867
868 static void
869 show_cases_row_popup (JmdSheet *sheet, int row, uint button, uint state, gpointer p)
870 {
871   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
872   GListModel *vmodel = NULL;
873   g_object_get (sheet, "vmodel", &vmodel, NULL);
874   if (vmodel == NULL)
875     return;
876
877   guint n_items = g_list_model_get_n_items (vmodel);
878
879   if (row >= n_items)
880     return;
881
882   if (button != 3)
883     return;
884
885   g_object_set_data (G_OBJECT (de->data_sheet_cases_row_popup), "item",
886                      GINT_TO_POINTER (row));
887
888   gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_row_popup), NULL);
889 }
890
891 static void
892 show_cases_column_popup (JmdSheet *sheet, int column, uint button, uint state,
893                          gpointer p)
894 {
895   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
896   GListModel *hmodel = NULL;
897   g_object_get (sheet, "hmodel", &hmodel, NULL);
898   if (hmodel == NULL)
899     return;
900
901   guint n_items = g_list_model_get_n_items (hmodel);
902
903   if (column >= n_items)
904     return;
905
906   if (button != 3)
907     return;
908
909   g_object_set_data (G_OBJECT (de->data_sheet_cases_column_popup), "item",
910                      GINT_TO_POINTER (column));
911
912   gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_column_popup), NULL);
913 }
914
915
916 static void
917 psppire_data_editor_init (PsppireDataEditor *de)
918 {
919   GtkWidget *hbox;
920   gchar *fontname = NULL;
921
922   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (de));
923   gtk_style_context_add_class (context, "psppire-data-editor");
924
925   de->font = NULL;
926   de->old_vbox_widget = NULL;
927
928   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
929
930   de->cell_ref_label = gtk_label_new ("");
931   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
932   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
933
934   de->datum_entry = psppire_value_entry_new ();
935   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
936                     "activate", G_CALLBACK (on_datum_entry_activate), de);
937
938   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
939   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
940   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
941
942   de->split = FALSE;
943   de->data_sheet = jmd_sheet_new ();
944
945   de->data_sheet_cases_column_popup = create_data_column_header_popup_menu (de);
946   de->data_sheet_cases_row_popup = create_data_row_header_popup_menu (de);
947   de->var_sheet_row_popup = create_var_row_header_popup_menu (de);
948
949   g_signal_connect (de->data_sheet, "row-header-pressed",
950                     G_CALLBACK (show_cases_row_popup), de);
951
952   g_signal_connect (de->data_sheet, "column-header-pressed",
953                     G_CALLBACK (show_cases_column_popup), de);
954
955   g_signal_connect (de->data_sheet, "selection-changed",
956                     G_CALLBACK (set_menu_items_sensitivity), de);
957
958   g_object_set (de->data_sheet,
959                 "forward-conversion", psppire_data_store_value_to_string,
960                 "reverse-conversion", myreversefunc,
961                 NULL);
962
963   GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
964   gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
965   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
966   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
967   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
968
969
970   g_signal_connect_swapped (de->data_sheet, "selection-changed",
971                     G_CALLBACK (on_data_selection_change), de);
972
973   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
974                             gtk_label_new_with_mnemonic (_("Data View")));
975
976   gtk_widget_show_all (de->vbox);
977
978   de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
979
980   PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
981
982   g_object_set (de->var_sheet,
983                 "hmodel", vsh,
984                 "select-renderer-func", select_renderer_func,
985                 NULL);
986
987   g_object_set (de->var_sheet,
988                 "forward-conversion", var_sheet_data_to_string,
989                 NULL);
990
991   g_signal_connect (de->var_sheet, "row-header-pressed",
992                     G_CALLBACK (show_variables_row_popup), de);
993
994   g_signal_connect (de->var_sheet, "selection-changed",
995                     G_CALLBACK (set_var_popup_sensitivity), de);
996
997
998   GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
999   gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
1000
1001   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
1002                             gtk_label_new_with_mnemonic (_("Variable View")));
1003
1004   gtk_widget_show_all (de->var_sheet);
1005
1006   g_signal_connect (de->var_sheet, "row-header-double-clicked",
1007                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
1008
1009   g_signal_connect (de->data_sheet, "column-header-double-clicked",
1010                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
1011
1012   g_object_set (de, "can-focus", FALSE, NULL);
1013
1014   if (psppire_conf_get_string (psppire_conf_new (),
1015                            "Data Editor", "font",
1016                                 &fontname) )
1017     {
1018       de->font = pango_font_description_from_string (fontname);
1019       g_free (fontname);
1020       set_font_recursively (GTK_WIDGET (de), de->font);
1021     }
1022
1023 }
1024
1025 GtkWidget*
1026 psppire_data_editor_new (PsppireDict *dict,
1027                          PsppireDataStore *data_store)
1028 {
1029   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
1030                         "dictionary",  dict,
1031                         "data-store",  data_store,
1032                         NULL);
1033 }
1034 \f
1035 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
1036    sheet(s) and variable sheet. */
1037 void
1038 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
1039 {
1040   g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
1041   g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
1042 }
1043
1044
1045 static void
1046 set_font_recursively (GtkWidget *w, gpointer data)
1047 {
1048   PangoFontDescription *font_desc = data;
1049
1050   GtkStyleContext *style = gtk_widget_get_style_context (w);
1051   GtkCssProvider *cssp = gtk_css_provider_new ();
1052
1053   gchar *str = pango_font_description_to_string (font_desc);
1054   gchar *css =
1055     g_strdup_printf ("* {font: %s}", str);
1056   g_free (str);
1057
1058   GError *err = NULL;
1059   gtk_css_provider_load_from_data (cssp, css, -1, &err);
1060   if (err)
1061     {
1062       g_warning ("Failed to load font css \"%s\": %s", css, err->message);
1063       g_error_free (err);
1064     }
1065   g_free (css);
1066
1067   gtk_style_context_add_provider (style,
1068                                   GTK_STYLE_PROVIDER (cssp),
1069                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1070   g_object_unref (cssp);
1071
1072
1073   if ( GTK_IS_CONTAINER (w))
1074     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
1075 }
1076
1077 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
1078 void
1079 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
1080 {
1081   gchar *font_name;
1082   set_font_recursively (GTK_WIDGET (de), font_desc);
1083
1084   if (de->font)
1085     pango_font_description_free (de->font);
1086   de->font = pango_font_description_copy (font_desc);
1087   font_name = pango_font_description_to_string (de->font);
1088
1089   psppire_conf_set_string (psppire_conf_new (),
1090                            "Data Editor", "font",
1091                            font_name);
1092   g_free (font_name);
1093 }
1094
1095 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
1096    If SPLIT is FALSE, un-splits it into a single pane. */
1097 void
1098 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
1099 {
1100   g_object_set (de, "split", split, NULL);
1101 }
1102
1103 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
1104    visible and selected in the active view in DE. */
1105 void
1106 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
1107 {
1108   gint page = gtk_notebook_get_current_page (GTK_NOTEBOOK (de));
1109
1110   switch (page)
1111     {
1112       case PSPPIRE_DATA_EDITOR_DATA_VIEW:
1113         jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
1114         jmd_sheet_set_active_cell (JMD_SHEET (de->data_sheet), dict_index, -1, NULL);
1115         break;
1116       case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
1117         jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
1118         jmd_sheet_set_active_cell (JMD_SHEET (de->var_sheet), -1, dict_index, NULL);
1119         break;
1120     }
1121 }
1122
1123 /* Set the datum at COL, ROW, to that contained in VALUE.
1124  */
1125 static void
1126 store_set_datum (GtkTreeModel *model, gint col, gint row,
1127                          const GValue *value)
1128 {
1129   PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
1130   GVariant *v = g_value_get_variant (value);
1131   union value uv;
1132   value_variant_get (&uv, v);
1133   const struct variable *var = psppire_dict_get_variable (store->dict, col);
1134   psppire_data_store_set_value (store, row, var, &uv);
1135   value_destroy_from_variant (&uv, v);
1136 }
1137
1138 void
1139 psppire_data_editor_paste (PsppireDataEditor *de)
1140 {
1141   JmdSheet *sheet = JMD_SHEET (de->data_sheet);
1142   GtkClipboard *clip =
1143     gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (sheet)),
1144                                    GDK_SELECTION_CLIPBOARD);
1145
1146   jmd_sheet_paste (sheet, clip, store_set_datum);
1147 }