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