Added Mnemonics to the value chooser widget and to the recode dialog.
[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   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       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, vr->rw[i].rb);
365
366       gtk_misc_set_alignment (GTK_MISC (vr->rw[i].label), 0, 0.5);
367
368       group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (vr->rw[i].rb));
369
370       /* Attach the buttons */
371       gtk_table_attach (GTK_TABLE (table), GTK_WIDGET (vr->rw[i].rb),
372                         0, 1,   row, row + 1,
373                         0, GTK_EXPAND | GTK_FILL,
374                         0, 0);
375
376       /* Attach the labels */
377       gtk_table_attach (GTK_TABLE (table), GTK_WIDGET (vr->rw[i].label),
378                         1, 2,   row, row + 1,
379                         GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
380                         0, 0);
381       ++row;
382
383       if (l->fill)
384         {
385           GtkWidget *fill = l->fill (l, &vr->rw[i]);
386
387           gtk_widget_set_sensitive (fill, FALSE);
388
389           gtk_table_attach_defaults (GTK_TABLE (table), fill, 1, 2,
390                                  row, row + 1);
391           ++row;
392
393           g_signal_connect (vr->rw[i].rb, "toggled", G_CALLBACK (set_sensitivity_from_toggle), fill);
394         }
395     }
396
397   gtk_frame_set_shadow_type (GTK_FRAME (vr), GTK_SHADOW_ETCHED_IN);
398
399   gtk_container_add (GTK_CONTAINER (aln), table);
400   gtk_container_add (GTK_CONTAINER (vr), aln);
401
402   gtk_widget_show_all (aln);
403 }
404
405
406 GtkWidget*
407 psppire_val_chooser_new (void)
408 {
409   return GTK_WIDGET (g_object_new (psppire_val_chooser_get_type (), NULL));
410 }
411
412
413
414 static void
415 psppire_val_chooser_realize (GtkWidget *w)
416 {
417   PsppireValChooser *vr = PSPPIRE_VAL_CHOOSER (w);
418
419   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(vr->rw[0].rb), TRUE);
420   gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (vr->rw[0].rb));
421
422   /* Chain up to the parent class */
423   GTK_WIDGET_CLASS (parent_class)->realize (w);
424 }
425
426
427 \f
428
429 /* A boxed type representing a value, or a range of values which may
430    potentially be replaced by something */
431
432
433 static struct old_value *
434 old_value_copy (struct old_value *ov)
435 {
436   struct old_value *copy = g_memdup (ov, sizeof (*copy));
437
438   if ( ov->type == OV_STRING )
439     copy->v.s = g_strdup (ov->v.s);
440
441   return copy;
442 }
443
444
445 static void
446 old_value_free (struct old_value *ov)
447 {
448   if (ov->type == OV_STRING)
449     g_free (ov->v.s);
450   g_free (ov);
451 }
452
453 static void
454 old_value_to_string (const GValue *src, GValue *dest)
455 {
456   const struct old_value *ov = g_value_get_boxed (src);
457
458   switch (ov->type)
459     {
460     case OV_NUMERIC:
461       {
462         gchar *text = g_strdup_printf ("%g", ov->v.v);
463         g_value_set_string (dest, text);
464         g_free (text);
465       }
466       break;
467     case OV_STRING:
468       g_value_set_string (dest, ov->v.s);
469       break;
470     case OV_MISSING:
471       g_value_set_string (dest, "MISSING");
472       break;
473     case OV_SYSMIS:
474       g_value_set_string (dest, "SYSMIS");
475       break;
476     case OV_ELSE:
477       g_value_set_string (dest, "ELSE");
478       break;
479     case OV_RANGE:
480       {
481         gchar *text;
482         char en_dash[6] = {0,0,0,0,0,0};
483
484         g_unichar_to_utf8 (0x2013, en_dash);
485
486         text = g_strdup_printf ("%g %s %g",
487                                        ov->v.range[0],
488                                        en_dash,
489                                        ov->v.range[1]);
490         g_value_set_string (dest, text);
491         g_free (text);
492       }
493       break;
494     case OV_LOW_UP:
495       {
496         gchar *text;
497         char en_dash[6] = {0,0,0,0,0,0};
498
499         g_unichar_to_utf8 (0x2013, en_dash);
500
501         text = g_strdup_printf ("LOWEST %s %g",
502                                 en_dash,
503                                 ov->v.range[1]);
504
505         g_value_set_string (dest, text);
506         g_free (text);
507       }
508       break;
509     case OV_HIGH_DOWN:
510       {
511         gchar *text;
512         char en_dash[6] = {0,0,0,0,0,0};
513
514         g_unichar_to_utf8 (0x2013, en_dash);
515
516         text = g_strdup_printf ("%g %s HIGHEST",
517                                 ov->v.range[0],
518                                 en_dash);
519
520         g_value_set_string (dest, text);
521         g_free (text);
522       }
523       break;
524     default:
525       g_warning ("Invalid type in old recode value");
526       g_value_set_string (dest, "???");
527       break;
528     };
529 }
530
531 GType
532 old_value_get_type (void)
533 {
534   static GType t = 0;
535
536   if (t == 0 )
537     {
538       t = g_boxed_type_register_static  ("psppire-recode-old-values",
539                                          (GBoxedCopyFunc) old_value_copy,
540                                          (GBoxedFreeFunc) old_value_free);
541
542       g_value_register_transform_func     (t, G_TYPE_STRING,
543                                            old_value_to_string);
544     }
545
546   return t;
547 }
548
549 \f
550
551 /* Generate a syntax fragment for NV and append it to STR */
552 void
553 old_value_append_syntax (GString *str, const struct old_value *ov)
554 {
555   switch (ov->type)
556     {
557     case OV_NUMERIC:
558       g_string_append_printf (str, "%g", ov->v.v);
559       break;
560     case OV_STRING:
561       {
562         struct string ds = DS_EMPTY_INITIALIZER;
563         syntax_gen_string (&ds, ss_cstr (ov->v.s));
564         g_string_append (str, ds_cstr (&ds));
565         ds_destroy (&ds);
566       }
567       break;
568     case OV_MISSING:
569       g_string_append (str, "MISSING");
570       break;
571     case OV_SYSMIS:
572       g_string_append (str, "SYSMIS");
573       break;
574     case OV_ELSE:
575       g_string_append (str, "ELSE");
576       break;
577     case OV_RANGE:
578       g_string_append_printf (str, "%g THRU %g",
579                               ov->v.range[0],
580                               ov->v.range[1]);
581       break;
582     case OV_LOW_UP:
583       g_string_append_printf (str, "LOWEST THRU %g",
584                               ov->v.range[1]);
585       break;
586     case OV_HIGH_DOWN:
587       g_string_append_printf (str, "%g THRU HIGHEST",
588                               ov->v.range[0]);
589       break;
590     default:
591       g_warning ("Invalid type in old recode value");
592       g_string_append (str, "???");
593       break;
594     };
595 }
596
597
598
599 /* Set OV according to the current state of VR */
600 void
601 psppire_val_chooser_get_status (PsppireValChooser *vr, struct old_value *ov)
602 {
603   int i;
604
605   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
606     {
607       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (vr->rw[i].rb)))
608         {
609           break;
610         }
611     }
612
613   range_opt[i].set (vr, ov, &vr->rw[i]);
614 }
615
616 /* This might need to be changed to something less naive.
617    In particular, what happends with dates, etc?
618  */
619 static gchar *
620 num_to_string (gdouble x)
621 {
622   return g_strdup_printf ("%g", x);
623 }
624
625
626 /* Set VR according to the value of OV */
627 void
628 psppire_val_chooser_set_status (PsppireValChooser *vr, const struct old_value *ov)
629 {
630   gint i;
631   if ( !ov )
632     return;
633
634   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
635     {
636       if (vr->rw[i].e1)
637         gtk_entry_set_text (vr->rw[i].e1, "");
638
639       if (vr->rw[i].e2)
640         gtk_entry_set_text (vr->rw[i].e2, "");
641     }
642
643   switch (ov->type)
644     {
645     case OV_STRING:
646       gtk_toggle_button_set_active (vr->rw[0].rb, TRUE);
647       gtk_entry_set_text (vr->rw[0].e1, ov->v.s);
648       break;
649       
650     case OV_NUMERIC:
651       {
652         gchar *str;
653         gtk_toggle_button_set_active (vr->rw[0].rb, TRUE);
654         
655         str = num_to_string (ov->v.v);
656         
657         gtk_entry_set_text (vr->rw[0].e1, str);
658         g_free (str);
659       }
660       break;
661
662       case OV_SYSMIS:
663         gtk_toggle_button_set_active (vr->rw[VC_SYSMIS].rb, TRUE);
664         break;
665
666       case OV_MISSING:
667         gtk_toggle_button_set_active (vr->rw[VC_MISSING].rb, TRUE);
668         break;
669
670       case OV_RANGE:
671         {
672           gchar *str = num_to_string (ov->v.range[0]);
673           gtk_toggle_button_set_active (vr->rw[VC_RANGE].rb, TRUE);
674           gtk_entry_set_text (vr->rw[VC_RANGE].e1, str);
675
676           g_free (str);
677
678           str = num_to_string (ov->v.range[1]);
679           gtk_entry_set_text (vr->rw[VC_RANGE].e2, str);
680           g_free (str);
681         }
682         break;
683
684       case OV_LOW_UP:
685         {
686           gchar *str = num_to_string (ov->v.range[1]);
687
688           gtk_toggle_button_set_active (vr->rw[VC_LOW_UP].rb, TRUE);
689
690           gtk_entry_set_text (vr->rw[VC_LOW_UP].e1, str);
691
692           g_free (str);
693         }
694         break;
695
696
697       case OV_HIGH_DOWN:
698         {
699           gchar *str = num_to_string (ov->v.range[0]);
700
701           gtk_toggle_button_set_active (vr->rw[VC_HIGH_DOWN].rb, TRUE);
702
703           gtk_entry_set_text (vr->rw[VC_HIGH_DOWN].e1, str);
704
705           g_free (str);
706         }
707         break;
708
709       case OV_ELSE:
710         gtk_toggle_button_set_active (vr->rw[VC_ELSE].rb, TRUE);
711         break;
712
713     default:
714       g_warning ("Unknown old value type");
715       break;
716     };
717 }