help: added help page info to variable sheet dialogs
[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", NULL);
479
480   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
481   gtk_container_add (GTK_CONTAINER (content_area),
482                      get_widget_assert (xml, "val-labs-dialog"));
483
484   dialog->value_entry = get_widget_assert (xml,"value_entry");
485   dialog->label_entry = get_widget_assert (xml,"label_entry");
486
487   dialog->add_button = get_widget_assert (xml, "val_labs_add");
488   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
489   dialog->change_button = get_widget_assert (xml, "val_labs_change");
490
491   dialog->treeview = get_widget_assert (xml,"treeview1");
492
493   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
494
495   renderer = gtk_cell_renderer_text_new ();
496
497   column = gtk_tree_view_column_new_with_attributes ("Title",
498                                                      renderer,
499                                                      "text",
500                                                      0,
501                                                      NULL);
502
503   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
504
505   dialog->change_handler_id =
506     g_signal_connect (dialog->label_entry,
507                      "changed",
508                      G_CALLBACK (on_label_entry_change), dialog);
509   g_signal_connect (dialog->label_entry, "activate",
510                     G_CALLBACK (on_label_entry_activate), dialog);
511
512   dialog->value_handler_id  =
513     g_signal_connect (dialog->value_entry,
514                      "changed",
515                      G_CALLBACK (on_value_entry_change), dialog);
516   g_signal_connect (dialog->value_entry, "activate",
517                     G_CALLBACK (on_value_entry_activate), dialog);
518
519   g_signal_connect (dialog->change_button,
520                    "clicked",
521                    G_CALLBACK (on_change), dialog);
522
523
524   g_signal_connect (dialog->treeview, "cursor-changed",
525                    G_CALLBACK (on_select_row), dialog);
526
527   g_signal_connect (dialog->remove_button, "clicked",
528                    G_CALLBACK (on_remove), dialog);
529
530   g_signal_connect (dialog->add_button, "clicked",
531                    G_CALLBACK (on_add), dialog);
532
533   dialog->labs = NULL;
534
535   g_object_unref (xml);
536
537   return obj;
538 }
539
540
541 /* Populate the components of the dialog box, from the 'labs' member
542    variable */
543 static void
544 repopulate_dialog (PsppireValLabsDialog *dialog)
545 {
546   const struct val_lab **labels;
547   size_t n_labels;
548   size_t i;
549
550   GtkTreeIter iter;
551
552   GtkListStore *list_store = gtk_list_store_new (2,
553                                                  G_TYPE_STRING,
554                                                  G_TYPE_DOUBLE);
555
556   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
557                          dialog->change_handler_id);
558   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
559                          dialog->value_handler_id);
560
561   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
562   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
563
564   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
565                          dialog->value_handler_id);
566   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
567                            dialog->change_handler_id);
568
569   labels = val_labs_sorted (dialog->labs);
570   n_labels = val_labs_count (dialog->labs);
571   for (i = 0; i < n_labels; i++)
572     {
573       const struct val_lab *vl = labels[i];
574
575       gchar *const vstr  =
576         value_to_text__ (vl->value, &dialog->format, dialog->encoding);
577
578       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
579                                            val_lab_get_escaped_label (vl));
580
581       gtk_list_store_append (list_store, &iter);
582       gtk_list_store_set (list_store, &iter,
583                           0, text,
584                           1, vl->value.f,
585                           -1);
586
587       g_free (text);
588       g_free (vstr);
589     }
590   free (labels);
591
592   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
593                           GTK_TREE_MODEL (list_store));
594
595   g_object_unref (list_store);
596
597 }
598
599 void
600 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
601                                       const struct variable *var)
602 {
603   val_labs_destroy (dialog->labs);
604   dialog->labs = NULL;
605
606   g_free (dialog->encoding);
607   dialog->encoding = NULL;
608
609   if (var != NULL)
610     {
611       dialog->labs = val_labs_clone (var_get_value_labels (var));
612       dialog->encoding = g_strdup (var_get_encoding (var));
613       dialog->format = *var_get_print_format (var);
614     }
615   else
616     dialog->format = F_8_0;
617
618   if (dialog->labs == NULL)
619     dialog->labs = val_labs_create (var_get_width (var));
620
621   repopulate_dialog (dialog);
622 }
623
624 const struct val_labs *
625 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
626 {
627   return dialog->labs;
628 }