1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011, 2014  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 #include <config.h>
18 
19 #include <float.h>
20 #include <gtk/gtk.h>
21 #include "dialog-common.h"
22 #include "psppire-val-chooser.h"
23 
24 #include "libpspp/str.h"
25 
26 
27 #include "ui/syntax-gen.h"
28 
29 #include <gettext.h>
30 #define _(msgid) gettext (msgid)
31 #define N_(msgid) msgid
32 
33 static void psppire_val_chooser_class_init    (PsppireValChooserClass *class);
34 static void psppire_val_chooser_init          (PsppireValChooser      *vc);
35 
36 static void psppire_val_chooser_realize       (GtkWidget *w);
37 
G_DEFINE_TYPE(PsppireValChooser,psppire_val_chooser,GTK_TYPE_FRAME)38 G_DEFINE_TYPE (PsppireValChooser, psppire_val_chooser, GTK_TYPE_FRAME)
39 
40 static void
41 psppire_val_chooser_finalize (GObject *object)
42 {
43 
44 }
45 
46 /* Properties */
47 enum
48 {
49   PROP_0,
50   PROP_IS_STRING,
51   PROP_SHOW_ELSE
52 };
53 
54 
55 enum
56   {
57     VC_VALUE,
58     VC_SYSMIS,
59     VC_MISSING,
60     VC_RANGE,
61     VC_LOW_UP,
62     VC_HIGH_DOWN,
63     VC_ELSE
64   };
65 
66 static void
psppire_val_chooser_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)67 psppire_val_chooser_set_property (GObject         *object,
68 			       guint            prop_id,
69 			       const GValue    *value,
70 			       GParamSpec      *pspec)
71 {
72   PsppireValChooser *vr = PSPPIRE_VAL_CHOOSER (object);
73 
74   switch (prop_id)
75     {
76     case PROP_SHOW_ELSE:
77       {
78 	gboolean x = g_value_get_boolean (value);
79 	gtk_widget_set_visible (GTK_WIDGET (vr->rw[VC_ELSE].rb), x);
80 	gtk_widget_set_visible (GTK_WIDGET (vr->rw[VC_ELSE].label), x);
81       }
82       break;
83     case PROP_IS_STRING:
84       vr->input_var_is_string = g_value_get_boolean (value);
85       gtk_widget_set_sensitive (GTK_WIDGET (vr->rw[VC_SYSMIS].rb), !vr->input_var_is_string);
86       gtk_widget_set_sensitive (GTK_WIDGET (vr->rw[VC_MISSING].rb), !vr->input_var_is_string);
87       gtk_widget_set_sensitive (GTK_WIDGET (vr->rw[VC_RANGE].rb), !vr->input_var_is_string);
88       gtk_widget_set_sensitive (GTK_WIDGET (vr->rw[VC_LOW_UP].rb), !vr->input_var_is_string);
89       gtk_widget_set_sensitive (GTK_WIDGET (vr->rw[VC_HIGH_DOWN].rb), !vr->input_var_is_string);
90       break;
91     default:
92       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
93       break;
94     };
95 }
96 
97 
98 static void
psppire_val_chooser_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)99 psppire_val_chooser_get_property (GObject         *object,
100 			       guint            prop_id,
101 			       GValue          *value,
102 			       GParamSpec      *pspec)
103 {
104   PsppireValChooser *vr = PSPPIRE_VAL_CHOOSER (object);
105 
106   switch (prop_id)
107     {
108     case PROP_SHOW_ELSE:
109       {
110 	gboolean x =
111 	  gtk_widget_get_visible (GTK_WIDGET (vr->rw[VC_ELSE].rb));
112 	g_value_set_boolean (value, x);
113       }
114       break;
115     case PROP_IS_STRING:
116       g_value_set_boolean (value, vr->input_var_is_string);
117       break;
118     default:
119       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
120       break;
121     };
122 }
123 
124 
125 static GObjectClass * parent_class = NULL;
126 
127 static void
psppire_val_chooser_class_init(PsppireValChooserClass * class)128 psppire_val_chooser_class_init (PsppireValChooserClass *class)
129 {
130   GObjectClass *object_class = G_OBJECT_CLASS (class);
131 
132   object_class->finalize = psppire_val_chooser_finalize;
133   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
134 
135   GParamSpec *is_string_spec =
136     g_param_spec_boolean ("is-string",
137 			  "String Value",
138 			  "Should the value range be a string value",
139 			  FALSE,
140 			  G_PARAM_READWRITE);
141 
142   GParamSpec *show_else_spec =
143     g_param_spec_boolean ("show-else",
144 			  "Show Else",
145 			  "Should the \"All other values\" item be visible",
146 			  TRUE,
147 			  G_PARAM_READWRITE);
148 
149 
150   parent_class = g_type_class_peek_parent (class);
151 
152   object_class->set_property = psppire_val_chooser_set_property;
153   object_class->get_property = psppire_val_chooser_get_property;
154 
155   widget_class->realize = psppire_val_chooser_realize;
156 
157   g_object_class_install_property (object_class,
158                                    PROP_IS_STRING,
159                                    is_string_spec);
160 
161   g_object_class_install_property (object_class,
162                                    PROP_SHOW_ELSE,
163                                    show_else_spec);
164 }
165 
166 
167 /* Set the focus of B to follow the sensitivity of A */
168 static void
focus_follows_sensitivity(GtkWidget * a,GParamSpec * pspec,GtkWidget * b)169 focus_follows_sensitivity (GtkWidget *a, GParamSpec *pspec, GtkWidget *b)
170 {
171   gboolean sens = gtk_widget_get_sensitive (a);
172 
173   g_object_set (b, "has-focus", sens, NULL);
174 }
175 
176 
177 struct layout;
178 typedef GtkWidget *filler_f (struct layout *, struct range_widgets *);
179 typedef void set_f (PsppireValChooser *, struct old_value *, const struct range_widgets *);
180 
181 struct layout
182 {
183   const gchar *label;
184   filler_f *fill;
185   set_f *set;
186 };
187 
188 
189 
simple_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * rw)190 static void simple_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets *rw)
191 {
192   const gchar *text = gtk_entry_get_text (rw->e1);
193 
194   if (vr->input_var_is_string)
195     {
196       ov->type = OV_STRING;
197       ov->v.s = g_strdup (text);
198     }
199   else
200     {
201       ov->type = OV_NUMERIC;
202       ov->v.v = g_strtod (text, 0);
203     }
204 }
205 
lo_up_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * rw)206 static void lo_up_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets  *rw)
207 {
208   const gchar *text = gtk_entry_get_text (rw->e1);
209 
210   ov->type = OV_LOW_UP;
211   ov->v.range[1] = g_strtod (text, 0);
212 }
213 
214 
hi_down_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * rw)215 static void hi_down_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets *rw)
216 {
217   const gchar *text = gtk_entry_get_text (rw->e1);
218 
219   ov->type = OV_HIGH_DOWN;
220   ov->v.range[0] = g_strtod (text, 0);
221 }
222 
missing_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * l)223 static void missing_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets *l)
224 {
225   ov->type = OV_MISSING;
226 }
227 
228 
sysmis_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * l)229 static void sysmis_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets *l)
230 {
231   ov->type = OV_SYSMIS;
232 }
233 
else_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * l)234 static void else_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets *l)
235 {
236   ov->type = OV_ELSE;
237 }
238 
239 
range_set(PsppireValChooser * vr,struct old_value * ov,const struct range_widgets * rw)240 static void range_set (PsppireValChooser *vr, struct old_value *ov, const struct range_widgets *rw)
241 {
242   const gchar *text = gtk_entry_get_text (rw->e1);
243 
244   ov->type = OV_RANGE;
245   ov->v.range[0] = g_strtod (text, 0);
246 
247   text = gtk_entry_get_text (rw->e2);
248   ov->v.range[1] = g_strtod (text, 0);
249 }
250 
range_entry(struct layout * l,struct range_widgets * rw)251 static GtkWidget * range_entry (struct layout *l, struct range_widgets *rw)
252 {
253   GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
254   GtkWidget *entrylo = gtk_entry_new ();
255   GtkWidget *label = gtk_label_new (_("through"));
256   GtkWidget *entryhi = gtk_entry_new ();
257 
258   rw->e1 = GTK_ENTRY (entrylo);
259   rw->e2 = GTK_ENTRY (entryhi);
260 
261   g_object_set (G_OBJECT (label),
262 		"valign", GTK_ALIGN_CENTER,
263 		"halign", GTK_ALIGN_START,
264 		NULL);
265 
266 
267   g_signal_connect (vbox, "notify::sensitive", G_CALLBACK (focus_follows_sensitivity), entrylo);
268 
269   gtk_box_pack_start (GTK_BOX (vbox), entrylo, TRUE, TRUE, 0);
270   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
271   gtk_box_pack_start (GTK_BOX (vbox), entryhi, TRUE, TRUE, 0);
272   return vbox;
273 }
274 
simple_entry(struct layout * l,struct range_widgets * rw)275 static GtkWidget * simple_entry (struct layout *l, struct range_widgets *rw)
276 {
277   GtkWidget *entry = gtk_entry_new ();
278 
279   rw->e1 = GTK_ENTRY (entry);
280 
281   g_signal_connect (entry, "notify::sensitive", G_CALLBACK (focus_follows_sensitivity), entry);
282   return entry;
283 }
284 
285 
286 static struct layout range_opt[n_VAL_CHOOSER_BUTTONS]=
287   {
288     {N_("_Value:"),                    simple_entry, simple_set },
289     {N_("_System Missing"),            NULL,         sysmis_set },
290     {N_("System _or User Missing"),    NULL,         missing_set},
291     {N_("_Range:"),                    range_entry,  range_set  },
292     {N_("Range, _LOWEST thru value"),  simple_entry, lo_up_set  },
293     {N_("Range, value thru _HIGHEST"), simple_entry, hi_down_set},
294     {N_("_All other values"),          NULL,         else_set   }
295   };
296 
297 static void
psppire_val_chooser_init(PsppireValChooser * vr)298 psppire_val_chooser_init (PsppireValChooser *vr)
299 {
300   gint i;
301   GtkWidget *grid = gtk_grid_new ();
302   GSList *group = NULL;
303   gint row = 0;
304 
305   g_object_set (G_OBJECT (grid),
306 		"margin-start", 5,
307 		"margin-end", 5,
308 		NULL);
309 
310   vr->input_var_is_string = FALSE;
311 
312   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
313     {
314       struct layout *l = &range_opt[i];
315       vr->rw[i].label = GTK_LABEL (gtk_label_new (gettext (l->label)));
316       gtk_label_set_use_underline (vr->rw[i].label, TRUE);
317       vr->rw[i].rb = GTK_TOGGLE_BUTTON (gtk_radio_button_new (group));
318       gtk_label_set_mnemonic_widget (vr->rw[i].label, GTK_WIDGET (vr->rw[i].rb));
319 
320       g_object_set (G_OBJECT (vr->rw[i].label),
321 		    "valign", GTK_ALIGN_CENTER,
322 		    "halign", GTK_ALIGN_START,
323 		    NULL);
324 
325       group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (vr->rw[i].rb));
326 
327       /* Attach the buttons */
328       gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (vr->rw[i].rb),
329 		       0, row, 1, 1);
330 
331       gtk_widget_set_hexpand (GTK_WIDGET (vr->rw[i].rb), FALSE);
332 
333       /* Attach the labels */
334       gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (vr->rw[i].label),
335 			1, row, 1, 1);
336 
337       gtk_widget_set_hexpand (GTK_WIDGET (vr->rw[i].label), TRUE);
338 
339 
340       ++row;
341 
342       if (l->fill)
343 	{
344 	  GtkWidget *fill = l->fill (l, &vr->rw[i]);
345 
346 	  gtk_widget_set_sensitive (fill, FALSE);
347 
348 	  gtk_grid_attach (GTK_GRID (grid), fill, 1, row, 1, 1);
349 
350 	  gtk_widget_set_hexpand (fill, TRUE);
351 
352 	  ++row;
353 
354       	  g_signal_connect (vr->rw[i].rb, "toggled", G_CALLBACK (set_sensitivity_from_toggle), fill);
355 	}
356     }
357 
358   gtk_frame_set_shadow_type (GTK_FRAME (vr), GTK_SHADOW_ETCHED_IN);
359 
360   gtk_container_add (GTK_CONTAINER (vr), grid);
361 
362   gtk_widget_show_all (grid);
363 }
364 
365 static void
psppire_val_chooser_realize(GtkWidget * w)366 psppire_val_chooser_realize (GtkWidget *w)
367 {
368   PsppireValChooser *vr = PSPPIRE_VAL_CHOOSER (w);
369 
370   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(vr->rw[0].rb), TRUE);
371   gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (vr->rw[0].rb));
372 
373   /* Chain up to the parent class */
374   GTK_WIDGET_CLASS (parent_class)->realize (w);
375 }
376 
377 
378 
379 
380 /* A boxed type representing a value, or a range of values which may
381    potentially be replaced by something */
382 
383 
384 static struct old_value *
old_value_copy(struct old_value * ov)385 old_value_copy (struct old_value *ov)
386 {
387   struct old_value *copy = g_memdup (ov, sizeof (*copy));
388 
389   if (ov->type == OV_STRING)
390     copy->v.s = g_strdup (ov->v.s);
391 
392   return copy;
393 }
394 
395 
396 static void
old_value_free(struct old_value * ov)397 old_value_free (struct old_value *ov)
398 {
399   if (ov->type == OV_STRING)
400     g_free (ov->v.s);
401   g_free (ov);
402 }
403 
404 static void
old_value_to_string(const GValue * src,GValue * dest)405 old_value_to_string (const GValue *src, GValue *dest)
406 {
407   const struct old_value *ov = g_value_get_boxed (src);
408 
409   switch (ov->type)
410     {
411     case OV_NUMERIC:
412       {
413 	gchar *text = g_strdup_printf ("%.*g", DBL_DIG + 1, ov->v.v);
414 	g_value_set_string (dest, text);
415 	g_free (text);
416       }
417       break;
418     case OV_STRING:
419       g_value_set_string (dest, ov->v.s);
420       break;
421     case OV_MISSING:
422       g_value_set_string (dest, "MISSING");
423       break;
424     case OV_SYSMIS:
425       g_value_set_string (dest, "SYSMIS");
426       break;
427     case OV_ELSE:
428       g_value_set_string (dest, "ELSE");
429       break;
430     case OV_RANGE:
431       {
432 	gchar *text;
433 	char en_dash[6] = {0,0,0,0,0,0};
434 
435 	g_unichar_to_utf8 (0x2013, en_dash);
436 
437 	text = g_strdup_printf ("%.*g %s %.*g",
438                                 DBL_DIG + 1, ov->v.range[0],
439                                 en_dash,
440                                 DBL_DIG + 1, ov->v.range[1]);
441 	g_value_set_string (dest, text);
442 	g_free (text);
443       }
444       break;
445     case OV_LOW_UP:
446       {
447 	gchar *text;
448 	char en_dash[6] = {0,0,0,0,0,0};
449 
450 	g_unichar_to_utf8 (0x2013, en_dash);
451 
452 	text = g_strdup_printf ("LOWEST %s %.*g",
453 				en_dash,
454 				DBL_DIG + 1, ov->v.range[1]);
455 
456 	g_value_set_string (dest, text);
457 	g_free (text);
458       }
459       break;
460     case OV_HIGH_DOWN:
461       {
462 	gchar *text;
463 	char en_dash[6] = {0,0,0,0,0,0};
464 
465 	g_unichar_to_utf8 (0x2013, en_dash);
466 
467 	text = g_strdup_printf ("%.*g %s HIGHEST",
468 				DBL_DIG + 1, ov->v.range[0],
469 				en_dash);
470 
471 	g_value_set_string (dest, text);
472 	g_free (text);
473       }
474       break;
475     default:
476       g_warning ("Invalid type in old recode value");
477       g_value_set_string (dest, "???");
478       break;
479     };
480 }
481 
482 GType
old_value_get_type(void)483 old_value_get_type (void)
484 {
485   static GType t = 0;
486 
487   if (t == 0)
488     {
489       t = g_boxed_type_register_static  ("psppire-recode-old-values",
490 					 (GBoxedCopyFunc) old_value_copy,
491 					 (GBoxedFreeFunc) old_value_free);
492 
493       g_value_register_transform_func     (t, G_TYPE_STRING,
494 					   old_value_to_string);
495     }
496 
497   return t;
498 }
499 
500 
501 
502 /* Generate a syntax fragment for NV and append it to STR */
503 void
old_value_append_syntax(struct string * str,const struct old_value * ov)504 old_value_append_syntax (struct string *str, const struct old_value *ov)
505 {
506   switch (ov->type)
507     {
508     case OV_NUMERIC:
509       ds_put_c_format (str, "%.*g", DBL_DIG + 1, ov->v.v);
510       break;
511     case OV_STRING:
512       {
513 	struct string ds = DS_EMPTY_INITIALIZER;
514 	syntax_gen_string (&ds, ss_cstr (ov->v.s));
515 	ds_put_cstr (str, ds_cstr (&ds));
516 	ds_destroy (&ds);
517       }
518       break;
519     case OV_MISSING:
520       ds_put_cstr (str, "MISSING");
521       break;
522     case OV_SYSMIS:
523       ds_put_cstr (str, "SYSMIS");
524       break;
525     case OV_ELSE:
526       ds_put_cstr (str, "ELSE");
527       break;
528     case OV_RANGE:
529       ds_put_c_format (str, "%.*g THRU %.*g",
530                        DBL_DIG + 1, ov->v.range[0],
531                        DBL_DIG + 1, ov->v.range[1]);
532       break;
533     case OV_LOW_UP:
534       ds_put_c_format (str, "LOWEST THRU %.*g",
535                        DBL_DIG + 1, ov->v.range[1]);
536       break;
537     case OV_HIGH_DOWN:
538       ds_put_c_format (str, "%.*g THRU HIGHEST",
539                        DBL_DIG + 1, ov->v.range[0]);
540       break;
541     default:
542       g_warning ("Invalid type in old recode value");
543       ds_put_cstr (str, "???");
544       break;
545     };
546 }
547 
548 
549 
550 /* Set OV according to the current state of VR */
551 void
psppire_val_chooser_get_status(PsppireValChooser * vr,struct old_value * ov)552 psppire_val_chooser_get_status (PsppireValChooser *vr, struct old_value *ov)
553 {
554   int i;
555 
556   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
557     {
558       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (vr->rw[i].rb)))
559 	{
560 	  range_opt[i].set (vr, ov, &vr->rw[i]);
561 	  break;
562 	}
563     }
564 }
565 
566 /* This might need to be changed to something less naive.
567    In particular, what happends with dates, etc?
568  */
569 static gchar *
num_to_string(gdouble x)570 num_to_string (gdouble x)
571 {
572   return g_strdup_printf ("%.*g", DBL_DIG + 1, x);
573 }
574 
575 
576 /* Set VR according to the value of OV */
577 void
psppire_val_chooser_set_status(PsppireValChooser * vr,const struct old_value * ov)578 psppire_val_chooser_set_status (PsppireValChooser *vr, const struct old_value *ov)
579 {
580   gint i;
581   if (!ov)
582     return;
583 
584   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
585     {
586       if (vr->rw[i].e1)
587 	gtk_entry_set_text (vr->rw[i].e1, "");
588 
589       if (vr->rw[i].e2)
590 	gtk_entry_set_text (vr->rw[i].e2, "");
591     }
592 
593   switch (ov->type)
594     {
595     case OV_STRING:
596       gtk_toggle_button_set_active (vr->rw[0].rb, TRUE);
597       gtk_entry_set_text (vr->rw[0].e1, ov->v.s);
598       break;
599 
600     case OV_NUMERIC:
601       {
602 	gchar *str;
603 	gtk_toggle_button_set_active (vr->rw[0].rb, TRUE);
604 
605 	str = num_to_string (ov->v.v);
606 
607 	gtk_entry_set_text (vr->rw[0].e1, str);
608 	g_free (str);
609       }
610       break;
611 
612       case OV_SYSMIS:
613 	gtk_toggle_button_set_active (vr->rw[VC_SYSMIS].rb, TRUE);
614 	break;
615 
616       case OV_MISSING:
617 	gtk_toggle_button_set_active (vr->rw[VC_MISSING].rb, TRUE);
618 	break;
619 
620       case OV_RANGE:
621 	{
622 	  gchar *str = num_to_string (ov->v.range[0]);
623 	  gtk_toggle_button_set_active (vr->rw[VC_RANGE].rb, TRUE);
624 	  gtk_entry_set_text (vr->rw[VC_RANGE].e1, str);
625 
626 	  g_free (str);
627 
628 	  str = num_to_string (ov->v.range[1]);
629 	  gtk_entry_set_text (vr->rw[VC_RANGE].e2, str);
630 	  g_free (str);
631 	}
632 	break;
633 
634       case OV_LOW_UP:
635 	{
636 	  gchar *str = num_to_string (ov->v.range[1]);
637 
638 	  gtk_toggle_button_set_active (vr->rw[VC_LOW_UP].rb, TRUE);
639 
640 	  gtk_entry_set_text (vr->rw[VC_LOW_UP].e1, str);
641 
642 	  g_free (str);
643 	}
644 	break;
645 
646 
647       case OV_HIGH_DOWN:
648 	{
649 	  gchar *str = num_to_string (ov->v.range[0]);
650 
651 	  gtk_toggle_button_set_active (vr->rw[VC_HIGH_DOWN].rb, TRUE);
652 
653 	  gtk_entry_set_text (vr->rw[VC_HIGH_DOWN].e1, str);
654 
655 	  g_free (str);
656 	}
657 	break;
658 
659       case OV_ELSE:
660 	gtk_toggle_button_set_active (vr->rw[VC_ELSE].rb, TRUE);
661 	break;
662 
663     default:
664       g_warning ("Unknown old value type");
665       break;
666     };
667 }
668