Warnings: missing initializer for value_tables and function type cast (GObject)
[pspp] / src / ui / gui / psppire-val-chooser.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011, 2014  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 <float.h>
20 #include <gtk/gtk.h>
21 #include "dialog-common.h"
22 #include "psppire-val-chooser.h"
23
24 #include "libpspp/str.h"
25
26
27 #include "ui/syntax-gen.h"
28
29 #include <gettext.h>
30 #define _(msgid) gettext (msgid)
31 #define N_(msgid) msgid
32
33 static void psppire_val_chooser_base_finalize (PsppireValChooserClass *, gpointer);
34 static void psppire_val_chooser_base_init     (PsppireValChooserClass *class);
35 static void psppire_val_chooser_class_init    (PsppireValChooserClass *class);
36 static void psppire_val_chooser_init          (PsppireValChooser      *vc);
37
38 static void psppire_val_chooser_realize       (GtkWidget *w);
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) (void (*)(void)) psppire_val_chooser_base_init,
51         (GBaseFinalizeFunc)  (void (*)(void)) psppire_val_chooser_base_finalize,
52         (GClassInitFunc)  (void (*)(void)) psppire_val_chooser_class_init,
53         (GClassFinalizeFunc) NULL,
54         NULL,
55         sizeof (PsppireValChooser),
56         0,
57         (GInstanceInitFunc) (void (*)(void)) psppire_val_chooser_init,
58         NULL /* value_table */
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_box_new (GTK_ORIENTATION_VERTICAL, 3);
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   g_object_set (G_OBJECT (label),
307                 "valign", GTK_ALIGN_CENTER,
308                 "halign", GTK_ALIGN_START,
309                 NULL);
310
311
312   g_signal_connect (vbox, "notify::sensitive", G_CALLBACK (focus_follows_sensitivity), entrylo);
313
314   gtk_box_pack_start (GTK_BOX (vbox), entrylo, TRUE, TRUE, 0);
315   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
316   gtk_box_pack_start (GTK_BOX (vbox), entryhi, TRUE, TRUE, 0);
317   return vbox;
318 }
319
320 static GtkWidget * simple_entry (struct layout *l, struct range_widgets *rw)
321 {
322   GtkWidget *entry = gtk_entry_new ();
323
324   rw->e1 = GTK_ENTRY (entry);
325
326   g_signal_connect (entry, "notify::sensitive", G_CALLBACK (focus_follows_sensitivity), entry);
327   return entry;
328 }
329
330
331 static struct layout range_opt[n_VAL_CHOOSER_BUTTONS]=
332   {
333     {N_("_Value:"),                    simple_entry, simple_set },
334     {N_("_System Missing"),            NULL,         sysmis_set },
335     {N_("System _or User Missing"),    NULL,         missing_set},
336     {N_("_Range:"),                    range_entry,  range_set  },
337     {N_("Range, _LOWEST thru value"),  simple_entry, lo_up_set  },
338     {N_("Range, value thru _HIGHEST"), simple_entry, hi_down_set},
339     {N_("_All other values"),          NULL,         else_set   }
340   };
341
342 static void
343 psppire_val_chooser_init (PsppireValChooser *vr)
344 {
345   gint i;
346   GtkWidget *grid = gtk_grid_new ();
347   GSList *group = NULL;
348   gint row = 0;
349
350   g_object_set (G_OBJECT (grid),
351                 "margin-start", 5,
352                 "margin-end", 5,
353                 NULL);
354
355   vr->input_var_is_string = FALSE;
356
357   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
358     {
359       struct layout *l = &range_opt[i];
360       vr->rw[i].label = GTK_LABEL (gtk_label_new (gettext (l->label)));
361       gtk_label_set_use_underline (vr->rw[i].label, TRUE);
362       vr->rw[i].rb = GTK_TOGGLE_BUTTON (gtk_radio_button_new (group));
363       gtk_label_set_mnemonic_widget (vr->rw[i].label, GTK_WIDGET (vr->rw[i].rb));
364
365       g_object_set (G_OBJECT (vr->rw[i].label),
366                     "valign", GTK_ALIGN_CENTER,
367                     "halign", GTK_ALIGN_START,
368                     NULL);
369
370       group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (vr->rw[i].rb));
371
372       /* Attach the buttons */
373       gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (vr->rw[i].rb),
374                        0, row, 1, 1);
375
376       gtk_widget_set_hexpand (GTK_WIDGET (vr->rw[i].rb), FALSE);
377
378       /* Attach the labels */
379       gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (vr->rw[i].label),
380                         1, row, 1, 1);
381
382       gtk_widget_set_hexpand (GTK_WIDGET (vr->rw[i].label), TRUE);
383
384
385       ++row;
386
387       if (l->fill)
388         {
389           GtkWidget *fill = l->fill (l, &vr->rw[i]);
390
391           gtk_widget_set_sensitive (fill, FALSE);
392
393           gtk_grid_attach (GTK_GRID (grid), fill, 1, row, 1, 1);
394
395           gtk_widget_set_hexpand (fill, TRUE);
396
397           ++row;
398
399           g_signal_connect (vr->rw[i].rb, "toggled", G_CALLBACK (set_sensitivity_from_toggle), fill);
400         }
401     }
402
403   gtk_frame_set_shadow_type (GTK_FRAME (vr), GTK_SHADOW_ETCHED_IN);
404
405   gtk_container_add (GTK_CONTAINER (vr), grid);
406
407   gtk_widget_show_all (grid);
408 }
409
410
411 GtkWidget*
412 psppire_val_chooser_new (void)
413 {
414   return GTK_WIDGET (g_object_new (psppire_val_chooser_get_type (), NULL));
415 }
416
417
418
419 static void
420 psppire_val_chooser_realize (GtkWidget *w)
421 {
422   PsppireValChooser *vr = PSPPIRE_VAL_CHOOSER (w);
423
424   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(vr->rw[0].rb), TRUE);
425   gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (vr->rw[0].rb));
426
427   /* Chain up to the parent class */
428   GTK_WIDGET_CLASS (parent_class)->realize (w);
429 }
430
431
432 \f
433
434 /* A boxed type representing a value, or a range of values which may
435    potentially be replaced by something */
436
437
438 static struct old_value *
439 old_value_copy (struct old_value *ov)
440 {
441   struct old_value *copy = g_memdup (ov, sizeof (*copy));
442
443   if (ov->type == OV_STRING)
444     copy->v.s = g_strdup (ov->v.s);
445
446   return copy;
447 }
448
449
450 static void
451 old_value_free (struct old_value *ov)
452 {
453   if (ov->type == OV_STRING)
454     g_free (ov->v.s);
455   g_free (ov);
456 }
457
458 static void
459 old_value_to_string (const GValue *src, GValue *dest)
460 {
461   const struct old_value *ov = g_value_get_boxed (src);
462
463   switch (ov->type)
464     {
465     case OV_NUMERIC:
466       {
467         gchar *text = g_strdup_printf ("%.*g", DBL_DIG + 1, ov->v.v);
468         g_value_set_string (dest, text);
469         g_free (text);
470       }
471       break;
472     case OV_STRING:
473       g_value_set_string (dest, ov->v.s);
474       break;
475     case OV_MISSING:
476       g_value_set_string (dest, "MISSING");
477       break;
478     case OV_SYSMIS:
479       g_value_set_string (dest, "SYSMIS");
480       break;
481     case OV_ELSE:
482       g_value_set_string (dest, "ELSE");
483       break;
484     case OV_RANGE:
485       {
486         gchar *text;
487         char en_dash[6] = {0,0,0,0,0,0};
488
489         g_unichar_to_utf8 (0x2013, en_dash);
490
491         text = g_strdup_printf ("%.*g %s %.*g",
492                                 DBL_DIG + 1, ov->v.range[0],
493                                 en_dash,
494                                 DBL_DIG + 1, ov->v.range[1]);
495         g_value_set_string (dest, text);
496         g_free (text);
497       }
498       break;
499     case OV_LOW_UP:
500       {
501         gchar *text;
502         char en_dash[6] = {0,0,0,0,0,0};
503
504         g_unichar_to_utf8 (0x2013, en_dash);
505
506         text = g_strdup_printf ("LOWEST %s %.*g",
507                                 en_dash,
508                                 DBL_DIG + 1, ov->v.range[1]);
509
510         g_value_set_string (dest, text);
511         g_free (text);
512       }
513       break;
514     case OV_HIGH_DOWN:
515       {
516         gchar *text;
517         char en_dash[6] = {0,0,0,0,0,0};
518
519         g_unichar_to_utf8 (0x2013, en_dash);
520
521         text = g_strdup_printf ("%.*g %s HIGHEST",
522                                 DBL_DIG + 1, ov->v.range[0],
523                                 en_dash);
524
525         g_value_set_string (dest, text);
526         g_free (text);
527       }
528       break;
529     default:
530       g_warning ("Invalid type in old recode value");
531       g_value_set_string (dest, "???");
532       break;
533     };
534 }
535
536 GType
537 old_value_get_type (void)
538 {
539   static GType t = 0;
540
541   if (t == 0)
542     {
543       t = g_boxed_type_register_static  ("psppire-recode-old-values",
544                                          (GBoxedCopyFunc) old_value_copy,
545                                          (GBoxedFreeFunc) old_value_free);
546
547       g_value_register_transform_func     (t, G_TYPE_STRING,
548                                            old_value_to_string);
549     }
550
551   return t;
552 }
553
554 \f
555
556 /* Generate a syntax fragment for NV and append it to STR */
557 void
558 old_value_append_syntax (struct string *str, const struct old_value *ov)
559 {
560   switch (ov->type)
561     {
562     case OV_NUMERIC:
563       ds_put_c_format (str, "%.*g", DBL_DIG + 1, ov->v.v);
564       break;
565     case OV_STRING:
566       {
567         struct string ds = DS_EMPTY_INITIALIZER;
568         syntax_gen_string (&ds, ss_cstr (ov->v.s));
569         ds_put_cstr (str, ds_cstr (&ds));
570         ds_destroy (&ds);
571       }
572       break;
573     case OV_MISSING:
574       ds_put_cstr (str, "MISSING");
575       break;
576     case OV_SYSMIS:
577       ds_put_cstr (str, "SYSMIS");
578       break;
579     case OV_ELSE:
580       ds_put_cstr (str, "ELSE");
581       break;
582     case OV_RANGE:
583       ds_put_c_format (str, "%.*g THRU %.*g",
584                        DBL_DIG + 1, ov->v.range[0],
585                        DBL_DIG + 1, ov->v.range[1]);
586       break;
587     case OV_LOW_UP:
588       ds_put_c_format (str, "LOWEST THRU %.*g",
589                        DBL_DIG + 1, ov->v.range[1]);
590       break;
591     case OV_HIGH_DOWN:
592       ds_put_c_format (str, "%.*g THRU HIGHEST",
593                        DBL_DIG + 1, ov->v.range[0]);
594       break;
595     default:
596       g_warning ("Invalid type in old recode value");
597       ds_put_cstr (str, "???");
598       break;
599     };
600 }
601
602
603
604 /* Set OV according to the current state of VR */
605 void
606 psppire_val_chooser_get_status (PsppireValChooser *vr, struct old_value *ov)
607 {
608   int i;
609
610   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
611     {
612       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (vr->rw[i].rb)))
613         {
614           range_opt[i].set (vr, ov, &vr->rw[i]);
615           break;
616         }
617     }
618 }
619
620 /* This might need to be changed to something less naive.
621    In particular, what happends with dates, etc?
622  */
623 static gchar *
624 num_to_string (gdouble x)
625 {
626   return g_strdup_printf ("%.*g", DBL_DIG + 1, x);
627 }
628
629
630 /* Set VR according to the value of OV */
631 void
632 psppire_val_chooser_set_status (PsppireValChooser *vr, const struct old_value *ov)
633 {
634   gint i;
635   if (!ov)
636     return;
637
638   for (i = 0; i < n_VAL_CHOOSER_BUTTONS; ++i)
639     {
640       if (vr->rw[i].e1)
641         gtk_entry_set_text (vr->rw[i].e1, "");
642
643       if (vr->rw[i].e2)
644         gtk_entry_set_text (vr->rw[i].e2, "");
645     }
646
647   switch (ov->type)
648     {
649     case OV_STRING:
650       gtk_toggle_button_set_active (vr->rw[0].rb, TRUE);
651       gtk_entry_set_text (vr->rw[0].e1, ov->v.s);
652       break;
653
654     case OV_NUMERIC:
655       {
656         gchar *str;
657         gtk_toggle_button_set_active (vr->rw[0].rb, TRUE);
658
659         str = num_to_string (ov->v.v);
660
661         gtk_entry_set_text (vr->rw[0].e1, str);
662         g_free (str);
663       }
664       break;
665
666       case OV_SYSMIS:
667         gtk_toggle_button_set_active (vr->rw[VC_SYSMIS].rb, TRUE);
668         break;
669
670       case OV_MISSING:
671         gtk_toggle_button_set_active (vr->rw[VC_MISSING].rb, TRUE);
672         break;
673
674       case OV_RANGE:
675         {
676           gchar *str = num_to_string (ov->v.range[0]);
677           gtk_toggle_button_set_active (vr->rw[VC_RANGE].rb, TRUE);
678           gtk_entry_set_text (vr->rw[VC_RANGE].e1, str);
679
680           g_free (str);
681
682           str = num_to_string (ov->v.range[1]);
683           gtk_entry_set_text (vr->rw[VC_RANGE].e2, str);
684           g_free (str);
685         }
686         break;
687
688       case OV_LOW_UP:
689         {
690           gchar *str = num_to_string (ov->v.range[1]);
691
692           gtk_toggle_button_set_active (vr->rw[VC_LOW_UP].rb, TRUE);
693
694           gtk_entry_set_text (vr->rw[VC_LOW_UP].e1, str);
695
696           g_free (str);
697         }
698         break;
699
700
701       case OV_HIGH_DOWN:
702         {
703           gchar *str = num_to_string (ov->v.range[0]);
704
705           gtk_toggle_button_set_active (vr->rw[VC_HIGH_DOWN].rb, TRUE);
706
707           gtk_entry_set_text (vr->rw[VC_HIGH_DOWN].e1, str);
708
709           g_free (str);
710         }
711         break;
712
713       case OV_ELSE:
714         gtk_toggle_button_set_active (vr->rw[VC_ELSE].rb, TRUE);
715         break;
716
717     default:
718       g_warning ("Unknown old value type");
719       break;
720     };
721 }