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