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