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