30b18219d785cd74de6c159352ca7ebcddd78004
[pspp] / src / ui / gui / psppire-value-entry.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2012 Free Software Foundation, Inc.
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 #include "psppire-value-entry.h"
19 #include "data/data-in.h"
20 #include "data/value-labels.h"
21 #include "data/variable.h"
22 #include "libpspp/cast.h"
23 #include "libpspp/i18n.h"
24 #include "ui/gui/helper.h"
25 #include "ui/gui/psppire-format.h"
26
27 static void psppire_value_entry_finalize (GObject *);
28
29 G_DEFINE_TYPE (PsppireValueEntry,
30                psppire_value_entry,
31                GTK_TYPE_COMBO_BOX);
32
33 enum
34   {
35     COL_LABEL,                  /* Value label string. */
36     COL_VALUE,                  /* union value *. */
37     N_COLUMNS
38   };
39
40 enum
41   {
42     PROP_0,
43     PROP_SHOW_VALUE_LABEL,
44     PROP_VARIABLE,
45     PROP_VALUE_LABELS,
46     PROP_FORMAT,
47     PROP_ENCODING,
48     PROP_WIDTH
49   };
50
51 enum  {EDIT_DONE, /* Emitted when the entry has changed and is ready to be fetched */
52        n_SIGNALS};
53
54 static guint signals [n_SIGNALS];
55
56
57 static void
58 psppire_value_entry_set_property (GObject      *object,
59                                   guint         prop_id,
60                                   const GValue *value,
61                                   GParamSpec   *pspec)
62 {
63   PsppireValueEntry *obj = PSPPIRE_VALUE_ENTRY (object);
64
65   switch (prop_id)
66     {
67     case PROP_SHOW_VALUE_LABEL:
68       psppire_value_entry_set_show_value_label (obj,
69                                                 g_value_get_boolean (value));
70       break;
71
72     case PROP_VARIABLE:
73       psppire_value_entry_set_variable (obj, g_value_get_pointer (value));
74       break;
75
76     case PROP_VALUE_LABELS:
77       psppire_value_entry_set_value_labels (obj, g_value_get_pointer (value));
78       break;
79
80     case PROP_FORMAT:
81       psppire_value_entry_set_format (obj, g_value_get_boxed (value));
82       break;
83
84     case PROP_ENCODING:
85       psppire_value_entry_set_encoding (obj, g_value_get_string (value));
86       break;
87
88     case PROP_WIDTH:
89       psppire_value_entry_set_width (obj, g_value_get_int (value));
90       break;
91
92     default:
93       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
94       break;
95     }
96 }
97
98 static void
99 psppire_value_entry_get_property (GObject      *object,
100                                   guint         prop_id,
101                                   GValue       *value,
102                                   GParamSpec   *pspec)
103 {
104   PsppireValueEntry *obj = PSPPIRE_VALUE_ENTRY (object);
105
106   switch (prop_id)
107     {
108     case PROP_SHOW_VALUE_LABEL:
109       g_value_set_boolean (value,
110                            psppire_value_entry_get_show_value_label (obj));
111       break;
112
113     case PROP_VARIABLE:
114       g_return_if_reached ();
115
116     case PROP_VALUE_LABELS:
117       g_value_set_pointer (value, obj->val_labs);
118       break;
119
120     case PROP_FORMAT:
121       g_value_set_boxed (value, &obj->format);
122       break;
123
124     case PROP_ENCODING:
125       g_value_set_string (value, psppire_value_entry_get_encoding (obj));
126       break;
127
128     case PROP_WIDTH:
129       g_value_set_int (value, psppire_value_entry_get_width (obj));
130       break;
131
132     default:
133       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
134       break;
135     }
136 }
137
138 static void
139 psppire_value_entry_text_changed (GtkEntryBuffer *buffer,
140                                   GParamSpec *pspec,
141                                   PsppireValueEntry *obj)
142 {
143   obj->cur_value = NULL;
144 }
145
146 static void
147 on_entry_activate (GtkWidget *w)
148 {
149   PsppireValueEntry *ve = PSPPIRE_VALUE_ENTRY (w);
150   g_signal_emit (w, signals [EDIT_DONE], 0);
151 }
152
153 static void
154 on_realize (GtkWidget *w)
155 {
156   GtkEntry *entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (w)));
157   GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry);
158
159   gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (w), COL_LABEL);
160
161   g_signal_connect (buffer, "notify::text",
162                     G_CALLBACK (psppire_value_entry_text_changed), w);
163
164   g_signal_connect_swapped (entry, "activate",
165                     G_CALLBACK (on_entry_activate), w);
166
167   gtk_widget_set_can_focus (GTK_WIDGET (entry), TRUE);
168
169   GTK_WIDGET_CLASS (psppire_value_entry_parent_class)->realize (w);
170 }
171
172
173 /*
174  The "has-entry" property for the parent class (GTK_COMBO_BOX) is
175  a) Construct-only ; and b) defaults to FALSE.
176  We want it to default to TRUE.  So we override it here.
177 */
178 static  GObject*
179 my_constructor (GType                  type,
180                 guint                  n_construct_properties,
181                 GObjectConstructParam *construct_properties)
182 {
183   GObject *o =
184     G_OBJECT_CLASS (psppire_value_entry_parent_class)->constructor
185     (type, n_construct_properties, construct_properties);
186
187   g_object_set (o, "has-entry", TRUE, NULL);
188
189   return o;
190 }
191
192 static void
193 psppire_value_entry_class_init (PsppireValueEntryClass *class)
194 {
195   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
196   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
197
198
199   gobject_class->finalize = psppire_value_entry_finalize;
200   gobject_class->set_property = psppire_value_entry_set_property;
201   gobject_class->get_property = psppire_value_entry_get_property;
202   gobject_class->constructor = my_constructor;
203   widget_class->realize = on_realize;
204
205   g_object_class_install_property (
206     gobject_class, PROP_SHOW_VALUE_LABEL,
207     g_param_spec_boolean ("show-value-label",
208                           "Show Value Label",
209                           "If true, a value that has a value label is shown "
210                           "as the label.  If false, all values are shown "
211                           "literally.",
212                           TRUE,
213                           G_PARAM_WRITABLE | G_PARAM_READABLE));
214
215   g_object_class_install_property (
216     gobject_class, PROP_VARIABLE,
217     g_param_spec_pointer ("variable",
218                           "Variable",
219                           "Set to configure the PsppireValueEntry according "
220                           "to the specified variable's value labels, format, "
221                           "width, and encoding.",
222                           G_PARAM_WRITABLE));
223
224   g_object_class_install_property (
225     gobject_class, PROP_VALUE_LABELS,
226     g_param_spec_pointer ("value-labels",
227                           "Value Labels",
228                           "The set of value labels from which the user may "
229                           "choose and which is used to display the value (if "
230                           "value labels are to be displayed)",
231                           G_PARAM_READABLE | G_PARAM_WRITABLE));
232
233   g_object_class_install_property (
234     gobject_class, PROP_FORMAT,
235     g_param_spec_boxed ("format",
236                         "Format",
237                         "The format used to display values (that are not "
238                         "displayed as value labels) and to interpret values "
239                         "entered.",
240                         PSPPIRE_TYPE_FORMAT,
241                         G_PARAM_READABLE | G_PARAM_WRITABLE));
242
243   g_object_class_install_property (
244     gobject_class, PROP_ENCODING,
245     g_param_spec_string ("encoding",
246                          "Encoding",
247                          "The encoding (e.g. \"UTF-8\") for string values.  "
248                          "For numeric values this setting has no effect.",
249                          "UTF-8",
250                          G_PARAM_READABLE | G_PARAM_WRITABLE));
251
252   g_object_class_install_property (
253     gobject_class, PROP_WIDTH,
254     g_param_spec_int ("width",
255                       "Width",
256                       "Width of the value, either 0 for a numeric value or "
257                       "a positive integer count of bytes for string values.",
258                       0, MAX_STRING,
259                       0,
260                       G_PARAM_READABLE | G_PARAM_WRITABLE));
261
262   signals [EDIT_DONE] =
263     g_signal_new ("edit-done",
264                   G_TYPE_FROM_CLASS (class),
265                   G_SIGNAL_RUN_FIRST,
266                   0,
267                   NULL, NULL,
268                   g_cclosure_marshal_VOID__VOID,
269                   G_TYPE_NONE,
270                   0);
271 }
272
273 static void
274 psppire_value_entry_init (PsppireValueEntry *obj)
275 {
276   obj->show_value_label = true;
277   obj->val_labs = NULL;
278   obj->format = F_8_0;
279   obj->encoding = NULL;
280   obj->cur_value = NULL;
281 }
282
283 static void
284 psppire_value_entry_finalize (GObject *gobject)
285 {
286   PsppireValueEntry *obj = PSPPIRE_VALUE_ENTRY (gobject);
287
288   val_labs_destroy (obj->val_labs);
289   g_free (obj->encoding);
290
291   G_OBJECT_CLASS (psppire_value_entry_parent_class)->finalize (gobject);
292 }
293
294 GtkWidget *
295 psppire_value_entry_new (void)
296 {
297   return GTK_WIDGET (g_object_new (PSPPIRE_TYPE_VALUE_ENTRY, NULL));
298 }
299
300 static void
301 psppire_value_entry_refresh_model (PsppireValueEntry *obj)
302 {
303   GtkTreeModel *model;
304   GtkTreeModel *old_model;
305
306   if (val_labs_count (obj->val_labs) > 0)
307     {
308       const struct val_lab **vls = val_labs_sorted (obj->val_labs);
309       size_t n_vls = val_labs_count (obj->val_labs);
310
311       GtkListStore *list_store;
312       size_t i;
313
314       list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
315       model = GTK_TREE_MODEL (list_store);
316       for (i = 0; i < n_vls; i++)
317         {
318           const struct val_lab *vl = vls[i];
319           GtkTreeIter iter;
320
321           gtk_list_store_append (list_store, &iter);
322           gtk_list_store_set (list_store, &iter,
323                               COL_LABEL, val_lab_get_label (vl),
324                               COL_VALUE, val_lab_get_value (vl),
325                               -1);
326         }
327       free (vls);
328     }
329   else
330     model = NULL;
331
332   old_model = gtk_combo_box_get_model (GTK_COMBO_BOX (obj));
333
334   if (old_model != model)
335     {
336       GtkEntry *entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (obj)));
337       gtk_entry_set_text (entry, "");
338     }
339
340   gtk_combo_box_set_model (GTK_COMBO_BOX (obj), model);
341   if (model != NULL)
342     g_object_unref (model);
343 }
344
345 void
346 psppire_value_entry_set_show_value_label (PsppireValueEntry *obj,
347                                           gboolean show_value_label)
348 {
349   if (obj->show_value_label != show_value_label)
350     {
351       obj->show_value_label = show_value_label;
352       g_object_notify (G_OBJECT (obj), "show-value-label");
353     }
354 }
355
356 gboolean
357 psppire_value_entry_get_show_value_label (const PsppireValueEntry *obj)
358 {
359   return obj->show_value_label;
360 }
361
362 void
363 psppire_value_entry_set_variable (PsppireValueEntry *obj,
364                                   const struct variable *var)
365 {
366   if (var != NULL)
367     {
368       psppire_value_entry_set_value_labels (obj, var_get_value_labels (var));
369       obj->format = *var_get_print_format (var);
370       psppire_value_entry_set_encoding (obj, var_get_encoding (var));
371     }
372   else
373     psppire_value_entry_set_value_labels (obj, NULL);
374 }
375
376 void
377 psppire_value_entry_set_value_labels (PsppireValueEntry *obj,
378                                       const struct val_labs *val_labs)
379 {
380   if (!val_labs_equal (obj->val_labs, val_labs))
381     {
382       obj->cur_value = NULL;
383
384       val_labs_destroy (obj->val_labs);
385       obj->val_labs = val_labs_clone (val_labs);
386
387       if (val_labs != NULL)
388         {
389           int width = val_labs_get_width (val_labs);
390           if (width != fmt_var_width (&obj->format))
391             obj->format = fmt_default_for_width (width);
392         }
393
394       psppire_value_entry_refresh_model (obj);
395
396       g_object_notify (G_OBJECT (obj), "value-labels");
397     }
398 }
399
400 const struct val_labs *
401 psppire_value_entry_get_value_labels (const PsppireValueEntry *obj)
402 {
403   return obj->val_labs;
404 }
405
406 void
407 psppire_value_entry_set_format (PsppireValueEntry *obj,
408                                 const struct fmt_spec *format)
409 {
410   if (!fmt_equal (format, &obj->format))
411     {
412       obj->cur_value = NULL;
413       obj->format = *format;
414
415       if (obj->val_labs
416           && val_labs_get_width (obj->val_labs) != fmt_var_width (format))
417         psppire_value_entry_set_value_labels (obj, NULL);
418
419       g_object_notify (G_OBJECT (obj), "format");
420     }
421 }
422
423 const struct fmt_spec *
424 psppire_value_entry_get_format (const PsppireValueEntry *obj)
425 {
426   return &obj->format;
427 }
428
429 void
430 psppire_value_entry_set_encoding (PsppireValueEntry *obj,
431                                   const gchar *encoding)
432 {
433   g_free (obj->encoding);
434   obj->encoding = encoding != NULL ? g_strdup (encoding) : NULL;
435
436   g_object_notify (G_OBJECT (obj), "encoding");
437 }
438
439 const gchar *
440 psppire_value_entry_get_encoding (const PsppireValueEntry *obj)
441 {
442   return obj->encoding ? obj->encoding : UTF8;
443 }
444
445 void
446 psppire_value_entry_set_width (PsppireValueEntry *obj, int width)
447 {
448   if (width != fmt_var_width (&obj->format))
449     {
450       struct fmt_spec format = fmt_default_for_width (width);
451       psppire_value_entry_set_format (obj, &format);
452     }
453 }
454
455 int
456 psppire_value_entry_get_width (const PsppireValueEntry *obj)
457 {
458   return fmt_var_width (&obj->format);
459 }
460
461 void
462 psppire_value_entry_set_value (PsppireValueEntry *obj,
463                                const union value *value,
464                                int width)
465 {
466   GtkEntry *entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (obj)));
467   gchar *string;
468
469   obj->cur_value = NULL;
470
471   if (value == NULL)
472     return;
473
474   if (obj->show_value_label)
475     {
476       struct val_lab *vl = val_labs_lookup (obj->val_labs, value);
477       if (vl != NULL)
478         {
479           gtk_entry_set_text (entry, val_lab_get_label (vl));
480           obj->cur_value = val_lab_get_value (vl);
481           return;
482         }
483     }
484
485   string = value_to_text__ (*value, &obj->format, obj->encoding);
486   gtk_entry_set_text (entry, string);
487   g_free (string);
488 }
489
490 gboolean
491 psppire_value_entry_get_value (PsppireValueEntry *obj,
492                                union value *value,
493                                int width)
494 {
495   GtkEntry *entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (obj)));
496   GtkTreeIter iter;
497
498   g_return_val_if_fail (fmt_var_width (&obj->format) == width, FALSE);
499
500   if (obj->cur_value)
501     {
502       value_copy (value, obj->cur_value, width);
503       return TRUE;
504     }
505   else if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (obj), &iter))
506     {
507       union value *v;
508
509       gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (obj)), &iter,
510                           COL_VALUE, &v,
511                           -1);
512       value_copy (value, v, width);
513       return TRUE;
514     }
515   else
516     {
517       const gchar *new_text;
518
519       new_text = gtk_entry_get_text (entry);
520       return data_in_msg (ss_cstr (new_text), UTF8,
521                           obj->format.type,
522                           value, width, obj->encoding);
523     }
524 }