5bbaf43df7bcc9c7adef3fa4f0af8fe00473ab7c
[pspp] / src / ui / gui / psppire-dialog-action-recode.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009, 2010, 2011, 2012, 2014, 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 #include <config.h>
20
21 #include "psppire-var-view.h"
22
23 #include "psppire-dialog-action-recode.h"
24 #include "builder-wrapper.h"
25 #include <ui/gui/dialog-common.h>
26
27 #include "psppire-acr.h"
28
29 #include "psppire-selector.h"
30 #include "psppire-val-chooser.h"
31
32 #include "helper.h"
33 #include <ui/syntax-gen.h>
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37 #define N_(msgid) msgid
38
39
40 /* This might need to be changed to something less naive.
41    In particular, what happends with dates, etc?
42  */
43 static gchar *
44 num_to_string (gdouble x)
45 {
46   return g_strdup_printf ("%.*g", DBL_DIG + 1, x);
47 }
48
49 /* Define a boxed type to represent a value which is a candidate
50    to replace an existing value */
51
52 enum new_value_type
53  {
54    NV_NUMERIC,
55    NV_STRING,
56    NV_SYSMIS,
57    NV_COPY
58  };
59
60
61 struct new_value
62 {
63   enum new_value_type type;
64   union {
65     double v;
66     gchar *s;
67   } v;
68 };
69
70
71 static struct new_value *
72 new_value_copy (struct new_value *nv)
73 {
74   struct new_value *copy = g_memdup (nv, sizeof (*copy));
75
76   if (nv->type == NV_STRING)
77     copy->v.s = xstrdup (nv->v.s);
78
79   return copy;
80 }
81
82
83 static void
84 new_value_free (struct new_value *nv)
85 {
86   if (nv->type == NV_STRING)
87     g_free (nv->v.s);
88
89   g_free (nv);
90 }
91
92
93 static void
94 new_value_to_string (const GValue *src, GValue *dest)
95 {
96   const struct new_value *nv = g_value_get_boxed (src);
97
98   g_assert (nv);
99
100   switch (nv->type)
101     {
102     case NV_NUMERIC:
103       {
104         gchar *text = g_strdup_printf ("%.*g", DBL_DIG + 1, nv->v.v);
105         g_value_set_string (dest, text);
106         g_free (text);
107       }
108       break;
109     case NV_STRING:
110       g_value_set_string (dest, nv->v.s);
111       break;
112     case NV_COPY:
113       g_value_set_string (dest, "COPY");
114       break;
115     case NV_SYSMIS:
116       g_value_set_string (dest, "SYSMIS");
117       break;
118     default:
119       /* Shouldn't ever happen */
120       g_warning ("Invalid type in new recode value");
121       g_value_set_string (dest, "???");
122       break;
123     }
124 }
125
126 GType
127 new_value_get_type (void)
128 {
129   static GType t = 0;
130
131   if (t == 0)
132     {
133       t = g_boxed_type_register_static  ("psppire-recode-new-values",
134                                          (GBoxedCopyFunc) new_value_copy,
135                                          (GBoxedFreeFunc) new_value_free);
136
137       g_value_register_transform_func (t, G_TYPE_STRING,
138                                        new_value_to_string);
139     }
140
141   return t;
142 }
143
144 \f
145
146 static void
147 on_string_toggled (GtkToggleButton *b, PsppireDialogActionRecode *rd)
148 {
149   gboolean active;
150   if (! rd->input_var_is_string)
151     return ;
152
153   active = gtk_toggle_button_get_active (b);
154   gtk_widget_set_sensitive (rd->convert_button, !active);
155 }
156
157
158 static void
159 on_convert_toggled (GtkToggleButton *b, PsppireDialogActionRecode *rd)
160 {
161   gboolean active;
162
163   g_return_if_fail (rd->input_var_is_string);
164
165   active = gtk_toggle_button_get_active (b);
166   gtk_widget_set_sensitive (rd->string_button, !active);
167 }
168
169 static void
170 focus_value_entry (GtkWidget *w, PsppireDialogActionRecode *rd)
171 {
172   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
173     gtk_widget_grab_focus (rd->new_value_entry);
174 }
175
176
177 /* Callback for the new_value_entry and new_value_togglebutton widgets.
178    It's used to enable/disable the acr. */
179 static void
180 set_acr (PsppireDialogActionRecode *rd)
181 {
182   const gchar *text;
183
184   if (!gtk_toggle_button_get_active
185        (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_NEW_VALUE])))
186     {
187       psppire_acr_set_enabled (PSPPIRE_ACR (rd->acr), TRUE);
188       return;
189     }
190
191   text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
192
193   psppire_acr_set_enabled (PSPPIRE_ACR (rd->acr), !g_str_equal (text, ""));
194 }
195
196 enum
197   {
198     COL_VALUE_OLD,
199     COL_VALUE_NEW,
200     n_COL_VALUES
201   };
202
203 /* Callback which gets called when a new row is selected
204    in the acr's variable treeview.
205    We use if to set the togglebuttons and entries to correspond to the
206    selected row.
207 */
208 static void
209 on_acr_selection_change (GtkTreeSelection *selection, gpointer data)
210 {
211   PsppireDialogActionRecode *rd = data;
212   GtkTreeModel *model = NULL;
213   GtkTreeIter iter;
214
215   GValue ov_value = {0};
216   GValue nv_value = {0};
217   struct old_value *ov = NULL;
218   struct new_value *nv = NULL;
219
220   if (! gtk_tree_selection_get_selected (selection, &model, &iter))
221     return;
222
223
224   gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
225                             COL_VALUE_OLD, &ov_value);
226
227   gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
228                             COL_VALUE_NEW, &nv_value);
229
230   ov = g_value_get_boxed (&ov_value);
231   nv = g_value_get_boxed (&nv_value);
232
233   if (nv)
234     {
235       switch (nv->type)
236         {
237         case NV_NUMERIC:
238           {
239             gchar *str = num_to_string (nv->v.v);
240
241             gtk_toggle_button_set_active
242               (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
243
244             gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), str);
245             g_free (str);
246           }
247           break;
248         case NV_STRING:
249           gtk_toggle_button_set_active
250             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
251
252           gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), nv->v.s);
253           break;
254         case NV_SYSMIS:
255           gtk_toggle_button_set_active
256             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS]), TRUE);
257
258           break;
259         case NV_COPY:
260           gtk_toggle_button_set_active
261             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY]), TRUE);
262
263           break;
264         default:
265           g_warning ("Invalid new value type");
266           break;
267         }
268
269       g_value_unset (&nv_value);
270     }
271
272   psppire_val_chooser_set_status (PSPPIRE_VAL_CHOOSER (rd->old_value_chooser), ov);
273 }
274
275 /* Initialise VAL to reflect the current status of RD */
276 static gboolean
277 set_old_value (GValue *val, const PsppireDialogActionRecode *rd)
278 {
279   PsppireValChooser *vc = PSPPIRE_VAL_CHOOSER (rd->old_value_chooser);
280
281   struct old_value ov;
282
283   psppire_val_chooser_get_status (vc, &ov);
284
285   g_value_init (val, old_value_get_type ());
286   g_value_set_boxed (val, &ov);
287
288   return TRUE;
289 }
290
291
292 /* Initialse VAL to reflect the current status of RD */
293 static gboolean
294 set_new_value (GValue *val, const PsppireDialogActionRecode *rd)
295 {
296   const gchar *text = NULL;
297   struct new_value nv;
298
299   if (gtk_toggle_button_get_active
300       (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE])))
301     {
302       text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
303       nv.type = NV_NUMERIC;
304
305       if (PSPPIRE_DIALOG_ACTION_RECODE_CLASS (G_OBJECT_GET_CLASS (rd))->target_is_string (rd))
306         nv.type = NV_STRING;
307
308       if (nv.type == NV_STRING)
309         nv.v.s = g_strdup (text);
310       else
311         nv.v.v = g_strtod (text, 0);
312     }
313   else if (gtk_toggle_button_get_active
314             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY])))
315     {
316       nv.type = NV_COPY;
317     }
318   else if (gtk_toggle_button_get_active
319             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS])))
320     {
321       nv.type = NV_SYSMIS;
322     }
323   else
324     return FALSE;
325
326   g_value_init (val, new_value_get_type ());
327   g_value_set_boxed (val, &nv);
328
329   return TRUE;
330 }
331
332 /* A function to set a value in a column in the ACR */
333 static gboolean
334 set_value (gint col, GValue  *val, gpointer data)
335 {
336   PsppireDialogActionRecode *rd = data;
337
338   switch (col)
339     {
340     case COL_VALUE_OLD:
341       set_old_value (val, rd);
342       break;
343     case COL_VALUE_NEW:
344       set_new_value (val, rd);
345       break;
346     default:
347       return FALSE;
348     }
349
350   return TRUE;
351 }
352
353 static void
354 set_old_and_new_button_sensitivity (GtkTreeSelection *sel, PsppireDialogActionRecode *rd)
355 {
356   GtkTreeModel *model = NULL;
357
358   GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
359
360   gtk_widget_set_sensitive (rd->old_and_new, rows != NULL);
361 }
362
363 static void
364 run_old_and_new_dialog (PsppireDialogActionRecode *rd)
365 {
366   gint response;
367   GtkListStore *local_store = clone_list_store (rd->value_map);
368   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (rd);
369
370   psppire_acr_set_model (PSPPIRE_ACR (rd->acr), local_store);
371   psppire_acr_set_get_value_func (PSPPIRE_ACR (rd->acr), set_value, rd);
372
373   {
374     /* Find the type of the first variable (it's invariant that
375        all variables are of the same type) */
376     const struct variable *v;
377     GtkTreeIter iter;
378     GtkTreeModel *model =
379       gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
380
381     gboolean not_empty = gtk_tree_model_get_iter_first (model, &iter);
382
383     g_return_if_fail (not_empty);
384
385     gtk_tree_model_get (model, &iter, 0, &v, -1);
386
387     rd->input_var_is_string = var_is_alpha (v);
388
389     g_object_set (rd->old_value_chooser, "is-string", rd->input_var_is_string, NULL);
390
391     gtk_widget_set_sensitive (rd->toggle [BUTTON_NEW_SYSMIS],
392                               var_is_numeric (v));
393
394     gtk_widget_set_sensitive (rd->convert_button, var_is_alpha (v));
395   }
396
397
398   response = psppire_dialog_run (rd->old_and_new_dialog);
399   psppire_acr_set_model (PSPPIRE_ACR (rd->acr), NULL);
400
401
402   if (response == PSPPIRE_RESPONSE_CONTINUE)
403     {
404       g_object_unref (rd->value_map);
405       rd->value_map = clone_list_store (local_store);
406     }
407   else
408     g_object_unref (local_store);
409
410
411   psppire_dialog_notify_change (PSPPIRE_DIALOG (pda->dialog));
412 }
413
414
415 /* Sets the sensitivity of TARGET dependent upon the active status
416    of BUTTON */
417 static void
418 toggle_sensitivity (GtkToggleButton *button, GtkWidget *target)
419 {
420   gboolean state = gtk_toggle_button_get_active (button);
421
422   /*  g_print ("%s Setting %p (%s) to %d because of %p\n",
423       __FUNCTION__, target, gtk_widget_get_name (target), state, button); */
424
425   gtk_widget_set_sensitive (target, state);
426 }
427
428
429 \f
430
431 static void
432 psppire_dialog_action_recode_class_init (PsppireDialogActionRecodeClass *class);
433
434 G_DEFINE_TYPE (PsppireDialogActionRecode, psppire_dialog_action_recode, PSPPIRE_TYPE_DIALOG_ACTION);
435
436 void
437 psppire_dialog_action_recode_refresh (PsppireDialogAction *rd_)
438 {
439   PsppireDialogActionRecode *rd = PSPPIRE_DIALOG_ACTION_RECODE (rd_);
440
441   GtkTreeModel *vars =
442     gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
443
444   gtk_list_store_clear (GTK_LIST_STORE (vars));
445
446   gtk_widget_set_sensitive (rd->change_button, FALSE);
447   gtk_widget_set_sensitive (rd->new_name_entry, FALSE);
448   gtk_widget_set_sensitive (rd->new_label_entry, FALSE);
449   gtk_widget_set_sensitive (rd->old_and_new, FALSE);
450
451   gtk_list_store_clear (GTK_LIST_STORE (rd->value_map));
452 }
453
454
455 GtkBuilder *
456 psppire_dialog_action_recode_pre_activate (PsppireDialogActionRecode *act,
457                                            void (*populate_treeview) (PsppireDialogActionRecode *))
458 {
459   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (act);
460
461   GtkBuilder *xml = builder_new ("recode.ui");
462
463   pda->dialog = get_widget_assert   (xml, "recode-dialog");
464   pda->source = get_widget_assert   (xml, "treeview1");
465
466
467   GtkWidget *selector = get_widget_assert (xml, "psppire-selector1");
468   act->old_and_new  = get_widget_assert (xml, "button1");
469
470   act->output_variable_box = get_widget_assert (xml,"frame4");
471
472   act->change_button = get_widget_assert (xml, "change-button");
473   act->variable_treeview =   get_widget_assert (xml, "treeview2");
474   act->new_name_entry = get_widget_assert (xml, "dest-name-entry");
475   act->new_label_entry = get_widget_assert (xml, "dest-label-entry");
476
477   act->value_map = gtk_list_store_new (2,
478                                        old_value_get_type (),
479                                        new_value_get_type ());
480
481   if (populate_treeview)
482     populate_treeview (act);
483
484   psppire_selector_set_allow (PSPPIRE_SELECTOR (selector), homogeneous_types);
485
486   /* Set up the Old & New Values subdialog */
487   {
488     act->string_button = get_widget_assert (xml, "checkbutton1");
489     act->width_entry   = get_widget_assert (xml, "spinbutton1");
490
491     act->convert_button = get_widget_assert (xml, "checkbutton2");
492
493     act->old_value_chooser = get_widget_assert (xml, "val-chooser");
494
495     act->new_value_entry = get_widget_assert (xml, "entry1");
496
497
498     act->toggle[BUTTON_NEW_VALUE]  = get_widget_assert (xml, "radiobutton1");
499     act->toggle[BUTTON_NEW_SYSMIS] = get_widget_assert (xml, "radiobutton2");
500     act->toggle[BUTTON_NEW_COPY]   = get_widget_assert (xml, "radiobutton3");
501
502     act->new_copy_label = get_widget_assert (xml, "label3");
503     act->strings_box    = get_widget_assert (xml, "table3");
504
505     act->old_and_new_dialog =
506       PSPPIRE_DIALOG (get_widget_assert (xml, "old-new-values-dialog"));
507
508     act->acr = get_widget_assert (xml, "psppire-acr1");
509
510     g_signal_connect_swapped (act->toggle[BUTTON_NEW_VALUE], "toggled",
511                               G_CALLBACK (set_acr), act);
512
513     g_signal_connect_after (act->toggle[BUTTON_NEW_VALUE], "toggled",
514                             G_CALLBACK (focus_value_entry), act);
515
516     g_signal_connect_swapped (act->new_value_entry, "changed",
517                               G_CALLBACK (set_acr), act);
518
519     {
520       GtkTreeSelection *sel;
521       /* Remove the ACR's default column.  We don't like it */
522       GtkTreeViewColumn *column = gtk_tree_view_get_column (PSPPIRE_ACR(act->acr)->tv, 0);
523       gtk_tree_view_remove_column (PSPPIRE_ACR (act->acr)->tv, column);
524
525
526       column =
527         gtk_tree_view_column_new_with_attributes (_("Old"),
528                                                   gtk_cell_renderer_text_new (),
529                                                   "text", 0,
530                                                   NULL);
531
532       gtk_tree_view_append_column (PSPPIRE_ACR (act->acr)->tv, column);
533
534       column =
535         gtk_tree_view_column_new_with_attributes (_("New"),
536                                                   gtk_cell_renderer_text_new (),
537                                                   "text", 1,
538                                                   NULL);
539
540       gtk_tree_view_append_column (PSPPIRE_ACR(act->acr)->tv, column);
541       g_object_set (PSPPIRE_ACR (act->acr)->tv, "headers-visible", TRUE, NULL);
542
543
544       sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (PSPPIRE_ACR(act->acr)->tv));
545       g_signal_connect (sel, "changed",
546                         G_CALLBACK (on_acr_selection_change), act);
547     }
548
549
550     g_signal_connect_swapped (act->old_and_new, "clicked",
551                               G_CALLBACK (run_old_and_new_dialog), act);
552
553
554     GtkTreeSelection *sel =
555       gtk_tree_view_get_selection (GTK_TREE_VIEW (act->variable_treeview));
556     g_signal_connect (sel, "changed",
557                       G_CALLBACK (set_old_and_new_button_sensitivity), act);
558
559     g_signal_connect (act->toggle[BUTTON_NEW_VALUE], "toggled",
560                       G_CALLBACK (toggle_sensitivity), act->new_value_entry);
561
562     g_signal_connect (act->string_button, "toggled",
563                       G_CALLBACK (toggle_sensitivity), act->width_entry);
564
565     g_signal_connect (act->string_button, "toggled",
566                       G_CALLBACK (on_string_toggled), act);
567
568     g_signal_connect (act->convert_button, "toggled",
569                       G_CALLBACK (on_convert_toggled), act);
570     }
571   return xml;
572 }
573
574 /* Generate a syntax fragment for NV and append it to STR */
575 static void
576 new_value_append_syntax (struct string *dds, const struct new_value *nv)
577 {
578   switch (nv->type)
579     {
580     case NV_NUMERIC:
581       ds_put_c_format (dds, "%.*g", DBL_DIG + 1, nv->v.v);
582       break;
583     case NV_STRING:
584       syntax_gen_string (dds, ss_cstr (nv->v.s));
585       break;
586     case NV_COPY:
587       ds_put_cstr (dds, "COPY");
588       break;
589     case NV_SYSMIS:
590       ds_put_cstr (dds, "SYSMIS");
591       break;
592     default:
593       /* Shouldn't ever happen */
594       g_warning ("Invalid type in new recode value");
595       ds_put_cstr (dds, "???");
596       break;
597     }
598 }
599
600
601 char *
602 psppire_dialog_action_recode_generate_syntax (const PsppireDialogAction *act,
603                                               void (*append_string_decls) (const PsppireDialogActionRecode *, struct string *),
604                                               void (*append_into_clause) (const PsppireDialogActionRecode *, struct string *),
605                                               void (*append_new_value_labels) (const PsppireDialogActionRecode *, struct string *))
606 {
607   PsppireDialogActionRecode *rd = PSPPIRE_DIALOG_ACTION_RECODE (act);
608   gboolean ok;
609   GtkTreeIter iter;
610   gchar *text;
611   struct string dds;
612
613   ds_init_empty (&dds);
614
615   append_string_decls (rd, &dds);
616
617   ds_put_cstr (&dds, "\nRECODE ");
618
619   psppire_var_view_append_names_str (PSPPIRE_VAR_VIEW (rd->variable_treeview), 0, &dds);
620
621   ds_put_cstr (&dds, "\n\t");
622
623   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rd->convert_button)))
624     {
625       ds_put_cstr (&dds, "(CONVERT) ");
626     }
627
628   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rd->value_map),
629                                            &iter);
630        ok;
631        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (rd->value_map), &iter))
632     {
633       GValue ov_value = {0};
634       GValue nv_value = {0};
635       struct old_value *ov;
636       struct new_value *nv;
637       gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
638                                 COL_VALUE_OLD, &ov_value);
639
640       gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
641                                 COL_VALUE_NEW, &nv_value);
642
643       ov = g_value_get_boxed (&ov_value);
644       nv = g_value_get_boxed (&nv_value);
645
646       ds_put_cstr (&dds, "(");
647
648       old_value_append_syntax (&dds, ov);
649       ds_put_cstr (&dds, " = ");
650       new_value_append_syntax (&dds, nv);
651
652       ds_put_cstr (&dds, ") ");
653       g_value_unset (&ov_value);
654       g_value_unset (&nv_value);
655     }
656
657   append_into_clause (rd, &dds);
658
659   ds_put_cstr (&dds, ".");
660
661   append_new_value_labels (rd, &dds);
662
663   ds_put_cstr (&dds, "\nEXECUTE.\n");
664
665
666   text = ds_steal_cstr (&dds);
667
668   ds_destroy (&dds);
669
670   return text;
671 }
672
673
674 static void
675 psppire_dialog_action_recode_class_init (PsppireDialogActionRecodeClass *class)
676 {
677 }
678
679
680 static void
681 psppire_dialog_action_recode_init (PsppireDialogActionRecode *act)
682 {
683 }
684