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