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