Add SET TVARS option. Closes bug #31566
[pspp] / src / ui / gui / t-test-independent-samples-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009, 2010, 2011, 2012  Free Software Foundation
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
19 #include <config.h>
20 #include <gtk/gtk.h>
21 #include "t-test-independent-samples-dialog.h"
22 #include "psppire-dict.h"
23 #include "psppire-var-store.h"
24 #include "psppire-var-view.h"
25 #include "psppire-value-entry.h"
26 #include "executor.h"
27 #include "psppire-data-window.h"
28 #include "psppire-dialog.h"
29 #include "dialog-common.h"
30 #include "dict-display.h"
31 #include "widget-io.h"
32 #include "t-test-options.h"
33 #include <ui/syntax-gen.h>
34
35 #include "builder-wrapper.h"
36 #include "helper.h"
37
38 #include <gl/xalloc.h>
39
40 #include <gettext.h>
41 #define _(msgid) gettext (msgid)
42 #define N_(msgid) msgid
43
44
45 enum group_definition
46   {
47     GROUPS_UNDEF,
48     GROUPS_VALUES,
49     GROUPS_CUT_POINT
50   };
51
52 struct tt_groups_dialog
53 {
54   GtkWidget *dialog;
55   GtkWidget *label;
56   GtkWidget *table1;
57   GtkWidget *table2;
58   GtkWidget *hbox1;
59
60   GtkWidget *values_toggle_button;
61   GtkWidget *cut_point_toggle_button;
62
63   GtkWidget *grp_entry[2];
64   GtkWidget *cut_point_entry;
65
66   enum group_definition group_defn;
67
68   union value grp_val[2];
69   union value cut_point;
70 };
71
72 static void
73 set_group_criterion_type (GtkToggleButton *button,
74                           struct tt_groups_dialog *groups)
75 {
76   gboolean by_values = gtk_toggle_button_get_active (button);
77
78   gtk_widget_set_sensitive (groups->label, by_values);
79   gtk_widget_set_sensitive (groups->table2, by_values);
80
81   gtk_widget_set_sensitive (groups->hbox1, !by_values);
82 }
83
84 static void
85 tt_groups_dialog_destroy (struct tt_groups_dialog *grps)
86 {
87
88   g_object_unref (grps->table1);
89   g_object_unref (grps->table2);
90
91   g_free (grps);
92 }
93
94 static struct tt_groups_dialog *
95 tt_groups_dialog_create (GtkBuilder *xml, GtkWindow *parent)
96 {
97   struct tt_groups_dialog *grps = xmalloc (sizeof (*grps));
98
99   grps->group_defn = GROUPS_UNDEF;
100
101   grps->dialog = get_widget_assert (xml, "define-groups-dialog");
102   grps->table1 = get_widget_assert (xml, "table1");
103   grps->table2 = get_widget_assert (xml, "table2");
104   grps->label  = get_widget_assert (xml, "label4");
105   grps->hbox1  = get_widget_assert (xml, "hbox1");
106
107   grps->grp_entry[0] = get_widget_assert (xml, "group1-entry");
108   grps->grp_entry[1] = get_widget_assert (xml, "group2-entry");
109   grps->cut_point_entry = get_widget_assert (xml, "cut-point-entry");
110
111   grps->cut_point_toggle_button = get_widget_assert (xml, "radiobutton4");
112   grps->values_toggle_button = get_widget_assert (xml, "radiobutton3");
113
114   g_object_ref (grps->table1);
115   g_object_ref (grps->table2);
116
117   g_signal_connect (grps->values_toggle_button, "toggled",
118                     G_CALLBACK (set_group_criterion_type), grps);
119
120   gtk_window_set_transient_for (GTK_WINDOW (grps->dialog), parent);
121
122   return grps;
123 }
124
125
126 struct tt_indep_samples_dialog
127 {
128   GtkBuilder *xml;  /* The xml that generated the widgets */
129   GtkWidget *dialog;
130   PsppireDict *dict;
131   GtkWidget *define_groups_button;
132   GtkWidget *groups_entry;
133
134   const struct variable *grp_var;
135
136   struct tt_groups_dialog *grps;
137   struct tt_options_dialog *opts;
138 };
139
140
141 /* Called whenever the group variable entry widget's contents change */
142 static void
143 on_grp_var_change (GtkEntry *entry,
144                                struct tt_indep_samples_dialog *tt_d)
145 {
146   const gchar *text = gtk_entry_get_text (entry);
147
148   const struct variable *v = psppire_dict_lookup_var (tt_d->dict, text);
149
150   gtk_widget_set_sensitive (tt_d->define_groups_button, v != NULL);
151
152   if (tt_d->grp_var)
153     {
154       int width = var_get_width (tt_d->grp_var);
155       value_destroy (&tt_d->grps->cut_point, width);
156       value_destroy (&tt_d->grps->grp_val[0], width);
157       value_destroy (&tt_d->grps->grp_val[1], width);
158     }
159
160   if (v)
161     {
162       const int width = var_get_width (v);
163       value_init (&tt_d->grps->cut_point, width);
164       value_init (&tt_d->grps->grp_val[0], width);
165       value_init (&tt_d->grps->grp_val[1], width);
166
167       if (width == 0)
168         {
169           tt_d->grps->cut_point.f  = SYSMIS;
170           tt_d->grps->grp_val[0].f = SYSMIS;
171           tt_d->grps->grp_val[1].f = SYSMIS;
172         }
173       else
174         {
175           tt_d->grps->cut_point.short_string[0] = '\0';
176           tt_d->grps->grp_val[0].short_string[0] = '\0';
177           tt_d->grps->grp_val[1].short_string[0] = '\0';
178         }
179     }
180
181   tt_d->grp_var = v;
182 }
183
184
185 static gchar *
186 generate_syntax (const struct tt_indep_samples_dialog *d)
187 {
188   gchar *text;
189
190   GtkWidget *tv =
191     get_widget_assert (d->xml, "indep-samples-t-test-treeview2");
192
193   GString *str = g_string_new ("T-TEST /VARIABLES=");
194
195   psppire_var_view_append_names (PSPPIRE_VAR_VIEW (tv), 0, str);
196
197   g_string_append (str, "\n\t/GROUPS=");
198
199   g_string_append (str, var_get_name (d->grp_var));
200
201   if (d->grps->group_defn != GROUPS_UNDEF)
202     {
203       g_string_append (str, "(");
204
205       {
206         const union value *val = 
207           (d->grps->group_defn == GROUPS_VALUES) ?
208           &d->grps->grp_val[0] :
209           &d->grps->cut_point;
210
211         struct string strx;        
212         ds_init_empty (&strx);
213         syntax_gen_value (&strx, val, var_get_width (d->grp_var),
214                           var_get_print_format (d->grp_var));
215       
216         g_string_append (str, ds_cstr (&strx));
217         ds_destroy (&strx);
218       }
219
220       if (d->grps->group_defn == GROUPS_VALUES)
221         {
222           g_string_append (str, ",");
223
224           {
225             struct string strx;
226             ds_init_empty (&strx);
227             
228             syntax_gen_value (&strx, &d->grps->grp_val[1], var_get_width (d->grp_var),
229                               var_get_print_format (d->grp_var));
230             
231             g_string_append (str, ds_cstr (&strx));
232             ds_destroy (&strx);
233           }
234         }
235
236       g_string_append (str, ")");
237     }
238
239   tt_options_dialog_append_syntax (d->opts, str);
240
241   g_string_append (str, ".\n");
242
243   text = str->str;
244
245   g_string_free (str, FALSE);
246
247   return text;
248 }
249
250 static void
251 refresh (struct tt_indep_samples_dialog *ttd)
252 {
253   GtkWidget *tv =
254     get_widget_assert (ttd->xml, "indep-samples-t-test-treeview2");
255
256   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tv));
257
258   gtk_entry_set_text (GTK_ENTRY (ttd->groups_entry), "");
259
260   gtk_list_store_clear (GTK_LIST_STORE (model));
261
262   gtk_widget_set_sensitive (ttd->define_groups_button, FALSE);
263 }
264
265
266 /* Return TRUE if VE contains a text which is not valid for VAR or if it
267    contains the SYSMIS value */
268 static gboolean
269 value_entry_contains_invalid (PsppireValueEntry *ve, const struct variable *var)
270 {
271   union value val;
272   gboolean result = FALSE;
273   const int width = var_get_width (var);
274   value_init (&val, width);
275
276   if ( psppire_value_entry_get_value (ve, &val, width))
277     {
278       if (var_is_value_missing (var, &val, MV_SYSTEM))
279         {
280           result = TRUE;
281         }
282     }
283   else
284     result = TRUE;
285
286   value_destroy (&val, width);
287
288
289   return result;
290 }
291
292
293 /* Returns TRUE iff the define groups subdialog has a
294    state which defines a valid group criterion */
295 static gboolean
296 define_groups_state_valid (gpointer data)
297 {
298   struct tt_indep_samples_dialog *dialog = data;
299
300   if (gtk_toggle_button_get_active
301       (GTK_TOGGLE_BUTTON (dialog->grps->values_toggle_button)))
302     {
303       if (value_entry_contains_invalid (PSPPIRE_VALUE_ENTRY (dialog->grps->grp_entry[0]), dialog->grp_var))
304         return FALSE;
305
306       if (value_entry_contains_invalid (PSPPIRE_VALUE_ENTRY (dialog->grps->grp_entry[1]), dialog->grp_var))
307         return FALSE;
308     }
309   else
310     {
311       if (value_entry_contains_invalid (PSPPIRE_VALUE_ENTRY (dialog->grps->cut_point_entry), dialog->grp_var))
312         return FALSE;
313     }
314
315   return TRUE;
316 }
317
318 static void
319 run_define_groups (struct tt_indep_samples_dialog *ttd)
320 {
321   struct tt_groups_dialog *grps = ttd->grps;
322
323   gint response;
324
325   GtkWidget *box = get_widget_assert (ttd->xml, "dialog-hbox2");
326
327   const gchar *text = gtk_entry_get_text (GTK_ENTRY (ttd->groups_entry));
328
329   const struct variable *v = psppire_dict_lookup_var (ttd->dict, text);
330
331   if ( grps->table2->parent)
332     gtk_container_remove (GTK_CONTAINER (grps->table2->parent), grps->table2);
333
334   if ( grps->table1->parent)
335     gtk_container_remove (GTK_CONTAINER (grps->table1->parent), grps->table1);
336
337
338   if ( var_is_numeric (v))
339     {
340       gtk_table_attach_defaults (GTK_TABLE (grps->table1), grps->table2,
341                                  1, 2, 1, 2);
342
343       gtk_container_add (GTK_CONTAINER (box), grps->table1);
344     }
345   else
346     {
347       gtk_container_add (GTK_CONTAINER (box), grps->table2);
348       grps->group_defn = GROUPS_VALUES;
349     }
350
351
352   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (grps->dialog),
353                                       define_groups_state_valid, ttd);
354
355   psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (grps->grp_entry[0]), ttd->grp_var);
356   psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (grps->grp_entry[1]), ttd->grp_var);
357   psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (grps->cut_point_entry), ttd->grp_var);
358
359   if ( grps->group_defn != GROUPS_CUT_POINT )
360     {
361       gtk_toggle_button_set_active
362         (GTK_TOGGLE_BUTTON (grps->cut_point_toggle_button), TRUE);
363
364       gtk_toggle_button_set_active
365         (GTK_TOGGLE_BUTTON (grps->values_toggle_button), TRUE);
366     }
367   else
368     {
369       gtk_toggle_button_set_active
370         (GTK_TOGGLE_BUTTON (grps->values_toggle_button), TRUE);
371
372       gtk_toggle_button_set_active
373         (GTK_TOGGLE_BUTTON (grps->cut_point_toggle_button), TRUE);
374     }
375
376   g_signal_emit_by_name (grps->grp_entry[0], "changed");
377   g_signal_emit_by_name (grps->grp_entry[1], "changed");
378   g_signal_emit_by_name (grps->cut_point_entry, "changed");
379
380   response = psppire_dialog_run (PSPPIRE_DIALOG (grps->dialog));
381
382   if (response == PSPPIRE_RESPONSE_CONTINUE)
383     {
384       const int width = var_get_width (ttd->grp_var);
385
386       if (gtk_toggle_button_get_active
387           (GTK_TOGGLE_BUTTON (grps->values_toggle_button)))
388         {
389           grps->group_defn = GROUPS_VALUES;
390
391           psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (grps->grp_entry[0]), &grps->grp_val[0], width);
392           psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (grps->grp_entry[1]), &grps->grp_val[1], width);
393         }
394       else
395         {
396           grps->group_defn = GROUPS_CUT_POINT;
397
398           psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (grps->cut_point_entry), &grps->cut_point, width);
399         }
400
401       psppire_dialog_notify_change (PSPPIRE_DIALOG (ttd->dialog));
402     }
403 }
404
405
406
407 static gboolean
408 dialog_state_valid (gpointer data)
409 {
410   struct tt_indep_samples_dialog *tt_d = data;
411
412   GtkWidget *tv_vars =
413     get_widget_assert (tt_d->xml, "indep-samples-t-test-treeview2");
414
415   GtkTreeModel *vars = gtk_tree_view_get_model (GTK_TREE_VIEW (tv_vars));
416
417   GtkTreeIter notused;
418
419   if ( 0 == strcmp ("", gtk_entry_get_text (GTK_ENTRY (tt_d->groups_entry))))
420     return FALSE;
421
422   if ( 0 == gtk_tree_model_get_iter_first (vars, &notused))
423     return FALSE;
424
425   if ( tt_d->grps->group_defn == GROUPS_UNDEF)
426     return FALSE;
427
428   return TRUE;
429 }
430
431 /* Pops up the dialog box */
432 void
433 t_test_independent_samples_dialog (PsppireDataWindow *de)
434 {
435   struct tt_indep_samples_dialog tt_d;
436   gint response;
437
438   PsppireVarStore *vs = NULL;
439
440   GtkBuilder *xml = builder_new ("t-test.ui");
441
442   GtkWidget *dict_view =
443     get_widget_assert (xml, "indep-samples-t-test-treeview1");
444
445   GtkWidget *selector2 =
446     get_widget_assert (xml, "indep-samples-t-test-selector2");
447
448   GtkWidget *selector1 =
449     get_widget_assert (xml, "indep-samples-t-test-selector1");
450
451   GtkWidget *options_button =
452     get_widget_assert (xml, "indep-samples-t-test-options-button");
453
454   g_object_get (de->data_editor, "var-store", &vs, NULL);
455
456   tt_d.dialog = get_widget_assert (xml, "t-test-independent-samples-dialog");
457   tt_d.xml = xml;
458   g_object_get (vs, "dictionary", &tt_d.dict, NULL);
459
460   tt_d.define_groups_button = get_widget_assert (xml, "define-groups-button");
461   tt_d.groups_entry = get_widget_assert (xml, "indep-samples-t-test-entry");
462   tt_d.opts = tt_options_dialog_create (GTK_WINDOW (de));
463   tt_d.grps = tt_groups_dialog_create (xml, GTK_WINDOW (de));
464
465   tt_d.grp_var = NULL;
466
467   gtk_window_set_transient_for (GTK_WINDOW (tt_d.dialog), GTK_WINDOW (de));
468
469   g_object_set (dict_view, "model", tt_d.dict, NULL);
470
471   psppire_selector_set_allow (PSPPIRE_SELECTOR (selector1),
472                               numeric_only);
473
474
475   psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector2),
476                                  is_currently_in_entry);
477
478   g_signal_connect_swapped (tt_d.define_groups_button, "clicked",
479                             G_CALLBACK (run_define_groups), &tt_d);
480
481
482   g_signal_connect_swapped (options_button, "clicked",
483                             G_CALLBACK (tt_options_dialog_run), tt_d.opts);
484
485
486   g_signal_connect_swapped (tt_d.dialog, "refresh", G_CALLBACK (refresh),
487                             &tt_d);
488
489   g_signal_connect (tt_d.groups_entry, "changed",
490                     G_CALLBACK (on_grp_var_change), &tt_d);
491
492
493   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (tt_d.dialog),
494                                       dialog_state_valid, &tt_d);
495
496   response = psppire_dialog_run (PSPPIRE_DIALOG (tt_d.dialog));
497
498   switch (response)
499     {
500     case GTK_RESPONSE_OK:
501       g_free (execute_syntax_string (de, generate_syntax (&tt_d)));
502       break;
503     case PSPPIRE_RESPONSE_PASTE:
504       g_free (paste_syntax_to_window (generate_syntax (&tt_d)));
505       break;
506     default:
507       break;
508     }
509
510   if (tt_d.grp_var)
511   {
512     int width = var_get_width (tt_d.grp_var);
513     value_destroy (&tt_d.grps->cut_point, width);
514     value_destroy (&tt_d.grps->grp_val[0], width);
515     value_destroy (&tt_d.grps->grp_val[1], width);
516   }
517
518   tt_options_dialog_destroy (tt_d.opts);
519   tt_groups_dialog_destroy (tt_d.grps);
520
521   g_object_unref (xml);
522 }
523
524