missing-val-dialog: Avoid destroying uninitialized data.
[pspp] / src / ui / gui / missing-val-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2005, 2006, 2009, 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 /*  This module describes the behaviour of the Missing Values dialog box,
18     used for input of the missing values in the variable sheet */
19
20 #include <config.h>
21 #include <gettext.h>
22 #define _(msgid) gettext (msgid)
23 #define N_(msgid) msgid
24
25
26 #include "builder-wrapper.h"
27 #include "helper.h"
28 #include <data/format.h>
29 #include "missing-val-dialog.h"
30 #include <data/missing-values.h>
31 #include <data/variable.h>
32 #include <data/data-in.h>
33
34
35 #include <gtk/gtk.h>
36
37 #include <string.h>
38
39
40 /* A simple (sub) dialog box for displaying user input errors */
41 static void
42 err_dialog (const gchar *msg, GtkWindow *window)
43 {
44   GtkWidget *hbox ;
45   GtkWidget *label = gtk_label_new (msg);
46
47   GtkWidget *dialog =
48     gtk_dialog_new_with_buttons ("PSPP",
49                                  window,
50                                  GTK_DIALOG_MODAL |
51                                  GTK_DIALOG_DESTROY_WITH_PARENT |
52                                  GTK_DIALOG_NO_SEPARATOR,
53                                  GTK_STOCK_OK,
54                                  GTK_RESPONSE_ACCEPT,
55                                  NULL);
56
57
58   GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR,
59                                              GTK_ICON_SIZE_DIALOG);
60
61   g_signal_connect_swapped (dialog,
62                             "response",
63                             G_CALLBACK (gtk_widget_destroy),
64                             dialog);
65
66   hbox = gtk_hbox_new (FALSE, 10);
67
68   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
69                      hbox);
70
71   gtk_box_pack_start (GTK_BOX (hbox), icon, TRUE, FALSE, 10);
72   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 10);
73
74   gtk_widget_show_all (dialog);
75 }
76
77
78 /* Callback which occurs when the OK button is clicked */
79 static void
80 missing_val_dialog_accept (GtkWidget *w, gpointer data)
81 {
82   struct missing_val_dialog *dialog = data;
83
84   if ( gtk_toggle_button_get_active (dialog->button_discrete))
85     {
86       gint nvals = 0;
87       gint badvals = 0;
88       gint i;
89       mv_clear(&dialog->mvl);
90       for(i = 0 ; i < 3 ; ++i )
91         {
92           gchar *text =
93             g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->mv[i])));
94
95           union value v;
96           if ( !text || strlen (g_strstrip (text)) == 0 )
97             {
98               g_free (text);
99               continue;
100             }
101
102           if ( text_to_value (text, dialog->pv, &v))
103             {
104               nvals++;
105               mv_add_value (&dialog->mvl, &v);
106             }
107           else
108               badvals++;
109           g_free (text);
110           value_destroy (&v, var_get_width (dialog->pv));
111         }
112       if ( nvals == 0 || badvals > 0 )
113         {
114           err_dialog (_("Incorrect value for variable type"),
115                      GTK_WINDOW (dialog->window));
116           return ;
117         }
118     }
119
120   if (gtk_toggle_button_get_active (dialog->button_range))
121     {
122       gchar *discrete_text ;
123
124       union value low_val ;
125       union value high_val;
126       const gchar *low_text = gtk_entry_get_text (GTK_ENTRY (dialog->low));
127       const gchar *high_text = gtk_entry_get_text (GTK_ENTRY (dialog->high));
128       gboolean low_ok;
129       gboolean high_ok;
130       gboolean ok;
131
132       low_ok = text_to_value (low_text, dialog->pv, &low_val) != NULL;
133       high_ok = text_to_value (high_text, dialog->pv, &high_val) != NULL;
134       ok = low_ok && high_ok && low_val.f <= high_val.f;
135       if (!ok)
136         {
137           err_dialog (_("Incorrect range specification"),
138                       GTK_WINDOW (dialog->window));
139           if (low_ok)
140             value_destroy (&low_val, var_get_width (dialog->pv));
141           if (high_ok)
142             value_destroy (&high_val, var_get_width (dialog->pv));
143           return;
144         }
145
146       discrete_text =
147         g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->discrete)));
148
149       mv_clear (&dialog->mvl);
150       mv_add_range (&dialog->mvl, low_val.f, high_val.f);
151
152       value_destroy (&low_val, var_get_width (dialog->pv));
153       value_destroy (&high_val, var_get_width (dialog->pv));
154
155       if ( discrete_text && strlen (g_strstrip (discrete_text)) > 0 )
156         {
157           union value discrete_val;
158           if ( !text_to_value (discrete_text, 
159                                dialog->pv,
160                                &discrete_val))
161             {
162               err_dialog (_("Incorrect value for variable type"),
163                          GTK_WINDOW (dialog->window) );
164               g_free (discrete_text);
165               value_destroy (&discrete_val, var_get_width (dialog->pv));
166               return;
167             }
168           mv_add_value (&dialog->mvl, &discrete_val);
169           value_destroy (&discrete_val, var_get_width (dialog->pv));
170         }
171       g_free (discrete_text);
172     }
173
174
175   if (gtk_toggle_button_get_active (dialog->button_none))
176     mv_clear (&dialog->mvl);
177
178   var_set_missing_values (dialog->pv, &dialog->mvl);
179
180   gtk_widget_hide (dialog->window);
181 }
182
183
184 /* Callback which occurs when the 'discrete' radiobutton is toggled */
185 static void
186 discrete (GtkToggleButton *button, gpointer data)
187 {
188   gint i;
189   struct missing_val_dialog *dialog = data;
190
191   for (i = 0 ; i < 3 ; ++i )
192     {
193       gtk_widget_set_sensitive (dialog->mv[i],
194                                gtk_toggle_button_get_active (button));
195     }
196 }
197
198 /* Callback which occurs when the 'range' radiobutton is toggled */
199 static void
200 range (GtkToggleButton *button, gpointer data)
201 {
202   struct missing_val_dialog *dialog = data;
203
204   const gboolean active = gtk_toggle_button_get_active (button);
205
206   gtk_widget_set_sensitive (dialog->low, active);
207   gtk_widget_set_sensitive (dialog->high, active);
208   gtk_widget_set_sensitive (dialog->discrete, active);
209 }
210
211
212
213 /* Callback for when the Missing Value dialog is closed using
214    the window delete button.*/
215 static gint
216 on_delete (GtkWidget *w, GdkEvent *e, gpointer data)
217 {
218   struct missing_val_dialog *dialog = data;
219
220   gtk_widget_hide (dialog->window);
221
222   return TRUE;
223 }
224
225
226 /* Creates the dialog structure */
227 struct missing_val_dialog *
228 missing_val_dialog_create (GtkWindow *toplevel)
229 {
230   GtkBuilder *xml = builder_new ("missing-val-dialog.ui");
231
232   struct missing_val_dialog *dialog = g_malloc (sizeof (*dialog));
233
234   dialog->window = get_widget_assert (xml, "missing_values_dialog");
235
236   gtk_window_set_transient_for
237     (GTK_WINDOW (dialog->window), toplevel);
238
239   g_signal_connect_swapped (get_widget_assert (xml, "missing_val_cancel"),
240                    "clicked", G_CALLBACK (gtk_widget_hide), dialog->window);
241
242   g_signal_connect (get_widget_assert (xml, "missing_val_ok"),
243                    "clicked", G_CALLBACK (missing_val_dialog_accept), dialog);
244
245   g_signal_connect (dialog->window, "delete-event",
246                     G_CALLBACK (on_delete), dialog);
247
248   dialog->mv[0] = get_widget_assert (xml, "mv0");
249   dialog->mv[1] = get_widget_assert (xml, "mv1");
250   dialog->mv[2] = get_widget_assert (xml, "mv2");
251
252   dialog->low = get_widget_assert (xml, "mv-low");
253   dialog->high = get_widget_assert (xml, "mv-high");
254   dialog->discrete = get_widget_assert (xml, "mv-discrete");
255
256
257   dialog->button_none     =
258     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "no_missing"));
259
260   dialog->button_discrete =
261     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "discrete_missing"));
262
263   dialog->button_range    =
264     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "range_missing"));
265
266
267   g_signal_connect (dialog->button_discrete, "toggled",
268                    G_CALLBACK (discrete), dialog);
269
270   g_signal_connect (dialog->button_range, "toggled",
271                    G_CALLBACK (range), dialog);
272
273   g_object_unref (xml);
274
275   return dialog;
276 }
277
278 /* Shows the dialog box and sets default values */
279 void
280 missing_val_dialog_show (struct missing_val_dialog *dialog)
281 {
282   gint i;
283   g_return_if_fail (dialog);
284   g_return_if_fail (dialog->pv);
285
286   mv_copy (&dialog->mvl, var_get_missing_values (dialog->pv));
287
288   /* Blank all entry boxes and make them insensitive */
289   gtk_entry_set_text (GTK_ENTRY (dialog->low), "");
290   gtk_entry_set_text (GTK_ENTRY (dialog->high), "");
291   gtk_entry_set_text (GTK_ENTRY (dialog->discrete), "");
292   gtk_widget_set_sensitive (dialog->low, FALSE);
293   gtk_widget_set_sensitive (dialog->high, FALSE);
294   gtk_widget_set_sensitive (dialog->discrete, FALSE);
295
296   gtk_widget_set_sensitive (GTK_WIDGET (dialog->button_range),
297                            var_is_numeric (dialog->pv));
298
299
300   for (i = 0 ; i < 3 ; ++i )
301     {
302       gtk_entry_set_text (GTK_ENTRY (dialog->mv[i]), "");
303       gtk_widget_set_sensitive (dialog->mv[i], FALSE);
304     }
305
306   if ( mv_has_range (&dialog->mvl))
307     {
308       union value low, high;
309       gchar *low_text;
310       gchar *high_text;
311       mv_get_range (&dialog->mvl, &low.f, &high.f);
312
313
314       low_text = value_to_text (low, dialog->pv);
315       high_text = value_to_text (high, dialog->pv);
316
317       gtk_entry_set_text (GTK_ENTRY (dialog->low), low_text);
318       gtk_entry_set_text (GTK_ENTRY (dialog->high), high_text);
319       g_free (low_text);
320       g_free (high_text);
321
322       if ( mv_has_value (&dialog->mvl))
323         {
324           gchar *text;
325           text = value_to_text (*mv_get_value (&dialog->mvl, 0), dialog->pv);
326           gtk_entry_set_text (GTK_ENTRY (dialog->discrete), text);
327           g_free (text);
328         }
329
330       gtk_toggle_button_set_active (dialog->button_range, TRUE);
331       gtk_widget_set_sensitive (dialog->low, TRUE);
332       gtk_widget_set_sensitive (dialog->high, TRUE);
333       gtk_widget_set_sensitive (dialog->discrete, TRUE);
334
335     }
336   else if ( mv_has_value (&dialog->mvl))
337     {
338       const int n = mv_n_values (&dialog->mvl);
339
340       for (i = 0 ; i < 3 ; ++i )
341         {
342           if ( i < n)
343             {
344               gchar *text ;
345
346               text = value_to_text (*mv_get_value (&dialog->mvl, i), dialog->pv);
347               gtk_entry_set_text (GTK_ENTRY (dialog->mv[i]), text);
348               g_free (text);
349             }
350           gtk_widget_set_sensitive (dialog->mv[i], TRUE);
351         }
352       gtk_toggle_button_set_active (dialog->button_discrete, TRUE);
353     }
354   else if ( mv_is_empty (&dialog->mvl))
355     {
356       gtk_toggle_button_set_active (dialog->button_none, TRUE);
357     }
358
359   gtk_widget_show (dialog->window);
360 }