1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2020 Free Software Foundation, Inc.
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.
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.
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/>. */
21 #include "builder-wrapper.h"
22 #include "dialog-common.h"
23 #include "dict-display.h"
24 #include "libpspp/str.h"
25 #include "psppire-data-store.h"
26 #include "psppire-data-window.h"
27 #include "psppire-dialog-action-select.h"
28 #include "psppire-dialog.h"
29 #include "psppire-dict.h"
30 #include "psppire-scanf.h"
31 #include "psppire-value-entry.h"
32 #include "psppire-var-view.h"
33 #include "widget-io.h"
35 #include <ui/syntax-gen.h>
40 #define _(msgid) gettext (msgid)
41 #define N_(msgid) msgid
43 static void psppire_dialog_action_select_class_init (PsppireDialogActionSelectClass *class);
45 G_DEFINE_TYPE (PsppireDialogActionSelect, psppire_dialog_action_select, PSPPIRE_TYPE_DIALOG_ACTION);
48 dialog_state_valid (gpointer data)
50 PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (data);
51 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (act->radiobutton_all)))
55 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (act->radiobutton_filter_variable)))
57 const gchar *text = gtk_entry_get_text (GTK_ENTRY (act->entry));
58 if (!psppire_dict_lookup_var (PSPPIRE_DIALOG_ACTION (act)->dict, text))
67 refresh (PsppireDialogAction *pda)
69 PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (pda);
71 gtk_entry_set_text (GTK_ENTRY (act->entry), "");
72 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (act->radiobutton_all), TRUE);
73 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (act->radiobutton_filter), TRUE);
75 gtk_label_set_text (GTK_LABEL (act->l1), "");
76 gtk_label_set_text (GTK_LABEL (act->l0), "");
81 set_radiobutton (GtkWidget *button, gpointer data)
83 GtkToggleButton *toggle = data;
84 gtk_toggle_button_set_active (toggle, TRUE);
88 static const gchar label1[] = N_("Approximately %3d%% of all cases.");
89 static const gchar label2[] = N_("Exactly %3d cases from the first %3d cases.");
92 /* Ensure that the range "first" and "last" spinbuttons are self consistent */
94 sample_consistent (GtkSpinButton *spin, PsppireDialogActionSelect *act)
96 gdouble size = gtk_spin_button_get_value (GTK_SPIN_BUTTON (act->spin_sample_size));
97 gdouble limit = gtk_spin_button_get_value (GTK_SPIN_BUTTON (act->spin_sample_limit));
101 if (spin == GTK_SPIN_BUTTON (act->spin_sample_size))
102 gtk_spin_button_set_value (GTK_SPIN_BUTTON (act->spin_sample_limit), size);
103 if (spin == GTK_SPIN_BUTTON (act->spin_sample_limit))
104 gtk_spin_button_set_value (GTK_SPIN_BUTTON (act->spin_sample_size), limit);
110 sample_subdialog (GtkButton *b, gpointer data)
113 PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (data);
114 PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (data);
116 PsppireDataStore *data_store = NULL;
117 g_object_get (PSPPIRE_DATA_WINDOW (pda->toplevel)->data_editor,
118 "data-store", &data_store,
121 gint case_count = psppire_data_store_get_case_count (data_store);
125 scd->hbox1 = psppire_scanf_new (gettext (label1), &scd->spinbutton);
127 gtk_widget_show (scd->hbox1);
129 gtk_grid_attach (GTK_GRID (scd->table),
134 g_signal_connect (scd->percent, "toggled",
135 G_CALLBACK (set_sensitivity_from_toggle), scd->hbox1);
137 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scd->percent), TRUE);
144 psppire_scanf_new (gettext (label2), &scd->spin_sample_size, &scd->spin_sample_limit);
146 gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spin_sample_size),
149 gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spin_sample_limit),
152 g_signal_connect (scd->spin_sample_size, "value-changed", G_CALLBACK (sample_consistent), scd);
153 g_signal_connect (scd->spin_sample_limit, "value-changed", G_CALLBACK (sample_consistent), scd);
156 gtk_widget_show (scd->hbox2);
157 gtk_widget_set_sensitive (scd->hbox2, FALSE);
159 gtk_grid_attach (GTK_GRID (scd->table),
163 g_signal_connect (scd->sample_n_cases, "toggled",
164 G_CALLBACK (set_sensitivity_from_toggle), scd->hbox2);
166 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scd->sample_n_cases), FALSE);
170 gtk_window_set_transient_for (GTK_WINDOW (scd->rsample_dialog),
171 GTK_WINDOW (pda->dialog));
173 response = psppire_dialog_run (PSPPIRE_DIALOG (scd->rsample_dialog));
175 if (response != PSPPIRE_RESPONSE_CONTINUE)
177 g_signal_handlers_disconnect_by_func
178 (G_OBJECT (scd->percent),
179 G_CALLBACK (set_sensitivity_from_toggle),
182 g_signal_handlers_disconnect_by_func
183 (G_OBJECT (scd->sample_n_cases),
184 G_CALLBACK (set_sensitivity_from_toggle),
187 gtk_widget_destroy(scd->hbox1);
188 gtk_widget_destroy(scd->hbox2);
189 scd->hbox1 = scd->hbox2 = NULL;
195 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
197 text = widget_printf (gettext(label1), scd->spinbutton);
198 gtk_label_set_text (GTK_LABEL (scd->l0), text);
203 widget_printf (gettext(label2), scd->spin_sample_size, scd->spin_sample_limit);
204 gtk_label_set_text (GTK_LABEL (scd->l0), text);
214 range_subdialog (GtkButton *b, gpointer data)
217 PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (data);
218 PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (data);
220 PsppireDataStore *data_store = NULL;
221 g_object_get (PSPPIRE_DATA_WINDOW (pda->toplevel)->data_editor,
222 "data-store", &data_store,
225 gint n_cases = psppire_data_store_get_case_count (data_store);
227 gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->last), 1, n_cases);
228 gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->first), 1, n_cases);
230 gtk_window_set_transient_for (GTK_WINDOW (scd->range_subdialog),
231 GTK_WINDOW (pda->dialog));
233 response = psppire_dialog_run (PSPPIRE_DIALOG (scd->range_subdialog));
234 if (response == PSPPIRE_RESPONSE_CONTINUE)
236 gchar *text = widget_printf (_("%d thru %d"), scd->first, scd->last);
237 gtk_label_set_text (GTK_LABEL (scd->l1), text);
242 /* Ensure that the range "first" and "last" spinbuttons are self consistent */
244 consistency (GtkSpinButton *spin, PsppireDialogActionSelect *act)
246 gdouble first = gtk_spin_button_get_value (GTK_SPIN_BUTTON (act->first));
247 gdouble last = gtk_spin_button_get_value (GTK_SPIN_BUTTON (act->last));
251 if (spin == GTK_SPIN_BUTTON (act->first))
252 gtk_spin_button_set_value (GTK_SPIN_BUTTON (act->last), first);
253 if (spin == GTK_SPIN_BUTTON (act->last))
254 gtk_spin_button_set_value (GTK_SPIN_BUTTON (act->first), last);
259 /* When the all cases label button is clicked, set the corresponding button
260 to active. This is a convenience thing, since the button itself has
261 a very small area and is hard to find with the mouse pointer. */
263 on_button_release (GtkWidget *w, GdkEvent *e, gpointer a)
265 PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (a);
267 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (act->radiobutton_all), TRUE);
273 psppire_dialog_action_select_activate (PsppireDialogAction *a, GVariant *param)
275 PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (a);
276 PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a);
278 GtkBuilder *xml = builder_new ("select-cases.ui");
280 pda->dialog = get_widget_assert (xml, "select-cases-dialog");
281 pda->source = get_widget_assert (xml, "select-cases-treeview");
283 g_object_set (pda->source,
284 "selection-mode", GTK_SELECTION_SINGLE,
287 act->entry = get_widget_assert (xml, "filter-variable-entry");
289 GtkWidget *selector = get_widget_assert (xml, "psppire-selector-filter");
290 psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
291 is_currently_in_entry);
293 act->rsample_dialog = get_widget_assert (xml, "select-cases-random-sample-dialog");
294 act->percent = get_widget_assert (xml, "radiobutton-sample-percent");
295 act->sample_n_cases = get_widget_assert (xml, "radiobutton-sample-n-cases");
296 act->table = get_widget_assert (xml, "select-cases-random-sample-table");
298 act->l0 = get_widget_assert (xml, "random-sample-label");
300 act->radiobutton_range = get_widget_assert (xml, "radiobutton-range");
301 act->range_subdialog = get_widget_assert (xml, "select-cases-range-dialog");
303 act->first = get_widget_assert (xml, "range-dialog-first");
304 act->last = get_widget_assert (xml, "range-dialog-last");
306 g_signal_connect (act->first, "value-changed", G_CALLBACK (consistency), act);
307 g_signal_connect (act->last, "value-changed", G_CALLBACK (consistency), act);
309 act->l1 = get_widget_assert (xml, "range-sample-label");
310 act->radiobutton_sample = get_widget_assert (xml, "radiobutton-sample");
312 act->radiobutton_all = get_widget_assert (xml, "radiobutton-all");
313 act->radiobutton_filter_variable = get_widget_assert (xml, "radiobutton-filter-variable");
315 act->radiobutton_filter = get_widget_assert (xml, "radiobutton-filter");
316 act->radiobutton_delete = get_widget_assert (xml, "radiobutton-delete");
318 GtkWidget *all_cases_event = get_widget_assert (xml, "all-cases-event");
319 g_signal_connect (all_cases_event, "button-release-event", G_CALLBACK (on_button_release), act);
321 GtkWidget *button_range = get_widget_assert (xml, "button-range");
322 GtkWidget *button_sample = get_widget_assert (xml, "button-sample");
324 GtkWidget *button_if =get_widget_assert (xml, "button-if");
326 GtkWidget *radiobutton_if = get_widget_assert (xml, "radiobutton-if");
328 GtkWidget *sample_label = get_widget_assert (xml, "random-sample-label");
330 g_signal_connect (act->radiobutton_all, "toggled",
331 G_CALLBACK (set_sensitivity_from_toggle_invert),
332 get_widget_assert (xml, "filter-delete-button-box"));
334 g_signal_connect (button_if, "clicked",
335 G_CALLBACK (set_radiobutton), radiobutton_if);
337 g_signal_connect (button_sample, "clicked",
338 G_CALLBACK (set_radiobutton), act->radiobutton_sample);
340 g_signal_connect (button_range, "clicked",
341 G_CALLBACK (set_radiobutton), act->radiobutton_range);
343 g_signal_connect (selector, "clicked",
344 G_CALLBACK (set_radiobutton), act->radiobutton_filter_variable);
346 g_signal_connect (selector, "selected",
347 G_CALLBACK (set_radiobutton), act->radiobutton_filter_variable);
349 g_signal_connect (act->radiobutton_range, "toggled",
350 G_CALLBACK (set_sensitivity_from_toggle),
353 g_signal_connect (act->radiobutton_sample, "toggled",
354 G_CALLBACK (set_sensitivity_from_toggle),
357 g_signal_connect (act->radiobutton_filter_variable, "toggled",
358 G_CALLBACK (set_sensitivity_from_toggle),
361 g_signal_connect (button_range,
362 "clicked", G_CALLBACK (range_subdialog), act);
364 g_signal_connect (button_sample,
365 "clicked", G_CALLBACK (sample_subdialog), act);
366 psppire_dialog_action_set_refresh (pda, refresh);
368 psppire_dialog_action_set_valid_predicate (pda,
375 generate_syntax_filter (const PsppireDialogAction *a)
377 PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
382 const gchar *filter = "filter_$";
383 const gchar key[]="case_$";
385 ds_init_empty (&dss);
387 if (gtk_toggle_button_get_active
388 (GTK_TOGGLE_BUTTON (scd->radiobutton_range)))
390 ds_put_c_format (&dss,
391 "COMPUTE filter_$ = ($CASENUM >= %ld "
392 "AND $CASENUM <= %ld).\n",
393 (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->first)),
394 (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->last)));
396 ds_put_cstr (&dss, "EXECUTE.\n");
398 else if (gtk_toggle_button_get_active
399 (GTK_TOGGLE_BUTTON (scd->radiobutton_sample)))
401 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
403 const double percentage =
404 gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
406 ds_put_c_format (&dss,
407 "COMPUTE %s = RV.UNIFORM (0,1) < %.*g.\n",
409 DBL_DIG + 1, percentage / 100.0);
414 gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spin_sample_size));
415 const gint from_n_cases =
416 gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spin_sample_limit));
419 const gchar ranvar[]="rv_$";
421 ds_put_c_format (&dss,
422 "COMPUTE %s = $CASENUM.\n", key);
424 ds_put_c_format (&dss,
425 "COMPUTE %s = %s > %d.\n",
426 filter, key, from_n_cases);
428 ds_put_c_format (&dss,
429 "COMPUTE %s = RV.UNIFORM (0, 1).\n",
432 ds_put_c_format (&dss,
433 "SORT CASES BY %s, %s.\n",
436 ds_put_cstr (&dss, "EXECUTE.\n");
439 ds_put_c_format (&dss,
440 "COMPUTE %s = $CASENUM.\n",
443 ds_put_c_format (&dss,
444 "COMPUTE %s = %s <= %d\n",
449 ds_put_cstr (&dss, "EXECUTE.\n");
452 ds_put_c_format (&dss,
453 "SORT CASES BY %s.\n",
456 ds_put_c_format (&dss,
457 "DELETE VARIABLES %s, %s.\n",
461 ds_put_cstr (&dss, "EXECUTE.\n");
465 filter = gtk_entry_get_text (GTK_ENTRY (scd->entry));
468 ds_put_c_format (&dss, "FILTER BY %s.\n", filter);
470 text = ds_steal_cstr (&dss);
479 generate_syntax_delete (const PsppireDialogAction *a)
481 PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
485 if (gtk_toggle_button_get_active
486 (GTK_TOGGLE_BUTTON (scd->radiobutton_all)))
488 return xstrdup ("\n");
491 ds_init_empty (&dss);
493 if (gtk_toggle_button_get_active
494 (GTK_TOGGLE_BUTTON (scd->radiobutton_sample)))
496 ds_put_cstr (&dss, "SAMPLE ");
498 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
500 const double percentage =
501 gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
502 ds_put_c_format (&dss, "%g.", percentage / 100.0);
507 gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spin_sample_size));
508 const gint from_n_cases =
509 gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spin_sample_limit));
511 ds_put_c_format (&dss, "%d FROM %d .", n_cases, from_n_cases);
515 else if (gtk_toggle_button_get_active
516 (GTK_TOGGLE_BUTTON (scd->radiobutton_range)))
518 ds_put_c_format (&dss,
519 "COMPUTE filter_$ = ($CASENUM >= %ld "
520 "AND $CASENUM <= %ld).\n",
521 (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->first)),
522 (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->last)));
523 ds_put_cstr (&dss, "EXECUTE.\n");
524 ds_put_c_format (&dss, "SELECT IF filter_$.\n");
527 else if (gtk_toggle_button_get_active
528 (GTK_TOGGLE_BUTTON (scd->radiobutton_filter_variable)))
530 ds_put_c_format (&dss, "SELECT IF (%s <> 0).",
531 gtk_entry_get_text (GTK_ENTRY (scd->entry)));
535 ds_put_cstr (&dss, "\n");
537 text = ds_steal_cstr (&dss);
546 generate_syntax (const PsppireDialogAction *a)
548 PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
550 /* In the simple case, all we need to do is cancel any existing filter */
551 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->radiobutton_all)))
553 return g_strdup ("FILTER OFF.\n");
556 /* Are we filtering or deleting ? */
557 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->radiobutton_delete)))
559 return generate_syntax_delete (a);
563 return generate_syntax_filter (a);
569 psppire_dialog_action_select_class_init (PsppireDialogActionSelectClass *class)
571 PSPPIRE_DIALOG_ACTION_CLASS (class)->initial_activate = psppire_dialog_action_select_activate;
573 PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax;
578 psppire_dialog_action_select_init (PsppireDialogActionSelect *act)