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