Oneway GUI: Add dialog box for the posthoc tests.
[pspp] / src / ui / gui / psppire-dialog-action-oneway.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2012, 2013, 2014, 2019  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 #include <config.h>
19
20 #include "psppire-dialog-action-oneway.h"
21
22 #include <float.h>
23
24 #include "psppire-var-view.h"
25 #include "psppire-acr.h"
26
27 #include "psppire-dialog.h"
28 #include "builder-wrapper.h"
29 #include "helper.h"
30
31
32 #include "gettext.h"
33 #define _(msgid) gettext (msgid)
34 #define N_(msgid) msgid
35
36 #define     POSTHOC_BONFERRONI  0x01
37 #define     POSTHOC_GH          0x02
38 #define     POSTHOC_LSD         0x04
39 #define     POSTHOC_SCHEFFE     0x08
40 #define     POSTHOC_SIDAK       0x10
41 #define     POSTHOC_TUKEY       0x20
42
43 static void next (GtkWidget *widget, PsppireDialogActionOneway *);
44 static void prev (GtkWidget *widget, PsppireDialogActionOneway *);
45 static void run_posthoc_dialog (PsppireDialogActionOneway *ow);
46 static void run_contrasts_dialog (PsppireDialogActionOneway *csd);
47 static void push_new_store (GArray *contrast_stack, PsppireDialogActionOneway *csd);
48
49
50 static void psppire_dialog_action_oneway_init            (PsppireDialogActionOneway      *act);
51 static void psppire_dialog_action_oneway_class_init      (PsppireDialogActionOnewayClass *class);
52
53 G_DEFINE_TYPE (PsppireDialogActionOneway, psppire_dialog_action_oneway, PSPPIRE_TYPE_DIALOG_ACTION);
54
55
56 static char *
57 generate_syntax (const PsppireDialogAction *act)
58 {
59   PsppireDialogActionOneway *ow = PSPPIRE_DIALOG_ACTION_ONEWAY (act);
60   gchar *text;
61   gint i;
62
63   gboolean descriptives = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->descriptives));
64   gboolean homogeneity = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->homogeneity));
65   struct string dss;
66
67   ds_init_cstr (&dss, "ONEWAY /VARIABLES=");
68
69   psppire_var_view_append_names_str (PSPPIRE_VAR_VIEW (ow->vars_treeview), 0, &dss);
70
71   ds_put_cstr (&dss, " BY ");
72
73   ds_put_cstr (&dss, gtk_entry_get_text (GTK_ENTRY (ow->factor_entry)));
74
75   if (descriptives || homogeneity)
76     {
77       ds_put_cstr (&dss, "\n\t/STATISTICS=");
78       if (descriptives)
79         ds_put_cstr (&dss, "DESCRIPTIVES ");
80       if (homogeneity)
81         ds_put_cstr (&dss, "HOMOGENEITY ");
82     }
83   if (ow->posthoc)
84     {
85       ds_put_cstr (&dss, "\n\t/POSTHOC=");
86       if ( ow->posthoc & POSTHOC_BONFERRONI)
87         ds_put_cstr (&dss, "BONFERRONI ");
88       if ( ow->posthoc & POSTHOC_GH)
89         ds_put_cstr (&dss, "GH ");
90       if ( ow->posthoc & POSTHOC_LSD)
91         ds_put_cstr (&dss, "LSD ");
92       if ( ow->posthoc & POSTHOC_SCHEFFE)
93         ds_put_cstr (&dss, "SCHEFFE ");
94       if ( ow->posthoc & POSTHOC_SIDAK)
95         ds_put_cstr (&dss, "SIDAK ");
96       if ( ow->posthoc & POSTHOC_TUKEY)
97         ds_put_cstr (&dss, "TUKEY ");
98       }
99   for (i = 0 ; i < ow->contrasts_array->len ; ++i )
100     {
101       GtkListStore *ls = g_array_index (ow->contrasts_array, GtkListStore*, i);
102       GtkTreeIter iter;
103       gboolean ok;
104
105       ds_put_cstr (&dss, "\n\t/CONTRAST=");
106
107       for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ls),
108                                                &iter);
109            ok;
110            ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ls), &iter))
111         {
112           gdouble v;
113
114           gtk_tree_model_get (GTK_TREE_MODEL (ls), &iter, 0, &v, -1);
115
116           ds_put_c_format (&dss, " %.*g", DBL_DIG + 1, v);
117         }
118     }
119
120   ds_put_cstr (&dss, ".\n");
121
122   text = ds_steal_cstr (&dss);
123   ds_destroy (&dss);
124
125   return text;
126 }
127
128
129 static gboolean
130 dialog_state_valid (gpointer data)
131 {
132   PsppireDialogActionOneway *ow = PSPPIRE_DIALOG_ACTION_ONEWAY (data);
133
134   GtkTreeModel *vars =
135     gtk_tree_view_get_model (GTK_TREE_VIEW (ow->vars_treeview));
136
137   GtkTreeIter notused;
138
139   if ( !gtk_tree_model_get_iter_first (vars, &notused) )
140     return FALSE;
141
142   if ( 0 == strcmp ("", gtk_entry_get_text (GTK_ENTRY (ow->factor_entry))))
143     return FALSE;
144
145
146   return TRUE;
147 }
148
149 static void
150 refresh (PsppireDialogAction *rd_)
151 {
152   PsppireDialogActionOneway *ow = PSPPIRE_DIALOG_ACTION_ONEWAY (rd_);
153
154   GtkTreeModel *model =
155     gtk_tree_view_get_model (GTK_TREE_VIEW (ow->vars_treeview));
156
157   gtk_entry_set_text (GTK_ENTRY (ow->factor_entry), "");
158
159   gtk_list_store_clear (GTK_LIST_STORE (model));
160 }
161
162
163 /* Callback for when the list store currently associated with the
164    treeview has changed.  It sets the widgets of the subdialog
165    to reflect the store's new state.
166 */
167 static void
168 list_store_changed (PsppireDialogActionOneway *csd)
169 {
170   gboolean ok;
171   gdouble total = 0.0;
172   GtkTreeIter iter;
173   GtkTreeModel *ls = NULL;
174   gchar *text =
175     g_strdup_printf (_("Contrast %d of %d"),
176                      csd->c, csd->temp_contrasts->len);
177
178   gtk_label_set_label (GTK_LABEL (csd->stack_label), text);
179
180   g_free (text);
181
182   gtk_widget_set_sensitive (csd->prev, csd->c > 1);
183
184   if ( csd->c > 0 )
185     ls = g_array_index (csd->temp_contrasts, GtkTreeModel*, csd->c - 1);
186
187   psppire_acr_set_model (PSPPIRE_ACR (csd->acr), GTK_LIST_STORE (ls));
188
189   /* Sensitive iff the liststore has two items or more */
190   gtk_widget_set_sensitive (csd->next,
191                             gtk_tree_model_iter_nth_child
192                             (ls, &iter,  NULL, 1));
193
194   for (ok = gtk_tree_model_get_iter_first (ls, &iter);
195        ok;
196        ok = gtk_tree_model_iter_next (ls, &iter)
197        )
198     {
199       gdouble v;
200       gtk_tree_model_get (ls, &iter, 0, &v, -1);
201       total += v;
202     }
203
204   text = g_strdup_printf ("%.*g", DBL_DIG + 1, total);
205
206   gtk_entry_set_text (GTK_ENTRY (csd->ctotal), text);
207
208   g_free (text);
209 }
210
211
212 /* Copy the contrasts array into the local array */
213 static GArray *
214 clone_contrasts_array (GArray *src_array)
215 {
216   gint i;
217
218   GArray *dest_array =
219     g_array_sized_new (FALSE, FALSE, sizeof (GtkListStore *),
220                        src_array->len);
221
222   for (i = 0 ; i < src_array->len ; ++i )
223     {
224
225       GtkTreeIter src_iter;
226       GtkListStore *src = g_array_index (src_array, GtkListStore*, i);
227       GtkListStore *dest;
228
229       /* Refuse to copy empty stores */
230       if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (src),
231                                            &src_iter))
232         continue;
233
234       dest = clone_list_store (src);
235
236       g_array_append_val (dest_array, dest);
237     }
238
239   return dest_array;
240 }
241
242
243 static GtkBuilder *
244 psppire_dialog_action_oneway_activate (PsppireDialogAction *a, GVariant *param)
245 {
246   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a);
247   PsppireDialogActionOneway *act = PSPPIRE_DIALOG_ACTION_ONEWAY (a);
248
249   GtkBuilder *xml = builder_new ( "oneway.ui");
250
251   GtkWidget *contrasts_button = get_widget_assert (xml, "contrasts-button");
252 /* Posthoc button */
253   GtkWidget *posthoc_button= get_widget_assert (xml, "posthoc-button");
254   GtkEntry *entry = GTK_ENTRY (get_widget_assert (xml, "entry1"));
255
256   pda->dialog = get_widget_assert   (xml, "oneway-anova-dialog");
257   pda->source = get_widget_assert   (xml, "oneway-anova-treeview1");
258
259   act->vars_treeview =  get_widget_assert (xml, "oneway-anova-treeview2");
260   act->factor_entry = get_widget_assert (xml, "oneway-anova-entry");
261
262   act->descriptives =  get_widget_assert (xml, "checkbutton1");
263   act->homogeneity =  get_widget_assert (xml, "checkbutton2");
264
265 /* Posthoc tests dialog */
266   act->posthoc_dialog = get_widget_assert (xml, "posthoc-dialog");
267
268   act->bonferroni_button = get_widget_assert (xml, "bonferroni-button");
269   act->gh_button = get_widget_assert (xml, "gh-button");
270   act->lsd_button = get_widget_assert (xml, "lsd-button");
271   act->scheffe_button = get_widget_assert (xml, "scheffe-button");
272   act->sidak_button = get_widget_assert (xml, "sidak-button");
273   act->tukey_button = get_widget_assert (xml, "tukey-button");
274
275   g_signal_connect_swapped (posthoc_button, "clicked",
276                     G_CALLBACK (run_posthoc_dialog), act);
277
278 /* Contrast dialog */
279   act->contrasts_dialog = get_widget_assert (xml, "contrasts-dialog");
280
281   act->next = get_widget_assert (xml, "next-button");
282   act->prev = get_widget_assert (xml, "prev-button");
283   act->ctotal = get_widget_assert (xml, "entry2");
284   act->acr = get_widget_assert (xml, "psppire-acr1");
285   act->stack_label = get_widget_assert (xml, "contrast-stack-label");
286   act->contrasts_array = g_array_new (FALSE, FALSE, sizeof (GtkListStore *));
287
288
289   g_signal_connect (act->next, "clicked", G_CALLBACK (next), act);
290   g_signal_connect (act->prev, "clicked", G_CALLBACK (prev), act);
291
292   psppire_acr_set_entry (PSPPIRE_ACR (act->acr), entry);
293
294   gtk_window_set_transient_for (GTK_WINDOW (act->contrasts_dialog),
295                                   GTK_WINDOW (pda->toplevel));
296
297
298   g_signal_connect_swapped (contrasts_button, "clicked",
299                     G_CALLBACK (run_contrasts_dialog), act);
300
301
302   psppire_dialog_action_set_valid_predicate (pda, dialog_state_valid);
303   psppire_dialog_action_set_refresh (pda, refresh);
304   return xml;
305 }
306
307 static void
308 psppire_dialog_action_oneway_class_init (PsppireDialogActionOnewayClass *class)
309 {
310   PSPPIRE_DIALOG_ACTION_CLASS (class)->initial_activate = psppire_dialog_action_oneway_activate;
311   PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax;
312 }
313
314
315 static void
316 psppire_dialog_action_oneway_init (PsppireDialogActionOneway *act)
317 {
318   act->contrasts_array = NULL;
319   act->c = -1;
320   act->posthoc = 0;
321 }
322 /* Posthoc dialog */
323 static void
324 run_posthoc_dialog (PsppireDialogActionOneway *ow)
325 {
326   gint response;
327
328   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ow->bonferroni_button),
329                                 ow->posthoc & POSTHOC_BONFERRONI);
330
331   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ow->gh_button),
332                                 ow->posthoc & POSTHOC_GH);
333
334   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ow->lsd_button),
335                                 ow->posthoc & POSTHOC_LSD);
336
337   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ow->scheffe_button),
338                                 ow->posthoc & POSTHOC_SCHEFFE);
339
340   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ow->sidak_button),
341                                 ow->posthoc & POSTHOC_SIDAK);
342
343   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ow->tukey_button),
344                                 ow->posthoc & POSTHOC_TUKEY);
345
346   response = psppire_dialog_run (PSPPIRE_DIALOG (ow->posthoc_dialog));
347
348   if ( response == PSPPIRE_RESPONSE_CONTINUE )
349     {
350       ow->posthoc= 0;
351       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->bonferroni_button) ))
352         ow->posthoc |= POSTHOC_BONFERRONI;
353
354       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->gh_button) ))
355         ow->posthoc |= POSTHOC_GH;
356
357       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->lsd_button) ))
358         ow->posthoc |= POSTHOC_LSD;
359
360       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->scheffe_button) ))
361         ow->posthoc |= POSTHOC_SCHEFFE;
362
363       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->sidak_button) ))
364         ow->posthoc |= POSTHOC_SIDAK;
365
366       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ow->tukey_button) ))
367         ow->posthoc |= POSTHOC_TUKEY;
368     }
369 }\f
370
371 static void
372 run_contrasts_dialog (PsppireDialogActionOneway *csd)
373 {
374   gint response;
375
376   csd->temp_contrasts = clone_contrasts_array (csd->contrasts_array);
377
378   csd->c = 1;
379
380   push_new_store (csd->temp_contrasts, csd);
381
382   response = psppire_dialog_run (PSPPIRE_DIALOG (csd->contrasts_dialog));
383
384   if ( response == PSPPIRE_RESPONSE_CONTINUE )
385     {
386       csd->contrasts_array = clone_contrasts_array (csd->temp_contrasts);
387     }
388 }
389
390
391 static void
392 push_new_store (GArray *contrast_stack, PsppireDialogActionOneway *csd)
393 {
394   GtkListStore *ls = gtk_list_store_new (1, G_TYPE_DOUBLE);
395
396   g_array_append_val (contrast_stack, ls);
397
398   g_signal_connect_swapped (ls, "row-deleted",
399                             G_CALLBACK (list_store_changed), csd);
400
401   g_signal_connect_swapped (ls, "row-changed",
402                             G_CALLBACK (list_store_changed), csd);
403
404   list_store_changed (csd);
405 }
406
407
408 static void
409 next (GtkWidget *widget, PsppireDialogActionOneway *csd)
410 {
411   if (csd->c >= csd->temp_contrasts->len)
412     push_new_store (csd->temp_contrasts, csd);
413
414   csd->c++;
415
416   list_store_changed (csd);
417 }
418
419
420 static void
421 prev (GtkWidget *widget, PsppireDialogActionOneway *csd)
422 {
423   if ( csd->c > 0 )
424     --csd->c;
425
426   list_store_changed (csd);
427 }