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