Refactoring: common function set_sensitivity from toggle
[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   GString *string = g_string_new ("");
350
351   const gchar *filter = "filter_$";
352   const gchar key[]="case_$";
353
354   if ( gtk_toggle_button_get_active
355        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
356                                               "radiobutton-range"))))
357     {
358       GtkSpinButton *first =
359         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
360                                            "range-dialog-first"));
361
362       GtkSpinButton *last =
363         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
364                                            "range-dialog-last"));
365
366       g_string_append_printf (string,
367                               "COMPUTE filter_$ = ($CASENUM >= %ld "
368                                "AND $CASENUM <= %ld).\n",
369                               (long) gtk_spin_button_get_value (first),
370                               (long) gtk_spin_button_get_value (last)
371                               );
372
373       g_string_append (string, "EXECUTE.\n");
374     }
375   else if ( gtk_toggle_button_get_active
376        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
377                                               "radiobutton-sample"))))
378     {
379       GtkWidget *random_sample =
380         get_widget_assert (scd->xml,
381                            "radiobutton-sample-percent");
382
383       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (random_sample)))
384         {
385           const double percentage =
386             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
387
388           g_string_append_printf (string,
389                                   "COMPUTE %s = RV.UNIFORM (0,1) < %g.\n",
390                                   filter,
391                                   percentage / 100.0 );
392         }
393       else
394         {
395           const gint n_cases =
396             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton1));
397           const gint from_n_cases =
398             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton2));
399
400
401           const gchar ranvar[]="rv_$";
402
403           g_string_append_printf (string,
404                                   "COMPUTE %s = $CASENUM.\n", key);
405
406           g_string_append_printf (string,
407                                   "COMPUTE %s = %s > %d.\n",
408                                   filter, key, from_n_cases);
409
410           g_string_append_printf (string,
411                                   "COMPUTE %s = RV.UNIFORM (0, 1).\n",
412                                   ranvar);
413
414           g_string_append_printf (string,
415                                   "SORT BY %s, %s.\n",
416                                   filter, ranvar);
417
418           g_string_append (string, "EXECUTE.\n");
419                                   
420
421           g_string_append_printf (string,
422                                   "COMPUTE %s = $CASENUM.\n",
423                                   filter );
424
425           g_string_append_printf (string,
426                                   "COMPUTE %s = %s <= %d\n",
427                                   filter,
428                                   filter,
429                                   n_cases );
430
431           g_string_append (string, "EXECUTE.\n");
432
433
434           g_string_append_printf (string,
435                                   "SORT BY %s.\n",
436                                   key);
437
438           g_string_append_printf (string,
439                                   "DELETE VARIABLES %s, %s.\n",
440                                   key, ranvar);
441
442         }
443
444       g_string_append (string, "EXECUTE.\n");
445
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
456   g_string_append_printf  (string, "FILTER BY %s.\n", filter);
457
458   text  = string->str;
459   g_string_free (string, FALSE);
460   return text;
461 }
462
463 static gchar *
464 generate_syntax_delete (const struct select_cases_dialog *scd)
465 {
466   gchar *text = NULL;
467   GString *string = NULL;
468
469   if ( gtk_toggle_button_get_active
470        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
471                                               "radiobutton-all"))))
472     {
473       return xstrdup ("\n");
474     }
475
476   string = g_string_new ("");
477
478   if ( gtk_toggle_button_get_active
479        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
480                                               "radiobutton-sample"))))
481   {
482     GtkWidget *random_sample =
483       get_widget_assert (scd->xml,
484                          "radiobutton-sample-percent");
485
486     g_string_append (string, "SAMPLE ");
487
488     if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (random_sample)))
489       {
490         const double percentage =
491           gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
492         g_string_append_printf (string, "%g.", percentage / 100.0);
493       }
494     else
495       {
496         const gint n_cases =
497           gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton1));
498         const gint from_n_cases =
499           gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton2));
500
501         g_string_append_printf (string, "%d FROM %d .", n_cases, from_n_cases);
502       }
503
504   }
505   else if ( gtk_toggle_button_get_active
506             (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
507                                                    "radiobutton-range"))))
508     {
509       GtkSpinButton *first =
510         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
511                                            "range-dialog-first"));
512
513       GtkSpinButton *last =
514         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
515                                            "range-dialog-last"));
516
517       g_string_append_printf (string,
518                               "COMPUTE filter_$ = ($CASENUM >= %ld "
519                                "AND $CASENUM <= %ld).\n",
520                               (long) gtk_spin_button_get_value (first),
521                               (long) gtk_spin_button_get_value (last)
522                               );
523       g_string_append (string, "EXECUTE.\n");
524       g_string_append_printf (string, "SELECT IF filter_$.\n");
525
526     }
527   else if ( gtk_toggle_button_get_active
528             (GTK_TOGGLE_BUTTON
529              (get_widget_assert (scd->xml,
530                                  "radiobutton-filter-variable"))))
531     {
532       GtkEntry *entry =
533         GTK_ENTRY (get_widget_assert (scd->xml,
534                                       "filter-variable-entry"));
535
536       g_string_append_printf (string, "SELECT IF (%s <> 0).",
537                               gtk_entry_get_text (entry));
538     }
539
540
541   g_string_append (string, "\n");
542
543
544
545   text  = string->str;
546   g_string_free (string, FALSE);
547   return text;
548 }
549
550
551 static gchar *
552 generate_syntax (const struct select_cases_dialog *scd)
553 {
554   /* In the simple case, all we need to do is cancel any existing filter */
555   if ( gtk_toggle_button_get_active
556        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
557                                               "radiobutton-all"))))
558     {
559       return g_strdup ("FILTER OFF.\n");
560     }
561
562
563   /* Are we filtering or deleting ? */
564   if ( gtk_toggle_button_get_active
565        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
566                                               "radiobutton-delete"))))
567     {
568       return generate_syntax_delete (scd);
569     }
570   else
571     {
572       return generate_syntax_filter (scd);
573     }
574
575 }
576
577
578