Allow options to specify intervals
[fio.git] / goptions.c
1 #include <locale.h>
2 #include <malloc.h>
3 #include <string.h>
4
5 #include <glib.h>
6 #include <cairo.h>
7 #include <gtk/gtk.h>
8
9 #include "fio.h"
10 #include "gfio.h"
11 #include "ghelpers.h"
12 #include "parse.h"
13
14 struct gopt {
15         GtkWidget *box;
16         unsigned int opt_index;
17         unsigned int opt_type;
18 };
19
20 struct gopt_combo {
21         struct gopt gopt;
22         GtkWidget *combo;
23 };
24
25 struct gopt_int {
26         struct gopt gopt;
27         GtkWidget *spin;
28 };
29
30 struct gopt_bool {
31         struct gopt gopt;
32         GtkWidget *check;
33 };
34
35 struct gopt_str {
36         struct gopt gopt;
37         GtkWidget *entry;
38 };
39
40 #define GOPT_RANGE_SPIN 4
41
42 struct gopt_range {
43         struct gopt gopt;
44         GtkWidget *spins[GOPT_RANGE_SPIN];
45 };
46
47 static struct gopt *gopt_new_str_store(struct fio_option *o, const char *text)
48 {
49         struct gopt_str *s;
50         GtkWidget *label;
51
52         s = malloc(sizeof(*s));
53
54         s->gopt.box = gtk_hbox_new(FALSE, 3);
55         label = gtk_label_new(o->name);
56         gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0);
57
58         s->entry = gtk_entry_new();
59         if (text)
60                 gtk_entry_set_text(GTK_ENTRY(s->entry), text);
61         gtk_entry_set_editable(GTK_ENTRY(s->entry), 1);
62
63         if (o->def)
64                 gtk_entry_set_text(GTK_ENTRY(s->entry), o->def);
65
66         gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0);
67         return &s->gopt;
68 }
69
70 static struct gopt_combo *__gopt_new_combo(struct fio_option *o)
71 {
72         struct gopt_combo *combo;
73         GtkWidget *label;
74
75         combo = malloc(sizeof(*combo));
76
77         combo->gopt.box = gtk_hbox_new(FALSE, 3);
78         label = gtk_label_new(o->name);
79         gtk_box_pack_start(GTK_BOX(combo->gopt.box), label, FALSE, FALSE, 0);
80
81         combo->combo = gtk_combo_box_new_text();
82         gtk_box_pack_start(GTK_BOX(combo->gopt.box), combo->combo, FALSE, FALSE, 0);
83
84         return combo;
85 }
86
87 static struct gopt *gopt_new_combo_str(struct fio_option *o, const char *text)
88 {
89         struct gopt_combo *combo;
90         struct value_pair *vp;
91         int i, active = 0;
92
93         combo = __gopt_new_combo(o);
94
95         i = 0;
96         vp = &o->posval[0];
97         while (vp->ival) {
98                 gtk_combo_box_append_text(GTK_COMBO_BOX(combo->combo), vp->ival);
99                 if (o->def && !strcmp(vp->ival, o->def))
100                         active = i;
101                 if (text && !strcmp(vp->ival, text))
102                         active = i;
103                 vp++;
104                 i++;
105         }
106
107         gtk_combo_box_set_active(GTK_COMBO_BOX(combo->combo), active);
108         return &combo->gopt;
109 }
110
111 static struct gopt *gopt_new_combo_int(struct fio_option *o, unsigned int *ip)
112 {
113         struct gopt_combo *combo;
114         struct value_pair *vp;
115         int i, active = 0;
116
117         combo = __gopt_new_combo(o);
118
119         i = 0;
120         vp = &o->posval[0];
121         while (vp->ival) {
122                 gtk_combo_box_append_text(GTK_COMBO_BOX(combo->combo), vp->ival);
123                 if (ip && vp->oval == *ip)
124                         active = i;
125                 vp++;
126                 i++;
127         }
128
129         gtk_combo_box_set_active(GTK_COMBO_BOX(combo->combo), active);
130         return &combo->gopt;
131 }
132
133 static struct gopt *__gopt_new_int(struct fio_option *o, unsigned long long *p)
134 {
135         unsigned long long defval;
136         struct gopt_int *i;
137         guint maxval, interval;
138         GtkWidget *label;
139
140         i = malloc(sizeof(*i));
141         i->gopt.box = gtk_hbox_new(FALSE, 3);
142         label = gtk_label_new(o->name);
143         gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
144
145         maxval = o->maxval;
146         if (!maxval)
147                 maxval = UINT_MAX;
148
149         defval = 0;
150         if (p)
151                 defval = *p;
152         else if (o->def) {
153                 long long val;
154
155                 check_str_bytes(o->def, &val, NULL);
156                 defval = val;
157         }
158
159         interval = 1.0;
160         if (o->interval)
161                 interval = o->interval;
162
163         i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval);
164         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
165         gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), defval);
166
167         gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
168         return &i->gopt;
169 }
170
171 static struct gopt *gopt_new_int(struct fio_option *o, unsigned int *ip)
172 {
173         unsigned long long ullp;
174
175         if (ip) {
176                 ullp = *ip;
177                 return __gopt_new_int(o, &ullp);
178         }
179
180         return __gopt_new_int(o, NULL);
181 }
182
183 static struct gopt *gopt_new_ullong(struct fio_option *o, unsigned long long *p)
184 {
185         return __gopt_new_int(o, p);
186 }
187
188 static struct gopt *gopt_new_bool(struct fio_option *o, unsigned int *val)
189 {
190         struct gopt_bool *b;
191         GtkWidget *label;
192         int defstate = 0;
193
194         b = malloc(sizeof(*b));
195         b->gopt.box = gtk_hbox_new(FALSE, 3);
196         label = gtk_label_new(o->name);
197         gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
198
199         b->check = gtk_check_button_new();
200         if (val)
201                 defstate = *val;
202         else if (o->def && !strcmp(o->def, "1"))
203                 defstate = 1;
204
205         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate);
206
207         gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
208         return &b->gopt;
209 }
210
211 /*
212  * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values.
213  * If the max is made smaller than min, adjust min down.
214  * If the min is made larger than max, adjust the max.
215  */
216 static void range_value_changed(GtkSpinButton *spin, gpointer data)
217 {
218         struct gopt_range *r = (struct gopt_range *) data;
219         int changed = -1, i;
220         gint val, mval;
221
222         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
223                 if (GTK_SPIN_BUTTON(r->spins[i]) == spin) {
224                         changed = i;
225                         break;
226                 }
227         }
228
229         assert(changed != -1);
230
231         /*
232          * Min changed
233          */
234         if (changed == 0 || changed == 2) {
235                 GtkWidget *mspin = r->spins[changed + 1];
236
237                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
238                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
239                 if (val > mval)
240                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
241         } else {
242                 GtkWidget *mspin = r->spins[changed - 1];
243
244                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
245                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
246                 if (val < mval)
247                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
248         }
249 }
250
251 static struct gopt *gopt_new_int_range(struct fio_option *o, unsigned int **ip)
252 {
253         struct gopt_range *r;
254         gint maxval, defval;
255         GtkWidget *label;
256         guint interval;
257         int i;
258
259         r = malloc(sizeof(*r));
260         r->gopt.box = gtk_hbox_new(FALSE, 3);
261         label = gtk_label_new(o->name);
262         gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
263
264         maxval = o->maxval;
265         if (!maxval)
266                 maxval = INT_MAX;
267
268         defval = 0;
269         if (o->def) {
270                 long long val;
271
272                 check_str_bytes(o->def, &val, NULL);
273                 defval = val;
274         }
275
276         interval = 1.0;
277         if (o->interval)
278                 interval = o->interval;
279
280         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
281                 r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval);
282                 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID);
283                 if (ip)
284                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), *ip[i]);
285                 else
286                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), defval);
287
288                 gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0);
289                 g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r);
290         }
291
292         return &r->gopt;
293 }
294
295 static void gopt_add_option(GtkWidget *hbox, struct fio_option *o,
296                             unsigned int opt_index, struct thread_options *to)
297 {
298         struct gopt *go = NULL;
299
300         switch (o->type) {
301         case FIO_OPT_STR_VAL:
302         case FIO_OPT_STR_VAL_TIME: {
303                 unsigned long long *ullp = NULL;
304
305                 if (o->off1)
306                         ullp = td_var(to, o->off1);
307
308                 go = gopt_new_ullong(o, ullp);
309                 break;
310                 }
311         case FIO_OPT_INT: {
312                 unsigned int *ip = NULL;
313
314                 if (o->off1)
315                         ip = td_var(to, o->off1);
316
317                 go = gopt_new_int(o, ip);
318                 break;
319                 }
320         case FIO_OPT_STR_SET:
321         case FIO_OPT_BOOL: {
322                 unsigned int *ip = NULL;
323
324                 if (o->off1)
325                         ip = td_var(to, o->off1);
326
327                 go = gopt_new_bool(o, ip);
328                 break;
329                 }
330         case FIO_OPT_STR: {
331                 unsigned int *ip = NULL;
332
333                 if (o->off1)
334                         ip = td_var(to, o->off1);
335
336                 go = gopt_new_combo_int(o, ip);
337                 break;
338                 }
339         case FIO_OPT_STR_STORE: {
340                 char *text = NULL;
341
342                 if (o->off1) {
343                         char **p = td_var(to, o->off1);
344                         text = *p;
345                 }
346
347                 if (!o->posval[0].ival) {
348                         go = gopt_new_str_store(o, text);
349                         break;
350                 }
351
352                 go = gopt_new_combo_str(o, text);
353                 break;
354                 }
355         case FIO_OPT_STR_MULTI:
356                 go = gopt_new_combo_str(o, NULL);
357                 break;
358         case FIO_OPT_RANGE: {
359                 unsigned int *ip[4] = { td_var(to, o->off1),
360                                         td_var(to, o->off2),
361                                         td_var(to, o->off3),
362                                         td_var(to, o->off4) };
363
364                 go = gopt_new_int_range(o, ip);
365                 break;
366                 }
367         /* still need to handle this one */
368         case FIO_OPT_FLOAT_LIST:
369                 break;
370         case FIO_OPT_DEPRECATED:
371                 break;
372         default:
373                 printf("ignore type %u\n", o->type);
374                 break;
375         }
376
377         if (go) {
378                 if (o->help)
379                         gtk_widget_set_tooltip_text(go->box, o->help);
380         
381                 gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5);
382                 go->opt_index = opt_index;
383                 go->opt_type = o->type;
384         }
385 }
386
387 static void gopt_add_options(GtkWidget **vboxes, struct thread_options *to)
388 {
389         GtkWidget *hbox = NULL;
390         int i;
391
392         for (i = 0; fio_options[i].name; i++) {
393                 struct fio_option *o = &fio_options[i];
394                 unsigned int mask = o->category;
395                 struct opt_group *og;
396
397                 while ((og = opt_group_from_mask(&mask)) != NULL) {
398                         GtkWidget *vbox = vboxes[ffz(~og->mask)];
399
400                         hbox = gtk_hbox_new(FALSE, 3);
401                         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
402                         gopt_add_option(hbox, o, i, to);
403                 }
404         }
405 }
406
407 static GtkWidget *gopt_add_group_tab(GtkWidget *notebook, struct opt_group *og)
408 {
409         GtkWidget *box, *vbox, *scroll;
410
411         scroll = gtk_scrolled_window_new(NULL, NULL);
412         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
413         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
414
415         vbox = gtk_vbox_new(FALSE, 3);
416         box = gtk_hbox_new(FALSE, 0);
417         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
418         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
419         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scroll, gtk_label_new(og->name));
420
421         return vbox;
422 }
423
424 static void gopt_add_group_tabs(GtkWidget *notebook, GtkWidget **vbox)
425 {
426         struct opt_group *og;
427         unsigned int i = 0;
428
429         do {
430                 unsigned int mask = (1U << i);
431
432                 og = opt_group_from_mask(&mask);
433                 if (!og)
434                         break;
435                 vbox[i] = gopt_add_group_tab(notebook, og);
436                 i++;
437         } while (1);
438 }
439
440 void gopt_get_options_window(GtkWidget *window, struct thread_options *o)
441 {
442         GtkWidget *dialog, *notebook;
443         GtkWidget *vboxes[__FIO_OPT_G_NR];
444
445         dialog = gtk_dialog_new_with_buttons("Fio options",
446                         GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
447                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
448                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
449
450         gtk_widget_set_size_request(GTK_WIDGET(dialog), 1024, 768);
451
452         notebook = gtk_notebook_new();
453         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
454         gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
455         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), notebook, TRUE, TRUE, 5);
456
457         gopt_add_group_tabs(notebook, vboxes);
458
459         gopt_add_options(vboxes, o);
460
461         gtk_widget_show_all(dialog);
462
463         gtk_dialog_run(GTK_DIALOG(dialog));
464
465         gtk_widget_destroy(dialog);
466 }