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