treewide: Use struct fmt_spec by value instead of pointer in most cases.
[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
130   obj->labs = val_labs_create (0);
131 }
132
133 static void
134 psppire_val_labs_dialog_finalize (GObject *obj)
135 {
136   PsppireValLabsDialog *dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
137
138   val_labs_destroy (dialog->labs);
139   g_free (dialog->encoding);
140
141   G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->finalize (obj);
142 }
143
144 PsppireValLabsDialog *
145 psppire_val_labs_dialog_new (const struct variable *var)
146 {
147   PsppireValLabsDialog *obj
148     = PSPPIRE_VAL_LABS_DIALOG (g_object_new (PSPPIRE_TYPE_VAL_LABS_DIALOG,
149                                              "variable", var,
150                                              NULL));
151
152   return obj;
153 }
154
155 struct val_labs *
156 psppire_val_labs_dialog_run (GtkWindow *parent_window,
157                              const struct variable *var)
158 {
159   PsppireValLabsDialog *dialog;
160   struct val_labs *labs;
161
162   dialog = psppire_val_labs_dialog_new (var);
163   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
164   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
165   gtk_widget_show (GTK_WIDGET (dialog));
166
167   gint response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
168   switch (response)
169     {
170       case GTK_RESPONSE_OK:
171         labs = val_labs_clone (psppire_val_labs_dialog_get_value_labels (dialog));
172         break;
173       default:
174         labs = NULL;
175         break;
176     }
177
178   gtk_widget_destroy (GTK_WIDGET (dialog));
179
180   return labs;
181 }
182
183 /* This callback occurs when the text in the label entry box
184    is changed */
185 static void
186 on_label_entry_change (GtkEntry *entry, gpointer data)
187 {
188   union value v;
189   const gchar *text ;
190   PsppireValLabsDialog *dialog = data;
191   g_assert (dialog->labs);
192
193   text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
194
195   text_to_value__ (text, dialog->format, dialog->encoding, &v);
196
197   if (val_labs_find (dialog->labs, &v))
198     {
199       gtk_widget_set_sensitive (dialog->change_button, TRUE);
200       gtk_widget_set_sensitive (dialog->add_button, FALSE);
201     }
202   else
203     {
204       gtk_widget_set_sensitive (dialog->change_button, FALSE);
205       gtk_widget_set_sensitive (dialog->add_button, TRUE);
206     }
207
208   value_destroy (&v, val_labs_get_width (dialog->labs));
209 }
210
211 /* This callback occurs when Enter is pressed in the label entry box. */
212 static void
213 on_label_entry_activate (GtkEntry *entry, gpointer data)
214 {
215   PsppireValLabsDialog *dialog = data;
216   do_change (dialog);
217 }
218
219 /* Return the value-label pair currently selected in the dialog box  */
220
221 /* Set the TREEVIEW list cursor to the item which has the value VAL */
222 static void
223 select_treeview_from_value (GtkTreeView *treeview, union value *val)
224 {
225   GtkTreePath *path ;
226
227   /*
228     We do this with a linear search through the model --- hardly
229     efficient, but the list is short ... */
230   GtkTreeIter iter;
231
232   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
233
234   gboolean success;
235   for (success = gtk_tree_model_get_iter_first (model, &iter);
236        success;
237        success = gtk_tree_model_iter_next (model, &iter))
238     {
239       union value v;
240       GValue gvalue = {0};
241
242       gtk_tree_model_get_value (model, &iter, 1, &gvalue);
243
244       v.f = g_value_get_double (&gvalue);
245
246       if (0 == memcmp (&v, val, sizeof (union value)))
247         {
248           break;
249         }
250     }
251
252   path = gtk_tree_model_get_path (model, &iter);
253   if (path)
254     {
255       gtk_tree_view_set_cursor (treeview, path, 0, 0);
256       gtk_tree_path_free (path);
257     }
258
259 }
260
261
262 /* This callback occurs when the text in the value entry box is
263    changed */
264 static void
265 on_value_entry_change (GtkEntry *entry, gpointer data)
266 {
267   const char *s;
268
269   PsppireValLabsDialog *dialog = data;
270
271   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
272
273   union value v;
274   text_to_value__ (text, dialog->format, dialog->encoding, &v);
275
276   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
277                          dialog->change_handler_id);
278
279   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
280
281
282   if ((s = val_labs_find (dialog->labs, &v)))
283     {
284       gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), s);
285       gtk_widget_set_sensitive (dialog->add_button, FALSE);
286       gtk_widget_set_sensitive (dialog->remove_button, TRUE);
287       select_treeview_from_value (GTK_TREE_VIEW (dialog->treeview), &v);
288     }
289   else
290     {
291       gtk_widget_set_sensitive (dialog->remove_button, FALSE);
292       gtk_widget_set_sensitive (dialog->add_button, TRUE);
293     }
294
295   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
296                          dialog->change_handler_id);
297
298   value_destroy (&v, val_labs_get_width (dialog->labs));
299 }
300
301 /* This callback occurs when Enter is pressed in the value entry box. */
302 static void
303 on_value_entry_activate (GtkEntry *entry, gpointer data)
304 {
305   PsppireValLabsDialog *dialog = data;
306
307   gtk_widget_grab_focus (dialog->label_entry);
308 }
309
310 static gboolean
311 get_selected_tuple (PsppireValLabsDialog *dialog,
312                     union value *valuep, const char **label)
313 {
314   GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
315
316   GtkTreeIter iter ;
317   GValue the_value = {0};
318   union value value;
319
320   GtkTreeSelection* sel =  gtk_tree_view_get_selection (treeview);
321
322   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
323
324   if (! gtk_tree_selection_get_selected (sel, &model, &iter))
325     return FALSE;
326
327   gtk_tree_model_get_value (model, &iter, 1, &the_value);
328
329   value.f = g_value_get_double (&the_value);
330   g_value_unset (&the_value);
331
332   if (valuep != NULL)
333     *valuep = value;
334   if (label != NULL)
335     {
336       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
337       if (vl != NULL)
338         *label = val_lab_get_escaped_label (vl);
339     }
340
341   return TRUE;
342 }
343
344
345 static void repopulate_dialog (PsppireValLabsDialog *dialog);
346
347 /* Callback which occurs when the "Change" button is clicked */
348 static void
349 on_change (GtkWidget *w, gpointer data)
350 {
351   PsppireValLabsDialog *dialog = data;
352   do_change (dialog);
353 }
354
355 static void
356 do_change (PsppireValLabsDialog *dialog)
357 {
358   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
359
360   union value v;
361
362   if (text_to_value__ (val_text, dialog->format, dialog->encoding, &v))
363     {
364       val_labs_replace (dialog->labs, &v,
365                         gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
366
367       gtk_widget_set_sensitive (dialog->change_button, FALSE);
368
369       repopulate_dialog (dialog);
370       gtk_widget_grab_focus (dialog->value_entry);
371
372       value_destroy (&v, val_labs_get_width (dialog->labs));
373     }
374 }
375
376 /* Callback which occurs when the "Add" button is clicked */
377 static void
378 on_add (GtkWidget *w, gpointer data)
379 {
380   PsppireValLabsDialog *dialog = data;
381
382   union value v;
383
384   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
385
386   if (text_to_value__ (text, dialog->format, dialog->encoding, &v))
387     {
388       if (val_labs_add (dialog->labs, &v,
389                         gtk_entry_get_text
390                         (GTK_ENTRY (dialog->label_entry))))
391         {
392           gtk_widget_set_sensitive (dialog->add_button, FALSE);
393
394           repopulate_dialog (dialog);
395           gtk_widget_grab_focus (dialog->value_entry);
396         }
397
398       value_destroy (&v, val_labs_get_width (dialog->labs));
399     }
400 }
401
402 /* Callback which occurs when the "Remove" button is clicked */
403 static void
404 on_remove (GtkWidget *w, gpointer data)
405 {
406   PsppireValLabsDialog *dialog = data;
407
408   union value value;
409   struct val_lab *vl;
410
411   if (! get_selected_tuple (dialog, &value, NULL))
412     return;
413
414   vl = val_labs_lookup (dialog->labs, &value);
415   if (vl != NULL)
416     val_labs_remove (dialog->labs, vl);
417
418   repopulate_dialog (dialog);
419   gtk_widget_grab_focus (dialog->value_entry);
420
421   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
422 }
423
424
425
426 /* Callback which occurs when a line item is selected in the list of
427    value--label pairs.*/
428 static void
429 on_select_row (GtkTreeView *treeview, gpointer data)
430 {
431   PsppireValLabsDialog *dialog = data;
432
433   union value value;
434   const char *label = NULL;
435
436   gchar *text;
437
438   if (! get_selected_tuple (dialog, &value, &label))
439     return;
440
441   text = value_to_text__ (value, dialog->format, dialog->encoding);
442
443   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
444                          dialog->value_handler_id);
445
446   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
447
448   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
449                          dialog->value_handler_id);
450   g_free (text);
451
452   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
453                          dialog->change_handler_id);
454
455
456   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
457                       label);
458
459   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
460                          dialog->change_handler_id);
461
462   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
463   gtk_widget_set_sensitive (dialog->change_button, FALSE);
464 }
465
466
467 /* Create a new dialog box
468    (there should  normally be only one)*/
469 static GObject *
470 psppire_val_labs_dialog_constructor (GType                  type,
471                                      guint                  n_properties,
472                                      GObjectConstructParam *properties)
473 {
474   PsppireValLabsDialog *dialog;
475   GtkTreeViewColumn *column;
476
477   GtkCellRenderer *renderer ;
478
479   GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
480
481   GtkContainer *content_area;
482   GObject *obj;
483
484   obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
485     type, n_properties, properties);
486   dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
487
488   g_object_set (dialog, "help-page", "VALUE-LABELS",
489                 "title", _("Value Labels"), NULL);
490
491   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
492   gtk_container_add (GTK_CONTAINER (content_area),
493                      get_widget_assert (xml, "val-labs-dialog"));
494
495   dialog->value_entry = get_widget_assert (xml,"value_entry");
496   dialog->label_entry = get_widget_assert (xml,"label_entry");
497
498   dialog->add_button = get_widget_assert (xml, "val_labs_add");
499   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
500   dialog->change_button = get_widget_assert (xml, "val_labs_change");
501
502   dialog->treeview = get_widget_assert (xml,"treeview1");
503
504   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
505
506   renderer = gtk_cell_renderer_text_new ();
507
508   column = gtk_tree_view_column_new_with_attributes ("Title",
509                                                      renderer,
510                                                      "text",
511                                                      0,
512                                                      NULL);
513
514   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
515
516   dialog->change_handler_id =
517     g_signal_connect (dialog->label_entry,
518                      "changed",
519                      G_CALLBACK (on_label_entry_change), dialog);
520   g_signal_connect (dialog->label_entry, "activate",
521                     G_CALLBACK (on_label_entry_activate), dialog);
522
523   dialog->value_handler_id  =
524     g_signal_connect (dialog->value_entry,
525                      "changed",
526                      G_CALLBACK (on_value_entry_change), dialog);
527   g_signal_connect (dialog->value_entry, "activate",
528                     G_CALLBACK (on_value_entry_activate), dialog);
529
530   g_signal_connect (dialog->change_button,
531                    "clicked",
532                    G_CALLBACK (on_change), dialog);
533
534
535   g_signal_connect (dialog->treeview, "cursor-changed",
536                    G_CALLBACK (on_select_row), dialog);
537
538   g_signal_connect (dialog->remove_button, "clicked",
539                    G_CALLBACK (on_remove), dialog);
540
541   g_signal_connect (dialog->add_button, "clicked",
542                    G_CALLBACK (on_add), dialog);
543
544   /* dialog->labs must not be set here, because as a member of a singleton
545      class its value persists "between" objects.  */
546   /* dialog->labs = NULL; */
547
548   g_object_unref (xml);
549
550   return obj;
551 }
552
553
554 /* Populate the components of the dialog box, from the 'labs' member
555    variable */
556 static void
557 repopulate_dialog (PsppireValLabsDialog *dialog)
558 {
559   const struct val_lab **labels;
560   size_t n_labels;
561   size_t i;
562
563   GtkTreeIter iter;
564
565   GtkListStore *list_store = gtk_list_store_new (2,
566                                                  G_TYPE_STRING,
567                                                  G_TYPE_DOUBLE);
568
569   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
570                          dialog->change_handler_id);
571   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
572                          dialog->value_handler_id);
573
574   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
575   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
576
577   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
578                          dialog->value_handler_id);
579   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
580                            dialog->change_handler_id);
581
582   labels = val_labs_sorted (dialog->labs);
583   n_labels = val_labs_count (dialog->labs);
584   for (i = 0; i < n_labels; i++)
585     {
586       const struct val_lab *vl = labels[i];
587
588       gchar *const vstr  =
589         value_to_text__ (vl->value, dialog->format, dialog->encoding);
590
591       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
592                                            val_lab_get_escaped_label (vl));
593
594       gtk_list_store_append (list_store, &iter);
595       gtk_list_store_set (list_store, &iter,
596                           0, text,
597                           1, vl->value.f,
598                           -1);
599
600       g_free (text);
601       g_free (vstr);
602     }
603   free (labels);
604
605   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
606                           GTK_TREE_MODEL (list_store));
607
608   g_object_unref (list_store);
609 }
610
611 void
612 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
613                                       const struct variable *var)
614 {
615   val_labs_destroy (dialog->labs);
616   dialog->labs = NULL;
617
618   g_free (dialog->encoding);
619   dialog->encoding = NULL;
620
621   if (var != NULL)
622     {
623       dialog->labs = val_labs_clone (var_get_value_labels (var));
624       dialog->encoding = g_strdup (var_get_encoding (var));
625       dialog->format = var_get_print_format (var);
626     }
627   else
628     dialog->format = F_8_0;
629
630   if (dialog->labs == NULL)
631     dialog->labs = val_labs_create (var_get_width (var));
632
633   repopulate_dialog (dialog);
634 }
635
636 const struct val_labs *
637 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
638 {
639   return dialog->labs;
640 }