Rework the recode-dialog to fit new selector / psppire-var-view objects.
[pspp-builds.git] / src / ui / gui / recode-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009  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 /* This module implements the RECODE dialog.
18
19    It has two forms.  One for recoding values into the same variable.
20    The second for recoding into different variables.
21 */
22
23 #include <config.h>
24
25 #include "recode-dialog.h"
26
27 #include "executor.h"
28
29 #include "psppire-var-view.h"
30
31 #include <gtk/gtk.h>
32
33 #include <xalloc.h>
34 #include <language/syntax-string-source.h>
35 #include <ui/gui/psppire-data-window.h>
36 #include <ui/gui/dialog-common.h>
37 #include <ui/gui/dict-display.h>
38 #include <ui/gui/helper.h>
39 #include <ui/gui/psppire-dialog.h>
40 #include <ui/gui/psppire-var-store.h>
41
42 #include <ui/syntax-gen.h>
43
44 #include "psppire-acr.h"
45
46 #include "gettext.h"
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
49
50
51 /* Define a boxed type to represent a value which is a candidate
52    to replace an existing value */
53
54 enum new_value_type
55  {
56    NV_NUMERIC,
57    NV_STRING,
58    NV_SYSMIS,
59    NV_COPY
60  };
61
62
63 struct new_value
64 {
65   enum new_value_type type;
66   union {
67     double v;
68     gchar *s;
69   } v;
70 };
71
72
73 static struct new_value *
74 new_value_copy (struct new_value *nv)
75 {
76   struct new_value *copy = g_memdup (nv, sizeof (*copy));
77
78   if ( nv->type == NV_STRING )
79     copy->v.s = xstrdup (nv->v.s);
80
81   return copy;
82 }
83
84
85 static void
86 new_value_free (struct new_value *nv)
87 {
88   if ( nv->type == NV_STRING )
89     g_free (nv->v.s);
90
91   g_free (nv);
92 }
93
94
95 static void
96 new_value_to_string (const GValue *src, GValue *dest)
97 {
98   const struct new_value *nv = g_value_get_boxed (src);
99
100   g_assert (nv);
101
102   switch (nv->type)
103     {
104     case NV_NUMERIC:
105       {
106         gchar *text = g_strdup_printf ("%g", nv->v.v);
107         g_value_set_string (dest, text);
108         g_free (text);
109       }
110       break;
111     case NV_STRING:
112       g_value_set_string (dest, nv->v.s);
113       break;
114     case NV_COPY:
115       g_value_set_string (dest, "COPY");
116       break;
117     case NV_SYSMIS:
118       g_value_set_string (dest, "SYSMIS");
119       break;
120     default:
121       /* Shouldn't ever happen */
122       g_warning ("Invalid type in new recode value");
123       g_value_set_string (dest, "???");
124       break;
125     }
126 }
127
128 static GType
129 new_value_get_type (void)
130 {
131   static GType t = 0;
132
133   if (t == 0 )
134     {
135       t = g_boxed_type_register_static  ("psppire-recode-new-values",
136                                          (GBoxedCopyFunc) new_value_copy,
137                                          (GBoxedFreeFunc) new_value_free);
138
139       g_value_register_transform_func (t, G_TYPE_STRING,
140                                        new_value_to_string);
141     }
142
143   return t;
144 }
145
146
147 \f
148
149
150 /* A boxed type representing a value, or a range of values which may
151    potentially be replaced by something */
152
153 enum old_value_type
154  {
155    OV_NUMERIC,
156    OV_STRING,
157    OV_SYSMIS,
158    OV_MISSING,
159    OV_RANGE,
160    OV_LOW_UP,
161    OV_HIGH_DOWN,
162    OV_ELSE
163  };
164
165 struct old_value
166  {
167    enum old_value_type type;
168    union {
169      double v;
170      gchar *s;
171      double range[2];
172    } v;
173  };
174
175
176 static struct old_value *
177 old_value_copy (struct old_value *ov)
178 {
179   struct old_value *copy = g_memdup (ov, sizeof (*copy));
180
181   if ( ov->type == OV_STRING )
182     copy->v.s = g_strdup (ov->v.s);
183
184   return copy;
185 }
186
187
188 static void
189 old_value_free (struct old_value *ov)
190 {
191   if (ov->type == OV_STRING)
192     g_free (ov->v.s);
193   g_free (ov);
194 }
195
196 static void
197 old_value_to_string (const GValue *src, GValue *dest)
198 {
199   const struct old_value *ov = g_value_get_boxed (src);
200
201   switch (ov->type)
202     {
203     case OV_NUMERIC:
204       {
205         gchar *text = g_strdup_printf ("%g", ov->v.v);
206         g_value_set_string (dest, text);
207         g_free (text);
208       }
209       break;
210     case OV_STRING:
211       g_value_set_string (dest, ov->v.s);
212       break;
213     case OV_MISSING:
214       g_value_set_string (dest, "MISSING");
215       break;
216     case OV_SYSMIS:
217       g_value_set_string (dest, "SYSMIS");
218       break;
219     case OV_ELSE:
220       g_value_set_string (dest, "ELSE");
221       break;
222     case OV_RANGE:
223       {
224         gchar *text;
225         char en_dash[6] = {0,0,0,0,0,0};
226
227         g_unichar_to_utf8 (0x2013, en_dash);
228
229         text = g_strdup_printf ("%g %s %g",
230                                        ov->v.range[0],
231                                        en_dash,
232                                        ov->v.range[1]);
233         g_value_set_string (dest, text);
234         g_free (text);
235       }
236       break;
237     case OV_LOW_UP:
238       {
239         gchar *text;
240         char en_dash[6] = {0,0,0,0,0,0};
241
242         g_unichar_to_utf8 (0x2013, en_dash);
243
244         text = g_strdup_printf ("LOWEST %s %g",
245                                 en_dash,
246                                 ov->v.range[1]);
247
248         g_value_set_string (dest, text);
249         g_free (text);
250       }
251       break;
252     case OV_HIGH_DOWN:
253       {
254         gchar *text;
255         char en_dash[6] = {0,0,0,0,0,0};
256
257         g_unichar_to_utf8 (0x2013, en_dash);
258
259         text = g_strdup_printf ("%g %s HIGHEST",
260                                 ov->v.range[0],
261                                 en_dash);
262
263         g_value_set_string (dest, text);
264         g_free (text);
265       }
266       break;
267     default:
268       g_warning ("Invalid type in old recode value");
269       g_value_set_string (dest, "???");
270       break;
271     };
272 }
273
274 static GType
275 old_value_get_type (void)
276 {
277   static GType t = 0;
278
279   if (t == 0 )
280     {
281       t = g_boxed_type_register_static  ("psppire-recode-old-values",
282                                          (GBoxedCopyFunc) old_value_copy,
283                                          (GBoxedFreeFunc) old_value_free);
284
285       g_value_register_transform_func     (t, G_TYPE_STRING,
286                                            old_value_to_string);
287     }
288
289   return t;
290 }
291
292 \f
293
294 enum
295   {
296     BUTTON_NEW_VALUE,
297     BUTTON_NEW_COPY,
298     BUTTON_NEW_SYSMIS,
299     BUTTON_OLD_VALUE,
300     BUTTON_OLD_SYSMIS,
301     BUTTON_OLD_MISSING,
302     BUTTON_OLD_RANGE,
303     BUTTON_OLD_LOW_UP,
304     BUTTON_OLD_HIGH_DOWN,
305     BUTTON_OLD_ELSE,
306     n_BUTTONS
307   };
308
309 struct recode_dialog
310 {
311   PsppireDict *dict;
312
313   GtkWidget *dialog;
314   PsppireDialog *old_and_new_dialog;
315
316   GtkWidget *dict_treeview;
317   GtkWidget *variable_treeview;
318   GtkWidget *toggle[n_BUTTONS];
319
320   GtkWidget *strings_box;
321   GtkWidget *convert_button;
322   GtkWidget *new_copy_label;
323
324   GtkWidget *ov_value_entry;
325   GtkWidget *new_value_entry;
326
327   GtkWidget *ov_range_lower_entry;
328   GtkWidget *ov_range_upper_entry;
329   GtkWidget *ov_low_up_entry;
330   GtkWidget *ov_high_down_entry;
331
332   GtkListStore *value_map;
333
334   /* Indicates that the INTO {new variables} form of the dialog
335      is being used */
336   gboolean different;
337
338   PsppireAcr *acr;
339
340   gboolean input_var_is_string;
341
342   GtkWidget *new_name_entry;
343   GtkWidget *new_label_entry;
344   GtkWidget *change_button;
345
346   GtkWidget *string_button;
347   GtkWidget *width_entry;
348
349   /* A hash table of struct nlp's indexed by variable */
350   GHashTable *varmap;
351 };
352
353
354 static void run_old_and_new_dialog (struct recode_dialog *rd);
355
356 static void
357 refresh (PsppireDialog *dialog, struct recode_dialog *rd)
358 {
359   GtkTreeModel *vars =
360     gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
361
362   gtk_list_store_clear (GTK_LIST_STORE (vars));
363
364   gtk_widget_set_sensitive (rd->change_button, FALSE);
365   gtk_widget_set_sensitive (rd->new_name_entry, FALSE);
366   gtk_widget_set_sensitive (rd->new_label_entry, FALSE);
367
368   if ( rd->different )
369     g_hash_table_remove_all (rd->varmap);
370
371   gtk_list_store_clear (GTK_LIST_STORE (rd->value_map));
372 }
373
374 static char * generate_syntax (const struct recode_dialog *rd);
375
376 enum {
377   COL_VALUE_OLD,
378   COL_VALUE_NEW,
379   n_COL_VALUES
380 };
381
382 /* Dialog is valid iff at least one variable has been selected,
383    AND the list of mappings is not empty.
384  */
385 static gboolean
386 dialog_state_valid (gpointer data)
387 {
388   GtkTreeIter not_used;
389   struct recode_dialog *rd = data;
390
391   if ( ! rd->value_map )
392     return FALSE;
393
394   if ( ! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rd->value_map),
395                                         &not_used) )
396     return FALSE;
397
398   if ( rd->different )
399     {
400       GtkTreeModel *model = GTK_TREE_MODEL (PSPPIRE_VAR_VIEW (rd->variable_treeview)->list);
401
402       if (g_hash_table_size (rd->varmap) != gtk_tree_model_iter_n_children (model, NULL) )
403         return FALSE;
404     }
405   else
406     {
407       GtkTreeModel *vars =
408         gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
409
410       if ( !gtk_tree_model_get_iter_first (vars, &not_used))
411         return FALSE;
412     }
413
414   return TRUE;
415 }
416
417 static void
418 on_old_new_show (struct recode_dialog *rd)
419 {
420   gtk_toggle_button_set_active
421     (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_OLD_VALUE]), TRUE);
422
423   g_signal_emit_by_name (rd->toggle[BUTTON_OLD_VALUE], "toggled");
424
425   gtk_toggle_button_set_active
426     (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_NEW_VALUE]), TRUE);
427
428   g_signal_emit_by_name (rd->toggle[BUTTON_NEW_VALUE], "toggled");
429
430   g_object_set (rd->toggle[BUTTON_NEW_COPY],
431                 "visible", rd->different, NULL);
432
433   g_object_set (rd->new_copy_label,
434                 "visible", rd->different, NULL);
435
436   g_object_set (rd->strings_box,
437                 "visible", rd->different, NULL);
438 }
439
440 /* Sets the sensitivity of TARGET dependent upon the active status
441    of BUTTON */
442 static void
443 toggle_sensitivity (GtkToggleButton *button, GtkWidget *target)
444 {
445   gboolean state = gtk_toggle_button_get_active (button);
446
447   /*  g_print ("%s Setting %p (%s) to %d because of %p\n",
448       __FUNCTION__, target, gtk_widget_get_name (target), state, button); */
449
450   gtk_widget_set_sensitive (target, state);
451 }
452
453 static void recode_dialog (PsppireDataWindow *de, gboolean diff);
454
455
456 /* Pops up the Recode Same version of the dialog box */
457 void
458 recode_same_dialog (GObject *o, gpointer data)
459 {
460   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
461
462   recode_dialog (de, FALSE);
463 }
464
465 /* Pops up the Recode Different version of the dialog box */
466 void
467 recode_different_dialog (GObject *o, gpointer data)
468 {
469   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
470
471   recode_dialog (de, TRUE);
472 }
473
474
475 /* This might need to be changed to something less naive.
476    In particular, what happends with dates, etc?
477  */
478 static gchar *
479 num_to_string (gdouble x)
480 {
481   return g_strdup_printf ("%g", x);
482 }
483
484 /* Callback which gets called when a new row is selected
485    in the acr's variable treeview.
486    We use if to set the togglebuttons and entries to correspond to the
487    selected row.
488 */
489 static void
490 on_acr_selection_change (GtkTreeSelection *selection, gpointer data)
491 {
492   struct recode_dialog *rd = data;
493   GtkTreeModel *model = NULL;
494   GtkTreeIter iter;
495
496   GValue ov_value = {0};
497   GValue nv_value = {0};
498   struct old_value *ov = NULL;
499   struct new_value *nv = NULL;
500
501   if ( ! gtk_tree_selection_get_selected (selection, &model, &iter) )
502     return;
503
504
505   gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
506                             COL_VALUE_OLD, &ov_value);
507
508   gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
509                             COL_VALUE_NEW, &nv_value);
510
511   ov = g_value_get_boxed (&ov_value);
512   nv = g_value_get_boxed (&nv_value);
513
514   if (nv)
515     {
516       switch (nv->type)
517         {
518         case NV_NUMERIC:
519           {
520             gchar *str = num_to_string (nv->v.v);
521
522             gtk_toggle_button_set_active
523               (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
524
525             gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), str);
526             g_free (str);
527           }
528           break;
529         case NV_STRING:
530           gtk_toggle_button_set_active
531             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
532
533           gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), nv->v.s);
534           break;
535         case NV_SYSMIS:
536           gtk_toggle_button_set_active
537             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS]), TRUE);
538
539           break;
540         case NV_COPY:
541           gtk_toggle_button_set_active
542             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY]), TRUE);
543
544           break;
545         default:
546           g_warning ("Invalid new value type");
547           break;
548         }
549
550       g_value_unset (&nv_value);
551     }
552
553   if ( ov )
554     {
555     switch (ov->type)
556       {
557       case OV_STRING:
558           gtk_toggle_button_set_active
559             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_VALUE]), TRUE);
560
561           gtk_entry_set_text (GTK_ENTRY (rd->ov_value_entry), ov->v.s);
562         break;
563
564       case OV_NUMERIC:
565         {
566           gchar *str;
567
568           gtk_toggle_button_set_active
569             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_VALUE]), TRUE);
570
571           str = num_to_string (ov->v.v);
572
573           gtk_entry_set_text (GTK_ENTRY (rd->ov_value_entry), str);
574
575           g_free (str);
576         }
577         break;
578
579       case OV_SYSMIS:
580         gtk_toggle_button_set_active
581           (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_SYSMIS]), TRUE);
582         break;
583
584       case OV_MISSING:
585         gtk_toggle_button_set_active
586           (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_MISSING]), TRUE);
587         break;
588
589       case OV_RANGE:
590         {
591           gchar *str;
592
593           gtk_toggle_button_set_active
594             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_RANGE]), TRUE);
595
596           str = num_to_string (ov->v.range[0]);
597
598           gtk_entry_set_text (GTK_ENTRY (rd->ov_range_lower_entry), str);
599
600           g_free (str);
601
602
603           str = num_to_string (ov->v.range[1]);
604
605           gtk_entry_set_text (GTK_ENTRY (rd->ov_range_upper_entry), str);
606
607           g_free (str);
608         }
609         break;
610
611       case OV_LOW_UP:
612         {
613           gchar *str = num_to_string (ov->v.range[1]);
614
615           gtk_toggle_button_set_active
616             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_LOW_UP]), TRUE);
617
618           gtk_entry_set_text (GTK_ENTRY (rd->ov_low_up_entry), str);
619
620           g_free (str);
621         }
622         break;
623
624       case OV_HIGH_DOWN:
625         {
626           gchar *str = num_to_string (ov->v.range[0]);
627
628           gtk_toggle_button_set_active
629             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_HIGH_DOWN]), TRUE);
630
631           gtk_entry_set_text (GTK_ENTRY (rd->ov_high_down_entry), str);
632
633           g_free (str);
634         }
635         break;
636
637       case OV_ELSE:
638         gtk_toggle_button_set_active
639           (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_OLD_ELSE]), TRUE);
640         break;
641
642       default:
643         g_warning ("Unknown old value type");
644         break;
645       };
646     g_value_unset (&ov_value);
647     }
648 }
649
650 /* Name-Label pair */
651 struct nlp
652 {
653   char *name;
654   char *label;
655 };
656
657 static struct nlp *
658 nlp_create (const char *name, const char *label)
659 {
660   struct nlp *nlp = xmalloc (sizeof *nlp);
661
662   nlp->name = g_strdup (name);
663
664   nlp->label = NULL;
665
666   if ( 0 != strcmp ("", label))
667     nlp->label = g_strdup (label);
668
669   return nlp;
670 }
671
672 static void
673 nlp_destroy (gpointer data)
674 {
675   struct nlp *nlp = data ;
676   if ( ! nlp )
677     return;
678
679   g_free (nlp->name);
680   g_free (nlp->label);
681   g_free (nlp);
682 }
683
684
685 /* Callback which gets called when a new row is selected
686    in the variable treeview.
687    It sets the name and label entry widgets to reflect the
688    currently selected row.
689  */
690 static void
691 on_selection_change (GtkTreeSelection *selection, gpointer data)
692 {
693   struct recode_dialog *rd = data;
694
695   GtkTreeModel *model = GTK_TREE_MODEL (PSPPIRE_VAR_VIEW (rd->variable_treeview)->list);
696
697   GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
698
699   if ( rows && !rows->next)
700     {
701       /* Exactly one row is selected */
702       struct nlp *nlp;
703       struct variable *var;
704       gboolean ok;
705       GtkTreeIter iter;
706
707       gtk_widget_set_sensitive  (rd->change_button, TRUE);
708       gtk_widget_set_sensitive  (rd->new_name_entry, TRUE);
709       gtk_widget_set_sensitive  (rd->new_label_entry, TRUE);
710
711       ok = gtk_tree_model_get_iter (model, &iter, (GtkTreePath*) rows->data);
712
713       gtk_tree_model_get (model, &iter,
714                           0, &var, 
715                           -1);
716
717       nlp = g_hash_table_lookup (rd->varmap, var);
718
719       if (nlp)
720         {
721           gtk_entry_set_text (GTK_ENTRY (rd->new_name_entry), nlp->name ? nlp->name : "");
722           gtk_entry_set_text (GTK_ENTRY (rd->new_label_entry), nlp->label ? nlp->label : "");
723         }
724       else
725         {
726           gtk_entry_set_text (GTK_ENTRY (rd->new_name_entry), "");
727           gtk_entry_set_text (GTK_ENTRY (rd->new_label_entry), "");
728         }
729     }
730   else
731     {
732       gtk_widget_set_sensitive  (rd->change_button, FALSE);
733       gtk_widget_set_sensitive  (rd->new_name_entry, FALSE);
734       gtk_widget_set_sensitive  (rd->new_label_entry, FALSE);
735
736       gtk_entry_set_text (GTK_ENTRY (rd->new_name_entry), "");
737       gtk_entry_set_text (GTK_ENTRY (rd->new_label_entry), "");
738     }
739
740
741   g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
742   g_list_free (rows);
743 }
744
745 static void
746 on_string_toggled (GtkToggleButton *b, struct recode_dialog *rd)
747 {
748   gboolean active;
749   if (! rd->input_var_is_string )
750     return ;
751
752   active = gtk_toggle_button_get_active (b);
753   gtk_widget_set_sensitive (rd->convert_button, !active);
754 }
755
756
757 static void
758 on_convert_toggled (GtkToggleButton *b, struct recode_dialog *rd)
759 {
760   gboolean active;
761
762   g_return_if_fail (rd->input_var_is_string);
763
764   active = gtk_toggle_button_get_active (b);
765   gtk_widget_set_sensitive (rd->string_button, !active);
766 }
767
768 static void
769 on_change_clicked (GObject *obj, gpointer data)
770 {
771   struct recode_dialog *rd = data;
772   struct variable *var = NULL;
773   struct nlp *nlp;
774   GtkTreeModel *model = GTK_TREE_MODEL (PSPPIRE_VAR_VIEW (rd->variable_treeview)->list);
775   GtkTreeIter iter;
776   GtkTreeSelection *selection =
777     gtk_tree_view_get_selection (GTK_TREE_VIEW (rd->variable_treeview));
778
779   GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
780
781   const gchar *dest_var_name =
782     gtk_entry_get_text (GTK_ENTRY (rd->new_name_entry));
783
784   const gchar *dest_var_label =
785     gtk_entry_get_text (GTK_ENTRY (rd->new_label_entry));
786
787   if ( NULL == rows || rows->next != NULL)
788     goto finish;
789
790   gtk_tree_model_get_iter (model, &iter, rows->data);
791
792   gtk_tree_model_get (model, &iter, 0, &var, -1);
793
794   g_hash_table_remove (rd->varmap, var);
795
796   nlp = nlp_create (dest_var_name, dest_var_label);
797
798   g_hash_table_insert (rd->varmap, var, nlp);
799
800   gtk_tree_model_row_changed (model, rows->data, &iter);
801
802  finish:
803   g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
804   g_list_free (rows);
805 }
806
807
808 /* If there's nothing selected in the variable treeview,
809    then automatically select the first item */
810 static void
811 select_something (GtkTreeModel *treemodel,
812                   GtkTreePath  *arg1,
813                   GtkTreeIter  *arg2,
814                   gpointer      data)
815 {
816   struct recode_dialog *rd = data;
817   GtkTreeSelection *sel;
818
819   sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rd->variable_treeview));
820
821   if ( gtk_tree_selection_count_selected_rows (sel) < 1)
822     {
823       GtkTreeIter iter;
824
825       gtk_tree_model_get_iter_first   (treemodel, &iter);
826
827       gtk_tree_selection_select_iter  (sel, &iter);
828     }
829 }
830
831
832 /* Callback for the new_value_entry and new_value_togglebutton widgets.
833    It's used to enable/disable the acr. */
834 static void
835 set_acr (struct recode_dialog *rd)
836 {
837   const gchar *text;
838
839   if ( !gtk_toggle_button_get_active
840        (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_NEW_VALUE])))
841     {
842       psppire_acr_set_enabled (rd->acr, TRUE);
843       return;
844     }
845
846   text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
847
848   psppire_acr_set_enabled (rd->acr, !g_str_equal (text, ""));
849 }
850
851 static void
852 render_new_var_name (GtkTreeViewColumn *tree_column,
853                      GtkCellRenderer *cell,
854                      GtkTreeModel *tree_model,
855                      GtkTreeIter *iter,
856                      gpointer data)
857 {
858   struct nlp *nlp = NULL;
859   struct recode_dialog *rd = data;
860
861   struct variable *var = NULL;
862
863   gtk_tree_model_get (tree_model, iter, 
864                       0, &var,
865                       -1);
866
867   nlp = g_hash_table_lookup (rd->varmap, var);
868
869   if ( nlp )
870     g_object_set (cell, "text", nlp->name, NULL);
871   else
872     g_object_set (cell, "text", "", NULL);
873 }
874
875
876
877 static void
878 recode_dialog (PsppireDataWindow *de, gboolean diff)
879 {
880   gint response;
881
882   struct recode_dialog rd;
883
884   GtkBuilder *builder = builder_new ("recode.ui");
885
886   GtkWidget *selector = get_widget_assert (builder, "psppire-selector1");
887
888   GtkWidget *oldandnew = get_widget_assert (builder, "button1");
889
890
891   GtkWidget *output_variable_box = get_widget_assert (builder,"frame4");
892
893   PsppireVarStore *vs = NULL;
894   g_object_get (de->data_editor, "var-store", &vs, NULL);
895
896   rd.change_button = get_widget_assert (builder, "change-button");
897   rd.varmap = NULL;
898   rd.dialog = get_widget_assert   (builder, "recode-dialog");
899   rd.dict_treeview = get_widget_assert (builder, "treeview1");
900   rd.variable_treeview =   get_widget_assert (builder, "treeview2");
901   rd.new_name_entry = get_widget_assert (builder, "dest-name-entry");
902   rd.new_label_entry = get_widget_assert (builder, "dest-label-entry");
903
904   g_object_get (vs, "dictionary", &rd.dict, NULL);
905
906   rd.value_map = gtk_list_store_new (2,
907                                      old_value_get_type (),
908                                      new_value_get_type ()
909                                      );
910
911   g_object_set (output_variable_box, "visible", diff, NULL);
912
913   if ( diff )
914     gtk_window_set_title (GTK_WINDOW (rd.dialog),
915                           _("Recode into Different Variables"));
916   else
917     gtk_window_set_title (GTK_WINDOW (rd.dialog),
918                           _("Recode into Same Variables"));
919
920   rd.different = diff;
921
922   gtk_window_set_transient_for (GTK_WINDOW (rd.dialog), GTK_WINDOW (de));
923
924   g_object_set (rd.dict_treeview, "model", rd.dict, NULL);
925
926   if (rd.different)
927     {
928       GtkTreeModel *model = GTK_TREE_MODEL (PSPPIRE_VAR_VIEW (rd.variable_treeview)->list);
929       GtkTreeSelection *sel;
930
931       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
932
933       GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes (_("New"),
934                                                                          renderer,
935                                                                          "text", NULL,
936                                                                          NULL);
937
938       gtk_tree_view_column_set_cell_data_func (col, renderer,
939                                                render_new_var_name,
940                                                &rd, NULL);
941
942
943       gtk_tree_view_append_column (GTK_TREE_VIEW (rd.variable_treeview), col);
944
945
946       col = gtk_tree_view_get_column (GTK_TREE_VIEW (rd.variable_treeview), 0);
947
948       g_object_set (col, "title", _("Old"), NULL);
949
950       g_object_set (rd.variable_treeview, "headers-visible", TRUE, NULL);
951
952       rd.varmap = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, nlp_destroy);
953
954       sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rd.variable_treeview));
955
956       g_signal_connect (sel, "changed",
957                         G_CALLBACK (on_selection_change), &rd);
958
959       g_signal_connect (rd.change_button, "clicked",
960                         G_CALLBACK (on_change_clicked),  &rd);
961
962 #if 0
963       g_signal_connect (model, "row-inserted",
964                         G_CALLBACK (select_something), &rd);
965 #endif
966     }
967
968   psppire_selector_set_allow (PSPPIRE_SELECTOR (selector), homogeneous_types);
969
970   /* Set up the Old & New Values subdialog */
971   {
972     rd.string_button = get_widget_assert (builder, "checkbutton1");
973     rd.width_entry   = get_widget_assert (builder, "spinbutton1");
974
975     rd.convert_button           = get_widget_assert (builder, "checkbutton2");
976
977     rd.ov_range_lower_entry = get_widget_assert (builder, "entry5");
978     rd.ov_range_upper_entry  = get_widget_assert (builder, "entry3");
979     rd.ov_low_up_entry       = get_widget_assert (builder, "entry6");
980     rd.ov_high_down_entry    = get_widget_assert (builder, "entry7");
981
982     rd.new_value_entry = get_widget_assert (builder, "entry1");
983     rd.ov_value_entry  = get_widget_assert (builder, "entry2");
984
985     rd.toggle[BUTTON_NEW_VALUE]  = get_widget_assert (builder, "radiobutton1");
986     rd.toggle[BUTTON_NEW_SYSMIS] = get_widget_assert (builder, "radiobutton2");
987     rd.toggle[BUTTON_NEW_COPY]   = get_widget_assert (builder, "radiobutton3");
988     rd.toggle[BUTTON_OLD_VALUE]  = get_widget_assert (builder, "radiobutton4");
989     rd.toggle[BUTTON_OLD_SYSMIS] = get_widget_assert (builder, "radiobutton6");
990     rd.toggle[BUTTON_OLD_MISSING]= get_widget_assert (builder, "radiobutton7");
991     rd.toggle[BUTTON_OLD_RANGE]  = get_widget_assert (builder, "radiobutton8");
992     rd.toggle[BUTTON_OLD_LOW_UP] = get_widget_assert (builder, "radiobutton10");
993     rd.toggle[BUTTON_OLD_HIGH_DOWN] = get_widget_assert (builder, "radiobutton5");
994     rd.toggle[BUTTON_OLD_ELSE]   = get_widget_assert (builder, "radiobutton11");
995
996     rd.new_copy_label = get_widget_assert (builder, "label3");
997     rd.strings_box    = get_widget_assert (builder, "table3");
998
999     rd.old_and_new_dialog =
1000       PSPPIRE_DIALOG (get_widget_assert (builder, "old-new-values-dialog"));
1001
1002     gtk_window_set_transient_for (GTK_WINDOW (rd.old_and_new_dialog),
1003                                   GTK_WINDOW (de));
1004
1005     rd.acr = PSPPIRE_ACR (get_widget_assert (builder, "psppire-acr1"));
1006
1007     g_signal_connect_swapped (rd.toggle[BUTTON_NEW_VALUE], "toggled",
1008                       G_CALLBACK (set_acr), &rd);
1009
1010     g_signal_connect_swapped (rd.new_value_entry, "changed",
1011                       G_CALLBACK (set_acr), &rd);
1012
1013     {
1014       GtkTreeSelection *sel;
1015       /* Remove the ACR's default column.  We don't like it */
1016       GtkTreeViewColumn *column = gtk_tree_view_get_column (rd.acr->tv, 0);
1017       gtk_tree_view_remove_column (rd.acr->tv, column);
1018
1019
1020       column =
1021         gtk_tree_view_column_new_with_attributes (_("Old"),
1022                                                   gtk_cell_renderer_text_new (),
1023                                                   "text", 0,
1024                                                   NULL);
1025
1026       gtk_tree_view_append_column (rd.acr->tv, column);
1027
1028       column =
1029         gtk_tree_view_column_new_with_attributes (_("New"),
1030                                                   gtk_cell_renderer_text_new (),
1031                                                   "text", 1,
1032                                                   NULL);
1033
1034       gtk_tree_view_append_column (rd.acr->tv, column);
1035       g_object_set (rd.acr->tv, "headers-visible", TRUE, NULL);
1036
1037
1038       sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rd.acr->tv));
1039       g_signal_connect (sel, "changed",
1040                         G_CALLBACK (on_acr_selection_change), &rd);
1041     }
1042
1043
1044     g_signal_connect_swapped (oldandnew, "clicked",
1045                               G_CALLBACK (run_old_and_new_dialog), &rd);
1046
1047
1048     g_signal_connect (rd.toggle[BUTTON_NEW_VALUE], "toggled",
1049                       G_CALLBACK (toggle_sensitivity), rd.new_value_entry);
1050
1051     g_signal_connect (rd.toggle[BUTTON_OLD_VALUE], "toggled",
1052                       G_CALLBACK (toggle_sensitivity), rd.ov_value_entry);
1053
1054     g_signal_connect (rd.toggle[BUTTON_OLD_RANGE], "toggled",
1055                       G_CALLBACK (toggle_sensitivity),
1056                       get_widget_assert (builder, "entry3"));
1057
1058     g_signal_connect (rd.toggle[BUTTON_OLD_RANGE], "toggled",
1059                       G_CALLBACK (toggle_sensitivity),
1060                       get_widget_assert (builder, "entry5"));
1061
1062     g_signal_connect (rd.toggle[BUTTON_OLD_LOW_UP], "toggled",
1063                       G_CALLBACK (toggle_sensitivity), rd.ov_low_up_entry);
1064
1065     g_signal_connect (rd.toggle[BUTTON_OLD_HIGH_DOWN], "toggled",
1066                       G_CALLBACK (toggle_sensitivity), rd.ov_high_down_entry);
1067
1068     g_signal_connect (rd.string_button, "toggled",
1069                       G_CALLBACK (toggle_sensitivity), rd.width_entry);
1070
1071     g_signal_connect (rd.string_button, "toggled",
1072                       G_CALLBACK (on_string_toggled), &rd);
1073
1074     g_signal_connect (rd.convert_button, "toggled",
1075                       G_CALLBACK (on_convert_toggled), &rd);
1076
1077     g_signal_connect_swapped (rd.old_and_new_dialog, "show",
1078                               G_CALLBACK (on_old_new_show), &rd);
1079   }
1080
1081   g_signal_connect (rd.dialog, "refresh", G_CALLBACK (refresh),  &rd);
1082
1083
1084   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (rd.dialog),
1085                                       dialog_state_valid, &rd);
1086
1087   response = psppire_dialog_run (PSPPIRE_DIALOG (rd.dialog));
1088
1089   switch (response)
1090     {
1091     case GTK_RESPONSE_OK:
1092       {
1093         gchar *syntax = generate_syntax (&rd);
1094
1095         struct getl_interface *sss = create_syntax_string_source (syntax);
1096         execute_syntax (sss);
1097
1098         g_free (syntax);
1099       }
1100       break;
1101     case PSPPIRE_RESPONSE_PASTE:
1102       {
1103         gchar *syntax = generate_syntax (&rd);
1104         paste_syntax_in_new_window (syntax);
1105
1106         g_free (syntax);
1107       }
1108       break;
1109     default:
1110       break;
1111     }
1112
1113   g_hash_table_destroy (rd.varmap);
1114
1115   gtk_list_store_clear (GTK_LIST_STORE (rd.value_map));
1116   g_object_unref (rd.value_map);
1117
1118   g_object_unref (builder);
1119 }
1120
1121 /* Initialise VAL to reflect the current status of RD */
1122 static gboolean
1123 set_old_value (GValue *val, const struct recode_dialog *rd)
1124 {
1125   const gchar *text = NULL;
1126   struct old_value ov;
1127   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1128                                      (rd->toggle [BUTTON_OLD_VALUE])))
1129     {
1130       text = gtk_entry_get_text (GTK_ENTRY (rd->ov_value_entry));
1131       if ( rd->input_var_is_string )
1132         {
1133           ov.type = OV_STRING;
1134           ov.v.s = g_strdup (text);
1135         }
1136       else
1137         {
1138           ov.type = OV_NUMERIC;
1139           ov.v.v = g_strtod (text, 0);
1140         }
1141     }
1142   else if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1143                                           (rd->toggle [BUTTON_OLD_MISSING])))
1144     {
1145       ov.type = OV_MISSING;
1146     }
1147   else if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1148                                           (rd->toggle [BUTTON_OLD_SYSMIS])))
1149     {
1150       ov.type = OV_SYSMIS;
1151     }
1152   else if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1153                                           (rd->toggle [BUTTON_OLD_ELSE])))
1154     {
1155       ov.type = OV_ELSE;
1156     }
1157   else if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1158                                           (rd->toggle [BUTTON_OLD_RANGE])))
1159     {
1160       const gchar *text;
1161       text = gtk_entry_get_text (GTK_ENTRY (rd->ov_range_lower_entry));
1162
1163       ov.type = OV_RANGE;
1164       ov.v.range[0] = g_strtod (text, 0);
1165
1166       text = gtk_entry_get_text (GTK_ENTRY (rd->ov_range_upper_entry));
1167       ov.v.range[1] = g_strtod (text, 0);
1168     }
1169   else if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1170                                           (rd->toggle [BUTTON_OLD_LOW_UP])))
1171     {
1172       const gchar *text =
1173         gtk_entry_get_text (GTK_ENTRY (rd->ov_low_up_entry));
1174
1175       ov.type = OV_LOW_UP;
1176       ov.v.range[1] = g_strtod (text, 0);
1177     }
1178   else if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
1179                                           (rd->toggle [BUTTON_OLD_HIGH_DOWN])))
1180     {
1181       const gchar *text =
1182         gtk_entry_get_text (GTK_ENTRY (rd->ov_high_down_entry));
1183
1184       ov.type = OV_HIGH_DOWN;
1185       ov.v.range[0] = g_strtod (text, 0);
1186     }
1187   else
1188     return FALSE;
1189
1190   g_value_init (val, old_value_get_type ());
1191   g_value_set_boxed (val, &ov);
1192
1193   return TRUE;
1194 }
1195
1196
1197 /* Initialse VAL to reflect the current status of RD */
1198 static gboolean
1199 set_new_value (GValue *val, const struct recode_dialog *rd)
1200 {
1201   const gchar *text = NULL;
1202   struct new_value nv;
1203
1204   if ( gtk_toggle_button_get_active
1205        (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE])))
1206     {
1207       text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
1208
1209       nv.type = NV_NUMERIC;
1210       if (
1211           (! rd->different && rd->input_var_is_string) ||
1212           ( rd->different &&
1213             gtk_toggle_button_get_active
1214                (GTK_TOGGLE_BUTTON (rd->string_button)))
1215           )
1216         {
1217           nv.type = NV_STRING;
1218         }
1219
1220       if ( nv.type == NV_STRING )
1221         nv.v.s = g_strdup (text);
1222       else
1223         nv.v.v = g_strtod (text, 0);
1224     }
1225   else if ( gtk_toggle_button_get_active
1226             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY])))
1227     {
1228       nv.type = NV_COPY;
1229     }
1230
1231   else if ( gtk_toggle_button_get_active
1232             (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS])))
1233     {
1234       nv.type = NV_SYSMIS;
1235     }
1236   else
1237     return FALSE;
1238
1239   g_value_init (val, new_value_get_type ());
1240   g_value_set_boxed (val, &nv);
1241
1242   return TRUE;
1243 }
1244
1245
1246 /* A function to set a value in a column in the ACR */
1247 gboolean
1248 set_value (gint col, GValue  *val, gpointer data)
1249 {
1250   struct recode_dialog *rd = data;
1251
1252   switch ( col )
1253     {
1254     case COL_VALUE_OLD:
1255       set_old_value (val, rd);
1256       break;
1257     case COL_VALUE_NEW:
1258       set_new_value (val, rd);
1259       break;
1260     default:
1261       return FALSE;
1262     }
1263
1264   return TRUE;
1265 }
1266
1267 static void
1268 run_old_and_new_dialog (struct recode_dialog *rd)
1269 {
1270   gint response;
1271   GtkListStore *local_store = clone_list_store (rd->value_map);
1272
1273   psppire_acr_set_model (rd->acr, local_store);
1274   psppire_acr_set_get_value_func (rd->acr, set_value, rd);
1275
1276   gtk_window_set_title (GTK_WINDOW (rd->old_and_new_dialog),
1277                         rd->different
1278                         ? _("Recode into Different Variables: Old and New Values ")
1279                         : _("Recode into Same Variables: Old and New Values")
1280                         );
1281
1282
1283   {
1284     /* Find the type of the first variable (it's invariant that
1285        all variables are of the same type) */
1286     const struct variable *v;
1287     GtkTreeIter iter;
1288     GtkTreeModel *model =
1289       gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
1290
1291     gboolean not_empty = gtk_tree_model_get_iter_first (model, &iter);
1292
1293     g_return_if_fail (not_empty);
1294
1295     gtk_tree_model_get (model, &iter, 0, &v, -1);
1296
1297     rd->input_var_is_string = var_is_alpha (v);
1298
1299     gtk_widget_set_sensitive (rd->toggle [BUTTON_OLD_SYSMIS],
1300                               var_is_numeric (v));
1301     gtk_widget_set_sensitive (rd->toggle [BUTTON_OLD_RANGE],
1302                               var_is_numeric (v));
1303     gtk_widget_set_sensitive (rd->toggle [BUTTON_OLD_LOW_UP],
1304                               var_is_numeric (v));
1305     gtk_widget_set_sensitive (rd->toggle [BUTTON_OLD_HIGH_DOWN],
1306                               var_is_numeric (v));
1307     gtk_widget_set_sensitive (rd->toggle [BUTTON_NEW_SYSMIS],
1308                               var_is_numeric (v));
1309
1310     gtk_widget_set_sensitive (rd->convert_button, var_is_alpha (v));
1311   }
1312
1313
1314   response = psppire_dialog_run (rd->old_and_new_dialog);
1315   psppire_acr_set_model (rd->acr, NULL);
1316
1317
1318   if ( response == PSPPIRE_RESPONSE_CONTINUE )
1319     {
1320       g_object_unref (rd->value_map);
1321       rd->value_map = clone_list_store (local_store);
1322     }
1323   else
1324     g_object_unref (local_store);
1325
1326
1327   psppire_dialog_notify_change (PSPPIRE_DIALOG (rd->dialog));
1328 }
1329
1330
1331 /* Generate a syntax fragment for NV and append it to STR */
1332 static void
1333 new_value_append_syntax (GString *str, const struct new_value *nv)
1334 {
1335   switch (nv->type)
1336     {
1337     case NV_NUMERIC:
1338       g_string_append_printf (str, "%g", nv->v.v);
1339       break;
1340     case NV_STRING:
1341       {
1342         struct string ds = DS_EMPTY_INITIALIZER;
1343         syntax_gen_string (&ds, ss_cstr (nv->v.s));
1344         g_string_append (str, ds_cstr (&ds));
1345         ds_destroy (&ds);
1346       }
1347       break;
1348     case NV_COPY:
1349       g_string_append (str, "COPY");
1350       break;
1351     case NV_SYSMIS:
1352       g_string_append (str, "SYSMIS");
1353       break;
1354     default:
1355       /* Shouldn't ever happen */
1356       g_warning ("Invalid type in new recode value");
1357       g_string_append (str, "???");
1358       break;
1359     }
1360 }
1361
1362
1363 /* Generate a syntax fragment for NV and append it to STR */
1364 static void
1365 old_value_append_syntax (GString *str, const struct old_value *ov)
1366 {
1367   switch (ov->type)
1368     {
1369     case OV_NUMERIC:
1370       g_string_append_printf (str, "%g", ov->v.v);
1371       break;
1372     case OV_STRING:
1373       {
1374         struct string ds = DS_EMPTY_INITIALIZER;
1375         syntax_gen_string (&ds, ss_cstr (ov->v.s));
1376         g_string_append (str, ds_cstr (&ds));
1377         ds_destroy (&ds);
1378       }
1379       break;
1380     case OV_MISSING:
1381       g_string_append (str, "MISSING");
1382       break;
1383     case OV_SYSMIS:
1384       g_string_append (str, "SYSMIS");
1385       break;
1386     case OV_ELSE:
1387       g_string_append (str, "ELSE");
1388       break;
1389     case OV_RANGE:
1390       g_string_append_printf (str, "%g THRU %g",
1391                               ov->v.range[0],
1392                               ov->v.range[1]);
1393       break;
1394     case OV_LOW_UP:
1395       g_string_append_printf (str, "LOWEST THRU %g",
1396                               ov->v.range[1]);
1397       break;
1398     case OV_HIGH_DOWN:
1399       g_string_append_printf (str, "%g THRU HIGHEST",
1400                               ov->v.range[0]);
1401       break;
1402     default:
1403       g_warning ("Invalid type in old recode value");
1404       g_string_append (str, "???");
1405       break;
1406     };
1407 }
1408
1409
1410
1411 static char *
1412 generate_syntax (const struct recode_dialog *rd)
1413 {
1414   gboolean ok;
1415   GtkTreeIter iter;
1416   gchar *text;
1417
1418   GString *str = g_string_sized_new (100);
1419
1420   /* Declare new string variables if applicable */
1421   if ( rd->different &&
1422        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rd->string_button)))
1423     {
1424       GHashTableIter iter;
1425
1426       struct variable *var = NULL;
1427       struct nlp *nlp = NULL;
1428
1429       g_hash_table_iter_init (&iter, rd->varmap);
1430       while (g_hash_table_iter_next (&iter, (void**) &var, (void**) &nlp))
1431         {
1432           g_string_append (str, "\nSTRING ");
1433           g_string_append (str, nlp->name);
1434           g_string_append_printf (str, " (A%d).",
1435                                   (int)
1436                                   gtk_spin_button_get_value (GTK_SPIN_BUTTON (rd->width_entry) )
1437                                   );
1438         }
1439     }
1440
1441   g_string_append (str, "\nRECODE ");
1442
1443   psppire_var_view_append_names (PSPPIRE_VAR_VIEW (rd->variable_treeview), 0, str);
1444
1445   g_string_append (str, "\n\t");
1446
1447   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rd->convert_button)))
1448     {
1449       g_string_append (str, "(CONVERT) ");
1450     }
1451
1452   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rd->value_map),
1453                                            &iter);
1454        ok;
1455        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (rd->value_map), &iter))
1456     {
1457       GValue ov_value = {0};
1458       GValue nv_value = {0};
1459       struct old_value *ov;
1460       struct new_value *nv;
1461       gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
1462                                 COL_VALUE_OLD, &ov_value);
1463
1464       gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
1465                                 COL_VALUE_NEW, &nv_value);
1466
1467       ov = g_value_get_boxed (&ov_value);
1468       nv = g_value_get_boxed (&nv_value);
1469
1470       g_string_append (str, "(");
1471
1472       old_value_append_syntax (str, ov);
1473       g_string_append (str, " = ");
1474       new_value_append_syntax (str, nv);
1475
1476       g_string_append (str, ") ");
1477       g_value_unset (&ov_value);
1478       g_value_unset (&nv_value);
1479     }
1480
1481
1482   if ( rd->different )
1483     {
1484
1485       GtkTreeIter iter;
1486       g_string_append (str, "\n\tINTO ");
1487
1488       for (ok = psppire_var_view_get_iter_first (PSPPIRE_VAR_VIEW (rd->variable_treeview), &iter);
1489            ok;
1490            ok = psppire_var_view_get_iter_next (PSPPIRE_VAR_VIEW (rd->variable_treeview), &iter))
1491           {
1492             struct nlp *nlp = NULL;
1493             const struct variable *var = psppire_var_view_get_variable (PSPPIRE_VAR_VIEW (rd->variable_treeview), 0, &iter);
1494
1495             nlp = g_hash_table_lookup (rd->varmap, var);
1496             
1497             g_string_append (str, nlp->name);
1498             g_string_append (str, " ");
1499           }
1500     }
1501
1502   g_string_append (str, ".");
1503
1504   /* If applicable, set labels for the new variables. */
1505   if ( rd->different )
1506     {
1507       GHashTableIter iter;
1508
1509       struct variable *var = NULL;
1510       struct nlp *nlp = NULL;
1511
1512       g_hash_table_iter_init (&iter, rd->varmap);
1513       while (g_hash_table_iter_next (&iter, (void**) &var, (void**) &nlp))
1514         {
1515           if (nlp->label)
1516             g_string_append_printf (str, "\nVARIABLE LABELS %s %s.",
1517                                     nlp->name, nlp->label);
1518         }
1519     }
1520
1521   g_string_append (str, "\nEXECUTE.\n");
1522
1523
1524   text = str->str;
1525
1526   g_string_free (str, FALSE);
1527
1528   return text;
1529 }