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