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