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