Merge remote-tracking branch 'origin/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, 2015  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                   "variable", var,
151                   NULL));
152 }
153
154 void
155 psppire_missing_val_dialog_run (GtkWindow *parent_window,
156                                 const struct variable *var,
157                                 struct missing_values *mv)
158 {
159   PsppireMissingValDialog *dialog;
160
161   dialog = psppire_missing_val_dialog_new (var);
162   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
163   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
164   gtk_widget_show (GTK_WIDGET (dialog));
165
166   if (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK)
167     mv_copy (mv, psppire_missing_val_dialog_get_missing_values (dialog));
168   else
169     mv_copy (mv, var_get_missing_values (var));
170
171   gtk_widget_destroy (GTK_WIDGET (dialog));
172 }
173
174
175 /* A simple (sub) dialog box for displaying user input errors */
176 static void
177 err_dialog (const gchar *msg, GtkWindow *window)
178 {
179   GtkWidget *hbox ;
180   GtkWidget *label = gtk_label_new (msg);
181
182   GtkWidget *dialog =
183     gtk_dialog_new_with_buttons ("PSPP",
184                                  window,
185                                  GTK_DIALOG_MODAL |
186                                  GTK_DIALOG_DESTROY_WITH_PARENT, 
187                                  GTK_STOCK_OK,
188                                  GTK_RESPONSE_ACCEPT,
189                                  NULL);
190
191
192   GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR,
193                                              GTK_ICON_SIZE_DIALOG);
194
195   g_signal_connect_swapped (dialog,
196                             "response",
197                             G_CALLBACK (gtk_widget_destroy),
198                             dialog);
199
200   hbox = gtk_hbox_new (FALSE, 10);
201
202   gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
203                      hbox);
204
205   gtk_box_pack_start (GTK_BOX (hbox), icon, TRUE, FALSE, 10);
206   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 10);
207
208   gtk_widget_show_all (dialog);
209 }
210
211
212 /* Acceptability predicate for PsppireMissingValDialog.
213
214    This function is also the only place that dialog->mvl gets updated. */
215 static gboolean
216 missing_val_dialog_acceptable (gpointer data)
217 {
218   PsppireMissingValDialog *dialog = data;
219
220   if ( gtk_toggle_button_get_active (dialog->button_discrete))
221     {
222       gint nvals = 0;
223       gint badvals = 0;
224       gint i;
225       mv_clear(&dialog->mvl);
226       for(i = 0 ; i < 3 ; ++i )
227         {
228           gchar *text =
229             g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->mv[i])));
230
231           union value v;
232           if ( !text || strlen (g_strstrip (text)) == 0 )
233             {
234               g_free (text);
235               continue;
236             }
237
238           if ( text_to_value__ (text, &dialog->format, dialog->encoding, &v))
239             {
240               nvals++;
241               mv_add_value (&dialog->mvl, &v);
242             }
243           else
244               badvals++;
245           g_free (text);
246           value_destroy (&v, fmt_var_width (&dialog->format));
247         }
248       if ( nvals == 0 || badvals > 0 )
249         {
250           err_dialog (_("Incorrect value for variable type"),
251                       GTK_WINDOW (dialog));
252           return FALSE;
253         }
254     }
255
256   if (gtk_toggle_button_get_active (dialog->button_range))
257     {
258       gchar *discrete_text ;
259
260       union value low_val ;
261       union value high_val;
262       const gchar *low_text = gtk_entry_get_text (GTK_ENTRY (dialog->low));
263       const gchar *high_text = gtk_entry_get_text (GTK_ENTRY (dialog->high));
264       gboolean low_ok;
265       gboolean high_ok;
266       gboolean ok;
267
268       low_ok = text_to_value__ (low_text, &dialog->format, dialog->encoding,
269                                 &low_val) != NULL;
270       high_ok = text_to_value__ (high_text, &dialog->format, dialog->encoding,
271                                  &high_val) != NULL;
272       ok = low_ok && high_ok && low_val.f <= high_val.f;
273       if (!ok)
274         {
275           err_dialog (_("Incorrect range specification"), GTK_WINDOW (dialog));
276           if (low_ok)
277             value_destroy (&low_val, fmt_var_width (&dialog->format));
278           if (high_ok)
279             value_destroy (&high_val, fmt_var_width (&dialog->format));
280           return FALSE;
281         }
282
283       discrete_text =
284         g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->discrete)));
285
286       mv_clear (&dialog->mvl);
287       mv_add_range (&dialog->mvl, low_val.f, high_val.f);
288
289       value_destroy (&low_val, fmt_var_width (&dialog->format));
290       value_destroy (&high_val, fmt_var_width (&dialog->format));
291
292       if ( discrete_text && strlen (g_strstrip (discrete_text)) > 0 )
293         {
294           union value discrete_val;
295           if ( !text_to_value__ (discrete_text,
296                                  &dialog->format,
297                                  dialog->encoding,
298                                  &discrete_val))
299             {
300               err_dialog (_("Incorrect value for variable type"),
301                          GTK_WINDOW (dialog) );
302               g_free (discrete_text);
303               value_destroy (&discrete_val, fmt_var_width (&dialog->format));
304               return FALSE;
305             }
306           mv_add_value (&dialog->mvl, &discrete_val);
307           value_destroy (&discrete_val, fmt_var_width (&dialog->format));
308         }
309       g_free (discrete_text);
310     }
311
312
313   if (gtk_toggle_button_get_active (dialog->button_none))
314     mv_clear (&dialog->mvl);
315
316   return TRUE;
317 }
318
319
320 /* Callback which occurs when the 'discrete' radiobutton is toggled */
321 static void
322 discrete (GtkToggleButton *button, gpointer data)
323 {
324   gint i;
325   PsppireMissingValDialog *dialog = data;
326
327   for (i = 0 ; i < 3 ; ++i )
328     {
329       gtk_widget_set_sensitive (dialog->mv[i],
330                                gtk_toggle_button_get_active (button));
331     }
332 }
333
334 /* Callback which occurs when the 'range' radiobutton is toggled */
335 static void
336 range (GtkToggleButton *button, gpointer data)
337 {
338   PsppireMissingValDialog *dialog = data;
339
340   const gboolean active = gtk_toggle_button_get_active (button);
341
342   gtk_widget_set_sensitive (dialog->low, active);
343   gtk_widget_set_sensitive (dialog->high, active);
344   gtk_widget_set_sensitive (dialog->discrete, active);
345 }
346
347
348
349 /* Shows the dialog box and sets default values */
350 static GObject *
351 psppire_missing_val_dialog_constructor (GType                  type,
352                                         guint                  n_properties,
353                                         GObjectConstructParam *properties)
354 {
355   PsppireMissingValDialog *dialog;
356   GtkContainer *content_area;
357   GtkBuilder *xml;
358   GObject *obj;
359
360   obj = G_OBJECT_CLASS (psppire_missing_val_dialog_parent_class)->constructor (
361     type, n_properties, properties);
362   dialog = PSPPIRE_MISSING_VAL_DIALOG (obj);
363
364   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
365   xml = builder_new ("missing-val-dialog.ui");
366   gtk_container_add (GTK_CONTAINER (content_area),
367                      get_widget_assert (xml, "missing-values-dialog"));
368
369   dialog->mv[0] = get_widget_assert (xml, "mv0");
370   dialog->mv[1] = get_widget_assert (xml, "mv1");
371   dialog->mv[2] = get_widget_assert (xml, "mv2");
372
373   dialog->low = get_widget_assert (xml, "mv-low");
374   dialog->high = get_widget_assert (xml, "mv-high");
375   dialog->discrete = get_widget_assert (xml, "mv-discrete");
376
377
378   dialog->button_none     =
379     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "no_missing"));
380
381   dialog->button_discrete =
382     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "discrete_missing"));
383
384   dialog->button_range    =
385     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "range_missing"));
386
387   psppire_dialog_set_accept_predicate (PSPPIRE_DIALOG (dialog),
388                                        missing_val_dialog_acceptable,
389                                        dialog);
390
391   g_signal_connect (dialog->button_discrete, "toggled",
392                    G_CALLBACK (discrete), dialog);
393
394   g_signal_connect (dialog->button_range, "toggled",
395                    G_CALLBACK (range), dialog);
396
397   g_object_unref (xml);
398
399   return obj;
400 }
401
402 void
403 psppire_missing_val_dialog_set_variable (PsppireMissingValDialog *dialog,
404                                          const struct variable *var)
405 {
406   enum val_type var_type;
407   gint i;
408
409   mv_destroy (&dialog->mvl);
410   g_free (dialog->encoding);
411
412   if (var != NULL)
413     {
414       mv_copy (&dialog->mvl, var_get_missing_values (var));
415       dialog->encoding = g_strdup (var_get_encoding (var));
416       dialog->format = *var_get_print_format (var);
417     }
418   else
419     {
420       mv_init (&dialog->mvl, 0);
421       dialog->encoding = NULL;
422       dialog->format = F_8_0;
423     }
424
425   /* Blank all entry boxes and make them insensitive */
426   gtk_entry_set_text (GTK_ENTRY (dialog->low), "");
427   gtk_entry_set_text (GTK_ENTRY (dialog->high), "");
428   gtk_entry_set_text (GTK_ENTRY (dialog->discrete), "");
429   gtk_widget_set_sensitive (dialog->low, FALSE);
430   gtk_widget_set_sensitive (dialog->high, FALSE);
431   gtk_widget_set_sensitive (dialog->discrete, FALSE);
432
433   var_type = val_type_from_width (fmt_var_width (&dialog->format));
434   gtk_widget_set_sensitive (GTK_WIDGET (dialog->button_range),
435                             var_type == VAL_NUMERIC);
436
437   if (var == NULL)
438     return;
439
440   for (i = 0 ; i < 3 ; ++i )
441     {
442       gtk_entry_set_text (GTK_ENTRY (dialog->mv[i]), "");
443       gtk_widget_set_sensitive (dialog->mv[i], FALSE);
444     }
445
446   if ( mv_has_range (&dialog->mvl))
447     {
448       union value low, high;
449       gchar *low_text;
450       gchar *high_text;
451       mv_get_range (&dialog->mvl, &low.f, &high.f);
452
453
454       low_text = value_to_text__ (low, &dialog->format, dialog->encoding);
455       high_text = value_to_text__ (high, &dialog->format, dialog->encoding);
456
457       gtk_entry_set_text (GTK_ENTRY (dialog->low), low_text);
458       gtk_entry_set_text (GTK_ENTRY (dialog->high), high_text);
459       g_free (low_text);
460       g_free (high_text);
461
462       if ( mv_has_value (&dialog->mvl))
463         {
464           gchar *text;
465           text = value_to_text__ (*mv_get_value (&dialog->mvl, 0),
466                                   &dialog->format, dialog->encoding);
467           gtk_entry_set_text (GTK_ENTRY (dialog->discrete), text);
468           g_free (text);
469         }
470
471       gtk_toggle_button_set_active (dialog->button_range, TRUE);
472       gtk_widget_set_sensitive (dialog->low, TRUE);
473       gtk_widget_set_sensitive (dialog->high, TRUE);
474       gtk_widget_set_sensitive (dialog->discrete, TRUE);
475
476     }
477   else if ( mv_has_value (&dialog->mvl))
478     {
479       const int n = mv_n_values (&dialog->mvl);
480
481       for (i = 0 ; i < 3 ; ++i )
482         {
483           if ( i < n)
484             {
485               gchar *text ;
486
487               text = value_to_text__ (*mv_get_value (&dialog->mvl, i),
488                                       &dialog->format, dialog->encoding);
489               gtk_entry_set_text (GTK_ENTRY (dialog->mv[i]), text);
490               g_free (text);
491             }
492           gtk_widget_set_sensitive (dialog->mv[i], TRUE);
493         }
494       gtk_toggle_button_set_active (dialog->button_discrete, TRUE);
495     }
496   else if ( mv_is_empty (&dialog->mvl))
497     {
498       gtk_toggle_button_set_active (dialog->button_none, TRUE);
499     }
500 }
501
502 const struct missing_values *
503 psppire_missing_val_dialog_get_missing_values (
504   const PsppireMissingValDialog *dialog)
505 {
506   return &dialog->mvl;
507 }