Revert "Fixed a use after free error when manipulating datasets."
[pspp] / src / ui / gui / val-labs-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2005, 2009, 2010, 2011, 2012, 2015, 2016,
3    2020  Free Software Foundation
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
19 /*  This module describes the behaviour of the Value Labels dialog box,
20     used for input of the value labels in the variable sheet */
21
22 #include <config.h>
23
24 #include "ui/gui/val-labs-dialog.h"
25
26 #include <string.h>
27
28 #include "data/value-labels.h"
29 #include "data/format.h"
30 #include "libpspp/i18n.h"
31 #include "ui/gui/builder-wrapper.h"
32 #include "ui/gui/helper.h"
33
34 #include <gettext.h>
35 #define _(msgid) gettext (msgid)
36 #define N_(msgid) msgid
37
38 static GObject *psppire_val_labs_dialog_constructor (GType type, guint,
39                                                      GObjectConstructParam *);
40 static void psppire_val_labs_dialog_finalize (GObject *);
41
42 G_DEFINE_TYPE (PsppireValLabsDialog,
43                psppire_val_labs_dialog,
44                PSPPIRE_TYPE_DIALOG);
45 enum
46   {
47     PROP_0,
48     PROP_VARIABLE,
49     PROP_VALUE_LABELS
50   };
51
52 static void do_change (PsppireValLabsDialog *);
53
54 static void
55 psppire_val_labs_dialog_set_property (GObject      *object,
56                                       guint         prop_id,
57                                       const GValue *value,
58                                       GParamSpec   *pspec)
59 {
60   PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
61
62   switch (prop_id)
63     {
64     case PROP_VARIABLE:
65       psppire_val_labs_dialog_set_variable (obj, g_value_get_pointer (value));
66       break;
67     case PROP_VALUE_LABELS:
68     default:
69       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
70       break;
71     }
72 }
73
74 static void
75 psppire_val_labs_dialog_get_property (GObject      *object,
76                                       guint         prop_id,
77                                       GValue       *value,
78                                       GParamSpec   *pspec)
79 {
80   PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
81
82   switch (prop_id)
83     {
84     case PROP_VALUE_LABELS:
85       g_value_set_pointer (value, obj->labs);
86       break;
87     case PROP_VARIABLE:
88     default:
89       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
90       break;
91     }
92 }
93
94 static void
95 psppire_val_labs_dialog_class_init (PsppireValLabsDialogClass *class)
96 {
97   GObjectClass *gobject_class;
98   gobject_class = G_OBJECT_CLASS (class);
99
100   gobject_class->constructor = psppire_val_labs_dialog_constructor;
101   gobject_class->finalize = psppire_val_labs_dialog_finalize;
102   gobject_class->set_property = psppire_val_labs_dialog_set_property;
103   gobject_class->get_property = psppire_val_labs_dialog_get_property;
104
105   g_object_class_install_property (
106     gobject_class, PROP_VARIABLE,
107     g_param_spec_pointer ("variable",
108                           "Variable",
109                           "Variable whose value labels are to be edited.  The "
110                           "variable's print format and encoding are also used "
111                           "for editing.",
112                           G_PARAM_WRITABLE));
113
114   g_object_class_install_property (
115     gobject_class, PROP_VALUE_LABELS,
116     g_param_spec_pointer ("value-labels",
117                           "Value Labels",
118                           "Edited value labels.",
119                           G_PARAM_READABLE));
120 }
121
122 static void
123 psppire_val_labs_dialog_init (PsppireValLabsDialog *obj)
124 {
125   /* We do all of our work on widgets in the constructor function, because that
126      runs after the construction properties have been set.  Otherwise
127      PsppireDialog's "orientation" property hasn't been set and therefore we
128      have no box to populate. */
129   obj->labs = val_labs_create (0);
130 }
131
132 static void
133 psppire_val_labs_dialog_finalize (GObject *obj)
134 {
135   PsppireValLabsDialog *dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
136
137   val_labs_destroy (dialog->labs);
138   g_free (dialog->encoding);
139
140   G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->finalize (obj);
141 }
142
143 PsppireValLabsDialog *
144 psppire_val_labs_dialog_new (const struct variable *var)
145 {
146   return PSPPIRE_VAL_LABS_DIALOG (
147     g_object_new (PSPPIRE_TYPE_VAL_LABS_DIALOG,
148                   "variable", var,
149                   NULL));
150 }
151
152 struct val_labs *
153 psppire_val_labs_dialog_run (GtkWindow *parent_window,
154                              const struct variable *var)
155 {
156   PsppireValLabsDialog *dialog;
157   struct val_labs *labs;
158
159   dialog = psppire_val_labs_dialog_new (var);
160   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
161   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
162   gtk_widget_show (GTK_WIDGET (dialog));
163
164   labs = (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK
165           ? val_labs_clone (psppire_val_labs_dialog_get_value_labels (dialog))
166           : NULL);
167
168   gtk_widget_destroy (GTK_WIDGET (dialog));
169
170   return labs;
171 }
172
173 /* This callback occurs when the text in the label entry box
174    is changed */
175 static void
176 on_label_entry_change (GtkEntry *entry, gpointer data)
177 {
178   union value v;
179   const gchar *text ;
180   PsppireValLabsDialog *dialog = data;
181   g_assert (dialog->labs);
182
183   text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
184
185   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
186
187   if (val_labs_find (dialog->labs, &v))
188     {
189       gtk_widget_set_sensitive (dialog->change_button, TRUE);
190       gtk_widget_set_sensitive (dialog->add_button, FALSE);
191     }
192   else
193     {
194       gtk_widget_set_sensitive (dialog->change_button, FALSE);
195       gtk_widget_set_sensitive (dialog->add_button, TRUE);
196     }
197
198   value_destroy (&v, val_labs_get_width (dialog->labs));
199 }
200
201 /* This callback occurs when Enter is pressed in the label entry box. */
202 static void
203 on_label_entry_activate (GtkEntry *entry, gpointer data)
204 {
205   PsppireValLabsDialog *dialog = data;
206   do_change (dialog);
207 }
208
209 /* Return the value-label pair currently selected in the dialog box  */
210
211 /* Set the TREEVIEW list cursor to the item which has the value VAL */
212 static void
213 select_treeview_from_value (GtkTreeView *treeview, union value *val)
214 {
215   GtkTreePath *path ;
216
217   /*
218     We do this with a linear search through the model --- hardly
219     efficient, but the list is short ... */
220   GtkTreeIter iter;
221
222   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
223
224   gboolean success;
225   for (success = gtk_tree_model_get_iter_first (model, &iter);
226        success;
227        success = gtk_tree_model_iter_next (model, &iter))
228     {
229       union value v;
230       GValue gvalue = {0};
231
232       gtk_tree_model_get_value (model, &iter, 1, &gvalue);
233
234       v.f = g_value_get_double (&gvalue);
235
236       if (0 == memcmp (&v, val, sizeof (union value)))
237         {
238           break;
239         }
240     }
241
242   path = gtk_tree_model_get_path (model, &iter);
243   if (path)
244     {
245       gtk_tree_view_set_cursor (treeview, path, 0, 0);
246       gtk_tree_path_free (path);
247     }
248
249 }
250
251
252 /* This callback occurs when the text in the value entry box is
253    changed */
254 static void
255 on_value_entry_change (GtkEntry *entry, gpointer data)
256 {
257   const char *s;
258
259   PsppireValLabsDialog *dialog = data;
260
261   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
262
263   union value v;
264   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
265
266   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
267                          dialog->change_handler_id);
268
269   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
270
271
272   if ((s = val_labs_find (dialog->labs, &v)))
273     {
274       gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), s);
275       gtk_widget_set_sensitive (dialog->add_button, FALSE);
276       gtk_widget_set_sensitive (dialog->remove_button, TRUE);
277       select_treeview_from_value (GTK_TREE_VIEW (dialog->treeview), &v);
278     }
279   else
280     {
281       gtk_widget_set_sensitive (dialog->remove_button, FALSE);
282       gtk_widget_set_sensitive (dialog->add_button, TRUE);
283     }
284
285   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
286                          dialog->change_handler_id);
287
288   value_destroy (&v, val_labs_get_width (dialog->labs));
289 }
290
291 /* This callback occurs when Enter is pressed in the value entry box. */
292 static void
293 on_value_entry_activate (GtkEntry *entry, gpointer data)
294 {
295   PsppireValLabsDialog *dialog = data;
296
297   gtk_widget_grab_focus (dialog->label_entry);
298 }
299
300 static gboolean
301 get_selected_tuple (PsppireValLabsDialog *dialog,
302                     union value *valuep, const char **label)
303 {
304   GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
305
306   GtkTreeIter iter ;
307   GValue the_value = {0};
308   union value value;
309
310   GtkTreeSelection* sel =  gtk_tree_view_get_selection (treeview);
311
312   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
313
314   if (! gtk_tree_selection_get_selected (sel, &model, &iter))
315     return FALSE;
316
317   gtk_tree_model_get_value (model, &iter, 1, &the_value);
318
319   value.f = g_value_get_double (&the_value);
320   g_value_unset (&the_value);
321
322   if (valuep != NULL)
323     *valuep = value;
324   if (label != NULL)
325     {
326       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
327       if (vl != NULL)
328         *label = val_lab_get_escaped_label (vl);
329     }
330
331   return TRUE;
332 }
333
334
335 static void repopulate_dialog (PsppireValLabsDialog *dialog);
336
337 /* Callback which occurs when the "Change" button is clicked */
338 static void
339 on_change (GtkWidget *w, gpointer data)
340 {
341   PsppireValLabsDialog *dialog = data;
342   do_change (dialog);
343 }
344
345 static void
346 do_change (PsppireValLabsDialog *dialog)
347 {
348   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
349
350   union value v;
351
352   if (text_to_value__ (val_text, &dialog->format, dialog->encoding, &v))
353     {
354       val_labs_replace (dialog->labs, &v,
355                         gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
356
357       gtk_widget_set_sensitive (dialog->change_button, FALSE);
358
359       repopulate_dialog (dialog);
360       gtk_widget_grab_focus (dialog->value_entry);
361
362       value_destroy (&v, val_labs_get_width (dialog->labs));
363     }
364 }
365
366 /* Callback which occurs when the "Add" button is clicked */
367 static void
368 on_add (GtkWidget *w, gpointer data)
369 {
370   PsppireValLabsDialog *dialog = data;
371
372   union value v;
373
374   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
375
376   if (text_to_value__ (text, &dialog->format, dialog->encoding, &v))
377     {
378       if (val_labs_add (dialog->labs, &v,
379                         gtk_entry_get_text
380                         (GTK_ENTRY (dialog->label_entry))))
381         {
382           gtk_widget_set_sensitive (dialog->add_button, FALSE);
383
384           repopulate_dialog (dialog);
385           gtk_widget_grab_focus (dialog->value_entry);
386         }
387
388       value_destroy (&v, val_labs_get_width (dialog->labs));
389     }
390 }
391
392 /* Callback which occurs when the "Remove" button is clicked */
393 static void
394 on_remove (GtkWidget *w, gpointer data)
395 {
396   PsppireValLabsDialog *dialog = data;
397
398   union value value;
399   struct val_lab *vl;
400
401   if (! get_selected_tuple (dialog, &value, NULL))
402     return;
403
404   vl = val_labs_lookup (dialog->labs, &value);
405   if (vl != NULL)
406     val_labs_remove (dialog->labs, vl);
407
408   repopulate_dialog (dialog);
409   gtk_widget_grab_focus (dialog->value_entry);
410
411   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
412 }
413
414
415
416 /* Callback which occurs when a line item is selected in the list of
417    value--label pairs.*/
418 static void
419 on_select_row (GtkTreeView *treeview, gpointer data)
420 {
421   PsppireValLabsDialog *dialog = data;
422
423   union value value;
424   const char *label = NULL;
425
426   gchar *text;
427
428   if (! get_selected_tuple (dialog, &value, &label))
429     return;
430
431   text = value_to_text__ (value, &dialog->format, dialog->encoding);
432
433   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
434                          dialog->value_handler_id);
435
436   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
437
438   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
439                          dialog->value_handler_id);
440   g_free (text);
441
442   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
443                          dialog->change_handler_id);
444
445
446   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
447                       label);
448
449   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
450                          dialog->change_handler_id);
451
452   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
453   gtk_widget_set_sensitive (dialog->change_button, FALSE);
454 }
455
456
457 /* Create a new dialog box
458    (there should  normally be only one)*/
459 static GObject *
460 psppire_val_labs_dialog_constructor (GType                  type,
461                                      guint                  n_properties,
462                                      GObjectConstructParam *properties)
463 {
464   PsppireValLabsDialog *dialog;
465   GtkTreeViewColumn *column;
466
467   GtkCellRenderer *renderer ;
468
469   GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
470
471   GtkContainer *content_area;
472   GObject *obj;
473
474   obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
475     type, n_properties, properties);
476   dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
477
478   g_object_set (dialog, "help_page", "VALUE-LABELS",
479                 "title", _("Value Labels"), NULL);
480
481   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
482   gtk_container_add (GTK_CONTAINER (content_area),
483                      get_widget_assert (xml, "val-labs-dialog"));
484
485   dialog->value_entry = get_widget_assert (xml,"value_entry");
486   dialog->label_entry = get_widget_assert (xml,"label_entry");
487
488   dialog->add_button = get_widget_assert (xml, "val_labs_add");
489   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
490   dialog->change_button = get_widget_assert (xml, "val_labs_change");
491
492   dialog->treeview = get_widget_assert (xml,"treeview1");
493
494   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
495
496   renderer = gtk_cell_renderer_text_new ();
497
498   column = gtk_tree_view_column_new_with_attributes ("Title",
499                                                      renderer,
500                                                      "text",
501                                                      0,
502                                                      NULL);
503
504   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
505
506   dialog->change_handler_id =
507     g_signal_connect (dialog->label_entry,
508                      "changed",
509                      G_CALLBACK (on_label_entry_change), dialog);
510   g_signal_connect (dialog->label_entry, "activate",
511                     G_CALLBACK (on_label_entry_activate), dialog);
512
513   dialog->value_handler_id  =
514     g_signal_connect (dialog->value_entry,
515                      "changed",
516                      G_CALLBACK (on_value_entry_change), dialog);
517   g_signal_connect (dialog->value_entry, "activate",
518                     G_CALLBACK (on_value_entry_activate), dialog);
519
520   g_signal_connect (dialog->change_button,
521                    "clicked",
522                    G_CALLBACK (on_change), dialog);
523
524
525   g_signal_connect (dialog->treeview, "cursor-changed",
526                    G_CALLBACK (on_select_row), dialog);
527
528   g_signal_connect (dialog->remove_button, "clicked",
529                    G_CALLBACK (on_remove), dialog);
530
531   g_signal_connect (dialog->add_button, "clicked",
532                    G_CALLBACK (on_add), dialog);
533
534   dialog->labs = NULL;
535
536   g_object_unref (xml);
537
538   return obj;
539 }
540
541
542 /* Populate the components of the dialog box, from the 'labs' member
543    variable */
544 static void
545 repopulate_dialog (PsppireValLabsDialog *dialog)
546 {
547   const struct val_lab **labels;
548   size_t n_labels;
549   size_t i;
550
551   GtkTreeIter iter;
552
553   GtkListStore *list_store = gtk_list_store_new (2,
554                                                  G_TYPE_STRING,
555                                                  G_TYPE_DOUBLE);
556
557   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
558                          dialog->change_handler_id);
559   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
560                          dialog->value_handler_id);
561
562   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
563   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
564
565   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
566                          dialog->value_handler_id);
567   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
568                            dialog->change_handler_id);
569
570   labels = val_labs_sorted (dialog->labs);
571   n_labels = val_labs_count (dialog->labs);
572   for (i = 0; i < n_labels; i++)
573     {
574       const struct val_lab *vl = labels[i];
575
576       gchar *const vstr  =
577         value_to_text__ (vl->value, &dialog->format, dialog->encoding);
578
579       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
580                                            val_lab_get_escaped_label (vl));
581
582       gtk_list_store_append (list_store, &iter);
583       gtk_list_store_set (list_store, &iter,
584                           0, text,
585                           1, vl->value.f,
586                           -1);
587
588       g_free (text);
589       g_free (vstr);
590     }
591   free (labels);
592
593   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
594                           GTK_TREE_MODEL (list_store));
595
596   g_object_unref (list_store);
597
598 }
599
600 void
601 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
602                                       const struct variable *var)
603 {
604   val_labs_destroy (dialog->labs);
605   dialog->labs = NULL;
606
607   g_free (dialog->encoding);
608   dialog->encoding = NULL;
609
610   if (var != NULL)
611     {
612       dialog->labs = val_labs_clone (var_get_value_labels (var));
613       dialog->encoding = g_strdup (var_get_encoding (var));
614       dialog->format = *var_get_print_format (var);
615     }
616   else
617     dialog->format = F_8_0;
618
619   if (dialog->labs == NULL)
620     dialog->labs = val_labs_create (var_get_width (var));
621
622   repopulate_dialog (dialog);
623 }
624
625 const struct val_labs *
626 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
627 {
628   return dialog->labs;
629 }