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