2c71ddef4f39bb4b4cf094fd812cffbea8def84e
[pspp] / src / ui / gui / crosstabs-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008  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 #include <config.h>
18
19 #include "checkbox-treeview.h"
20 #include "crosstabs-dialog.h"
21
22 #include <gtk/gtk.h>
23 #include <stdlib.h>
24
25 #include <language/syntax-string-source.h>
26 #include <ui/gui/psppire-data-window.h>
27 #include <ui/gui/dialog-common.h>
28 #include <ui/gui/dict-display.h>
29 #include "helper.h"
30 #include <ui/gui/psppire-dialog.h>
31 #include <ui/gui/psppire-var-store.h>
32 #include <ui/gui/helper.h>
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36 #define N_(msgid) msgid
37
38
39 #define CROSSTABS_STATS                       \
40   CS (CHISQ, N_("Chisq"))                         \
41   CS (PHI, N_("Phi"))         \
42   CS (CC, N_("CC"))                   \
43   CS (LAMBDA, N_("Lambda"))                   \
44   CS (UC, N_("UC")) \
45   CS (BTAU, N_("BTau"))                 \
46   CS (CTAU, N_("CTau"))                 \
47   CS (RISK, N_("Risk"))  \
48   CS (GAMMA, N_("Gamma"))                       \
49   CS (D, N_("D"))                         \
50   CS (KAPPA, N_("Kappa"))                 \
51   CS (ETA, N_("Eta"))  \
52   CS (CORR, N_("Corr")) \
53   CS (STATS_NONE, N_("None"))
54
55 #define CROSSTABS_CELLS \
56   CS (COUNT, N_("Count"))   \
57   CS (ROW, N_("Row"))   \
58   CS (COLUMN, N_("Column"))   \
59   CS (TOTAL, N_("Total"))   \
60   CS (EXPECTED, N_("Expected"))   \
61   CS (RESIDUAL, N_("Residual"))   \
62   CS (SRESIDUAL, N_("Std. Residual"))   \
63   CS (ASRESIDUAL, N_("Adjusted Std. Residual"))   \
64   CS (CELLS_NONE, N_("None"))
65
66 enum
67   {
68 #define CS(NAME, LABEL) CS_##NAME,
69     CROSSTABS_STATS
70 #undef CS
71     N_CROSSTABS_STATS
72   };
73
74 enum
75   {
76 #define CS(NAME, LABEL) CS_##NAME,
77     CROSSTABS_CELLS
78 #undef CS
79     N_CROSSTABS_CELLS
80   };
81
82 enum
83   {
84 #define CS(NAME, LABEL) B_CS_##NAME = 1u << CS_##NAME,
85     CROSSTABS_STATS
86     CROSSTABS_CELLS
87 #undef CS
88     B_CS_STATS_ALL = (1u << N_CROSSTABS_STATS) - 1,
89     B_CS_CELLS_ALL = (1u << N_CROSSTABS_CELLS) - 1,
90     B_CS_STATS_DEFAULT = B_CS_CHISQ,
91     B_CS_CELL_DEFAULT = B_CS_COUNT | B_CS_ROW | B_CS_COLUMN | B_CS_TOTAL,
92     B_CS_NONE
93   };
94
95 static const struct checkbox_entry_item stats[] =
96   {
97 #define CS(NAME, LABEL) {#NAME, LABEL},
98     CROSSTABS_STATS \
99     CS(NONE, N_("None"))
100 #undef CS
101   };
102
103 static const struct checkbox_entry_item cells[] =
104   {
105 #define CS(NAME, LABEL) {#NAME, LABEL},
106     CROSSTABS_CELLS \
107     CS(NONE, N_("None"))
108 #undef CS
109   };
110
111 enum
112   {
113     LABEL,
114     NO_LABEL,
115     NO_VAL_LABEL,
116   };
117 struct format_options
118 {
119   gboolean avalue;
120   gboolean pivot;
121   gboolean table;
122 };
123
124 struct crosstabs_dialog
125 {
126   GtkTreeView *row_vars;
127   GtkTreeView *col_vars;
128   PsppireDict *dict;
129
130   GtkToggleButton *table_button;
131   GtkToggleButton *pivot_button;
132
133   GtkWidget *format_dialog;
134   GtkWidget *cell_dialog;
135   GtkWidget *stat_dialog;
136
137   GtkToggleButton  *avalue;
138   GtkTreeModel *stat;
139   GtkTreeModel *cell;
140
141   GtkWidget *stat_view;
142   GtkWidget *cell_view;
143   GtkToggleButton *label;
144   GtkToggleButton *no_label;
145   GtkToggleButton *no_val_label;
146   struct format_options current_opts;
147 };
148
149 static void
150 refresh (PsppireDialog *dialog, struct crosstabs_dialog *cd)
151 {
152   GtkTreeModel *liststore = gtk_tree_view_get_model (cd->row_vars);
153   gtk_list_store_clear (GTK_LIST_STORE (liststore));
154   
155   liststore = gtk_tree_view_get_model (cd->col_vars);
156   gtk_list_store_clear (GTK_LIST_STORE (liststore));
157 }
158 static void
159 on_format_clicked (struct crosstabs_dialog *cd)
160 {
161   int ret;
162   gboolean lab;
163   gboolean no_lab;
164   gboolean no_val_lab;
165
166   if (cd->current_opts.avalue)
167     {
168       gtk_toggle_button_set_active (cd->avalue, TRUE);
169     }
170   if (cd->current_opts.table)
171     {
172       gtk_toggle_button_set_active (cd->table_button, TRUE);
173     }
174   if (cd->current_opts.pivot)
175     {
176       gtk_toggle_button_set_active (cd->pivot_button, TRUE);
177     }
178   lab = gtk_toggle_button_get_active (cd->label);
179   no_lab = gtk_toggle_button_get_active (cd->no_label);
180   no_val_lab = gtk_toggle_button_get_active (cd->no_val_label);
181   if (!lab)
182     if (!no_lab)
183       if (!no_val_lab)
184         gtk_toggle_button_set_active (cd->label, TRUE);
185
186
187   ret = psppire_dialog_run (PSPPIRE_DIALOG (cd->format_dialog));
188
189   if ( ret == PSPPIRE_RESPONSE_CONTINUE )
190     {
191       cd->current_opts.avalue = (gtk_toggle_button_get_active (cd->avalue) == TRUE ) 
192         ? TRUE : FALSE;
193       cd->current_opts.table = (gtk_toggle_button_get_active (cd->table_button) == TRUE)
194         ? TRUE : FALSE;
195       cd->current_opts.pivot = (gtk_toggle_button_get_active (cd->pivot_button) == TRUE)
196         ? TRUE : FALSE;
197     }
198   else
199     {
200       gtk_toggle_button_set_active (cd->label, lab);
201       gtk_toggle_button_set_active (cd->no_label, no_lab);
202       gtk_toggle_button_set_active (cd->no_val_label, no_val_lab);
203     }
204 }
205
206 static void
207 on_statistics_clicked (struct crosstabs_dialog *cd)
208 {
209   GtkListStore *liststore;
210   int ret;
211
212   liststore = clone_list_store (GTK_LIST_STORE (cd->stat));
213
214   ret = psppire_dialog_run (PSPPIRE_DIALOG (cd->stat_dialog));
215
216   if ( ret == PSPPIRE_RESPONSE_CONTINUE )
217     {
218       g_object_unref (liststore);
219     }
220   else
221     {
222       g_object_unref (cd->stat);
223       gtk_tree_view_set_model (GTK_TREE_VIEW (cd->stat_view) , GTK_TREE_MODEL (liststore));
224       cd->stat = GTK_TREE_MODEL (liststore);
225     }
226 }
227 static void
228 on_cell_clicked (struct crosstabs_dialog *cd)
229 {
230   GtkListStore *liststore;
231   int ret;
232
233   liststore = clone_list_store (GTK_LIST_STORE (cd->cell));
234
235   ret = psppire_dialog_run (PSPPIRE_DIALOG (cd->cell_dialog));
236
237   if ( ret == PSPPIRE_RESPONSE_CONTINUE )
238     {
239       g_object_unref (liststore);
240     }
241   else
242     {
243       g_object_unref (cd->cell);
244       gtk_tree_view_set_model (GTK_TREE_VIEW (cd->cell_view) , GTK_TREE_MODEL (liststore));
245       cd->cell = GTK_TREE_MODEL (liststore);
246     }
247 }
248
249 static char *
250 generate_syntax (const struct crosstabs_dialog *cd)
251 {
252   gint i;
253   int n;
254   guint selected;
255   GtkTreeIter iter;
256   gboolean ok;
257
258   gchar *text;
259   GString *string = g_string_new ("CROSSTABS");
260
261   g_string_append (string, "\n\t/TABLES=");
262   append_variable_names (string, cd->dict, GTK_TREE_VIEW (cd->row_vars), 0);
263   g_string_append (string, "\tBY\t");
264   append_variable_names (string, cd->dict, GTK_TREE_VIEW (cd->col_vars), 0);
265
266   g_string_append (string, "\n\t/FORMAT=");
267
268   if (cd->current_opts.avalue)
269     {
270       g_string_append (string, "AVALUE");
271     }
272   else 
273     {
274       g_string_append (string, "DVALUE");
275     }
276   g_string_append (string, " ");
277   if (gtk_toggle_button_get_active (cd->label))
278     {
279       g_string_append (string, "LABELS");
280     }
281   else if (gtk_toggle_button_get_active (cd->no_label))
282     {
283       g_string_append (string, "NOLABELS");
284     }
285   else if (gtk_toggle_button_get_active (cd->no_val_label))
286     {
287       g_string_append (string, "NOVALLABS");
288     }
289   g_string_append (string, " ");
290   if (cd->current_opts.table)
291     g_string_append (string, "TABLES");
292   else
293     g_string_append (string, "NOTABLES");
294   g_string_append (string, " ");
295
296   if (cd->current_opts.pivot)
297     g_string_append (string, "PIVOT");
298   else 
299     g_string_append (string, "NOPIVOT");
300
301   selected = 0;
302   for (i = 0, ok = gtk_tree_model_get_iter_first (cd->stat, &iter); ok; 
303        i++, ok = gtk_tree_model_iter_next (cd->stat, &iter))
304     {
305       gboolean toggled;
306       gtk_tree_model_get (cd->stat, &iter,
307                           CHECKBOX_COLUMN_SELECTED, &toggled, -1); 
308       if (toggled) 
309         selected |= 1u << i; 
310       else 
311         selected &= ~(1u << i);
312     }
313
314   if (!(selected & (1u << CS_STATS_NONE)))
315     {
316       if (selected)
317         {
318           g_string_append (string, "\n\t/STATISTICS=");
319           n = 0;
320           for (i = 0; i < N_CROSSTABS_STATS; i++)
321             if (selected & (1u << i))
322               {
323                 if (n++)
324                   g_string_append (string, " ");
325                 g_string_append (string, stats[i].name);
326               }
327         }
328     }
329
330   selected = 0;
331   for (i = 0, ok = gtk_tree_model_get_iter_first (cd->cell, &iter); ok; 
332        i++, ok = gtk_tree_model_iter_next (cd->cell, &iter))
333     {
334       gboolean toggled;
335       gtk_tree_model_get (cd->cell, &iter,
336                           CHECKBOX_COLUMN_SELECTED, &toggled, -1); 
337       if (toggled) 
338         selected |= 1u << i; 
339       else 
340         selected &= ~(1u << i);
341     }
342
343   g_string_append (string, "\n\t/CELLS=");
344   if (selected & (1u << CS_CELLS_NONE))
345     g_string_append (string, "NONE");
346   else
347     {
348       n = 0;
349       for (i = 0; i < N_CROSSTABS_CELLS; i++)
350         if (selected & (1u << i))
351           {
352             if (n++)
353               g_string_append (string, " ");
354             g_string_append (string, cells[i].name);
355           }
356     }
357   
358   g_string_append (string, ".\n");
359
360   text = string->str;
361
362   g_string_free (string, FALSE);
363
364   return text;
365 }
366
367 /* Dialog is valid iff at least one row and one column variable has
368    been selected. */
369 static gboolean
370 dialog_state_valid (gpointer data)
371 {
372   struct crosstabs_dialog *cd = data;
373
374   GtkTreeModel *row_vars = gtk_tree_view_get_model (cd->row_vars);
375   GtkTreeModel *col_vars = gtk_tree_view_get_model (cd->col_vars);
376
377   GtkTreeIter notused;
378
379   return (gtk_tree_model_get_iter_first (row_vars, &notused) 
380     && gtk_tree_model_get_iter_first (col_vars, &notused));
381 }
382
383 /* Pops up the Crosstabs dialog box */
384 void
385 crosstabs_dialog (GObject *o, gpointer data)
386 {
387   gint response;
388   struct crosstabs_dialog cd;
389
390   GtkBuilder *xml = builder_new ("crosstabs.ui");
391   PsppireVarStore *vs = NULL;
392
393   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
394
395
396   GtkWidget *dialog = get_widget_assert   (xml, "crosstabs-dialog");
397   GtkWidget *source = get_widget_assert   (xml, "dict-treeview");
398   GtkWidget *dest_rows =   get_widget_assert   (xml, "rows");
399   GtkWidget *dest_cols =   get_widget_assert   (xml, "cols");
400   GtkWidget *row_selector = get_widget_assert (xml, "row-selector");
401   GtkWidget *col_selector = get_widget_assert (xml, "col-selector");
402   GtkWidget *format_button = get_widget_assert (xml, "format-button");
403   GtkWidget *stat_button = get_widget_assert (xml, "stats-button");
404   GtkWidget *cell_button = get_widget_assert (xml, "cell-button");
405
406
407   cd.stat_view = get_widget_assert (xml, "stats-view");
408   cd.cell_view = get_widget_assert (xml, "cell-view");
409
410   g_object_get (de->data_editor, "var-store", &vs, NULL);
411
412   put_checkbox_items_in_treeview (GTK_TREE_VIEW(cd.stat_view),
413                                   B_CS_STATS_DEFAULT,
414                                   N_CROSSTABS_STATS,
415                                   stats
416                                   );
417   put_checkbox_items_in_treeview (GTK_TREE_VIEW(cd.cell_view),
418                                   B_CS_CELL_DEFAULT,
419                                   N_CROSSTABS_CELLS,
420                                   cells
421                                   );
422
423   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
424
425   g_object_set (source, "dictionary", vs->dict, NULL);
426
427   set_dest_model (GTK_TREE_VIEW (dest_rows), vs->dict);
428   set_dest_model (GTK_TREE_VIEW (dest_cols), vs->dict);
429
430   psppire_selector_set_subjects (PSPPIRE_SELECTOR (row_selector),
431                                  source,
432                                  dest_rows,
433                                  insert_source_row_into_tree_view,
434                                  NULL,
435                                  NULL);
436
437   psppire_selector_set_subjects (PSPPIRE_SELECTOR (col_selector),
438                                  source,
439                                  dest_cols,
440                                  insert_source_row_into_tree_view,
441                                  NULL,
442                                  NULL);
443
444   cd.row_vars = GTK_TREE_VIEW (dest_rows);
445   cd.col_vars = GTK_TREE_VIEW (dest_cols);
446   cd.dict = vs->dict;
447   cd.format_dialog = get_widget_assert (xml, "format-dialog");
448   cd.table_button = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "print-tables"));
449   cd.pivot_button = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "pivot"));
450   cd.label = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "radiobutton1"));
451   cd.no_label = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "radiobutton2"));
452   cd.no_val_label = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "radiobutton3"));
453   cd.stat_dialog = get_widget_assert (xml, "stat-dialog");
454   cd.cell_dialog = get_widget_assert (xml, "cell-dialog");
455
456   cd.stat = gtk_tree_view_get_model (GTK_TREE_VIEW (cd.stat_view));
457   cd.cell = gtk_tree_view_get_model (GTK_TREE_VIEW (cd.cell_view));
458   cd.avalue = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "ascending"));
459   cd.current_opts.avalue = TRUE;
460   cd.current_opts.table = TRUE;
461   cd.current_opts.pivot = TRUE;
462
463   gtk_window_set_transient_for (GTK_WINDOW (cd.format_dialog), GTK_WINDOW (de));
464   gtk_window_set_transient_for (GTK_WINDOW (cd.cell_dialog), GTK_WINDOW (de));
465   gtk_window_set_transient_for (GTK_WINDOW (cd.stat_dialog), GTK_WINDOW (de));
466
467   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &cd);
468
469   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (dialog),
470                                       dialog_state_valid, &cd);
471
472   g_signal_connect_swapped (format_button, "clicked",
473                             G_CALLBACK (on_format_clicked),  &cd);
474   g_signal_connect_swapped (stat_button, "clicked",
475                             G_CALLBACK (on_statistics_clicked),  &cd);
476   g_signal_connect_swapped (cell_button, "clicked",
477                             G_CALLBACK (on_cell_clicked),  &cd);
478
479   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
480
481
482   switch (response)
483     {
484     case GTK_RESPONSE_OK:
485       {
486         gchar *syntax = generate_syntax (&cd);
487
488         struct getl_interface *sss = create_syntax_string_source (syntax);
489         execute_syntax (sss);
490
491         g_free (syntax);
492       }
493       break;
494     case PSPPIRE_RESPONSE_PASTE:
495       {
496         gchar *syntax = generate_syntax (&cd);
497
498         paste_syntax_in_new_window (syntax);
499
500         g_free (syntax);
501       }
502       break;
503     default:
504       break;
505     }
506
507   g_object_unref (xml);
508 }