Select Cases Dialog: Convert to PsppireDialogAction
[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 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   return TRUE;
51 }
52
53
54 static void
55 refresh (PsppireDialogAction *pda)
56 {
57   PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (pda);
58
59   gtk_entry_set_text (GTK_ENTRY (act->entry), "");
60   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (act->radiobutton_all), TRUE);
61   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (act->radiobutton_filter), TRUE);
62
63   gtk_label_set_text (GTK_LABEL (act->l1), "");
64   gtk_label_set_text (GTK_LABEL (act->l0), "");
65 }
66
67
68 static void
69 set_radiobutton (GtkWidget *button, gpointer data)
70 {
71   GtkToggleButton *toggle = data;
72   gtk_toggle_button_set_active (toggle, TRUE);
73 }
74
75
76 static const gchar label1[] = N_("Approximately %3d%% of all cases.");
77 static const gchar label2[] = N_("Exactly %3d cases from the first %3d cases.");
78
79
80 static void
81 sample_subdialog (GtkButton *b, gpointer data)
82 {
83   gint response;
84   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (data);
85   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (data);
86
87   PsppireDataStore *data_store = NULL;
88   g_object_get (PSPPIRE_DATA_WINDOW (pda->toplevel)->data_editor,
89                 "data-store", &data_store,
90                 NULL);
91
92   gint case_count = psppire_data_store_get_case_count (data_store);
93
94   if (!scd->hbox1)
95     {
96       scd->hbox1 = psppire_scanf_new (gettext (label1), &scd->spinbutton);
97
98       gtk_widget_show (scd->hbox1);
99
100       gtk_grid_attach (GTK_GRID (scd->table),
101                        scd->hbox1,
102                        1, 0,
103                        1, 1);
104
105       g_signal_connect (scd->percent, "toggled",
106                         G_CALLBACK (set_sensitivity_from_toggle), scd->hbox1);
107
108       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scd->percent), TRUE);
109     }
110
111
112   if (!scd->hbox2)
113     {
114       scd->hbox2 =
115         psppire_scanf_new (gettext (label2), &scd->spinbutton1, &scd->spinbutton2);
116
117       gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spinbutton1),
118                                  1, case_count);
119
120       gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spinbutton2),
121                                  1, case_count);
122
123       gtk_widget_show (scd->hbox2);
124       gtk_widget_set_sensitive (scd->hbox2, FALSE);
125
126       gtk_grid_attach (GTK_GRID (scd->table),
127                        scd->hbox2,
128                        1, 1, 1, 1);
129
130       g_signal_connect (scd->sample_n_cases, "toggled",
131                         G_CALLBACK (set_sensitivity_from_toggle), scd->hbox2);
132
133       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scd->sample_n_cases), FALSE);
134     }
135
136
137   gtk_window_set_transient_for (GTK_WINDOW (scd->rsample_dialog),
138                                 GTK_WINDOW (pda->dialog));
139
140   response = psppire_dialog_run (PSPPIRE_DIALOG (scd->rsample_dialog));
141
142   if ( response != PSPPIRE_RESPONSE_CONTINUE)
143     {
144       g_signal_handlers_disconnect_by_func
145         (G_OBJECT (scd->percent),
146          G_CALLBACK (set_sensitivity_from_toggle),
147          scd->hbox1);
148
149       g_signal_handlers_disconnect_by_func
150         (G_OBJECT (scd->sample_n_cases),
151          G_CALLBACK (set_sensitivity_from_toggle),
152          scd->hbox2);
153
154       gtk_widget_destroy(scd->hbox1);
155       gtk_widget_destroy(scd->hbox2);
156       scd->hbox1 = scd->hbox2 = NULL;
157     }
158   else
159     {
160       gchar *text;
161
162       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
163         {
164           text = widget_printf (gettext(label1), scd->spinbutton);
165           gtk_label_set_text (GTK_LABEL (scd->l0), text);
166         }
167       else
168         {
169           text =
170             widget_printf (gettext(label2), scd->spinbutton1, scd->spinbutton2);
171           gtk_label_set_text (GTK_LABEL (scd->l0), text);
172
173         }
174       g_free (text);
175     }
176 }
177
178
179
180 static void
181 range_subdialog (GtkButton *b, gpointer data)
182 {
183   gint response;
184   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (data);
185   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (data);
186
187   PsppireDataStore *data_store = NULL;
188   g_object_get (PSPPIRE_DATA_WINDOW (pda->toplevel)->data_editor,
189                 "data-store", &data_store,
190                 NULL);
191
192   gint n_cases = psppire_data_store_get_case_count (data_store);
193
194   gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->last),  1,  n_cases);
195   gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->first), 1,  n_cases);
196
197   gtk_window_set_transient_for (GTK_WINDOW (scd->range_subdialog),
198                                 GTK_WINDOW (pda->dialog));
199
200   response = psppire_dialog_run (PSPPIRE_DIALOG (scd->range_subdialog));
201   if ( response == PSPPIRE_RESPONSE_CONTINUE)
202     {
203       gchar *text = widget_printf (_("%d thru %d"), scd->first, scd->last);
204       gtk_label_set_text (GTK_LABEL (scd->l1), text);
205       g_free (text);
206     }
207 }
208
209
210 static void
211 psppire_dialog_action_select_activate (PsppireDialogAction *a)
212 {
213   PsppireDialogActionSelect *act = PSPPIRE_DIALOG_ACTION_SELECT (a);
214   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a);
215
216   GHashTable *thing = psppire_dialog_action_get_hash_table (pda);
217   GtkBuilder *xml = g_hash_table_lookup (thing, a);
218   if (!xml)
219     {
220       xml = builder_new ("select-cases.ui");
221       g_hash_table_insert (thing, a, xml);
222
223
224       pda->dialog = get_widget_assert (xml, "select-cases-dialog");
225       pda->source = get_widget_assert   (xml, "select-cases-treeview");
226
227       g_object_set (pda->source, 
228                     "selection-mode", GTK_SELECTION_SINGLE,
229                     NULL);
230       
231       act->entry = get_widget_assert (xml, "filter-variable-entry");
232
233       GtkWidget *selector = get_widget_assert (xml, "psppire-selector-filter");
234       psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
235                                         is_currently_in_entry);
236
237       act->rsample_dialog = get_widget_assert (xml, "select-cases-random-sample-dialog");
238       act->percent = get_widget_assert (xml, "radiobutton-sample-percent");
239       act->sample_n_cases = get_widget_assert (xml, "radiobutton-sample-n-cases");
240       act->table = get_widget_assert (xml, "select-cases-random-sample-table");
241
242       act->l0 = get_widget_assert (xml, "random-sample-label");;
243
244       act->radiobutton_range = get_widget_assert (xml, "radiobutton-range");
245       act->range_subdialog = get_widget_assert (xml, "select-cases-range-dialog");
246
247       act->first = get_widget_assert (xml, "range-dialog-first");
248       act->last = get_widget_assert (xml, "range-dialog-last");
249
250       act->l1 = get_widget_assert (xml, "range-sample-label");
251       act->radiobutton_sample =  get_widget_assert (xml, "radiobutton-sample");
252
253       act->radiobutton_all = get_widget_assert (xml, "radiobutton-all");
254       act->radiobutton_filter_variable =  get_widget_assert (xml, "radiobutton-filter-variable");
255
256       act->radiobutton_filter =  get_widget_assert (xml, "radiobutton-filter");
257       act->radiobutton_delete = get_widget_assert (xml,   "radiobutton-delete");
258
259
260       GtkWidget *button_range = get_widget_assert (xml, "button-range");
261       GtkWidget *button_sample = get_widget_assert (xml, "button-sample");
262
263       GtkWidget *button_if =get_widget_assert (xml, "button-if");
264
265       GtkWidget *radiobutton_if = get_widget_assert (xml, "radiobutton-if");
266
267       GtkWidget *sample_label = get_widget_assert (xml, "random-sample-label");
268
269       g_signal_connect (act->radiobutton_all, "toggled",
270                         G_CALLBACK (set_sensitivity_from_toggle_invert),
271                         get_widget_assert (xml, "filter-delete-button-box"));
272
273       g_signal_connect (button_if, "clicked",
274                         G_CALLBACK (set_radiobutton), radiobutton_if);
275
276       g_signal_connect (button_sample, "clicked",
277                         G_CALLBACK (set_radiobutton), act->radiobutton_sample);
278
279       g_signal_connect (button_range,  "clicked",
280                         G_CALLBACK (set_radiobutton), act->radiobutton_range);
281
282       g_signal_connect (selector, "clicked",
283                         G_CALLBACK (set_radiobutton), act->radiobutton_filter_variable);
284
285       g_signal_connect (selector, "selected",
286                         G_CALLBACK (set_radiobutton), act->radiobutton_filter_variable);
287
288       g_signal_connect (act->radiobutton_range, "toggled",
289                         G_CALLBACK (set_sensitivity_from_toggle),
290                         act->l1);
291
292       g_signal_connect (act->radiobutton_sample, "toggled",
293                         G_CALLBACK (set_sensitivity_from_toggle),
294                         sample_label);
295
296       g_signal_connect (act->radiobutton_filter_variable, "toggled",
297                         G_CALLBACK (set_sensitivity_from_toggle),
298                         act->entry);
299
300       g_signal_connect (button_range,
301                         "clicked", G_CALLBACK (range_subdialog), act);
302
303       g_signal_connect (button_sample,
304                         "clicked", G_CALLBACK (sample_subdialog), act);
305     }
306
307   psppire_dialog_action_set_refresh (pda, refresh);
308
309   psppire_dialog_action_set_valid_predicate (pda,
310                                         dialog_state_valid);
311
312   if (PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_select_parent_class)->activate)
313     PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_select_parent_class)->activate (pda);
314 }
315
316
317 static char *
318 generate_syntax_filter (PsppireDialogAction *a)
319 {
320   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
321
322   gchar *text = NULL;
323   struct string dss;
324
325   const gchar *filter = "filter_$";
326   const gchar key[]="case_$";
327
328   ds_init_empty (&dss);
329
330   if (gtk_toggle_button_get_active
331       (GTK_TOGGLE_BUTTON (scd->radiobutton_range)))
332     {
333       ds_put_c_format (&dss,
334                        "COMPUTE filter_$ = ($CASENUM >= %ld "
335                        "AND $CASENUM <= %ld).\n",
336                        (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->first)),
337                        (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->last)));
338
339       ds_put_cstr (&dss, "EXECUTE.\n");
340     }
341   else if ( gtk_toggle_button_get_active
342             (GTK_TOGGLE_BUTTON (scd->radiobutton_sample)))
343     {
344       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
345         {
346           const double percentage =
347             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
348
349           ds_put_c_format (&dss,
350                            "COMPUTE %s = RV.UNIFORM (0,1) < %.*g.\n",
351                            filter,
352                            DBL_DIG + 1, percentage / 100.0 );
353         }
354       else
355         {
356           const gint n_cases =
357             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton1));
358           const gint from_n_cases =
359             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton2));
360
361
362           const gchar ranvar[]="rv_$";
363
364           ds_put_c_format (&dss,
365                            "COMPUTE %s = $CASENUM.\n", key);
366
367           ds_put_c_format (&dss,
368                            "COMPUTE %s = %s > %d.\n",
369                            filter, key, from_n_cases);
370
371           ds_put_c_format (&dss,
372                            "COMPUTE %s = RV.UNIFORM (0, 1).\n",
373                            ranvar);
374
375           ds_put_c_format (&dss,
376                            "SORT BY %s, %s.\n",
377                            filter, ranvar);
378
379           ds_put_cstr (&dss, "EXECUTE.\n");
380                                   
381
382           ds_put_c_format (&dss,
383                            "COMPUTE %s = $CASENUM.\n",
384                            filter );
385
386           ds_put_c_format (&dss,
387                            "COMPUTE %s = %s <= %d\n",
388                            filter,
389                            filter,
390                            n_cases );
391
392           ds_put_cstr (&dss, "EXECUTE.\n");
393
394
395           ds_put_c_format (&dss,
396                            "SORT BY %s.\n",
397                            key);
398
399           ds_put_c_format (&dss,
400                            "DELETE VARIABLES %s, %s.\n",
401                            key, ranvar);
402         }
403
404       ds_put_cstr (&dss, "EXECUTE.\n");
405     }
406   else
407     {
408       filter = gtk_entry_get_text (GTK_ENTRY (scd->entry));
409     }
410
411   ds_put_c_format (&dss, "FILTER BY %s.\n", filter);
412
413   text  = ds_steal_cstr (&dss);
414
415   ds_destroy (&dss);
416
417   return text;
418 }
419
420
421 static gchar *
422 generate_syntax_delete (PsppireDialogAction *a)
423 {
424   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
425   gchar *text = NULL;
426   struct string dss;
427
428   if ( gtk_toggle_button_get_active
429        (GTK_TOGGLE_BUTTON (scd->radiobutton_all)))
430     {
431       return xstrdup ("\n");
432     }
433
434   ds_init_empty (&dss);
435
436   if ( gtk_toggle_button_get_active
437        (GTK_TOGGLE_BUTTON (scd->radiobutton_sample)))
438     {
439       ds_put_cstr (&dss, "SAMPLE ");
440       
441       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->percent)))
442         {
443           const double percentage =
444             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
445           ds_put_c_format (&dss, "%g.", percentage / 100.0);
446         }
447       else
448         {
449           const gint n_cases =
450             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton1));
451           const gint from_n_cases =
452             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton2));
453           
454           ds_put_c_format (&dss, "%d FROM %d .", n_cases, from_n_cases);
455         }
456       
457     }
458   else if ( gtk_toggle_button_get_active
459             (GTK_TOGGLE_BUTTON (scd->radiobutton_range)))
460     {
461       ds_put_c_format (&dss,
462                        "COMPUTE filter_$ = ($CASENUM >= %ld "
463                        "AND $CASENUM <= %ld).\n",
464                        (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->first)),
465                        (long) gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->last)));
466       ds_put_cstr (&dss, "EXECUTE.\n");
467       ds_put_c_format (&dss, "SELECT IF filter_$.\n");
468
469     }
470   else if (gtk_toggle_button_get_active
471             (GTK_TOGGLE_BUTTON (scd->radiobutton_filter_variable)))
472     {
473       ds_put_c_format (&dss, "SELECT IF (%s <> 0).",
474                        gtk_entry_get_text (GTK_ENTRY (scd->entry)));
475     }
476
477
478   ds_put_cstr (&dss, "\n");
479
480   text = ds_steal_cstr (&dss);
481
482   ds_destroy (&dss);
483
484   return text;
485 }
486
487
488 static gchar *
489 generate_syntax (PsppireDialogAction *a)
490 {
491   PsppireDialogActionSelect *scd = PSPPIRE_DIALOG_ACTION_SELECT (a);
492
493   /* In the simple case, all we need to do is cancel any existing filter */
494   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->radiobutton_all)))
495     {
496       return g_strdup ("FILTER OFF.\n");
497     }
498   
499   /* Are we filtering or deleting ? */
500   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scd->radiobutton_delete)))
501     {
502       return generate_syntax_delete (a);
503     }
504   else
505     {
506       return generate_syntax_filter (a);
507     }
508 }
509
510
511 static void
512 psppire_dialog_action_select_class_init (PsppireDialogActionSelectClass *class)
513 {
514   psppire_dialog_action_set_activation (class, psppire_dialog_action_select_activate);
515
516   PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax;
517 }
518
519
520 static void
521 psppire_dialog_action_select_init (PsppireDialogActionSelect *act)
522 {
523 }
524