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