Merge 'master' into 'gtk3'.
[pspp] / src / ui / gui / psppire-scanf.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011 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
18 #include <config.h>
19 #include <gtk/gtk.h>
20 #include "psppire-scanf.h"
21
22 #include <gl/printf-parse.h>
23 #include <stdarg.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "xalloc.h"
27
28
29 static void psppire_scanf_class_init          (PsppireScanfClass *class);
30 static void psppire_scanf_init                (PsppireScanf      *w);
31
32 G_DEFINE_TYPE (PsppireScanf, psppire_scanf, GTK_TYPE_HBOX)
33
34 /* Properties */
35 enum
36 {
37   PROP_0,
38   PROP_FORMAT,
39   PROP_NCONV,
40   PROP_USE_UNDERLINE,
41   PROP_MNEMONIC_WIDGET
42 };
43
44 /* Create a GtkLabel and pack it into BOX.
45    The label is created using part of the string at S, and the directives
46    at DIRS[DIR_IDX] and subsequent.
47
48    After this function returns, *S points to the first unused character.
49 */
50 static void
51 ship_label (PsppireScanf *box, const char **s,
52             const char_directives *dirs, size_t dir_idx)
53 {
54   GtkWidget *label ;
55   GString *str = g_string_new (*s);
56
57   if ( dirs)
58     {
59       char_directive dir = dirs->dir[dir_idx];
60       int n = 0;
61
62       while (dir_idx < dirs->count && dir.conversion == '%' )
63         {
64           g_string_erase (str, dir.dir_start - *s, 1);
65           dir = dirs->dir[++dir_idx];
66           n++;
67         }
68
69       g_string_truncate (str, dir.dir_start - *s - n);
70
71       if ( dir_idx >= dirs->count)
72         *s = NULL;
73       else
74         *s = dir.dir_end;
75     }
76
77   label = gtk_label_new (str->str);
78
79   g_string_free (str, TRUE);
80
81   gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
82   gtk_widget_show (label);
83 }
84
85 static void
86 guts (PsppireScanf *scanf)
87 {
88   gint i;
89   arguments a;
90   const char *s = scanf->format;
91
92   /* Get the number of args into D */
93   g_return_if_fail (0 == printf_parse (scanf->format, &scanf->d, &a));
94
95   if ( scanf->d.count > 0)
96     scanf->widgets = xcalloc (scanf->d.count, sizeof (*scanf->widgets));
97
98   /* A is not used, so get rid of it */
99   if (a.arg != a.direct_alloc_arg)
100     free (a.arg);
101
102   for (i = 0 ; i < scanf->d.count ; ++i )
103     {
104       GtkWidget **w;
105       char_directive dir = scanf->d.dir[i];
106       int precision = 0;
107       int width = 0;
108
109       if ( dir.precision_start && dir.precision_end)
110         precision = strtol (dir.precision_start + 1,
111                             (char **) &dir.precision_end, 10);
112
113       if ( dir.width_start && dir.width_end )
114         width = strtol (dir.width_start, (char **) &dir.width_end, 10);
115
116       if ( dir.dir_start > s )
117         ship_label (scanf, &s, &scanf->d, i);
118
119       if ( dir.conversion == '%')
120         {
121           if (s) s++;
122           continue;
123         }
124
125       w = &scanf->widgets [dir.arg_index];
126       switch (dir.conversion)
127         {
128         case 'd':
129         case 'i':
130         case 'f':
131           {
132             *w = gtk_spin_button_new_with_range (0, 100.0, 1.0);
133             g_object_set (*w, "digits", precision, NULL);
134           }
135           break;
136         case 's':
137           *w = gtk_entry_new ();
138           break;
139         };
140       g_object_set (*w, "width-chars", width, NULL);
141       gtk_box_pack_start (GTK_BOX (scanf), *w, FALSE, FALSE, 0);
142       gtk_widget_show (*w);
143     }
144
145   if ( s && *s )
146     ship_label (scanf, &s, NULL, 0);
147
148 }
149
150
151 static void
152 set_mnemonic (PsppireScanf *scanf)
153 {
154   if (scanf->use_underline || scanf->mnemonic_widget)
155     {
156       GList *l = gtk_container_get_children (GTK_CONTAINER (scanf));
157       while (l)
158         {
159           if ( GTK_IS_LABEL (l->data))
160             {
161               const gchar *t = gtk_label_get_label (l->data);
162               if  ( g_strstr_len (t, -1,  "_"))
163                 {
164                   g_object_set (l->data,
165                                 "use-underline", TRUE,
166                                 "mnemonic-widget", scanf->mnemonic_widget,
167                                 NULL);
168
169                   break;
170                 }
171             }
172           l = l->next;
173         }
174       g_list_free (l);
175     }
176 }
177
178 static void
179 psppire_scanf_set_property (GObject         *object,
180                             guint            prop_id,
181                             const GValue    *value,
182                             GParamSpec      *pspec)
183 {
184   PsppireScanf *scanf = PSPPIRE_SCANF (object);
185
186   switch (prop_id)
187     {
188     case PROP_FORMAT:
189       scanf->format = g_value_get_string (value);
190       guts (scanf);
191       break;
192     case PROP_MNEMONIC_WIDGET:
193       scanf->mnemonic_widget = g_value_get_object (value);
194       set_mnemonic (scanf);
195       break;
196     case PROP_USE_UNDERLINE:
197       scanf->use_underline = g_value_get_boolean (value);
198       set_mnemonic (scanf);
199       break;
200     default:
201       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
202       break;
203     };
204 }
205
206
207 static void
208 psppire_scanf_get_property (GObject         *object,
209                             guint            prop_id,
210                             GValue          *value,
211                             GParamSpec      *pspec)
212 {
213   PsppireScanf *scanf = PSPPIRE_SCANF (object);
214
215   switch (prop_id)
216     {
217     case PROP_FORMAT:
218       g_value_set_string (value, scanf->format);
219       break;
220     case PROP_NCONV:
221       g_value_set_int (value, scanf->d.count);
222       break;
223     case PROP_USE_UNDERLINE:
224       g_value_set_boolean (value, scanf->use_underline);
225       break;
226     case PROP_MNEMONIC_WIDGET:
227       g_value_set_object (value, scanf->mnemonic_widget);
228       break;
229     default:
230       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
231       break;
232     };
233 }
234
235
236 static GObjectClass *parent_class = NULL;
237
238 static void
239 psppire_scanf_dispose (GObject *obj)
240 {
241   PsppireScanf *w = (PsppireScanf *)obj;
242
243   if (w->dispose_has_run)
244     return;
245
246   /* Make sure dispose does not run twice. */
247   w->dispose_has_run = TRUE;
248
249
250   /* Chain up to the parent class */
251   G_OBJECT_CLASS (parent_class)->dispose (obj);
252 }
253
254 static void
255 psppire_scanf_finalize (GObject *obj)
256 {
257   PsppireScanf *w = PSPPIRE_SCANF (obj);
258
259   free (w->widgets);
260
261   if (w->d.dir != w->d.direct_alloc_dir)
262     free (w->d.dir);
263
264    /* Chain up to the parent class */
265    G_OBJECT_CLASS (parent_class)->finalize (obj);
266 }
267
268 static void
269 psppire_scanf_class_init (PsppireScanfClass *class)
270 {
271   GObjectClass *object_class = G_OBJECT_CLASS (class);
272
273   GParamSpec *format_spec =
274     g_param_spec_string ("format",
275                        "Format",
276                        "A Scanf style format string",
277                        NULL,
278                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
279
280   GParamSpec *nconv_spec =
281     g_param_spec_int ("n-conv",
282                        "Conversions",
283                        "The number of conversions in the format string",
284                       0, G_MAXINT, 0,
285                        G_PARAM_READABLE);
286
287
288   GParamSpec *use_underline_spec =
289     g_param_spec_boolean ("use-underline",
290                        "Use Underline",
291                        "If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key",
292                           FALSE,
293                           G_PARAM_READWRITE);
294
295
296   GParamSpec *mnemonic_widget_spec =
297     g_param_spec_object ("mnemonic-widget",
298                        "Mnemonic widget",
299                        "The widget which is to be activated when the Scanf's mnemonic key is pressed.  Has no effect if use-underline is false.",
300                          GTK_TYPE_WIDGET,
301                          G_PARAM_READWRITE);
302
303
304   parent_class = g_type_class_peek_parent (class);
305
306   object_class->dispose = psppire_scanf_dispose;
307   object_class->finalize = psppire_scanf_finalize;
308
309   object_class->set_property = psppire_scanf_set_property;
310   object_class->get_property = psppire_scanf_get_property;
311
312   g_object_class_install_property (object_class,
313                                    PROP_NCONV,
314                                    nconv_spec);
315
316   g_object_class_install_property (object_class,
317                                    PROP_FORMAT,
318                                    format_spec);
319
320   g_object_class_install_property (object_class,
321                                    PROP_USE_UNDERLINE,
322                                    use_underline_spec);
323
324   g_object_class_install_property (object_class,
325                                    PROP_MNEMONIC_WIDGET,
326                                    mnemonic_widget_spec);
327 }
328
329
330
331 static void
332 psppire_scanf_init (PsppireScanf *w)
333 {
334 }
335
336 gchar
337 psppire_get_conversion_char (PsppireScanf *w, gint n)
338 {
339   g_return_val_if_fail ( n < w->d.count, '\0');
340   return w->d.dir[n].conversion;
341 }
342
343 GtkWidget *
344 psppire_scanf_get_child (PsppireScanf *w, gint n)
345 {
346   g_return_val_if_fail ( n < w->d.count, NULL);
347   return w->widgets[n];
348 }
349
350
351 /*
352    This widget is a GtkHBox populated with GtkLabel and GtkEntry widgets.
353    Each conversion in FMT will cause a GtkEntry (possibly a GtkSpinButton) to
354    be created.  Any text between conversions produces a GtkLabel.
355    There should be N arguments following FMT should be of type GtkEntry **,
356    where N is the number of conversions.
357    These arguments will be filled with a pointer to the corresponding widgets.
358    Their properties may be changed, but they should not be unrefed.
359  */
360 GtkWidget *
361 psppire_scanf_new (const gchar *fmt, ...)
362 {
363   gint n, i;
364   va_list ap;
365
366   GtkWidget *w = GTK_WIDGET (g_object_new (psppire_scanf_get_type (),
367                                    "format", fmt, NULL));
368
369   g_object_get (w, "n-conv", &n, NULL);
370
371   va_start (ap, fmt);
372
373   for (i = 0 ; i < n ; ++i )
374     {
375       GtkWidget **field;
376
377       if ( psppire_get_conversion_char (PSPPIRE_SCANF (w), i) == '%')
378         continue;
379
380       field = va_arg (ap, GtkWidget **);
381
382       *field = psppire_scanf_get_child (PSPPIRE_SCANF (w), i);
383     }
384   va_end (ap);
385
386   return w;
387 }