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