Remove call to property which no longer exists
[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 void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in,
571                     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 void
640 insert_new_case_at_posn  (PsppireDataEditor *de, gint posn)
641 {
642   psppire_data_store_insert_new_case (de->data_store, posn);
643
644   gtk_widget_queue_draw (GTK_WIDGET (de));
645 }
646
647 static void
648 insert_new_case (PsppireDataEditor *de)
649 {
650   gint posn = GPOINTER_TO_INT (g_object_get_data
651                                 (G_OBJECT (de->data_sheet_cases_row_popup), "item"));
652
653   insert_new_case_at_posn (de, posn);
654 }
655
656 void
657 data_delete_variables (PsppireDataEditor *de)
658 {
659   JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
660
661   psppire_dict_delete_variables (de->dict, range->start_x,
662                                  (range->end_x - range->start_x + 1));
663
664   gtk_widget_queue_draw (GTK_WIDGET (de->data_sheet));
665 }
666
667 void
668 var_delete_variables (PsppireDataEditor *de)
669 {
670   JmdRange *range = JMD_SHEET(de->var_sheet)->selection;
671
672   psppire_dict_delete_variables (de->dict, range->start_y,
673                                  (range->end_y - range->start_y + 1));
674
675   gtk_widget_queue_draw (GTK_WIDGET (de->var_sheet));
676 }
677
678 void
679 insert_new_variable_at_posn (PsppireDataEditor *de, gint posn)
680 {
681   const struct variable *v = psppire_dict_insert_variable (de->dict, posn, NULL);
682   psppire_data_store_insert_value (de->data_store, var_get_width(v),
683                                    var_get_case_index (v));
684
685   gtk_widget_queue_draw (GTK_WIDGET (de));
686 }
687
688 static void
689 insert_new_variable_data (PsppireDataEditor *de)
690 {
691   gint posn = GPOINTER_TO_INT (g_object_get_data
692                                 (G_OBJECT (de->data_sheet_cases_column_popup),
693                                  "item"));
694
695   insert_new_variable_at_posn (de, posn);
696 }
697
698 static void
699 insert_new_variable_var (PsppireDataEditor *de)
700 {
701   gint item = GPOINTER_TO_INT (g_object_get_data
702                                 (G_OBJECT (de->var_sheet_row_popup),
703                                  "item"));
704
705   const struct variable *v = psppire_dict_insert_variable (de->dict, item, NULL);
706   psppire_data_store_insert_value (de->data_store, var_get_width(v),
707                                    var_get_case_index (v));
708
709   gtk_widget_queue_draw (GTK_WIDGET (de));
710 }
711
712
713 static GtkWidget *
714 create_var_row_header_popup_menu (PsppireDataEditor *de)
715 {
716   GtkWidget *menu = gtk_menu_new ();
717
718   GtkWidget *item =
719     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
720   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
721                             de);
722   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
723
724   item = gtk_separator_menu_item_new ();
725   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
726
727   de->var_clear_variables_menu_item =
728     gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
729   g_signal_connect_swapped (de->var_clear_variables_menu_item, "activate",
730                             G_CALLBACK (var_delete_variables), de);
731   gtk_widget_set_sensitive (de->var_clear_variables_menu_item, FALSE);
732   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->var_clear_variables_menu_item);
733
734   gtk_widget_show_all (menu);
735   return menu;
736 }
737
738 static GtkWidget *
739 create_data_row_header_popup_menu (PsppireDataEditor *de)
740 {
741   GtkWidget *menu = gtk_menu_new ();
742
743   GtkWidget *item =
744     gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
745
746   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), de);
747   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
748
749   item = gtk_separator_menu_item_new ();
750   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
751
752   de->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
753   gtk_widget_set_sensitive (de->data_clear_cases_menu_item, FALSE);
754   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_clear_cases_menu_item);
755   g_signal_connect_swapped (de->data_clear_cases_menu_item, "activate",
756                             G_CALLBACK (delete_cases), de);
757
758   gtk_widget_show_all (menu);
759   return menu;
760 }
761
762 static GtkWidget *
763 create_data_column_header_popup_menu (PsppireDataEditor *de)
764 {
765   GtkWidget *menu = gtk_menu_new ();
766
767   GtkWidget *item =
768     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
769   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_data),
770                             de);
771   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
772
773   item = gtk_separator_menu_item_new ();
774   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
775
776   de->data_clear_variables_menu_item =
777     gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
778   g_signal_connect_swapped (de->data_clear_variables_menu_item, "activate",
779                             G_CALLBACK (data_delete_variables), de);
780   gtk_widget_set_sensitive (de->data_clear_variables_menu_item, FALSE);
781   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_clear_variables_menu_item);
782
783   item = gtk_separator_menu_item_new ();
784   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
785
786   de->data_sort_ascending_menu_item =
787     gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
788   g_signal_connect_swapped (de->data_sort_ascending_menu_item, "activate",
789                             G_CALLBACK (sort_ascending), de);
790   gtk_widget_set_sensitive (de->data_sort_ascending_menu_item, FALSE);
791   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_sort_ascending_menu_item);
792
793   de->data_sort_descending_menu_item =
794     gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
795   g_signal_connect_swapped (de->data_sort_descending_menu_item, "activate",
796                             G_CALLBACK (sort_descending), de);
797   gtk_widget_set_sensitive (de->data_sort_descending_menu_item, FALSE);
798   gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_sort_descending_menu_item);
799
800   gtk_widget_show_all (menu);
801   return menu;
802 }
803
804 static void
805 set_var_popup_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
806 {
807
808   JmdRange *range = selection;
809   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
810   gint width = gtk_tree_model_get_n_columns (sheet->data_model);
811
812   gboolean whole_row_selected = (range->start_x == 0 &&
813                                  range->end_x == width - 1 - 1);
814   /*  PsppireDict has an "extra" column: TVM_COL_VAR   ^^^ */
815   gtk_widget_set_sensitive (de->var_clear_variables_menu_item, whole_row_selected);
816 }
817
818 static void
819 set_menu_items_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
820 {
821   JmdRange *range = selection;
822   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
823   gint width = gtk_tree_model_get_n_columns (sheet->data_model);
824   gint length = psppire_data_store_get_case_count (de->data_store);
825
826
827   gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
828   gtk_widget_set_sensitive (de->data_clear_cases_menu_item, whole_row_selected);
829
830
831   gboolean whole_column_selected =
832     (range->start_y == 0 && range->end_y == length - 1);
833   gtk_widget_set_sensitive (de->data_clear_variables_menu_item,
834                             whole_column_selected);
835   gtk_widget_set_sensitive (de->data_sort_ascending_menu_item,
836                             whole_column_selected);
837   gtk_widget_set_sensitive (de->data_sort_descending_menu_item,
838                             whole_column_selected);
839 }
840
841 static void
842 show_variables_row_popup (JmdSheet *sheet, int row, uint button,
843                           uint state, gpointer p)
844 {
845   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
846   GListModel *vmodel = NULL;
847   g_object_get (sheet, "vmodel", &vmodel, NULL);
848   if (vmodel == NULL)
849     return;
850
851   guint n_items = g_list_model_get_n_items (vmodel);
852
853   if (row >= n_items)
854     return;
855
856   if (button != 3)
857     return;
858
859   g_object_set_data (G_OBJECT (de->var_sheet_row_popup), "item",
860                      GINT_TO_POINTER (row));
861
862   gtk_menu_popup_at_pointer (GTK_MENU (de->var_sheet_row_popup), NULL);
863 }
864
865 static void
866 show_cases_row_popup (JmdSheet *sheet, int row, uint button, uint state, gpointer p)
867 {
868   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
869   GListModel *vmodel = NULL;
870   g_object_get (sheet, "vmodel", &vmodel, NULL);
871   if (vmodel == NULL)
872     return;
873
874   guint n_items = g_list_model_get_n_items (vmodel);
875
876   if (row >= n_items)
877     return;
878
879   if (button != 3)
880     return;
881
882   g_object_set_data (G_OBJECT (de->data_sheet_cases_row_popup), "item",
883                      GINT_TO_POINTER (row));
884
885   gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_row_popup), NULL);
886 }
887
888 static void
889 show_cases_column_popup (JmdSheet *sheet, int column, uint button, uint state,
890                          gpointer p)
891 {
892   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
893   GListModel *hmodel = NULL;
894   g_object_get (sheet, "hmodel", &hmodel, NULL);
895   if (hmodel == NULL)
896     return;
897
898   guint n_items = g_list_model_get_n_items (hmodel);
899
900   if (column >= n_items)
901     return;
902
903   if (button != 3)
904     return;
905
906   g_object_set_data (G_OBJECT (de->data_sheet_cases_column_popup), "item",
907                      GINT_TO_POINTER (column));
908
909   gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_column_popup), NULL);
910 }
911
912
913 static void
914 psppire_data_editor_init (PsppireDataEditor *de)
915 {
916   GtkWidget *hbox;
917   gchar *fontname = NULL;
918
919   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (de));
920   gtk_style_context_add_class (context, "psppire-data-editor");
921
922   de->font = NULL;
923   de->old_vbox_widget = NULL;
924
925   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
926
927   de->cell_ref_label = gtk_label_new ("");
928   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
929   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
930
931   de->datum_entry = psppire_value_entry_new ();
932   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
933                     "activate", G_CALLBACK (on_datum_entry_activate), de);
934
935   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
936   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
937   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
938
939   de->split = FALSE;
940   de->data_sheet = jmd_sheet_new ();
941
942   de->data_sheet_cases_column_popup = create_data_column_header_popup_menu (de);
943   de->data_sheet_cases_row_popup = create_data_row_header_popup_menu (de);
944   de->var_sheet_row_popup = create_var_row_header_popup_menu (de);
945
946   g_signal_connect (de->data_sheet, "row-header-pressed",
947                     G_CALLBACK (show_cases_row_popup), de);
948
949   g_signal_connect (de->data_sheet, "column-header-pressed",
950                     G_CALLBACK (show_cases_column_popup), de);
951
952   g_signal_connect (de->data_sheet, "selection-changed",
953                     G_CALLBACK (set_menu_items_sensitivity), de);
954
955   jmd_sheet_set_conversion_func (JMD_SHEET (de->data_sheet),
956                                  psppire_data_store_value_to_string,
957                                  myreversefunc);
958
959   GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
960   gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
961   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
962   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
963   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
964
965
966   g_signal_connect_swapped (de->data_sheet, "selection-changed",
967                     G_CALLBACK (on_data_selection_change), de);
968
969   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
970                             gtk_label_new_with_mnemonic (_("Data View")));
971
972   gtk_widget_show_all (de->vbox);
973
974   de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
975
976   PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
977
978   g_object_set (de->var_sheet,
979                 "hmodel", vsh,
980                 "select-renderer-func", select_renderer_func,
981                 NULL);
982
983   jmd_sheet_set_conversion_func (JMD_SHEET (de->var_sheet),
984                                  var_sheet_data_to_string, NULL);
985
986   g_signal_connect (de->var_sheet, "row-header-pressed",
987                     G_CALLBACK (show_variables_row_popup), de);
988
989   g_signal_connect (de->var_sheet, "selection-changed",
990                     G_CALLBACK (set_var_popup_sensitivity), de);
991
992
993   GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
994   gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
995
996   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
997                             gtk_label_new_with_mnemonic (_("Variable View")));
998
999   gtk_widget_show_all (de->var_sheet);
1000
1001   g_signal_connect (de->var_sheet, "row-header-double-clicked",
1002                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
1003
1004   g_signal_connect (de->data_sheet, "column-header-double-clicked",
1005                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
1006
1007   g_object_set (de, "can-focus", FALSE, NULL);
1008
1009   if (psppire_conf_get_string (psppire_conf_new (),
1010                            "Data Editor", "font",
1011                                 &fontname) )
1012     {
1013       de->font = pango_font_description_from_string (fontname);
1014       g_free (fontname);
1015       set_font_recursively (GTK_WIDGET (de), de->font);
1016     }
1017
1018 }
1019
1020 GtkWidget*
1021 psppire_data_editor_new (PsppireDict *dict,
1022                          PsppireDataStore *data_store)
1023 {
1024   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
1025                         "dictionary",  dict,
1026                         "data-store",  data_store,
1027                         NULL);
1028 }
1029 \f
1030 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
1031    sheet(s) and variable sheet. */
1032 void
1033 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
1034 {
1035   g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
1036   g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
1037 }
1038
1039
1040 static void
1041 set_font_recursively (GtkWidget *w, gpointer data)
1042 {
1043   PangoFontDescription *font_desc = data;
1044
1045   GtkStyleContext *style = gtk_widget_get_style_context (w);
1046   GtkCssProvider *cssp = gtk_css_provider_new ();
1047
1048   gchar *str = pango_font_description_to_string (font_desc);
1049   gchar *css =
1050     g_strdup_printf ("* {font: %s}", str);
1051   g_free (str);
1052
1053   GError *err = NULL;
1054   gtk_css_provider_load_from_data (cssp, css, -1, &err);
1055   if (err)
1056     {
1057       g_warning ("Failed to load font css \"%s\": %s", css, err->message);
1058       g_error_free (err);
1059     }
1060   g_free (css);
1061
1062   gtk_style_context_add_provider (style,
1063                                   GTK_STYLE_PROVIDER (cssp),
1064                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1065   g_object_unref (cssp);
1066
1067
1068   if ( GTK_IS_CONTAINER (w))
1069     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
1070 }
1071
1072 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
1073 void
1074 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
1075 {
1076   gchar *font_name;
1077   set_font_recursively (GTK_WIDGET (de), font_desc);
1078
1079   if (de->font)
1080     pango_font_description_free (de->font);
1081   de->font = pango_font_description_copy (font_desc);
1082   font_name = pango_font_description_to_string (de->font);
1083
1084   psppire_conf_set_string (psppire_conf_new (),
1085                            "Data Editor", "font",
1086                            font_name);
1087   g_free (font_name);
1088 }
1089
1090 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
1091    If SPLIT is FALSE, un-splits it into a single pane. */
1092 void
1093 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
1094 {
1095   g_object_set (de, "split", split, NULL);
1096 }
1097
1098 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
1099    visible and selected in the active view in DE. */
1100 void
1101 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
1102 {
1103   gint page = gtk_notebook_get_current_page (GTK_NOTEBOOK (de));
1104
1105   switch (page)
1106     {
1107       case PSPPIRE_DATA_EDITOR_DATA_VIEW:
1108         jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
1109         jmd_sheet_set_active_cell (JMD_SHEET (de->data_sheet), dict_index, -1, NULL);
1110         break;
1111       case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
1112         jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
1113         jmd_sheet_set_active_cell (JMD_SHEET (de->var_sheet), -1, dict_index, NULL);
1114         break;
1115     }
1116 }