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