Merge 'master' into 'gtk3'.
[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
22 #include "ui/gui/missing-val-dialog.h"
23
24 #include "builder-wrapper.h"
25 #include "helper.h"
26 #include <data/format.h>
27 #include "missing-val-dialog.h"
28 #include <data/missing-values.h>
29 #include <data/variable.h>
30 #include <data/data-in.h>
31
32 #include <gtk/gtk.h>
33
34 #include <string.h>
35
36 #include <gettext.h>
37 #define _(msgid) gettext (msgid)
38 #define N_(msgid) msgid
39
40 static GObject *psppire_missing_val_dialog_constructor (
41   GType type, guint, GObjectConstructParam *);
42 static void psppire_missing_val_dialog_finalize (GObject *);
43
44 G_DEFINE_TYPE (PsppireMissingValDialog,
45                psppire_missing_val_dialog,
46                PSPPIRE_TYPE_DIALOG);
47 enum
48   {
49     PROP_0,
50     PROP_VARIABLE,
51     PROP_MISSING_VALUES
52   };
53
54 static void
55 psppire_missing_val_dialog_set_property (GObject      *object,
56                                          guint         prop_id,
57                                          const GValue *value,
58                                          GParamSpec   *pspec)
59 {
60   PsppireMissingValDialog *obj = PSPPIRE_MISSING_VAL_DIALOG (object);
61
62   switch (prop_id)
63     {
64     case PROP_VARIABLE:
65       psppire_missing_val_dialog_set_variable (obj,
66                                                g_value_get_pointer (value));
67       break;
68     case PROP_MISSING_VALUES:
69     default:
70       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
71       break;
72     }
73 }
74
75 static void
76 psppire_missing_val_dialog_get_property (GObject      *object,
77                                          guint         prop_id,
78                                          GValue       *value,
79                                          GParamSpec   *pspec)
80 {
81   PsppireMissingValDialog *obj = PSPPIRE_MISSING_VAL_DIALOG (object);
82
83   switch (prop_id)
84     {
85     case PROP_MISSING_VALUES:
86       g_value_set_pointer (value, &obj->mvl);
87       break;
88     case PROP_VARIABLE:
89     default:
90       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
91       break;
92     }
93 }
94
95 static void
96 psppire_missing_val_dialog_class_init (PsppireMissingValDialogClass *class)
97 {
98   GObjectClass *gobject_class;
99   gobject_class = G_OBJECT_CLASS (class);
100
101   gobject_class->constructor = psppire_missing_val_dialog_constructor;
102   gobject_class->finalize = psppire_missing_val_dialog_finalize;
103   gobject_class->set_property = psppire_missing_val_dialog_set_property;
104   gobject_class->get_property = psppire_missing_val_dialog_get_property;
105
106   g_object_class_install_property (
107     gobject_class, PROP_VARIABLE,
108     g_param_spec_pointer ("variable",
109                           "Variable",
110                           "Variable whose missing values are to be edited.  "
111                           "The variable's print format and encoding are also "
112                           "used for editing.",
113                           G_PARAM_WRITABLE));
114
115   g_object_class_install_property (
116     gobject_class, PROP_MISSING_VALUES,
117     g_param_spec_pointer ("missing-values",
118                           "Missing Values",
119                           "Edited missing values.",
120                           G_PARAM_READABLE));
121 }
122
123 static void
124 psppire_missing_val_dialog_init (PsppireMissingValDialog *dialog)
125 {
126   /* We do all of our work on widgets in the constructor function, because that
127      runs after the construction properties have been set.  Otherwise
128      PsppireDialog's "orientation" property hasn't been set and therefore we
129      have no box to populate. */
130   mv_init (&dialog->mvl, 0);
131   dialog->encoding = NULL;
132 }
133
134 static void
135 psppire_missing_val_dialog_finalize (GObject *obj)
136 {
137   PsppireMissingValDialog *dialog = PSPPIRE_MISSING_VAL_DIALOG (obj);
138
139   mv_destroy (&dialog->mvl);
140   g_free (dialog->encoding);
141
142   G_OBJECT_CLASS (psppire_missing_val_dialog_parent_class)->finalize (obj);
143 }
144
145 PsppireMissingValDialog *
146 psppire_missing_val_dialog_new (const struct variable *var)
147 {
148   return PSPPIRE_MISSING_VAL_DIALOG (
149     g_object_new (PSPPIRE_TYPE_MISSING_VAL_DIALOG,
150                   "orientation", PSPPIRE_VERTICAL,
151                   "variable", var,
152                   NULL));
153 }
154
155 void
156 psppire_missing_val_dialog_run (GtkWindow *parent_window,
157                                 const struct variable *var,
158                                 struct missing_values *mv)
159 {
160   PsppireMissingValDialog *dialog;
161
162   dialog = psppire_missing_val_dialog_new (var);
163   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
164   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
165   gtk_widget_show (GTK_WIDGET (dialog));
166
167   if (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK)
168     mv_copy (mv, psppire_missing_val_dialog_get_missing_values (dialog));
169   else
170     mv_copy (mv, var_get_missing_values (var));
171
172   gtk_widget_destroy (GTK_WIDGET (dialog));
173 }
174
175
176 /* A simple (sub) dialog box for displaying user input errors */
177 static void
178 err_dialog (const gchar *msg, GtkWindow *window)
179 {
180   GtkWidget *hbox ;
181   GtkWidget *label = gtk_label_new (msg);
182
183   GtkWidget *dialog =
184     gtk_dialog_new_with_buttons ("PSPP",
185                                  window,
186                                  GTK_DIALOG_MODAL |
187                                  GTK_DIALOG_DESTROY_WITH_PARENT, 
188                                  GTK_STOCK_OK,
189                                  GTK_RESPONSE_ACCEPT,
190                                  NULL);
191
192
193   GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR,
194                                              GTK_ICON_SIZE_DIALOG);
195
196   g_signal_connect_swapped (dialog,
197                             "response",
198                             G_CALLBACK (gtk_widget_destroy),
199                             dialog);
200
201   hbox = gtk_hbox_new (FALSE, 10);
202
203   gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
204                      hbox);
205
206   gtk_box_pack_start (GTK_BOX (hbox), icon, TRUE, FALSE, 10);
207   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 10);
208
209   gtk_widget_show_all (dialog);
210 }
211
212
213 /* Acceptability predicate for PsppireMissingValDialog.
214
215    This function is also the only place that dialog->mvl gets updated. */
216 static gboolean
217 missing_val_dialog_acceptable (gpointer data)
218 {
219   PsppireMissingValDialog *dialog = data;
220
221   if ( gtk_toggle_button_get_active (dialog->button_discrete))
222     {
223       gint nvals = 0;
224       gint badvals = 0;
225       gint i;
226       mv_clear(&dialog->mvl);
227       for(i = 0 ; i < 3 ; ++i )
228         {
229           gchar *text =
230             g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->mv[i])));
231
232           union value v;
233           if ( !text || strlen (g_strstrip (text)) == 0 )
234             {
235               g_free (text);
236               continue;
237             }
238
239           if ( text_to_value__ (text, &dialog->format, dialog->encoding, &v))
240             {
241               nvals++;
242               mv_add_value (&dialog->mvl, &v);
243             }
244           else
245               badvals++;
246           g_free (text);
247           value_destroy (&v, fmt_var_width (&dialog->format));
248         }
249       if ( nvals == 0 || badvals > 0 )
250         {
251           err_dialog (_("Incorrect value for variable type"),
252                       GTK_WINDOW (dialog));
253           return FALSE;
254         }
255     }
256
257   if (gtk_toggle_button_get_active (dialog->button_range))
258     {
259       gchar *discrete_text ;
260
261       union value low_val ;
262       union value high_val;
263       const gchar *low_text = gtk_entry_get_text (GTK_ENTRY (dialog->low));
264       const gchar *high_text = gtk_entry_get_text (GTK_ENTRY (dialog->high));
265       gboolean low_ok;
266       gboolean high_ok;
267       gboolean ok;
268
269       low_ok = text_to_value__ (low_text, &dialog->format, dialog->encoding,
270                                 &low_val) != NULL;
271       high_ok = text_to_value__ (high_text, &dialog->format, dialog->encoding,
272                                  &high_val) != NULL;
273       ok = low_ok && high_ok && low_val.f <= high_val.f;
274       if (!ok)
275         {
276           err_dialog (_("Incorrect range specification"), GTK_WINDOW (dialog));
277           if (low_ok)
278             value_destroy (&low_val, fmt_var_width (&dialog->format));
279           if (high_ok)
280             value_destroy (&high_val, fmt_var_width (&dialog->format));
281           return FALSE;
282         }
283
284       discrete_text =
285         g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->discrete)));
286
287       mv_clear (&dialog->mvl);
288       mv_add_range (&dialog->mvl, low_val.f, high_val.f);
289
290       value_destroy (&low_val, fmt_var_width (&dialog->format));
291       value_destroy (&high_val, fmt_var_width (&dialog->format));
292
293       if ( discrete_text && strlen (g_strstrip (discrete_text)) > 0 )
294         {
295           union value discrete_val;
296           if ( !text_to_value__ (discrete_text,
297                                  &dialog->format,
298                                  dialog->encoding,
299                                  &discrete_val))
300             {
301               err_dialog (_("Incorrect value for variable type"),
302                          GTK_WINDOW (dialog) );
303               g_free (discrete_text);
304               value_destroy (&discrete_val, fmt_var_width (&dialog->format));
305               return FALSE;
306             }
307           mv_add_value (&dialog->mvl, &discrete_val);
308           value_destroy (&discrete_val, fmt_var_width (&dialog->format));
309         }
310       g_free (discrete_text);
311     }
312
313
314   if (gtk_toggle_button_get_active (dialog->button_none))
315     mv_clear (&dialog->mvl);
316
317   return TRUE;
318 }
319
320
321 /* Callback which occurs when the 'discrete' radiobutton is toggled */
322 static void
323 discrete (GtkToggleButton *button, gpointer data)
324 {
325   gint i;
326   PsppireMissingValDialog *dialog = data;
327
328   for (i = 0 ; i < 3 ; ++i )
329     {
330       gtk_widget_set_sensitive (dialog->mv[i],
331                                gtk_toggle_button_get_active (button));
332     }
333 }
334
335 /* Callback which occurs when the 'range' radiobutton is toggled */
336 static void
337 range (GtkToggleButton *button, gpointer data)
338 {
339   PsppireMissingValDialog *dialog = data;
340
341   const gboolean active = gtk_toggle_button_get_active (button);
342
343   gtk_widget_set_sensitive (dialog->low, active);
344   gtk_widget_set_sensitive (dialog->high, active);
345   gtk_widget_set_sensitive (dialog->discrete, active);
346 }
347
348
349
350 /* Shows the dialog box and sets default values */
351 static GObject *
352 psppire_missing_val_dialog_constructor (GType                  type,
353                                         guint                  n_properties,
354                                         GObjectConstructParam *properties)
355 {
356   PsppireMissingValDialog *dialog;
357   GtkContainer *content_area;
358   GtkBuilder *xml;
359   GObject *obj;
360
361   obj = G_OBJECT_CLASS (psppire_missing_val_dialog_parent_class)->constructor (
362     type, n_properties, properties);
363   dialog = PSPPIRE_MISSING_VAL_DIALOG (obj);
364
365   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog)->box);
366   xml = builder_new ("missing-val-dialog.ui");
367   gtk_container_add (GTK_CONTAINER (content_area),
368                      get_widget_assert (xml, "missing-values-dialog"));
369
370   dialog->mv[0] = get_widget_assert (xml, "mv0");
371   dialog->mv[1] = get_widget_assert (xml, "mv1");
372   dialog->mv[2] = get_widget_assert (xml, "mv2");
373
374   dialog->low = get_widget_assert (xml, "mv-low");
375   dialog->high = get_widget_assert (xml, "mv-high");
376   dialog->discrete = get_widget_assert (xml, "mv-discrete");
377
378
379   dialog->button_none     =
380     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "no_missing"));
381
382   dialog->button_discrete =
383     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "discrete_missing"));
384
385   dialog->button_range    =
386     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "range_missing"));
387
388   psppire_dialog_set_accept_predicate (PSPPIRE_DIALOG (dialog),
389                                        missing_val_dialog_acceptable,
390                                        dialog);
391
392   g_signal_connect (dialog->button_discrete, "toggled",
393                    G_CALLBACK (discrete), dialog);
394
395   g_signal_connect (dialog->button_range, "toggled",
396                    G_CALLBACK (range), dialog);
397
398   g_object_unref (xml);
399
400   return obj;
401 }
402
403 void
404 psppire_missing_val_dialog_set_variable (PsppireMissingValDialog *dialog,
405                                          const struct variable *var)
406 {
407   enum val_type var_type;
408   gint i;
409
410   mv_destroy (&dialog->mvl);
411   g_free (dialog->encoding);
412
413   if (var != NULL)
414     {
415       mv_copy (&dialog->mvl, var_get_missing_values (var));
416       dialog->encoding = g_strdup (var_get_encoding (var));
417       dialog->format = *var_get_print_format (var);
418     }
419   else
420     {
421       mv_init (&dialog->mvl, 0);
422       dialog->encoding = NULL;
423       dialog->format = F_8_0;
424     }
425
426   /* Blank all entry boxes and make them insensitive */
427   gtk_entry_set_text (GTK_ENTRY (dialog->low), "");
428   gtk_entry_set_text (GTK_ENTRY (dialog->high), "");
429   gtk_entry_set_text (GTK_ENTRY (dialog->discrete), "");
430   gtk_widget_set_sensitive (dialog->low, FALSE);
431   gtk_widget_set_sensitive (dialog->high, FALSE);
432   gtk_widget_set_sensitive (dialog->discrete, FALSE);
433
434   var_type = val_type_from_width (fmt_var_width (&dialog->format));
435   gtk_widget_set_sensitive (GTK_WIDGET (dialog->button_range),
436                             var_type == VAL_NUMERIC);
437
438   if (var == NULL)
439     return;
440
441   for (i = 0 ; i < 3 ; ++i )
442     {
443       gtk_entry_set_text (GTK_ENTRY (dialog->mv[i]), "");
444       gtk_widget_set_sensitive (dialog->mv[i], FALSE);
445     }
446
447   if ( mv_has_range (&dialog->mvl))
448     {
449       union value low, high;
450       gchar *low_text;
451       gchar *high_text;
452       mv_get_range (&dialog->mvl, &low.f, &high.f);
453
454
455       low_text = value_to_text__ (low, &dialog->format, dialog->encoding);
456       high_text = value_to_text__ (high, &dialog->format, dialog->encoding);
457
458       gtk_entry_set_text (GTK_ENTRY (dialog->low), low_text);
459       gtk_entry_set_text (GTK_ENTRY (dialog->high), high_text);
460       g_free (low_text);
461       g_free (high_text);
462
463       if ( mv_has_value (&dialog->mvl))
464         {
465           gchar *text;
466           text = value_to_text__ (*mv_get_value (&dialog->mvl, 0),
467                                   &dialog->format, dialog->encoding);
468           gtk_entry_set_text (GTK_ENTRY (dialog->discrete), text);
469           g_free (text);
470         }
471
472       gtk_toggle_button_set_active (dialog->button_range, TRUE);
473       gtk_widget_set_sensitive (dialog->low, TRUE);
474       gtk_widget_set_sensitive (dialog->high, TRUE);
475       gtk_widget_set_sensitive (dialog->discrete, TRUE);
476
477     }
478   else if ( mv_has_value (&dialog->mvl))
479     {
480       const int n = mv_n_values (&dialog->mvl);
481
482       for (i = 0 ; i < 3 ; ++i )
483         {
484           if ( i < n)
485             {
486               gchar *text ;
487
488               text = value_to_text__ (*mv_get_value (&dialog->mvl, i),
489                                       &dialog->format, dialog->encoding);
490               gtk_entry_set_text (GTK_ENTRY (dialog->mv[i]), text);
491               g_free (text);
492             }
493           gtk_widget_set_sensitive (dialog->mv[i], TRUE);
494         }
495       gtk_toggle_button_set_active (dialog->button_discrete, TRUE);
496     }
497   else if ( mv_is_empty (&dialog->mvl))
498     {
499       gtk_toggle_button_set_active (dialog->button_none, TRUE);
500     }
501 }
502
503 const struct missing_values *
504 psppire_missing_val_dialog_get_missing_values (
505   const PsppireMissingValDialog *dialog)
506 {
507   return &dialog->mvl;
508 }