Replace more uses of 'cnt' by 'n'.
[pspp] / src / ui / gui / psppire-dialog-action-select.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2020 Free Software Foundation, Inc.
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
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"
34
35 #include <ui/syntax-gen.h>
36
37
38
39 #include <gettext.h>
40 #define _(msgid) gettext (msgid)
41 #define N_(msgid) msgid
42
43 static void psppire_dialog_action_select_class_init (PsppireDialogActionSelectClass *class);
44
45 G_DEFINE_TYPE (PsppireDialogActionSelect, psppire_dialog_action_select, PSPPIRE_TYPE_DIALOG_ACTION);
46
47 static gboolean
48 dialog_state_valid (gpointer data)
49 {
50   PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (data);
51   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (act->radiobutton_all)))
52     {
53       return TRUE;
54     }
55   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (act->radiobutton_filter_variable)))
56     {
57       const gchar *text = gtk_entry_get_text (GTK_ENTRY (act->entry));
58       if (!psppire_dict_lookup_var (PSPPIRE_DIALOG_ACTION (act)->dict, text))
59         return FALSE;
60     }
61
62   return TRUE;
63 }
64
65
66 static void
67 refresh (PsppireDialogAction *pda)
68 {
69   PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (pda);
70
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);
74
75   gtk_label_set_text (GTK_LABEL (act->l1), "");
76   gtk_label_set_text (GTK_LABEL (act->l0), "");
77 }
78
79
80 static void
81 set_radiobutton (GtkWidget *button, gpointer data)
82 {
83   GtkToggleButton *toggle = data;
84   gtk_toggle_button_set_active (toggle, TRUE);
85 }
86
87
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.");
90
91
92 /* Ensure that the range "first" and "last" spinbuttons are self consistent */
93 static void
94 sample_consistent (GtkSpinButton *spin, PsppireDialogActionSelect *act)
95 {
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));
98
99   if (limit < size)
100     {
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);
105     }
106 }
107
108
109 static void
110 sample_subdialog (GtkButton *b, gpointer data)
111 {
112   gint response;
113   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (data);
114   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (data);
115
116   PsppireDataStore *data_store = NULL;
117   g_object_get (PSPPIRE_DATA_WINDOW (pda->toplevel)->data_editor,
118                 "data-store", &data_store,
119                 NULL);
120
121   gint case_count = psppire_data_store_get_case_count (data_store);
122
123   if (!scd->hbox1)
124     {
125       scd->hbox1 = psppire_scanf_new (gettext (label1), &scd->spinbutton);
126
127       gtk_widget_show (scd->hbox1);
128
129       gtk_grid_attach (GTK_GRID (scd->table),
130                        scd->hbox1,
131                        1, 0,
132                        1, 1);
133
134       g_signal_connect (scd->percent, "toggled",
135                         G_CALLBACK (set_sensitivity_from_toggle), scd->hbox1);
136
137       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scd->percent), TRUE);
138     }
139
140
141   if (!scd->hbox2)
142     {
143       scd->hbox2 =
144         psppire_scanf_new (gettext (label2), &scd->spin_sample_size, &scd->spin_sample_limit);
145
146       gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spin_sample_size),
147                                  1, case_count);
148
149       gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spin_sample_limit),
150                                  1, case_count);
151
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);
154
155
156       gtk_widget_show (scd->hbox2);
157       gtk_widget_set_sensitive (scd->hbox2, FALSE);
158
159       gtk_grid_attach (GTK_GRID (scd->table),
160                        scd->hbox2,
161                        1, 1, 1, 1);
162
163       g_signal_connect (scd->sample_n_cases, "toggled",
164                         G_CALLBACK (set_sensitivity_from_toggle), scd->hbox2);
165
166       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scd->sample_n_cases), FALSE);
167     }
168
169
170   gtk_window_set_transient_for (GTK_WINDOW (scd->rsample_dialog),
171                                 GTK_WINDOW (pda->dialog));
172
173   response = psppire_dialog_run (PSPPIRE_DIALOG (scd->rsample_dialog));
174
175   if (response != PSPPIRE_RESPONSE_CONTINUE)
176     {
177       g_signal_handlers_disconnect_by_func
178         (G_OBJECT (scd->percent),
179          G_CALLBACK (set_sensitivity_from_toggle),
180          scd->hbox1);
181
182       g_signal_handlers_disconnect_by_func
183         (G_OBJECT (scd->sample_n_cases),
184          G_CALLBACK (set_sensitivity_from_toggle),
185          scd->hbox2);
186
187       gtk_widget_destroy(scd->hbox1);
188       gtk_widget_destroy(scd->hbox2);
189       scd->hbox1 = scd->hbox2 = NULL;
190     }
191   else
192     {
193       gchar *text;
194
195       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
196         {
197           text = widget_printf (gettext(label1), scd->spinbutton);
198           gtk_label_set_text (GTK_LABEL (scd->l0), text);
199         }
200       else
201         {
202           text =
203             widget_printf (gettext(label2), scd->spin_sample_size, scd->spin_sample_limit);
204           gtk_label_set_text (GTK_LABEL (scd->l0), text);
205
206         }
207       g_free (text);
208     }
209 }
210
211
212
213 static void
214 range_subdialog (GtkButton *b, gpointer data)
215 {
216   gint response;
217   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (data);
218   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (data);
219
220   PsppireDataStore *data_store = NULL;
221   g_object_get (PSPPIRE_DATA_WINDOW (pda->toplevel)->data_editor,
222                 "data-store", &data_store,
223                 NULL);
224
225   gint n_cases = psppire_data_store_get_case_count (data_store);
226
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);
229
230   gtk_window_set_transient_for (GTK_WINDOW (scd->range_subdialog),
231                                 GTK_WINDOW (pda->dialog));
232
233   response = psppire_dialog_run (PSPPIRE_DIALOG (scd->range_subdialog));
234   if (response == PSPPIRE_RESPONSE_CONTINUE)
235     {
236       gchar *text = widget_printf (_("%d thru %d"), scd->first, scd->last);
237       gtk_label_set_text (GTK_LABEL (scd->l1), text);
238       g_free (text);
239     }
240 }
241
242 /* Ensure that the range "first" and "last" spinbuttons are self consistent */
243 static void
244 consistency (GtkSpinButton *spin, PsppireDialogActionSelect *act)
245 {
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));
248
249   if (last < first)
250     {
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);
255     }
256 }
257
258
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.  */
262 static gboolean
263 on_button_release  (GtkWidget *w, GdkEvent *e, gpointer a)
264 {
265   PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (a);
266
267   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (act->radiobutton_all), TRUE);
268
269   return FALSE;
270 }
271
272 static GtkBuilder *
273 psppire_dialog_action_select_activate (PsppireDialogAction *a, GVariant *param)
274 {
275   PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (a);
276   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a);
277
278   GtkBuilder *xml = builder_new ("select-cases.ui");
279
280   pda->dialog = get_widget_assert (xml, "select-cases-dialog");
281   pda->source = get_widget_assert   (xml, "select-cases-treeview");
282
283   g_object_set (pda->source,
284                 "selection-mode", GTK_SELECTION_SINGLE,
285                 NULL);
286
287   act->entry = get_widget_assert (xml, "filter-variable-entry");
288
289   GtkWidget *selector = get_widget_assert (xml, "psppire-selector-filter");
290   psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
291                                     is_currently_in_entry);
292
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");
297
298   act->l0 = get_widget_assert (xml, "random-sample-label");
299
300   act->radiobutton_range = get_widget_assert (xml, "radiobutton-range");
301   act->range_subdialog = get_widget_assert (xml, "select-cases-range-dialog");
302
303   act->first = get_widget_assert (xml, "range-dialog-first");
304   act->last = get_widget_assert (xml, "range-dialog-last");
305
306   g_signal_connect (act->first, "value-changed", G_CALLBACK (consistency), act);
307   g_signal_connect (act->last, "value-changed", G_CALLBACK (consistency), act);
308
309   act->l1 = get_widget_assert (xml, "range-sample-label");
310   act->radiobutton_sample =  get_widget_assert (xml, "radiobutton-sample");
311
312   act->radiobutton_all = get_widget_assert (xml, "radiobutton-all");
313   act->radiobutton_filter_variable =  get_widget_assert (xml, "radiobutton-filter-variable");
314
315   act->radiobutton_filter =  get_widget_assert (xml, "radiobutton-filter");
316   act->radiobutton_delete = get_widget_assert (xml,   "radiobutton-delete");
317
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);
320
321   GtkWidget *button_range = get_widget_assert (xml, "button-range");
322   GtkWidget *button_sample = get_widget_assert (xml, "button-sample");
323
324   GtkWidget *button_if =get_widget_assert (xml, "button-if");
325
326   GtkWidget *radiobutton_if = get_widget_assert (xml, "radiobutton-if");
327
328   GtkWidget *sample_label = get_widget_assert (xml, "random-sample-label");
329
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"));
333
334   g_signal_connect (button_if, "clicked",
335                     G_CALLBACK (set_radiobutton), radiobutton_if);
336
337   g_signal_connect (button_sample, "clicked",
338                     G_CALLBACK (set_radiobutton), act->radiobutton_sample);
339
340   g_signal_connect (button_range,  "clicked",
341                     G_CALLBACK (set_radiobutton), act->radiobutton_range);
342
343   g_signal_connect (selector, "clicked",
344                     G_CALLBACK (set_radiobutton), act->radiobutton_filter_variable);
345
346   g_signal_connect (selector, "selected",
347                     G_CALLBACK (set_radiobutton), act->radiobutton_filter_variable);
348
349   g_signal_connect (act->radiobutton_range, "toggled",
350                     G_CALLBACK (set_sensitivity_from_toggle),
351                     act->l1);
352
353   g_signal_connect (act->radiobutton_sample, "toggled",
354                     G_CALLBACK (set_sensitivity_from_toggle),
355                     sample_label);
356
357   g_signal_connect (act->radiobutton_filter_variable, "toggled",
358                     G_CALLBACK (set_sensitivity_from_toggle),
359                     act->entry);
360
361   g_signal_connect (button_range,
362                     "clicked", G_CALLBACK (range_subdialog), act);
363
364   g_signal_connect (button_sample,
365                     "clicked", G_CALLBACK (sample_subdialog), act);
366   psppire_dialog_action_set_refresh (pda, refresh);
367
368   psppire_dialog_action_set_valid_predicate (pda,
369                                              dialog_state_valid);
370   return xml;
371 }
372
373
374 static char *
375 generate_syntax_filter (const PsppireDialogAction *a)
376 {
377   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
378
379   gchar *text = NULL;
380   struct string dss;
381
382   const gchar *filter = "filter_$";
383   const gchar key[]="case_$";
384
385   ds_init_empty (&dss);
386
387   if (gtk_toggle_button_get_active
388       (GTK_TOGGLE_BUTTON (scd->radiobutton_range)))
389     {
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)));
395
396       ds_put_cstr (&dss, "EXECUTE.\n");
397     }
398   else if (gtk_toggle_button_get_active
399             (GTK_TOGGLE_BUTTON (scd->radiobutton_sample)))
400     {
401       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
402         {
403           const double percentage =
404             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
405
406           ds_put_c_format (&dss,
407                            "COMPUTE %s = RV.UNIFORM (0,1) < %.*g.\n",
408                            filter,
409                            DBL_DIG + 1, percentage / 100.0);
410         }
411       else
412         {
413           const gint n_cases =
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));
417
418
419           const gchar ranvar[]="rv_$";
420
421           ds_put_c_format (&dss,
422                            "COMPUTE %s = $CASENUM.\n", key);
423
424           ds_put_c_format (&dss,
425                            "COMPUTE %s = %s > %d.\n",
426                            filter, key, from_n_cases);
427
428           ds_put_c_format (&dss,
429                            "COMPUTE %s = RV.UNIFORM (0, 1).\n",
430                            ranvar);
431
432           ds_put_c_format (&dss,
433                            "SORT CASES BY %s, %s.\n",
434                            filter, ranvar);
435
436           ds_put_cstr (&dss, "EXECUTE.\n");
437
438
439           ds_put_c_format (&dss,
440                            "COMPUTE %s = $CASENUM.\n",
441                            filter);
442
443           ds_put_c_format (&dss,
444                            "COMPUTE %s = %s <= %d\n",
445                            filter,
446                            filter,
447                            n_cases);
448
449           ds_put_cstr (&dss, "EXECUTE.\n");
450
451
452           ds_put_c_format (&dss,
453                            "SORT CASES BY %s.\n",
454                            key);
455
456           ds_put_c_format (&dss,
457                            "DELETE VARIABLES %s, %s.\n",
458                            key, ranvar);
459         }
460
461       ds_put_cstr (&dss, "EXECUTE.\n");
462     }
463   else
464     {
465       filter = gtk_entry_get_text (GTK_ENTRY (scd->entry));
466     }
467
468   ds_put_c_format (&dss, "FILTER BY %s.\n", filter);
469
470   text  = ds_steal_cstr (&dss);
471
472   ds_destroy (&dss);
473
474   return text;
475 }
476
477
478 static gchar *
479 generate_syntax_delete (const PsppireDialogAction *a)
480 {
481   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
482   gchar *text = NULL;
483   struct string dss;
484
485   if (gtk_toggle_button_get_active
486        (GTK_TOGGLE_BUTTON (scd->radiobutton_all)))
487     {
488       return xstrdup ("\n");
489     }
490
491   ds_init_empty (&dss);
492
493   if (gtk_toggle_button_get_active
494        (GTK_TOGGLE_BUTTON (scd->radiobutton_sample)))
495     {
496       ds_put_cstr (&dss, "SAMPLE ");
497
498       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
499         {
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);
503         }
504       else
505         {
506           const gint n_cases =
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));
510
511           ds_put_c_format (&dss, "%d FROM %d .", n_cases, from_n_cases);
512         }
513
514     }
515   else if (gtk_toggle_button_get_active
516             (GTK_TOGGLE_BUTTON (scd->radiobutton_range)))
517     {
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");
525
526     }
527   else if (gtk_toggle_button_get_active
528             (GTK_TOGGLE_BUTTON (scd->radiobutton_filter_variable)))
529     {
530       ds_put_c_format (&dss, "SELECT IF (%s <> 0).",
531                        gtk_entry_get_text (GTK_ENTRY (scd->entry)));
532     }
533
534
535   ds_put_cstr (&dss, "\n");
536
537   text = ds_steal_cstr (&dss);
538
539   ds_destroy (&dss);
540
541   return text;
542 }
543
544
545 static gchar *
546 generate_syntax (const PsppireDialogAction *a)
547 {
548   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
549
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)))
552     {
553       return g_strdup ("FILTER OFF.\n");
554     }
555
556   /* Are we filtering or deleting ? */
557   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->radiobutton_delete)))
558     {
559       return generate_syntax_delete (a);
560     }
561   else
562     {
563       return generate_syntax_filter (a);
564     }
565 }
566
567
568 static void
569 psppire_dialog_action_select_class_init (PsppireDialogActionSelectClass *class)
570 {
571   PSPPIRE_DIALOG_ACTION_CLASS (class)->initial_activate = psppire_dialog_action_select_activate;
572
573   PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax;
574 }
575
576
577 static void
578 psppire_dialog_action_select_init (PsppireDialogActionSelect *act)
579 {
580 }
581