1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2009, 2010, 2011, 2012, 2014, 2016,
3 2020 Free Software Foundation
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.
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.
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/>. */
21 #include "psppire-var-view.h"
23 #include "psppire-dialog-action-recode.h"
24 #include "builder-wrapper.h"
25 #include <ui/gui/dialog-common.h>
27 #include "psppire-acr.h"
29 #include "psppire-selector.h"
30 #include "psppire-val-chooser.h"
33 #include <ui/syntax-gen.h>
36 #define _(msgid) gettext (msgid)
37 #define N_(msgid) msgid
40 /* This might need to be changed to something less naive.
41 In particular, what happends with dates, etc?
44 num_to_string (gdouble x)
46 return g_strdup_printf ("%.*g", DBL_DIG + 1, x);
49 /* Define a boxed type to represent a value which is a candidate
50 to replace an existing value */
63 enum new_value_type type;
71 static struct new_value *
72 new_value_copy (struct new_value *nv)
74 struct new_value *copy = g_memdup (nv, sizeof (*copy));
76 if (nv->type == NV_STRING)
77 copy->v.s = xstrdup (nv->v.s);
84 new_value_free (struct new_value *nv)
86 if (nv->type == NV_STRING)
94 new_value_to_string (const GValue *src, GValue *dest)
96 const struct new_value *nv = g_value_get_boxed (src);
104 gchar *text = g_strdup_printf ("%.*g", DBL_DIG + 1, nv->v.v);
105 g_value_set_string (dest, text);
110 g_value_set_string (dest, nv->v.s);
113 g_value_set_string (dest, "COPY");
116 g_value_set_string (dest, "SYSMIS");
119 /* Shouldn't ever happen */
120 g_warning ("Invalid type in new recode value");
121 g_value_set_string (dest, "???");
127 new_value_get_type (void)
133 t = g_boxed_type_register_static ("psppire-recode-new-values",
134 (GBoxedCopyFunc) new_value_copy,
135 (GBoxedFreeFunc) new_value_free);
137 g_value_register_transform_func (t, G_TYPE_STRING,
138 new_value_to_string);
147 on_string_toggled (GtkToggleButton *b, PsppireDialogActionRecode *rd)
150 if (! rd->input_var_is_string)
153 active = gtk_toggle_button_get_active (b);
154 gtk_widget_set_sensitive (rd->convert_button, !active);
159 on_convert_toggled (GtkToggleButton *b, PsppireDialogActionRecode *rd)
163 g_return_if_fail (rd->input_var_is_string);
165 active = gtk_toggle_button_get_active (b);
166 gtk_widget_set_sensitive (rd->string_button, !active);
170 focus_value_entry (GtkWidget *w, PsppireDialogActionRecode *rd)
172 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
173 gtk_widget_grab_focus (rd->new_value_entry);
177 /* Callback for the new_value_entry and new_value_togglebutton widgets.
178 It's used to enable/disable the acr. */
180 set_acr (PsppireDialogActionRecode *rd)
184 if (!gtk_toggle_button_get_active
185 (GTK_TOGGLE_BUTTON (rd->toggle[BUTTON_NEW_VALUE])))
187 psppire_acr_set_enabled (PSPPIRE_ACR (rd->acr), TRUE);
191 text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
193 psppire_acr_set_enabled (PSPPIRE_ACR (rd->acr), !g_str_equal (text, ""));
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
209 on_acr_selection_change (GtkTreeSelection *selection, gpointer data)
211 PsppireDialogActionRecode *rd = data;
212 GtkTreeModel *model = NULL;
215 GValue ov_value = {0};
216 GValue nv_value = {0};
217 struct old_value *ov = NULL;
218 struct new_value *nv = NULL;
220 if (! gtk_tree_selection_get_selected (selection, &model, &iter))
224 gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
225 COL_VALUE_OLD, &ov_value);
227 gtk_tree_model_get_value (GTK_TREE_MODEL (model), &iter,
228 COL_VALUE_NEW, &nv_value);
230 ov = g_value_get_boxed (&ov_value);
231 nv = g_value_get_boxed (&nv_value);
239 gchar *str = num_to_string (nv->v.v);
241 gtk_toggle_button_set_active
242 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
244 gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), str);
249 gtk_toggle_button_set_active
250 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE]), TRUE);
252 gtk_entry_set_text (GTK_ENTRY (rd->new_value_entry), nv->v.s);
255 gtk_toggle_button_set_active
256 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS]), TRUE);
260 gtk_toggle_button_set_active
261 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY]), TRUE);
265 g_warning ("Invalid new value type");
269 g_value_unset (&nv_value);
272 psppire_val_chooser_set_status (PSPPIRE_VAL_CHOOSER (rd->old_value_chooser), ov);
275 /* Initialise VAL to reflect the current status of RD */
277 set_old_value (GValue *val, const PsppireDialogActionRecode *rd)
279 PsppireValChooser *vc = PSPPIRE_VAL_CHOOSER (rd->old_value_chooser);
283 psppire_val_chooser_get_status (vc, &ov);
285 g_value_init (val, old_value_get_type ());
286 g_value_set_boxed (val, &ov);
292 /* Initialse VAL to reflect the current status of RD */
294 set_new_value (GValue *val, const PsppireDialogActionRecode *rd)
296 const gchar *text = NULL;
299 if (gtk_toggle_button_get_active
300 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_VALUE])))
302 text = gtk_entry_get_text (GTK_ENTRY (rd->new_value_entry));
303 nv.type = NV_NUMERIC;
305 if (PSPPIRE_DIALOG_ACTION_RECODE_CLASS (G_OBJECT_GET_CLASS (rd))->target_is_string (rd))
308 if (nv.type == NV_STRING)
309 nv.v.s = g_strdup (text);
311 nv.v.v = g_strtod (text, 0);
313 else if (gtk_toggle_button_get_active
314 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_COPY])))
318 else if (gtk_toggle_button_get_active
319 (GTK_TOGGLE_BUTTON (rd->toggle [BUTTON_NEW_SYSMIS])))
326 g_value_init (val, new_value_get_type ());
327 g_value_set_boxed (val, &nv);
332 /* A function to set a value in a column in the ACR */
334 set_value (gint col, GValue *val, gpointer data)
336 PsppireDialogActionRecode *rd = data;
341 set_old_value (val, rd);
344 set_new_value (val, rd);
354 set_old_and_new_button_sensitivity (GtkTreeSelection *sel, PsppireDialogActionRecode *rd)
356 GtkTreeModel *model = NULL;
358 GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
360 gtk_widget_set_sensitive (rd->old_and_new, rows != NULL);
364 run_old_and_new_dialog (PsppireDialogActionRecode *rd)
367 GtkListStore *local_store = clone_list_store (rd->value_map);
368 PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (rd);
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);
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;
378 GtkTreeModel *model =
379 gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
381 gboolean not_empty = gtk_tree_model_get_iter_first (model, &iter);
383 g_return_if_fail (not_empty);
385 gtk_tree_model_get (model, &iter, 0, &v, -1);
387 rd->input_var_is_string = var_is_alpha (v);
389 g_object_set (rd->old_value_chooser, "is-string", rd->input_var_is_string, NULL);
391 gtk_widget_set_sensitive (rd->toggle [BUTTON_NEW_SYSMIS],
394 gtk_widget_set_sensitive (rd->convert_button, var_is_alpha (v));
398 response = psppire_dialog_run (rd->old_and_new_dialog);
399 psppire_acr_set_model (PSPPIRE_ACR (rd->acr), NULL);
402 if (response == PSPPIRE_RESPONSE_CONTINUE)
404 g_object_unref (rd->value_map);
405 rd->value_map = clone_list_store (local_store);
408 g_object_unref (local_store);
411 psppire_dialog_notify_change (PSPPIRE_DIALOG (pda->dialog));
415 /* Sets the sensitivity of TARGET dependent upon the active status
418 toggle_sensitivity (GtkToggleButton *button, GtkWidget *target)
420 gboolean state = gtk_toggle_button_get_active (button);
422 /* g_print ("%s Setting %p (%s) to %d because of %p\n",
423 __FUNCTION__, target, gtk_widget_get_name (target), state, button); */
425 gtk_widget_set_sensitive (target, state);
432 psppire_dialog_action_recode_class_init (PsppireDialogActionRecodeClass *class);
434 G_DEFINE_TYPE (PsppireDialogActionRecode, psppire_dialog_action_recode, PSPPIRE_TYPE_DIALOG_ACTION);
437 psppire_dialog_action_recode_refresh (PsppireDialogAction *rd_)
439 PsppireDialogActionRecode *rd = PSPPIRE_DIALOG_ACTION_RECODE (rd_);
442 gtk_tree_view_get_model (GTK_TREE_VIEW (rd->variable_treeview));
444 gtk_list_store_clear (GTK_LIST_STORE (vars));
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);
451 gtk_list_store_clear (GTK_LIST_STORE (rd->value_map));
456 psppire_dialog_action_recode_pre_activate (PsppireDialogActionRecode *act,
457 void (*populate_treeview) (PsppireDialogActionRecode *))
459 PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (act);
461 GtkBuilder *xml = builder_new ("recode.ui");
463 pda->dialog = get_widget_assert (xml, "recode-dialog");
464 pda->source = get_widget_assert (xml, "treeview1");
467 GtkWidget *selector = get_widget_assert (xml, "psppire-selector1");
468 act->old_and_new = get_widget_assert (xml, "button1");
470 act->output_variable_box = get_widget_assert (xml,"frame4");
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");
477 act->value_map = gtk_list_store_new (2,
478 old_value_get_type (),
479 new_value_get_type ());
481 if (populate_treeview)
482 populate_treeview (act);
484 psppire_selector_set_allow (PSPPIRE_SELECTOR (selector), homogeneous_types);
486 /* Set up the Old & New Values subdialog */
488 act->string_button = get_widget_assert (xml, "checkbutton1");
489 act->width_entry = get_widget_assert (xml, "spinbutton1");
491 act->convert_button = get_widget_assert (xml, "checkbutton2");
493 act->old_value_chooser = get_widget_assert (xml, "val-chooser");
495 act->new_value_entry = get_widget_assert (xml, "entry1");
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");
502 act->new_copy_label = get_widget_assert (xml, "label3");
503 act->strings_box = get_widget_assert (xml, "table3");
505 act->old_and_new_dialog =
506 PSPPIRE_DIALOG (get_widget_assert (xml, "old-new-values-dialog"));
508 act->acr = get_widget_assert (xml, "psppire-acr1");
510 g_signal_connect_swapped (act->toggle[BUTTON_NEW_VALUE], "toggled",
511 G_CALLBACK (set_acr), act);
513 g_signal_connect_after (act->toggle[BUTTON_NEW_VALUE], "toggled",
514 G_CALLBACK (focus_value_entry), act);
516 g_signal_connect_swapped (act->new_value_entry, "changed",
517 G_CALLBACK (set_acr), act);
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);
527 gtk_tree_view_column_new_with_attributes (_("Old"),
528 gtk_cell_renderer_text_new (),
532 gtk_tree_view_append_column (PSPPIRE_ACR (act->acr)->tv, column);
535 gtk_tree_view_column_new_with_attributes (_("New"),
536 gtk_cell_renderer_text_new (),
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);
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);
550 g_signal_connect_swapped (act->old_and_new, "clicked",
551 G_CALLBACK (run_old_and_new_dialog), act);
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);
559 g_signal_connect (act->toggle[BUTTON_NEW_VALUE], "toggled",
560 G_CALLBACK (toggle_sensitivity), act->new_value_entry);
562 g_signal_connect (act->string_button, "toggled",
563 G_CALLBACK (toggle_sensitivity), act->width_entry);
565 g_signal_connect (act->string_button, "toggled",
566 G_CALLBACK (on_string_toggled), act);
568 g_signal_connect (act->convert_button, "toggled",
569 G_CALLBACK (on_convert_toggled), act);
574 /* Generate a syntax fragment for NV and append it to STR */
576 new_value_append_syntax (struct string *dds, const struct new_value *nv)
581 ds_put_c_format (dds, "%.*g", DBL_DIG + 1, nv->v.v);
584 syntax_gen_string (dds, ss_cstr (nv->v.s));
587 ds_put_cstr (dds, "COPY");
590 ds_put_cstr (dds, "SYSMIS");
593 /* Shouldn't ever happen */
594 g_warning ("Invalid type in new recode value");
595 ds_put_cstr (dds, "???");
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 *))
607 PsppireDialogActionRecode *rd = PSPPIRE_DIALOG_ACTION_RECODE (act);
613 ds_init_empty (&dds);
615 append_string_decls (rd, &dds);
617 ds_put_cstr (&dds, "\nRECODE ");
619 psppire_var_view_append_names_str (PSPPIRE_VAR_VIEW (rd->variable_treeview), 0, &dds);
621 ds_put_cstr (&dds, "\n\t");
623 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rd->convert_button)))
625 ds_put_cstr (&dds, "(CONVERT) ");
628 for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rd->value_map),
631 ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (rd->value_map), &iter))
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);
640 gtk_tree_model_get_value (GTK_TREE_MODEL (rd->value_map), &iter,
641 COL_VALUE_NEW, &nv_value);
643 ov = g_value_get_boxed (&ov_value);
644 nv = g_value_get_boxed (&nv_value);
646 ds_put_cstr (&dds, "(");
648 old_value_append_syntax (&dds, ov);
649 ds_put_cstr (&dds, " = ");
650 new_value_append_syntax (&dds, nv);
652 ds_put_cstr (&dds, ") ");
653 g_value_unset (&ov_value);
654 g_value_unset (&nv_value);
657 append_into_clause (rd, &dds);
659 ds_put_cstr (&dds, ".");
661 append_new_value_labels (rd, &dds);
663 ds_put_cstr (&dds, "\nEXECUTE.\n");
666 text = ds_steal_cstr (&dds);
675 psppire_dialog_action_recode_class_init (PsppireDialogActionRecodeClass *class)
681 psppire_dialog_action_recode_init (PsppireDialogActionRecode *act)