016682e432c19ee913a5e73473a2c255425b710e
[pspp-builds.git] / src / ui / gui / frequencies-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011  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 "frequencies-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 <ui/gui/helper.h>
30 #include <ui/gui/psppire-dialog.h>
31 #include <ui/gui/psppire-var-store.h>
32 #include "executor.h"
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36 #define N_(msgid) msgid
37
38
39 #define FREQUENCY_STATS                       \
40   FS (MEAN, N_("Mean"))                         \
41   FS (STDDEV, N_("Standard deviation"))         \
42   FS (MINIMUM, N_("Minimum"))                   \
43   FS (MAXIMUM, N_("Maximum"))                   \
44   FS (SEMEAN, N_("Standard error of the mean")) \
45   FS (VARIANCE, N_("Variance"))                 \
46   FS (SKEWNESS, N_("Skewness"))                 \
47   FS (SESKEW, N_("Standard error of the skewness"))  \
48   FS (RANGE, N_("Range"))                       \
49   FS (MODE, N_("Mode"))                         \
50   FS (KURTOSIS, N_("Kurtosis"))                 \
51   FS (SEKURT, N_("Standard error of the kurtosis"))  \
52   FS (MEDIAN, N_("Median"))      \
53   FS (SUM, N_("Sum"))
54
55 enum
56   {
57 #define FS(NAME, LABEL) FS_##NAME,
58     FREQUENCY_STATS
59 #undef FS
60     N_FREQUENCY_STATS
61   };
62
63 enum
64   {
65 #define FS(NAME, LABEL) B_FS_##NAME = 1u << FS_##NAME,
66     FREQUENCY_STATS
67 #undef FS
68     B_FS_ALL = (1u << N_FREQUENCY_STATS) - 1,
69     B_FS_DEFAULT = B_FS_MEAN | B_FS_STDDEV | B_FS_MINIMUM | B_FS_MAXIMUM
70   };
71
72
73 static const struct checkbox_entry_item stats[] =
74   {
75 #define FS(NAME, LABEL) {#NAME, LABEL},
76     FREQUENCY_STATS
77 #undef FS
78   };
79
80
81
82 enum frq_order
83   {
84     FRQ_AVALUE,
85     FRQ_DVALUE,
86     FRQ_ACOUNT,
87     FRQ_DCOUNT
88   };
89
90 enum frq_table
91   {
92     FRQ_TABLE,
93     FRQ_NOTABLE,
94     FRQ_LIMIT
95   };
96
97 struct tables_options
98 {
99   enum frq_order order;
100   enum frq_table table;
101   int limit;
102 };
103
104 enum frq_scale
105   {
106     FRQ_FREQ,
107     FRQ_PERCENT
108   };
109
110 struct charts_options
111   {
112     bool use_min;
113     double min;
114     bool use_max;
115     double max;
116     bool draw_hist;
117     bool draw_normal;
118     enum frq_scale scale;
119     bool draw_pie;
120     bool pie_include_missing;
121   };
122
123 struct frequencies_dialog
124 {
125   /* Main dialog. */
126   GtkTreeView *stat_vars;
127   PsppireDict *dict;
128
129   GtkWidget *tables_button;
130   GtkWidget *charts_button;
131
132   GtkToggleButton *include_missing;
133
134   GtkTreeModel *stats;
135
136   /* Frequency Tables dialog. */
137   GtkWidget *tables_dialog;
138   struct tables_options tables_opts;
139
140   GtkToggleButton *always;
141   GtkToggleButton *never;
142   GtkToggleButton *limit;
143   GtkSpinButton *limit_spinbutton;
144
145   GtkToggleButton  *avalue;
146   GtkToggleButton  *dvalue;
147   GtkToggleButton  *afreq;
148   GtkToggleButton  *dfreq;
149
150   /* Charts dialog. */
151   GtkWidget *charts_dialog;
152   struct charts_options charts_opts;
153
154   GtkToggleButton *freqs;
155   GtkToggleButton *percents;
156
157   GtkToggleButton *min;
158   GtkSpinButton *min_spin;
159   GtkToggleButton *max;
160   GtkSpinButton *max_spin;
161
162   GtkToggleButton *hist;
163   GtkToggleButton *normal;
164
165   GtkToggleButton *pie;
166   GtkToggleButton *pie_include_missing;
167 };
168
169 static void
170 refresh (PsppireDialog *dialog, struct frequencies_dialog *fd)
171 {
172   GtkTreeIter iter;
173   size_t i;
174   bool ok;
175
176   GtkTreeModel *liststore = gtk_tree_view_get_model (fd->stat_vars);
177   gtk_list_store_clear (GTK_LIST_STORE (liststore));
178
179   for (i = 0, ok = gtk_tree_model_get_iter_first (fd->stats, &iter); ok;
180        i++, ok = gtk_tree_model_iter_next (fd->stats, &iter))
181     gtk_list_store_set (GTK_LIST_STORE (fd->stats), &iter,
182                         CHECKBOX_COLUMN_SELECTED,
183                         (B_FS_DEFAULT & (1u << i)) ? true : false, -1);
184 }
185
186 static char *
187 generate_syntax (const struct frequencies_dialog *fd)
188 {
189   GtkTreeIter iter;
190   gboolean ok;
191   gint i;
192   guint selected = 0;
193
194   gchar *text;
195   GString *string = g_string_new ("FREQUENCIES");
196
197   g_string_append (string, "\n\t/VARIABLES=");
198   psppire_var_view_append_names (PSPPIRE_VAR_VIEW (fd->stat_vars), 0, string);
199
200   g_string_append (string, "\n\t/FORMAT=");
201
202   switch (fd->tables_opts.order)
203     {
204     case FRQ_AVALUE:
205       g_string_append (string, "AVALUE");
206       break;
207     case FRQ_DVALUE:
208       g_string_append (string, "DVALUE");
209       break;
210     case FRQ_ACOUNT:
211       g_string_append (string, "AFREQ");
212       break;
213     case FRQ_DCOUNT:
214       g_string_append (string, "DFREQ");
215       break;
216     default:
217       g_assert_not_reached();
218     }
219
220   g_string_append (string, " ");
221
222   switch (fd->tables_opts.table)
223     {
224     case FRQ_TABLE:
225       g_string_append (string, "TABLE");
226       break;
227     case FRQ_NOTABLE:
228       g_string_append (string, "NOTABLE");
229       break;
230     case FRQ_LIMIT:
231       g_string_append_printf (string, "LIMIT (%d)", fd->tables_opts.limit);
232       break;
233     }
234
235
236   for (i = 0, ok = gtk_tree_model_get_iter_first (fd->stats, &iter); ok;
237        i++, ok = gtk_tree_model_iter_next (fd->stats, &iter))
238     {
239       gboolean toggled;
240       gtk_tree_model_get (fd->stats, &iter,
241                           CHECKBOX_COLUMN_SELECTED, &toggled, -1);
242       if (toggled)
243         selected |= 1u << i;
244     }
245
246   if (selected != B_FS_DEFAULT)
247     {
248       g_string_append (string, "\n\t/STATISTICS=");
249       if (selected == B_FS_ALL)
250         g_string_append (string, "ALL");
251       else if (selected == 0)
252         g_string_append (string, "NONE");
253       else
254         {
255           int n = 0;
256           if ((selected & B_FS_DEFAULT) == B_FS_DEFAULT)
257             {
258               g_string_append (string, "DEFAULT");
259               selected &= ~B_FS_DEFAULT;
260               n++;
261             }
262           for (i = 0; i < N_FREQUENCY_STATS; i++)
263             if (selected & (1u << i))
264               {
265                 if (n++)
266                   g_string_append (string, " ");
267                 g_string_append (string, stats[i].name);
268               }
269         }
270     }
271
272   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->include_missing)))
273     g_string_append (string, "\n\t/MISSING=INCLUDE");
274
275   if (fd->charts_opts.draw_hist)
276     {
277       g_string_append (string, "\n\t/HISTOGRAM=");
278       g_string_append (string,
279                        fd->charts_opts.draw_normal ? "NORMAL" : "NONORMAL");
280
281       if (fd->charts_opts.scale == FRQ_PERCENT)
282         g_string_append (string, " PERCENT");
283
284       if (fd->charts_opts.use_min)
285         g_string_append_printf (string, " MIN(%.15g)", fd->charts_opts.min);
286       if (fd->charts_opts.use_max)
287         g_string_append_printf (string, " MAX(%.15g)", fd->charts_opts.max);
288     }
289
290   if (fd->charts_opts.draw_pie)
291     {
292       g_string_append (string, "\n\t/PIECHART=");
293
294       if (fd->charts_opts.pie_include_missing)
295         g_string_append (string, " MISSING");
296
297       if (fd->charts_opts.use_min)
298         g_string_append_printf (string, " MIN(%.15g)", fd->charts_opts.min);
299       if (fd->charts_opts.use_max)
300         g_string_append_printf (string, " MAX(%.15g)", fd->charts_opts.max);
301     }
302
303   g_string_append (string, ".\n");
304
305   text = string->str;
306
307   g_string_free (string, FALSE);
308
309   return text;
310 }
311
312 /* Dialog is valid iff at least one variable has been selected */
313 static gboolean
314 dialog_state_valid (gpointer data)
315 {
316   struct frequencies_dialog *fd = data;
317
318   GtkTreeModel *vars = gtk_tree_view_get_model (fd->stat_vars);
319
320   GtkTreeIter notused;
321
322   return gtk_tree_model_get_iter_first (vars, &notused);
323 }
324
325
326 static void
327 on_tables_clicked (struct frequencies_dialog *fd)
328 {
329   int ret;
330
331   switch (fd->tables_opts.order)
332     {
333     case FRQ_AVALUE:
334       gtk_toggle_button_set_active (fd->avalue, TRUE);
335       break;
336     case FRQ_DVALUE:
337       gtk_toggle_button_set_active (fd->dvalue, TRUE);
338       break;
339     case FRQ_ACOUNT:
340       gtk_toggle_button_set_active (fd->afreq, TRUE);
341       break;
342     case FRQ_DCOUNT:
343       gtk_toggle_button_set_active (fd->dfreq, TRUE);
344       break;
345     };
346
347   switch (fd->tables_opts.table)
348     {
349     case FRQ_TABLE:
350       gtk_toggle_button_set_active (fd->always, TRUE);
351       break;
352     case FRQ_NOTABLE:
353       gtk_toggle_button_set_active (fd->never, TRUE);
354       break;
355     case FRQ_LIMIT:
356       gtk_toggle_button_set_active (fd->limit, TRUE);
357       break;
358     }
359   gtk_spin_button_set_value (fd->limit_spinbutton,
360                              fd->tables_opts.limit);
361   g_signal_emit_by_name (fd->limit, "toggled");
362
363   ret = psppire_dialog_run (PSPPIRE_DIALOG (fd->tables_dialog));
364
365   if ( ret == PSPPIRE_RESPONSE_CONTINUE )
366     {
367       if (gtk_toggle_button_get_active (fd->avalue))
368         fd->tables_opts.order = FRQ_AVALUE;
369       else if (gtk_toggle_button_get_active (fd->dvalue))
370         fd->tables_opts.order = FRQ_DVALUE;
371       else if (gtk_toggle_button_get_active (fd->afreq))
372         fd->tables_opts.order = FRQ_ACOUNT;
373       else if (gtk_toggle_button_get_active (fd->dfreq))
374         fd->tables_opts.order = FRQ_DCOUNT;
375
376       if (gtk_toggle_button_get_active (fd->always))
377         fd->tables_opts.table = FRQ_TABLE;
378       else if (gtk_toggle_button_get_active (fd->never))
379         fd->tables_opts.table = FRQ_NOTABLE;
380       else
381         fd->tables_opts.table = FRQ_LIMIT;
382
383       fd->tables_opts.limit = gtk_spin_button_get_value (fd->limit_spinbutton);
384     }
385 }
386
387 static void
388 on_charts_clicked (struct frequencies_dialog *fd)
389 {
390   int ret;
391
392   gtk_toggle_button_set_active (fd->min, fd->charts_opts.use_min);
393   gtk_spin_button_set_value (fd->min_spin, fd->charts_opts.min);
394   g_signal_emit_by_name (fd->min, "toggled");
395
396   gtk_toggle_button_set_active (fd->max, fd->charts_opts.use_max);
397   gtk_spin_button_set_value (fd->max_spin, fd->charts_opts.max);
398   g_signal_emit_by_name (fd->max, "toggled");
399
400   gtk_toggle_button_set_active (fd->hist, fd->charts_opts.draw_hist);
401   gtk_toggle_button_set_active (fd->normal, fd->charts_opts.draw_normal);
402   g_signal_emit_by_name (fd->hist, "toggled");
403
404   switch (fd->charts_opts.scale)
405     {
406     case FRQ_FREQ:
407       gtk_toggle_button_set_active (fd->freqs, TRUE);
408       break;
409     case FRQ_DVALUE:
410       gtk_toggle_button_set_active (fd->percents, TRUE);
411       break;
412     };
413
414
415   gtk_toggle_button_set_active (fd->pie, fd->charts_opts.draw_pie);
416   gtk_toggle_button_set_active (fd->pie_include_missing,
417                                 fd->charts_opts.pie_include_missing);
418   g_signal_emit_by_name (fd->pie, "toggled");
419
420   ret = psppire_dialog_run (PSPPIRE_DIALOG (fd->charts_dialog));
421
422   if ( ret == PSPPIRE_RESPONSE_CONTINUE )
423     {
424       fd->charts_opts.use_min = gtk_toggle_button_get_active (fd->min);
425       fd->charts_opts.min = gtk_spin_button_get_value (fd->min_spin);
426
427       fd->charts_opts.use_max = gtk_toggle_button_get_active (fd->max);
428       fd->charts_opts.max = gtk_spin_button_get_value (fd->max_spin);
429
430       fd->charts_opts.draw_hist = gtk_toggle_button_get_active (fd->hist);
431       fd->charts_opts.draw_normal = gtk_toggle_button_get_active (fd->normal);
432       if (gtk_toggle_button_get_active (fd->freqs))
433         fd->charts_opts.scale = FRQ_FREQ;
434       else if (gtk_toggle_button_get_active (fd->percents))
435         fd->charts_opts.scale = FRQ_PERCENT;
436
437       fd->charts_opts.draw_pie = gtk_toggle_button_get_active (fd->pie);
438       fd->charts_opts.pie_include_missing
439         = gtk_toggle_button_get_active (fd->pie_include_missing);
440     }
441 }
442
443
444 /* Makes widget W's sensitivity follow the active state of TOGGLE */
445 static void
446 sensitive_if_active (GtkToggleButton *toggle, GtkWidget *w)
447 {
448   gboolean active = gtk_toggle_button_get_active (toggle);
449
450   gtk_widget_set_sensitive (w, active);
451 }
452
453 /* Pops up the Frequencies dialog box */
454 void
455 frequencies_dialog (PsppireDataWindow *de)
456 {
457   gint response;
458
459   struct frequencies_dialog fd;
460
461   GtkBuilder *xml = builder_new ("frequencies.ui");
462
463   GtkWidget *dialog = get_widget_assert   (xml, "frequencies-dialog");
464   GtkWidget *source = get_widget_assert   (xml, "dict-treeview");
465   GtkWidget *dest =   get_widget_assert   (xml, "var-treeview");
466   GtkWidget *tables_button = get_widget_assert (xml, "tables-button");
467   GtkWidget *charts_button = get_widget_assert (xml, "charts-button");
468   GtkWidget *stats_treeview = get_widget_assert (xml, "stats-treeview");
469
470   PsppireVarStore *vs = NULL;
471
472   g_object_get (de->data_editor, "var-store", &vs, NULL);
473
474   put_checkbox_items_in_treeview (GTK_TREE_VIEW(stats_treeview),
475                                   B_FS_DEFAULT,
476                                   N_FREQUENCY_STATS,
477                                   stats
478                                   );
479
480
481   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
482
483   g_object_get (vs, "dictionary", &fd.dict, NULL);
484   g_object_set (source, "model", fd.dict, NULL);
485
486   fd.stat_vars = GTK_TREE_VIEW (dest);
487   fd.tables_button = get_widget_assert (xml, "tables-button");
488   fd.charts_button = get_widget_assert (xml, "charts-button");
489
490   fd.include_missing = GTK_TOGGLE_BUTTON (
491     get_widget_assert (xml, "include_missing"));
492
493   fd.stats = gtk_tree_view_get_model (GTK_TREE_VIEW (stats_treeview));
494
495   /* Frequency Tables dialog. */
496   fd.tables_dialog = get_widget_assert (xml, "tables-dialog");
497   fd.tables_opts.order = FRQ_AVALUE;
498   fd.tables_opts.table = FRQ_TABLE;
499   fd.tables_opts.limit = 50;
500
501   fd.always = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "always"));
502   fd.never = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "never"));
503   fd.limit  = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "limit"));
504   fd.limit_spinbutton =
505     GTK_SPIN_BUTTON (get_widget_assert (xml, "limit-spin"));
506   g_signal_connect (fd.limit, "toggled",
507                     G_CALLBACK (sensitive_if_active), fd.limit_spinbutton);
508
509   fd.avalue = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "avalue"));
510   fd.dvalue = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "dvalue"));
511   fd.afreq  = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "afreq"));
512   fd.dfreq  = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "dfreq"));
513
514   gtk_window_set_transient_for (GTK_WINDOW (fd.tables_dialog),
515                                 GTK_WINDOW (de));
516
517   /* Charts dialog. */
518   fd.charts_dialog = get_widget_assert (xml, "charts-dialog");
519   fd.charts_opts.use_min = false;
520   fd.charts_opts.min = 0;
521   fd.charts_opts.use_max = false;
522   fd.charts_opts.max = 100;
523   fd.charts_opts.draw_hist = false;
524   fd.charts_opts.draw_normal = false;
525   fd.charts_opts.scale = FRQ_FREQ;
526   fd.charts_opts.draw_pie = false;
527   fd.charts_opts.pie_include_missing = false;
528
529   fd.freqs = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "freqs"));
530   fd.percents = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "percents"));
531
532   fd.min = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "min"));
533   fd.min_spin = GTK_SPIN_BUTTON (get_widget_assert (xml, "min-spin"));
534   g_signal_connect (fd.min, "toggled",
535                     G_CALLBACK (sensitive_if_active), fd.min_spin);
536   fd.max = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "max"));
537   fd.max_spin = GTK_SPIN_BUTTON (get_widget_assert (xml, "max-spin"));
538   g_signal_connect (fd.max, "toggled",
539                     G_CALLBACK (sensitive_if_active), fd.max_spin);
540
541   fd.hist = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "hist"));
542   fd.normal = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "normal"));
543   g_signal_connect (fd.hist, "toggled",
544                     G_CALLBACK (sensitive_if_active), fd.normal);
545
546   fd.pie = GTK_TOGGLE_BUTTON (get_widget_assert (xml, "pie"));
547   fd.pie_include_missing = GTK_TOGGLE_BUTTON (
548     get_widget_assert (xml, "pie-include-missing"));
549   g_signal_connect (fd.pie, "toggled",
550                     G_CALLBACK (sensitive_if_active), fd.pie_include_missing);
551
552   gtk_window_set_transient_for (GTK_WINDOW (fd.charts_dialog),
553                                 GTK_WINDOW (de));
554
555   /* Main dialog. */
556   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &fd);
557
558   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (dialog),
559                                       dialog_state_valid, &fd);
560
561   g_signal_connect_swapped (tables_button, "clicked",
562                             G_CALLBACK (on_tables_clicked),  &fd);
563   g_signal_connect_swapped (charts_button, "clicked",
564                             G_CALLBACK (on_charts_clicked),  &fd);
565
566   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
567
568
569   switch (response)
570     {
571     case GTK_RESPONSE_OK:
572       g_free (execute_syntax_string (de, generate_syntax (&fd)));
573       break;
574     case PSPPIRE_RESPONSE_PASTE:
575       g_free (paste_syntax_to_window (generate_syntax (&fd)));
576       break;
577     default:
578       break;
579     }
580
581   g_object_unref (xml);
582 }