CORRELATIONS: Fixed bug displaying non-sqaure correlation matrices
[pspp] / src / ui / gui / select-cases-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
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 "select-cases-dialog.h"
20 #include <gtk/gtk.h>
21 #include "executor.h"
22 #include "psppire-dialog.h"
23 #include "psppire-data-window.h"
24 #include "psppire-selector.h"
25 #include "dict-display.h"
26 #include "dialog-common.h"
27 #include "widget-io.h"
28 #include "psppire-scanf.h"
29 #include "builder-wrapper.h"
30 #include "helper.h"
31
32 #include <xalloc.h>
33
34
35 #include <gettext.h>
36 #define _(msgid) gettext (msgid)
37 #define N_(msgid) msgid
38
39
40
41 /* FIXME: These shouldn't be here */
42 #include "psppire-data-store.h"
43
44
45 struct select_cases_dialog
46 {
47   /* The XML that created the dialog */
48   GtkBuilder *xml;
49
50   GtkWidget *spinbutton ;
51   GtkWidget *spinbutton1 ;
52   GtkWidget *spinbutton2 ;
53
54   GtkWidget *hbox1;
55   GtkWidget *hbox2;
56
57   PsppireDataStore *data_store;
58 };
59
60 static gchar * generate_syntax (const struct select_cases_dialog *scd);
61
62
63 static void
64 set_sensitivity_from_toggle (GtkToggleButton *togglebutton,  gpointer data)
65 {
66   GtkWidget *w = data;
67   gboolean active = gtk_toggle_button_get_active (togglebutton);
68
69   gtk_widget_set_sensitive (w, active);
70 }
71
72 static void
73 set_sensitivity_from_toggle_invert (GtkToggleButton *togglebutton,
74                                     gpointer data)
75 {
76   GtkWidget *w = data;
77   gboolean active = gtk_toggle_button_get_active (togglebutton);
78
79   gtk_widget_set_sensitive (w, !active);
80 }
81
82
83
84 static const gchar label1[]=N_("Approximately %3d%% of all cases.");
85 static const gchar label2[]=N_("Exactly %3d cases from the first %3d cases.");
86
87
88 static void
89 sample_subdialog (GtkButton *b, gpointer data)
90 {
91   gint response;
92   struct select_cases_dialog *scd = data;
93
94   gint case_count = psppire_data_store_get_case_count (scd->data_store);
95
96   GtkWidget *parent_dialog = get_widget_assert (scd->xml,
97                                                 "select-cases-dialog");
98   GtkWidget *dialog = get_widget_assert (scd->xml,
99                                          "select-cases-random-sample-dialog");
100   GtkWidget *percent = get_widget_assert (scd->xml,
101                                           "radiobutton-sample-percent");
102   GtkWidget *sample_n_cases = get_widget_assert (scd->xml,
103                                                  "radiobutton-sample-n-cases");
104   GtkWidget *table = get_widget_assert (scd->xml,
105                                         "select-cases-random-sample-table");
106
107   if ( ! scd->hbox1 )
108     {
109       scd->hbox1 = psppire_scanf_new (gettext (label1), &scd->spinbutton);
110
111       gtk_widget_show (scd->hbox1);
112
113       gtk_table_attach_defaults (GTK_TABLE (table),
114                                  scd->hbox1, 1, 2, 0, 1);
115
116       g_signal_connect (percent, "toggled",
117                         G_CALLBACK (set_sensitivity_from_toggle), scd->hbox1);
118
119       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (percent), TRUE);
120     }
121
122
123   if ( ! scd->hbox2 )
124     {
125       scd->hbox2 =
126         psppire_scanf_new (gettext (label2), &scd->spinbutton1, &scd->spinbutton2);
127
128       gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spinbutton1),
129                                  1, case_count);
130
131       gtk_spin_button_set_range (GTK_SPIN_BUTTON (scd->spinbutton2),
132                                  1, case_count);
133
134       gtk_widget_show (scd->hbox2);
135       gtk_widget_set_sensitive (scd->hbox2, FALSE);
136
137       gtk_table_attach_defaults (GTK_TABLE (table),
138                                  scd->hbox2, 1, 2, 1, 2);
139
140       g_signal_connect (sample_n_cases, "toggled",
141                         G_CALLBACK (set_sensitivity_from_toggle), scd->hbox2);
142
143       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sample_n_cases), FALSE);
144     }
145
146
147   gtk_window_set_transient_for (GTK_WINDOW (dialog),
148                                 GTK_WINDOW (parent_dialog));
149
150   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
151
152   if ( response != PSPPIRE_RESPONSE_CONTINUE)
153     {
154       g_signal_handlers_disconnect_by_func
155         (G_OBJECT (percent),
156          G_CALLBACK (set_sensitivity_from_toggle),
157          scd->hbox1);
158
159       g_signal_handlers_disconnect_by_func
160         (G_OBJECT (sample_n_cases),
161          G_CALLBACK (set_sensitivity_from_toggle),
162          scd->hbox2);
163
164       gtk_widget_destroy(scd->hbox1);
165       gtk_widget_destroy(scd->hbox2);
166       scd->hbox1 = scd->hbox2 = NULL;
167     }
168   else
169     {
170       gchar *text;
171       GtkWidget *l0 = get_widget_assert (scd->xml, "random-sample-label");
172
173       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (percent)))
174         {
175           text = widget_printf (gettext(label1), scd->spinbutton);
176           gtk_label_set_text (GTK_LABEL (l0), text);
177         }
178       else
179         {
180           text =
181             widget_printf (gettext(label2), scd->spinbutton1, scd->spinbutton2);
182           gtk_label_set_text (GTK_LABEL (l0), text);
183
184         }
185       g_free (text);
186
187     }
188 }
189
190 static void
191 range_subdialog (GtkButton *b, gpointer data)
192 {
193   gint response;
194   struct select_cases_dialog *scd = data;
195
196   gint n_cases = psppire_data_store_get_case_count (scd->data_store);
197
198   GtkWidget *parent_dialog = get_widget_assert (scd->xml,
199                                                 "select-cases-dialog");
200
201   GtkWidget *dialog = get_widget_assert (scd->xml,
202                                          "select-cases-range-dialog");
203
204   GtkWidget *first = get_widget_assert (scd->xml,
205                                         "range-dialog-first");
206
207   GtkWidget *last = get_widget_assert (scd->xml,
208                                         "range-dialog-last");
209
210
211   gtk_spin_button_set_range (GTK_SPIN_BUTTON (last),  1,  n_cases);
212   gtk_spin_button_set_range (GTK_SPIN_BUTTON (first), 1,  n_cases);
213
214   gtk_window_set_transient_for (GTK_WINDOW (dialog),
215                                 GTK_WINDOW (parent_dialog));
216
217
218   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
219   if ( response == PSPPIRE_RESPONSE_CONTINUE)
220     {
221       GtkWidget *first = get_widget_assert (scd->xml, "range-dialog-first");
222       GtkWidget *last = get_widget_assert (scd->xml, "range-dialog-last");
223       GtkWidget *l1 = get_widget_assert (scd->xml, "range-sample-label");
224       gchar *text = widget_printf (_("%d thru %d"), first, last);
225
226       gtk_label_set_text (GTK_LABEL (l1), text);
227
228       g_free (text);
229     }
230 }
231
232 static void
233 set_radiobutton (GtkWidget *button, gpointer data)
234 {
235   GtkToggleButton *toggle = data;
236   gtk_toggle_button_set_active (toggle, TRUE);
237 }
238
239 /* Pops up the Select Cases dialog box */
240 void
241 select_cases_dialog (PsppireDataWindow *de)
242 {
243   gint response;
244   struct select_cases_dialog scd = {0,0,0,0,0,0};
245   GtkWidget *dialog   ;
246   GtkWidget *entry = NULL;
247   GtkWidget *selector ;
248   GtkWidget *button_range;
249   GtkWidget *button_sample;
250
251   scd.xml = builder_new ("select-cases.ui");
252
253   g_object_get (de->data_editor, "data-store", &scd.data_store, NULL);
254
255   button_range = get_widget_assert (scd.xml, "button-range");
256   button_sample = get_widget_assert (scd.xml, "button-sample");
257   entry = get_widget_assert (scd.xml, "filter-variable-entry");
258   selector = get_widget_assert (scd.xml, "psppire-selector-filter");
259
260   {
261     GtkWidget *button_if =
262       get_widget_assert (scd.xml, "button-if");
263
264     GtkWidget *radiobutton_if =
265       get_widget_assert (scd.xml, "radiobutton-if");
266
267     GtkWidget *radiobutton_all =
268       get_widget_assert (scd.xml, "radiobutton-all");
269
270     GtkWidget *radiobutton_sample =
271       get_widget_assert (scd.xml, "radiobutton-sample");
272
273     GtkWidget *radiobutton_range =
274       get_widget_assert (scd.xml, "radiobutton-range");
275
276     GtkWidget *radiobutton_filter =
277       get_widget_assert (scd.xml, "radiobutton-filter-variable");
278
279     GtkWidget *range_label =
280       get_widget_assert (scd.xml, "range-sample-label");
281
282     GtkWidget *sample_label =
283       get_widget_assert (scd.xml, "random-sample-label");
284
285     g_signal_connect (radiobutton_all, "toggled",
286                       G_CALLBACK (set_sensitivity_from_toggle_invert),
287                       get_widget_assert (scd.xml, "filter-delete-button-box")
288                       );
289
290     g_signal_connect (button_if, "clicked",
291                       G_CALLBACK (set_radiobutton), radiobutton_if);
292
293     g_signal_connect (button_sample, "clicked",
294                       G_CALLBACK (set_radiobutton), radiobutton_sample);
295
296     g_signal_connect (button_range,  "clicked",
297                       G_CALLBACK (set_radiobutton), radiobutton_range);
298
299     g_signal_connect (selector, "clicked",
300                       G_CALLBACK (set_radiobutton), radiobutton_filter);
301
302     g_signal_connect (selector, "selected",
303                       G_CALLBACK (set_radiobutton), radiobutton_filter);
304
305     g_signal_connect (radiobutton_range, "toggled",
306                       G_CALLBACK (set_sensitivity_from_toggle),
307                       range_label
308                       );
309
310     g_signal_connect (radiobutton_sample, "toggled",
311                       G_CALLBACK (set_sensitivity_from_toggle),
312                       sample_label
313                       );
314
315     g_signal_connect (radiobutton_filter, "toggled",
316                       G_CALLBACK (set_sensitivity_from_toggle),
317                       entry
318                       );
319   }
320
321
322
323   dialog = get_widget_assert (scd.xml, "select-cases-dialog");
324   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
325
326   {
327     GtkWidget *source = get_widget_assert   (scd.xml, "select-cases-treeview");
328
329     g_object_set (source, "model",
330                   scd.data_store->dict,
331                   "selection-mode",
332                   GTK_SELECTION_SINGLE, NULL);
333
334     psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
335                                    is_currently_in_entry);
336   }
337
338
339
340   g_signal_connect (button_range,
341                     "clicked", G_CALLBACK (range_subdialog), &scd);
342
343
344   g_signal_connect (button_sample,
345                     "clicked", G_CALLBACK (sample_subdialog), &scd);
346
347
348   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
349
350   switch (response)
351     {
352     case GTK_RESPONSE_OK:
353       g_free (execute_syntax_string (de, generate_syntax (&scd)));
354       break;
355     case PSPPIRE_RESPONSE_PASTE:
356       g_free (paste_syntax_to_window (generate_syntax (&scd)));
357       break;
358     default:
359       break;
360     }
361
362   g_object_unref (scd.xml);
363 }
364
365
366 static gchar *
367 generate_syntax_filter (const struct select_cases_dialog *scd)
368 {
369   gchar *text = NULL;
370   GString *string = g_string_new ("");
371
372   const gchar *filter = "filter_$";
373   const gchar key[]="case_$";
374
375   if ( gtk_toggle_button_get_active
376        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
377                                               "radiobutton-range"))))
378     {
379       GtkSpinButton *first =
380         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
381                                            "range-dialog-first"));
382
383       GtkSpinButton *last =
384         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
385                                            "range-dialog-last"));
386
387       g_string_append_printf (string,
388                               "COMPUTE filter_$ = ($CASENUM >= %ld "
389                                "AND $CASENUM <= %ld).\n",
390                               (long) gtk_spin_button_get_value (first),
391                               (long) gtk_spin_button_get_value (last)
392                               );
393
394       g_string_append (string, "EXECUTE.\n");
395     }
396   else if ( gtk_toggle_button_get_active
397        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
398                                               "radiobutton-sample"))))
399     {
400       GtkWidget *random_sample =
401         get_widget_assert (scd->xml,
402                            "radiobutton-sample-percent");
403
404       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (random_sample)))
405         {
406           const double percentage =
407             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
408
409           g_string_append_printf (string,
410                                   "COMPUTE %s = RV.UNIFORM (0,1) < %g.\n",
411                                   filter,
412                                   percentage / 100.0 );
413         }
414       else
415         {
416           const gint n_cases =
417             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton1));
418           const gint from_n_cases =
419             gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton2));
420
421
422           const gchar ranvar[]="rv_$";
423
424           g_string_append_printf (string,
425                                   "COMPUTE %s = $CASENUM.\n", key);
426
427           g_string_append_printf (string,
428                                   "COMPUTE %s = %s > %d.\n",
429                                   filter, key, from_n_cases);
430
431           g_string_append_printf (string,
432                                   "COMPUTE %s = RV.UNIFORM (0, 1).\n",
433                                   ranvar);
434
435           g_string_append_printf (string,
436                                   "SORT BY %s, %s.\n",
437                                   filter, ranvar);
438
439           g_string_append (string, "EXECUTE.\n");
440                                   
441
442           g_string_append_printf (string,
443                                   "COMPUTE %s = $CASENUM.\n",
444                                   filter );
445
446           g_string_append_printf (string,
447                                   "COMPUTE %s = %s <= %d\n",
448                                   filter,
449                                   filter,
450                                   n_cases );
451
452           g_string_append (string, "EXECUTE.\n");
453
454
455           g_string_append_printf (string,
456                                   "SORT BY %s.\n",
457                                   key);
458
459           g_string_append_printf (string,
460                                   "DELETE VARIABLES %s, %s.\n",
461                                   key, ranvar);
462
463         }
464
465       g_string_append (string, "EXECUTE.\n");
466
467     }
468   else
469     {
470       GtkEntry *entry =
471         GTK_ENTRY (get_widget_assert (scd->xml,
472                                       "filter-variable-entry"));
473       filter = gtk_entry_get_text (entry);
474     }
475
476
477   g_string_append_printf  (string, "FILTER BY %s.\n", filter);
478
479   text  = string->str;
480   g_string_free (string, FALSE);
481   return text;
482 }
483
484 static gchar *
485 generate_syntax_delete (const struct select_cases_dialog *scd)
486 {
487   gchar *text = NULL;
488   GString *string = NULL;
489
490   if ( gtk_toggle_button_get_active
491        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
492                                               "radiobutton-all"))))
493     {
494       return xstrdup ("\n");
495     }
496
497   string = g_string_new ("");
498
499   if ( gtk_toggle_button_get_active
500        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
501                                               "radiobutton-sample"))))
502   {
503     GtkWidget *random_sample =
504       get_widget_assert (scd->xml,
505                          "radiobutton-sample-percent");
506
507     g_string_append (string, "SAMPLE ");
508
509     if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (random_sample)))
510       {
511         const double percentage =
512           gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton));
513         g_string_append_printf (string, "%g.", percentage / 100.0);
514       }
515     else
516       {
517         const gint n_cases =
518           gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton1));
519         const gint from_n_cases =
520           gtk_spin_button_get_value (GTK_SPIN_BUTTON (scd->spinbutton2));
521
522         g_string_append_printf (string, "%d FROM %d .", n_cases, from_n_cases);
523       }
524
525   }
526   else if ( gtk_toggle_button_get_active
527             (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
528                                                    "radiobutton-range"))))
529     {
530       GtkSpinButton *first =
531         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
532                                            "range-dialog-first"));
533
534       GtkSpinButton *last =
535         GTK_SPIN_BUTTON (get_widget_assert (scd->xml,
536                                            "range-dialog-last"));
537
538       g_string_append_printf (string,
539                               "COMPUTE filter_$ = ($CASENUM >= %ld "
540                                "AND $CASENUM <= %ld).\n",
541                               (long) gtk_spin_button_get_value (first),
542                               (long) gtk_spin_button_get_value (last)
543                               );
544       g_string_append (string, "EXECUTE.\n");
545       g_string_append_printf (string, "SELECT IF filter_$.\n");
546
547     }
548   else if ( gtk_toggle_button_get_active
549             (GTK_TOGGLE_BUTTON
550              (get_widget_assert (scd->xml,
551                                  "radiobutton-filter-variable"))))
552     {
553       GtkEntry *entry =
554         GTK_ENTRY (get_widget_assert (scd->xml,
555                                       "filter-variable-entry"));
556
557       g_string_append_printf (string, "SELECT IF (%s <> 0).",
558                               gtk_entry_get_text (entry));
559     }
560
561
562   g_string_append (string, "\n");
563
564
565
566   text  = string->str;
567   g_string_free (string, FALSE);
568   return text;
569 }
570
571
572 static gchar *
573 generate_syntax (const struct select_cases_dialog *scd)
574 {
575   /* In the simple case, all we need to do is cancel any existing filter */
576   if ( gtk_toggle_button_get_active
577        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
578                                               "radiobutton-all"))))
579     {
580       return g_strdup ("FILTER OFF.\n");
581     }
582
583
584   /* Are we filtering or deleting ? */
585   if ( gtk_toggle_button_get_active
586        (GTK_TOGGLE_BUTTON (get_widget_assert (scd->xml,
587                                               "radiobutton-delete"))))
588     {
589       return generate_syntax_delete (scd);
590     }
591   else
592     {
593       return generate_syntax_filter (scd);
594     }
595
596 }
597
598
599