Recode Dialog: Convert to PsppireDialogAction
[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  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17
18 #include <config.h>
19
20 #include "psppire-var-view.h"
21
22 #include "psppire-dialog-action-recode.h"
23 #include "builder-wrapper.h"
24 #include <ui/gui/dialog-common.h>
25
26 #include "psppire-acr.h"
27
28 #include "psppire-selector.h"
29 #include "psppire-val-chooser.h"
30
31 #include "helper.h"
32 #include <ui/syntax-gen.h>
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36 #define N_(msgid) msgid
37
38
39 /* This might need to be changed to something less naive.
40    In particular, what happends with dates, etc?
41  */
42 static gchar *
43 num_to_string (gdouble x)
44 {
45   return g_strdup_printf ("%.*g", DBL_DIG + 1, x);
46 }
47
48 /* Define a boxed type to represent a value which is a candidate
49    to replace an existing value */
50
51 enum new_value_type
52  {
53    NV_NUMERIC,
54    NV_STRING,
55    NV_SYSMIS,
56    NV_COPY
57  };
58
59
60 struct new_value
61 {
62   enum new_value_type type;
63   union {
64     double v;
65     gchar *s;
66   } v;
67 };
68
69
70 static struct new_value *
71 new_value_copy (struct new_value *nv)
72 {
73   struct new_value *copy = g_memdup (nv, sizeof (*copy));
74
75   if ( nv->type == NV_STRING )
76     copy->v.s = xstrdup (nv->v.s);
77
78   return copy;
79 }
80
81
82 static void
83 new_value_free (struct new_value *nv)
84 {
85   if ( nv->type == NV_STRING )
86     g_free (nv->v.s);
87
88   g_free (nv);
89 }
90
91
92 static void
93 new_value_to_string (const GValue *src, GValue *dest)
94 {
95   const struct new_value *nv = g_value_get_boxed (src);
96
97   g_assert (nv);
98
99   switch (nv->type)
100     {
101     case NV_NUMERIC:
102       {
103         gchar *text = g_strdup_printf ("%.*g", DBL_DIG + 1, nv->v.v);
104         g_value_set_string (dest, text);
105         g_free (text);
106       }
107       break;
108     case NV_STRING:
109       g_value_set_string (dest, nv->v.s);
110       break;
111     case NV_COPY:
112       g_value_set_string (dest, "COPY");
113       break;
114     case NV_SYSMIS:
115       g_value_set_string (dest, "SYSMIS");
116       break;
117     default:
118       /* Shouldn't ever happen */
119       g_warning ("Invalid type in new recode value");
120       g_value_set_string (dest, "???");
121       break;
122     }
123 }
124
125 static GType
126 new_value_get_type (void)
127 {
128   static GType t = 0;
129
130   if (t == 0 )
131     {
132       t = g_boxed_type_register_static  ("psppire-recode-new-values",
133                                          (GBoxedCopyFunc) new_value_copy,
134                                          (GBoxedFreeFunc) new_value_free);
135
136       g_value_register_transform_func (t, G_TYPE_STRING,
137                                        new_value_to_string);
138     }
139
140   return t;
141 }
142
143 \f
144
145 static void
146 on_string_toggled (GtkToggleButton *b, PsppireDialogActionRecode *rd)
147 {
148   gboolean active;
149   if (! rd->input_var_is_string )
150     return ;
151
152   active = gtk_toggle_button_get_active (b);
153   gtk_widget_set_sensitive (rd->convert_button, !active);
154 }
155
156 /* Name-Label pair */
157 struct nlp
158 {
159   char *name;
160   char *label;
161 };
162
163 static struct nlp *
164 nlp_create (const char *name, const char *label)
165 {
166   struct nlp *nlp = xmalloc (sizeof *nlp);
167
168   nlp->name = g_strdup (name);
169
170   nlp->label = NULL;
171
172   if ( 0 != strcmp ("", label))
173     nlp->label = g_strdup (label);
174
175   return nlp;
176 }
177
178 static void
179 nlp_destroy (gpointer data)
180 {
181   struct nlp *nlp = data ;
182   if ( ! nlp )
183     return;
184
185   g_free (nlp->name);
186   g_free (nlp->label);
187   g_free (nlp);
188 }
189
190
191 /* Callback which gets called when a new row is selected
192    in the variable treeview.
193    It sets the name and label entry widgets to reflect the
194    currently selected row.
195  */
196 static void
197 on_selection_change (GtkTreeSelection *selection, gpointer data)
198 {
199   PsppireDialogActionRecode *rd = data;
200
201   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
202
203   GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
204
205   if ( rows && !rows->next)
206     {
207       /* Exactly one row is selected */
208       struct nlp *nlp;
209       struct variable *var;
210       gboolean ok;
211       GtkTreeIter iter;
212
213       gtk_widget_set_sensitive  (rd->change_button, TRUE);
214       gtk_widget_set_sensitive  (rd->new_name_entry, TRUE);
215       gtk_widget_set_sensitive  (rd->new_label_entry, TRUE);
216
217       ok = gtk_tree_model_get_iter (model, &iter, (GtkTreePath*) rows->data);
218       g_return_if_fail (ok);
219
220       gtk_tree_model_get (model, &iter,
221                           0, &var, 
222                           -1);
223
224       nlp = g_hash_table_lookup (rd->varmap, var);
225
226       if (nlp)
227         {
228           gtk_entry_set_text (GTK_ENTRY (rd->new_name_entry), nlp->name ? nlp->name : "");
229           gtk_entry_set_text (GTK_ENTRY (rd->new_label_entry), nlp->label ? nlp->label : "");
230         }
231       else
232         {
233           gtk_entry_set_text (GTK_ENTRY (rd->new_name_entry), "");
234           gtk_entry_set_text (GTK_ENTRY (rd->new_label_entry), "");
235         }
236     }
237   else
238     {
239       gtk_widget_set_sensitive  (rd->change_button, FALSE);
240       gtk_widget_set_sensitive  (rd->new_name_entry, FALSE);
241       gtk_widget_set_sensitive  (rd->new_label_entry, FALSE);
242
243       gtk_entry_set_text (GTK_ENTRY (rd->new_name_entry), "");
244       gtk_entry_set_text (GTK_ENTRY (rd->new_label_entry), "");
245     }
246
247
248   g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
249   g_list_free (rows);
250 }
251
252
253 static void
254 on_convert_toggled (GtkToggleButton *b, PsppireDialogActionRecode *rd)
255 {
256   gboolean active;
257
258   g_return_if_fail (rd->input_var_is_string);
259
260   active = gtk_toggle_button_get_active (b);
261   gtk_widget_set_sensitive (rd->string_button, !active);
262 }
263
264 static void
265 on_change_clicked (GObject *obj, gpointer data)
266 {
267   PsppireDialogActionRecode *rd = data;
268   struct variable *var = NULL;
269   struct nlp *nlp;
270
271   GtkTreeModel *model =  gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
272
273   GtkTreeIter iter;
274   GtkTreeSelection *selection =
275     gtk_tree_view_get_selection (GTK_TREE_VIEW (rd->variable_treeview));
276
277   GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
278
279   const gchar *dest_var_name =
280     gtk_entry_get_text (GTK_ENTRY (rd->new_name_entry));
281
282   const gchar *dest_var_label =
283     gtk_entry_get_text (GTK_ENTRY (rd->new_label_entry));
284
285   if ( NULL == rows || rows->next != NULL)
286     goto finish;
287
288   gtk_tree_model_get_iter (model, &iter, rows->data);
289
290   gtk_tree_model_get (model, &iter, 0, &var, -1);
291
292   g_hash_table_remove (rd->varmap, var);
293
294   nlp = nlp_create (dest_var_name, dest_var_label);
295
296   g_hash_table_insert (rd->varmap, var, nlp);
297
298   gtk_tree_model_row_changed (model, rows->data, &iter);
299
300  finish:
301   g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
302   g_list_free (rows);
303 }
304
305
306 static void
307 focus_value_entry (GtkWidget *w, PsppireDialogActionRecode *rd)
308 {
309   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
310     gtk_widget_grab_focus (rd->new_value_entry);
311 }
312
313
314 /* Callback for the new_value_entry and new_value_togglebutton widgets.
315    It's used to enable/disable the acr. */
316 static void
317 set_acr (PsppireDialogActionRecode *rd)
318 {
319   const gchar *text;
320
321   if ( !gtk_toggle_button_get_active
322        (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_NEW_VALUE])))
323     {
324       psppire_acr_set_enabled (PSPPIRE_ACR (rd->acr), TRUE);
325       return;
326     }
327
328   text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
329
330   psppire_acr_set_enabled (PSPPIRE_ACR (rd->acr), !g_str_equal (text, ""));
331 }
332
333 enum {
334   COL_VALUE_OLD,
335   COL_VALUE_NEW,
336   n_COL_VALUES
337 };
338
339 /* Dialog is valid iff at least one variable has been selected,
340    AND the list of mappings is not empty.
341  */
342 static gboolean
343 dialog_state_valid (gpointer data)
344 {
345   GtkTreeIter not_used;
346   PsppireDialogActionRecode *rd = data;
347
348   if ( ! rd->value_map )
349     return FALSE;
350
351   if ( ! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rd->value_map),
352                                         &not_used) )
353     return FALSE;
354
355   if ( rd->different )
356     {
357       GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
358
359       if (g_hash_table_size (rd->varmap) != gtk_tree_model_iter_n_children (model, NULL) )
360         return FALSE;
361     }
362   else
363     {
364       GtkTreeModel *vars =
365         gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
366
367       if ( !gtk_tree_model_get_iter_first (vars, &not_used))
368         return FALSE;
369     }
370
371   return TRUE;
372 }
373
374
375 /* Callback which gets called when a new row is selected
376    in the acr's variable treeview.
377    We use if to set the togglebuttons and entries to correspond to the
378    selected row.
379 */
380 static void
381 on_acr_selection_change (GtkTreeSelection *selection, gpointer data)
382 {
383   PsppireDialogActionRecode *rd = data;
384   GtkTreeModel *model = NULL;
385   GtkTreeIter iter;
386
387   GValue ov_value = {0};
388   GValue nv_value = {0};
389   struct old_value *ov = NULL;
390   struct new_value *nv = NULL;
391
392   if ( ! gtk_tree_selection_get_selected (selection, &model, &iter) )
393     return;
394
395
396   gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
397                             COL_VALUE_OLD, &ov_value);
398
399   gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
400                             COL_VALUE_NEW, &nv_value);
401
402   ov = g_value_get_boxed (&ov_value);
403   nv = g_value_get_boxed (&nv_value);
404
405   if (nv)
406     {
407       switch (nv->type)
408         {
409         case NV_NUMERIC:
410           {
411             gchar *str = num_to_string (nv->v.v);
412
413             gtk_toggle_button_set_active
414               (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
415
416             gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), str);
417             g_free (str);
418           }
419           break;
420         case NV_STRING:
421           gtk_toggle_button_set_active
422             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
423
424           gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), nv->v.s);
425           break;
426         case NV_SYSMIS:
427           gtk_toggle_button_set_active
428             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS]), TRUE);
429
430           break;
431         case NV_COPY:
432           gtk_toggle_button_set_active
433             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY]), TRUE);
434
435           break;
436         default:
437           g_warning ("Invalid new value type");
438           break;
439         }
440
441       g_value_unset (&nv_value);
442     }
443
444   psppire_val_chooser_set_status (PSPPIRE_VAL_CHOOSER (rd->old_value_chooser), ov);
445 }
446
447 /* Initialise VAL to reflect the current status of RD */
448 static gboolean
449 set_old_value (GValue *val, const PsppireDialogActionRecode *rd)
450 {
451   PsppireValChooser *vc = PSPPIRE_VAL_CHOOSER (rd->old_value_chooser);
452
453   struct old_value ov;
454
455   psppire_val_chooser_get_status (vc, &ov);
456
457   g_value_init (val, old_value_get_type ());
458   g_value_set_boxed (val, &ov);
459
460   return TRUE;
461 }
462
463
464 /* Initialse VAL to reflect the current status of RD */
465 static gboolean
466 set_new_value (GValue *val, const PsppireDialogActionRecode *rd)
467 {
468   const gchar *text = NULL;
469   struct new_value nv;
470
471   if ( gtk_toggle_button_get_active
472        (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE])))
473     {
474       text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
475
476       nv.type = NV_NUMERIC;
477       if (
478           (! rd->different && rd->input_var_is_string) ||
479           ( rd->different &&
480             gtk_toggle_button_get_active
481                (GTK_TOGGLE_BUTTON (rd->string_button)))
482           )
483         {
484           nv.type = NV_STRING;
485         }
486
487       if ( nv.type == NV_STRING )
488         nv.v.s = g_strdup (text);
489       else
490         nv.v.v = g_strtod (text, 0);
491     }
492   else if ( gtk_toggle_button_get_active
493             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY])))
494     {
495       nv.type = NV_COPY;
496     }
497
498   else if ( gtk_toggle_button_get_active
499             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS])))
500     {
501       nv.type = NV_SYSMIS;
502     }
503   else
504     return FALSE;
505
506   g_value_init (val, new_value_get_type ());
507   g_value_set_boxed (val, &nv);
508
509   return TRUE;
510 }
511
512 /* A function to set a value in a column in the ACR */
513 static gboolean
514 set_value (gint col, GValue  *val, gpointer data)
515 {
516   PsppireDialogActionRecode *rd = data;
517
518   switch ( col )
519     {
520     case COL_VALUE_OLD:
521       set_old_value (val, rd);
522       break;
523     case COL_VALUE_NEW:
524       set_new_value (val, rd);
525       break;
526     default:
527       return FALSE;
528     }
529
530   return TRUE;
531 }
532
533 static void
534 run_old_and_new_dialog (PsppireDialogActionRecode *rd)
535 {
536   gint response;
537   GtkListStore *local_store = clone_list_store (rd->value_map);
538   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (rd);
539
540   psppire_acr_set_model (PSPPIRE_ACR (rd->acr), local_store);
541   psppire_acr_set_get_value_func (PSPPIRE_ACR (rd->acr), set_value, rd);
542
543   gtk_window_set_title (GTK_WINDOW (rd->old_and_new_dialog),
544                         rd->different
545                         ? _("Recode into Different Variables: Old and New Values ")
546                         : _("Recode into Same Variables: Old and New Values")
547                         );
548
549
550   {
551     /* Find the type of the first variable (it's invariant that
552        all variables are of the same type) */
553     const struct variable *v;
554     GtkTreeIter iter;
555     GtkTreeModel *model =
556       gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
557
558     gboolean not_empty = gtk_tree_model_get_iter_first (model, &iter);
559
560     g_return_if_fail (not_empty);
561
562     gtk_tree_model_get (model, &iter, 0, &v, -1);
563
564     rd->input_var_is_string = var_is_alpha (v);
565
566     g_object_set (rd->old_value_chooser, "is-string", rd->input_var_is_string, NULL);
567
568     gtk_widget_set_sensitive (rd->toggle [BUTTON_NEW_SYSMIS],
569                               var_is_numeric (v));
570
571     gtk_widget_set_sensitive (rd->convert_button, var_is_alpha (v));
572   }
573
574
575   response = psppire_dialog_run (rd->old_and_new_dialog);
576   psppire_acr_set_model (PSPPIRE_ACR (rd->acr), NULL);
577
578
579   if ( response == PSPPIRE_RESPONSE_CONTINUE )
580     {
581       g_object_unref (rd->value_map);
582       rd->value_map = clone_list_store (local_store);
583     }
584   else
585     g_object_unref (local_store);
586
587
588   psppire_dialog_notify_change (PSPPIRE_DIALOG (pda->dialog));
589 }
590
591 static void
592 on_old_new_show (PsppireDialogActionRecode *rd)
593 {
594   gtk_toggle_button_set_active
595     (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_NEW_VALUE]), TRUE);
596
597   g_signal_emit_by_name (rd->toggle[BUTTON_NEW_VALUE], "toggled");
598
599   g_object_set (rd->toggle[BUTTON_NEW_COPY],
600                 "visible", rd->different, NULL);
601
602   g_object_set (rd->new_copy_label,
603                 "visible", rd->different, NULL);
604
605   g_object_set (rd->strings_box,
606                 "visible", rd->different, NULL);
607 }
608
609
610 /* Sets the sensitivity of TARGET dependent upon the active status
611    of BUTTON */
612 static void
613 toggle_sensitivity (GtkToggleButton *button, GtkWidget *target)
614 {
615   gboolean state = gtk_toggle_button_get_active (button);
616
617   /*  g_print ("%s Setting %p (%s) to %d because of %p\n",
618       __FUNCTION__, target, gtk_widget_get_name (target), state, button); */
619
620   gtk_widget_set_sensitive (target, state);
621 }
622
623 static void
624 render_new_var_name (GtkTreeViewColumn *tree_column,
625                      GtkCellRenderer *cell,
626                      GtkTreeModel *tree_model,
627                      GtkTreeIter *iter,
628                      gpointer data)
629 {
630   struct nlp *nlp = NULL;
631   PsppireDialogActionRecode *rd = data;
632
633   struct variable *var = NULL;
634
635   gtk_tree_model_get (tree_model, iter, 
636                       0, &var,
637                       -1);
638
639   nlp = g_hash_table_lookup (rd->varmap, var);
640
641   if ( nlp )
642     g_object_set (cell, "text", nlp->name, NULL);
643   else
644     g_object_set (cell, "text", "", NULL);
645 }
646
647
648
649 \f
650
651 static void
652 psppire_dialog_action_recode_class_init (PsppireDialogActionRecodeClass *class);
653
654 G_DEFINE_TYPE (PsppireDialogActionRecode, psppire_dialog_action_recode, PSPPIRE_TYPE_DIALOG_ACTION);
655
656 static void
657 refresh (PsppireDialogAction *rd_)
658 {
659   PsppireDialogActionRecode *rd = PSPPIRE_DIALOG_ACTION_RECODE (rd_);
660
661   GtkTreeModel *vars =
662     gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
663
664   gtk_list_store_clear (GTK_LIST_STORE (vars));
665
666   gtk_widget_set_sensitive (rd->change_button, FALSE);
667   gtk_widget_set_sensitive (rd->new_name_entry, FALSE);
668   gtk_widget_set_sensitive (rd->new_label_entry, FALSE);
669
670   if ( rd->different && rd->varmap )
671     g_hash_table_remove_all (rd->varmap);
672
673   gtk_list_store_clear (GTK_LIST_STORE (rd->value_map));
674 }
675
676
677
678 static void
679 psppire_dialog_action_recode_activate (PsppireDialogAction *a)
680 {
681   PsppireDialogActionRecode *act = PSPPIRE_DIALOG_ACTION_RECODE (a);
682   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a);
683
684   GHashTable *thing = psppire_dialog_action_get_hash_table (pda);
685   GtkBuilder *xml = g_hash_table_lookup (thing, a);
686   if (!xml)
687     {
688       xml = builder_new ("recode.ui");
689       g_hash_table_insert (thing, a, xml);
690
691       pda->dialog = get_widget_assert   (xml, "recode-dialog");
692       pda->source = get_widget_assert   (xml, "treeview1");
693
694   
695       GtkWidget *selector = get_widget_assert (xml, "psppire-selector1");
696       GtkWidget *oldandnew = get_widget_assert (xml, "button1");
697
698
699       GtkWidget *output_variable_box = get_widget_assert (xml,"frame4");
700
701       act->change_button = get_widget_assert (xml, "change-button");
702       act->varmap = NULL;
703       act->variable_treeview =   get_widget_assert (xml, "treeview2");
704       act->new_name_entry = get_widget_assert (xml, "dest-name-entry");
705       act->new_label_entry = get_widget_assert (xml, "dest-label-entry");
706
707       act->value_map = gtk_list_store_new (2,
708                                            old_value_get_type (),
709                                            new_value_get_type ());
710
711       if (act->different)
712         gtk_window_set_title (GTK_WINDOW (pda->dialog),
713                               _("Recode into Different Variables"));
714       else
715         gtk_window_set_title (GTK_WINDOW (pda->dialog),
716                               _("Recode into Same Variables"));
717
718       g_object_set (output_variable_box, "visible", act->different, NULL);
719
720       if (act->different)
721         {
722           GtkTreeSelection *sel;
723
724           GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
725
726           GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes (_("New"),
727                                                                              renderer,
728                                                                              "text", NULL,
729                                                                              NULL);
730
731           gtk_tree_view_column_set_cell_data_func (col, renderer,
732                                                    render_new_var_name,
733                                                    act, NULL);
734
735
736           gtk_tree_view_append_column (GTK_TREE_VIEW (act->variable_treeview), col);
737
738
739           col = gtk_tree_view_get_column (GTK_TREE_VIEW (act->variable_treeview), 0);
740
741           g_object_set (col, "title", _("Old"), NULL);
742
743           g_object_set (act->variable_treeview, "headers-visible", TRUE, NULL);
744
745           act->varmap = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, nlp_destroy);
746
747           sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (act->variable_treeview));
748
749           g_signal_connect (sel, "changed",
750                             G_CALLBACK (on_selection_change), act);
751
752           g_signal_connect (act->change_button, "clicked",
753                             G_CALLBACK (on_change_clicked),  act);
754         }
755
756       
757       psppire_selector_set_allow (PSPPIRE_SELECTOR (selector), homogeneous_types);
758
759       /* Set up the Old & New Values subdialog */
760       {
761         act->string_button = get_widget_assert (xml, "checkbutton1");
762         act->width_entry   = get_widget_assert (xml, "spinbutton1");
763
764         act->convert_button = get_widget_assert (xml, "checkbutton2");
765
766         act->old_value_chooser = get_widget_assert (xml, "val-chooser");
767
768         act->new_value_entry = get_widget_assert (xml, "entry1");
769
770
771         act->toggle[BUTTON_NEW_VALUE]  = get_widget_assert (xml, "radiobutton1");
772         act->toggle[BUTTON_NEW_SYSMIS] = get_widget_assert (xml, "radiobutton2");
773         act->toggle[BUTTON_NEW_COPY]   = get_widget_assert (xml, "radiobutton3");
774
775         act->new_copy_label = get_widget_assert (xml, "label3");
776         act->strings_box    = get_widget_assert (xml, "table3");
777
778         act->old_and_new_dialog =
779           PSPPIRE_DIALOG (get_widget_assert (xml, "old-new-values-dialog"));
780
781         act->acr = get_widget_assert (xml, "psppire-acr1");
782
783         g_signal_connect_swapped (act->toggle[BUTTON_NEW_VALUE], "toggled",
784                                   G_CALLBACK (set_acr), act);
785
786         g_signal_connect_after (act->toggle[BUTTON_NEW_VALUE], "toggled",
787                                 G_CALLBACK (focus_value_entry), act);
788
789         g_signal_connect_swapped (act->new_value_entry, "changed",
790                                   G_CALLBACK (set_acr), act);
791
792         {
793           GtkTreeSelection *sel;
794           /* Remove the ACR's default column.  We don't like it */
795           GtkTreeViewColumn *column = gtk_tree_view_get_column (PSPPIRE_ACR(act->acr)->tv, 0);
796           gtk_tree_view_remove_column (PSPPIRE_ACR (act->acr)->tv, column);
797
798
799           column =
800             gtk_tree_view_column_new_with_attributes (_("Old"),
801                                                       gtk_cell_renderer_text_new (),
802                                                       "text", 0,
803                                                       NULL);
804
805           gtk_tree_view_append_column (PSPPIRE_ACR (act->acr)->tv, column);
806
807           column =
808             gtk_tree_view_column_new_with_attributes (_("New"),
809                                                       gtk_cell_renderer_text_new (),
810                                                       "text", 1,
811                                                       NULL);
812
813           gtk_tree_view_append_column (PSPPIRE_ACR(act->acr)->tv, column);
814           g_object_set (PSPPIRE_ACR (act->acr)->tv, "headers-visible", TRUE, NULL);
815
816
817           sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (PSPPIRE_ACR(act->acr)->tv));
818           g_signal_connect (sel, "changed",
819                             G_CALLBACK (on_acr_selection_change), act);
820         }
821
822
823         g_signal_connect_swapped (oldandnew, "clicked",
824                                   G_CALLBACK (run_old_and_new_dialog), act);
825
826
827         g_signal_connect (act->toggle[BUTTON_NEW_VALUE], "toggled",
828                           G_CALLBACK (toggle_sensitivity), act->new_value_entry);
829
830         g_signal_connect (act->string_button, "toggled",
831                           G_CALLBACK (toggle_sensitivity), act->width_entry);
832
833         g_signal_connect (act->string_button, "toggled",
834                           G_CALLBACK (on_string_toggled), act);
835
836         g_signal_connect (act->convert_button, "toggled",
837                           G_CALLBACK (on_convert_toggled), act);
838
839         g_signal_connect_swapped (act->old_and_new_dialog, "show",
840                                   G_CALLBACK (on_old_new_show), act);
841       }
842     }
843
844   psppire_dialog_action_set_refresh (pda, refresh);
845
846   psppire_dialog_action_set_valid_predicate (pda,
847                                         dialog_state_valid);
848
849   if (PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_recode_parent_class)->activate)
850     PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_recode_parent_class)->activate (pda);
851 }
852
853 /* Generate a syntax fragment for NV and append it to STR */
854 static void
855 new_value_append_syntax (struct string *dds, const struct new_value *nv)
856 {
857   switch (nv->type)
858     {
859     case NV_NUMERIC:
860       ds_put_c_format (dds, "%.*g", DBL_DIG + 1, nv->v.v);
861       break;
862     case NV_STRING:
863       syntax_gen_string (dds, ss_cstr (nv->v.s));
864       break;
865     case NV_COPY:
866       ds_put_cstr (dds, "COPY");
867       break;
868     case NV_SYSMIS:
869       ds_put_cstr (dds, "SYSMIS");
870       break;
871     default:
872       /* Shouldn't ever happen */
873       g_warning ("Invalid type in new recode value");
874       ds_put_cstr (dds, "???");
875       break;
876     }
877 }
878
879 static char *
880 generate_syntax (const PsppireDialogActionRecode *rd)
881 {
882   gboolean ok;
883   GtkTreeIter iter;
884   gchar *text;
885   struct string dds;
886
887   ds_init_empty (&dds);
888
889
890   /* Declare new string variables if applicable */
891   if ( rd->different &&
892        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rd->string_button)))
893     {
894       GHashTableIter iter;
895
896       struct variable *var = NULL;
897       struct nlp *nlp = NULL;
898
899       g_hash_table_iter_init (&iter, rd->varmap);
900       while (g_hash_table_iter_next (&iter, (void**) &var, (void**) &nlp))
901         {
902           ds_put_cstr (&dds, "\nSTRING ");
903           ds_put_cstr (&dds, nlp->name);
904           ds_put_c_format (&dds, " (A%d).",
905                                   (int)
906                                   gtk_spin_button_get_value (GTK_SPIN_BUTTON (rd->width_entry) )
907                                   );
908         }
909     }
910
911   ds_put_cstr (&dds, "\nRECODE ");
912
913   psppire_var_view_append_names_str (PSPPIRE_VAR_VIEW (rd->variable_treeview), 0, &dds);
914
915   ds_put_cstr (&dds, "\n\t");
916
917   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rd->convert_button)))
918     {
919       ds_put_cstr (&dds, "(CONVERT) ");
920     }
921
922   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rd->value_map),
923                                            &iter);
924        ok;
925        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (rd->value_map), &iter))
926     {
927       GValue ov_value = {0};
928       GValue nv_value = {0};
929       struct old_value *ov;
930       struct new_value *nv;
931       gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
932                                 COL_VALUE_OLD, &ov_value);
933
934       gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
935                                 COL_VALUE_NEW, &nv_value);
936
937       ov = g_value_get_boxed (&ov_value);
938       nv = g_value_get_boxed (&nv_value);
939
940       ds_put_cstr (&dds, "(");
941
942       old_value_append_syntax (&dds, ov);
943       ds_put_cstr (&dds, " = ");
944       new_value_append_syntax (&dds, nv);
945
946       ds_put_cstr (&dds, ") ");
947       g_value_unset (&ov_value);
948       g_value_unset (&nv_value);
949     }
950
951
952   if ( rd->different )
953     {
954
955       GtkTreeIter iter;
956       ds_put_cstr (&dds, "\n\tINTO ");
957
958       for (ok = psppire_var_view_get_iter_first (PSPPIRE_VAR_VIEW (rd->variable_treeview), &iter);
959            ok;
960            ok = psppire_var_view_get_iter_next (PSPPIRE_VAR_VIEW (rd->variable_treeview), &iter))
961           {
962             struct nlp *nlp = NULL;
963             const struct variable *var = psppire_var_view_get_variable (PSPPIRE_VAR_VIEW (rd->variable_treeview), 0, &iter);
964
965             nlp = g_hash_table_lookup (rd->varmap, var);
966             
967             ds_put_cstr (&dds, nlp->name);
968             ds_put_cstr (&dds, " ");
969           }
970     }
971
972   ds_put_cstr (&dds, ".");
973
974   /* If applicable, set labels for the new variables. */
975   if ( rd->different )
976     {
977       GHashTableIter iter;
978
979       struct variable *var = NULL;
980       struct nlp *nlp = NULL;
981
982       g_hash_table_iter_init (&iter, rd->varmap);
983       while (g_hash_table_iter_next (&iter, (void**) &var, (void**) &nlp))
984         {
985           if (nlp->label)
986             {
987               struct string sl;
988               ds_init_empty (&sl);
989               syntax_gen_string (&sl, ss_cstr (nlp->label));
990               ds_put_c_format (&dds, "\nVARIABLE LABELS %s %s.",
991                                       nlp->name, ds_cstr (&sl));
992
993               ds_destroy (&sl);
994             }
995         }
996     }
997
998   ds_put_cstr (&dds, "\nEXECUTE.\n");
999
1000
1001   text = ds_steal_cstr (&dds);
1002
1003   ds_destroy (&dds);
1004
1005   return text;
1006 }
1007
1008
1009 enum
1010 {
1011   PROP_0,
1012   PROP_DIFF
1013 };
1014
1015 static void
1016 __set_property (GObject         *object,
1017                 guint            prop_id,
1018                 const GValue    *value,
1019                 GParamSpec      *pspec)
1020 {
1021   PsppireDialogActionRecode *act = PSPPIRE_DIALOG_ACTION_RECODE (object);
1022
1023   switch (prop_id)
1024     {
1025     case PROP_DIFF:
1026       act->different = g_value_get_boolean (value);
1027       break;
1028     default:
1029       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1030       break;
1031     };
1032 }
1033
1034
1035 static void
1036 __get_property (GObject         *object,
1037                 guint            prop_id,
1038                 GValue          *value,
1039                 GParamSpec      *pspec)
1040 {
1041   PsppireDialogActionRecode *act = PSPPIRE_DIALOG_ACTION_RECODE (object);
1042
1043   switch (prop_id)
1044     {
1045     case PROP_DIFF:
1046       g_value_set_boolean (value, act->different);
1047       break;
1048     default:
1049       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1050       break;
1051     };
1052 }
1053
1054
1055 static void
1056 psppire_dialog_action_recode_class_init (PsppireDialogActionRecodeClass *class)
1057 {
1058   GObjectClass *object_class = G_OBJECT_CLASS (class);
1059
1060   psppire_dialog_action_set_activation (class, psppire_dialog_action_recode_activate);
1061
1062   PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax;
1063
1064   GParamSpec *diff_spec =
1065     g_param_spec_boolean ("recode-to-new-variable",
1066                           "Recode to New Variable",
1067                           "True iff the new values should be placed in a new variable",
1068                           FALSE,
1069                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
1070
1071
1072   object_class->set_property = __set_property;
1073   object_class->get_property = __get_property;
1074   
1075   g_object_class_install_property (object_class,
1076                                    PROP_DIFF,
1077                                    diff_spec);
1078 }
1079
1080
1081 static void
1082 psppire_dialog_action_recode_init (PsppireDialogActionRecode *act)
1083 {
1084 }
1085