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