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