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