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