671a798fdfeaa6cb83003c465e360ff8e8d429f6
[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         gulong sig_handler;
19         struct gopt_job_view *gjv;
20 };
21
22 struct gopt_combo {
23         struct gopt gopt;
24         GtkWidget *combo;
25 };
26
27 struct gopt_int {
28         struct gopt gopt;
29         unsigned int lastval;
30         GtkWidget *spin;
31 };
32
33 struct gopt_bool {
34         struct gopt gopt;
35         GtkWidget *check;
36 };
37
38 struct gopt_str {
39         struct gopt gopt;
40         GtkWidget *entry;
41 };
42
43 struct gopt_str_val {
44         struct gopt gopt;
45         GtkWidget *spin;
46         GtkWidget *combo;
47         unsigned int maxindex;
48 };
49
50 #define GOPT_RANGE_SPIN 4
51
52 struct gopt_range {
53         struct gopt gopt;
54         GtkWidget *spins[GOPT_RANGE_SPIN];
55 };
56
57 struct gopt_str_multi {
58         struct gopt gopt;
59         GtkWidget *checks[PARSE_MAX_VP];
60 };
61
62 struct gopt_frame_widget {
63         GtkWidget *vbox[2];
64         unsigned int nr;
65 };
66
67 struct gopt_job_view {
68         struct flist_head list;
69         struct gopt_frame_widget g_widgets[__FIO_OPT_G_NR];
70         GtkWidget *widgets[FIO_MAX_OPTS];
71         GtkWidget *vboxes[__FIO_OPT_C_NR];
72 };
73
74 static GNode *gopt_dep_tree;
75
76 static GtkWidget *gopt_get_group_frame(struct gopt_job_view *gjv,
77                                        GtkWidget *box, unsigned int groupmask)
78 {
79         unsigned int mask, group;
80         struct opt_group *og;
81         GtkWidget *frame, *hbox;
82         struct gopt_frame_widget *gfw;
83
84         if (!groupmask)
85                 return 0;
86
87         mask = groupmask;
88         og = opt_group_cat_from_mask(&mask);
89         if (!og)
90                 return NULL;
91
92         group = ffz(~groupmask);
93         gfw = &gjv->g_widgets[group];
94         if (!gfw->vbox[0]) {
95                 frame = gtk_frame_new(og->name);
96                 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3);
97                 hbox = gtk_hbox_new(FALSE, 0);
98                 gtk_container_add(GTK_CONTAINER(frame), hbox);
99                 gfw->vbox[0] = gtk_vbox_new(TRUE, 5);
100                 gfw->vbox[1] = gtk_vbox_new(TRUE, 5);
101                 gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[0], TRUE, TRUE, 5);
102                 gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[1], TRUE, TRUE, 5);
103         }
104
105         hbox = gtk_hbox_new(FALSE, 3);
106         gtk_box_pack_start(GTK_BOX(gfw->vbox[gfw->nr++ & 1]), hbox, FALSE, FALSE, 5);
107         return hbox;
108 }
109
110 /*
111  * Mark children as invisible, if needed.
112  */
113 static void gopt_set_children_visible(struct gopt_job_view *gjv,
114                                       struct fio_option *parent,
115                                       gboolean visible)
116 {
117         GNode *child, *node;
118
119         if (parent->hide_on_set)
120                 visible = !visible;
121
122         node = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
123         child = g_node_first_child(node);
124         while (child) {
125                 struct fio_option *o = child->data;
126                 struct gopt *g = o->gui_data;
127
128                 /*
129                  * Recurse into child, if it also has children
130                  */
131                 if (g_node_n_children(child))
132                         gopt_set_children_visible(gjv, o, visible);
133
134                 if (gjv->widgets[g->opt_index])
135                         gtk_widget_set_sensitive(gjv->widgets[g->opt_index], visible);
136
137                 child = g_node_next_sibling(child);
138         }
139 }
140
141 static void gopt_str_changed(GtkEntry *entry, gpointer data)
142 {
143         struct gopt_str *s = (struct gopt_str *) data;
144         struct fio_option *o = &fio_options[s->gopt.opt_index];
145         const gchar *text;
146         int set;
147
148         text = gtk_entry_get_text(GTK_ENTRY(s->entry));
149         set = strcmp(text, "") != 0;
150
151         gopt_set_children_visible(s->gopt.gjv, o, set);
152 }
153
154 static void gopt_mark_index(struct gopt_job_view *gjv, struct gopt *gopt,
155                             unsigned int idx)
156 {
157         assert(!gjv->widgets[idx]);
158         gopt->opt_index = idx;
159         gopt->gjv = gjv;
160         gjv->widgets[idx] = gopt->box;
161 }
162
163 static void gopt_str_destroy(GtkWidget *w, gpointer data)
164 {
165         struct gopt_str *s = (struct gopt_str *) data;
166
167         free(s);
168         gtk_widget_destroy(w);
169 }
170
171 static struct gopt *gopt_new_str_store(struct gopt_job_view *gjv,
172                                        struct fio_option *o, const char *text,
173                                        unsigned int idx)
174 {
175         struct gopt_str *s;
176         GtkWidget *label;
177
178         s = malloc(sizeof(*s));
179         memset(s, 0, sizeof(*s));
180
181         s->gopt.box = gtk_hbox_new(FALSE, 3);
182         if (!o->lname)
183                 label = gtk_label_new(o->name);
184         else
185                 label = gtk_label_new(o->lname);
186
187         s->entry = gtk_entry_new();
188         gopt_mark_index(gjv, &s->gopt, idx);
189         if (text)
190                 gtk_entry_set_text(GTK_ENTRY(s->entry), text);
191         gtk_entry_set_editable(GTK_ENTRY(s->entry), 1);
192
193         if (o->def)
194                 gtk_entry_set_text(GTK_ENTRY(s->entry), o->def);
195
196         s->gopt.sig_handler = g_signal_connect(GTK_OBJECT(s->entry), "changed", G_CALLBACK(gopt_str_changed), s);
197         g_signal_connect(GTK_OBJECT(s->entry), "destroy", G_CALLBACK(gopt_str_destroy), s);
198
199         gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0);
200         gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0);
201         return &s->gopt;
202 }
203
204 static void gopt_combo_changed(GtkComboBox *box, gpointer data)
205 {
206         struct gopt_combo *c = (struct gopt_combo *) data;
207         struct fio_option *o = &fio_options[c->gopt.opt_index];
208         unsigned int index;
209
210         index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
211
212         gopt_set_children_visible(c->gopt.gjv, o, index);
213 }
214
215 static void gopt_combo_destroy(GtkWidget *w, gpointer data)
216 {
217         struct gopt_combo *c = (struct gopt_combo *) data;
218
219         free(c);
220         gtk_widget_destroy(w);
221 }
222
223 static struct gopt_combo *__gopt_new_combo(struct gopt_job_view *gjv,
224                                            struct fio_option *o,
225                                            unsigned int idx)
226 {
227         struct gopt_combo *c;
228         GtkWidget *label;
229
230         c = malloc(sizeof(*c));
231         memset(c, 0, sizeof(*c));
232
233         c->gopt.box = gtk_hbox_new(FALSE, 3);
234         if (!o->lname)
235                 label = gtk_label_new(o->name);
236         else
237                 label = gtk_label_new(o->lname);
238
239         c->combo = gtk_combo_box_new_text();
240         gopt_mark_index(gjv, &c->gopt, idx);
241         g_signal_connect(GTK_OBJECT(c->combo), "destroy", G_CALLBACK(gopt_combo_destroy), c);
242
243         gtk_box_pack_start(GTK_BOX(c->gopt.box), c->combo, FALSE, FALSE, 0);
244         gtk_box_pack_start(GTK_BOX(c->gopt.box), label, FALSE, FALSE, 0);
245
246         return c;
247 }
248
249 static struct gopt *gopt_new_combo_str(struct gopt_job_view *gjv,
250                                        struct fio_option *o, const char *text,
251                                        unsigned int idx)
252 {
253         struct gopt_combo *c;
254         struct value_pair *vp;
255         int i, active = 0;
256
257         c = __gopt_new_combo(gjv, o, idx);
258
259         i = 0;
260         vp = &o->posval[0];
261         while (vp->ival) {
262                 gtk_combo_box_append_text(GTK_COMBO_BOX(c->combo), vp->ival);
263                 if (o->def && !strcmp(vp->ival, o->def))
264                         active = i;
265                 if (text && !strcmp(vp->ival, text))
266                         active = i;
267                 vp++;
268                 i++;
269         }
270
271         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
272         c->gopt.sig_handler = g_signal_connect(GTK_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
273         return &c->gopt;
274 }
275
276 static struct gopt *gopt_new_combo_int(struct gopt_job_view *gjv,
277                                        struct fio_option *o, unsigned int *ip,
278                                        unsigned int idx)
279 {
280         struct gopt_combo *c;
281         struct value_pair *vp;
282         int i, active = 0;
283
284         c = __gopt_new_combo(gjv, o, idx);
285
286         i = 0;
287         vp = &o->posval[0];
288         while (vp->ival) {
289                 gtk_combo_box_append_text(GTK_COMBO_BOX(c->combo), vp->ival);
290                 if (ip && vp->oval == *ip)
291                         active = i;
292                 vp++;
293                 i++;
294         }
295
296         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
297         c->gopt.sig_handler = g_signal_connect(GTK_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
298         return &c->gopt;
299 }
300
301 static struct gopt *gopt_new_str_multi(struct gopt_job_view *gjv,
302                                        struct fio_option *o, unsigned int idx)
303 {
304         struct gopt_str_multi *m;
305         struct value_pair *vp;
306         GtkWidget *frame, *hbox;
307         int i;
308
309         m = malloc(sizeof(*m));
310         memset(m, 0, sizeof(*m));
311         m->gopt.box = gtk_hbox_new(FALSE, 3);
312         gopt_mark_index(gjv, &m->gopt, idx);
313
314         if (!o->lname)
315                 frame = gtk_frame_new(o->name);
316         else
317                 frame = gtk_frame_new(o->lname);
318         gtk_box_pack_start(GTK_BOX(m->gopt.box), frame, FALSE, FALSE, 3);
319
320         hbox = gtk_hbox_new(FALSE, 3);
321         gtk_container_add(GTK_CONTAINER(frame), hbox);
322
323         i = 0;
324         vp = &o->posval[0];
325         while (vp->ival) {
326                 m->checks[i] = gtk_check_button_new_with_label(vp->ival);
327                 gtk_widget_set_tooltip_text(m->checks[i], vp->help);
328                 gtk_box_pack_start(GTK_BOX(hbox), m->checks[i], FALSE, FALSE, 3);
329                 vp++;
330         }
331
332         return &m->gopt;
333 }
334
335 static void gopt_int_changed(GtkSpinButton *spin, gpointer data)
336 {
337         struct gopt_int *i = (struct gopt_int *) data;
338         struct fio_option *o = &fio_options[i->gopt.opt_index];
339         GtkAdjustment *adj;
340         int value, delta;
341
342         adj = gtk_spin_button_get_adjustment(spin);
343         value = gtk_adjustment_get_value(adj);
344         delta = value - i->lastval;
345         i->lastval = value;
346
347         if (o->inv_opt) {
348                 struct gopt *b_inv = o->inv_opt->gui_data;
349                 struct gopt_int *i_inv = container_of(b_inv, struct gopt_int, gopt);
350                 int cur_val;
351
352                 assert(o->type == o->inv_opt->type);
353
354                 cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin));
355                 cur_val -= delta;
356                 g_signal_handler_block(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
357                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val);
358                 g_signal_handler_unblock(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
359         }
360 }
361
362 static void gopt_int_destroy(GtkWidget *w, gpointer data)
363 {
364         struct gopt_int *i = (struct gopt_int *) data;
365
366         free(i);
367         gtk_widget_destroy(w);
368 }
369
370 static struct gopt_int *__gopt_new_int(struct gopt_job_view *gjv,
371                                        struct fio_option *o,
372                                        unsigned long long *p, unsigned int idx)
373 {
374         unsigned long long defval;
375         struct gopt_int *i;
376         guint maxval, interval;
377         GtkWidget *label;
378
379         i = malloc(sizeof(*i));
380         memset(i, 0, sizeof(*i));
381         i->gopt.box = gtk_hbox_new(FALSE, 3);
382         if (!o->lname)
383                 label = gtk_label_new(o->name);
384         else
385                 label = gtk_label_new(o->lname);
386
387         maxval = o->maxval;
388         if (!maxval)
389                 maxval = UINT_MAX;
390
391         defval = 0;
392         if (p)
393                 defval = *p;
394         else if (o->def) {
395                 long long val;
396
397                 check_str_bytes(o->def, &val, NULL);
398                 defval = val;
399         }
400
401         interval = 1.0;
402         if (o->interval)
403                 interval = o->interval;
404
405         i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval);
406         gopt_mark_index(gjv, &i->gopt, idx);
407         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
408         gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), defval);
409         i->lastval = defval;
410         i->gopt.sig_handler = g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i);
411         g_signal_connect(G_OBJECT(i->spin), "destroy", G_CALLBACK(gopt_int_destroy), i);
412
413         gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
414         gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
415
416         return i;
417 }
418
419 static struct gopt *gopt_new_int(struct gopt_job_view *gjv,
420                                  struct fio_option *o, unsigned int *ip,
421                                  unsigned int idx)
422 {
423         unsigned long long ullp;
424         struct gopt_int *i;
425
426         if (ip) {
427                 ullp = *ip;
428                 i = __gopt_new_int(gjv, o, &ullp, idx);
429         } else
430                 i = __gopt_new_int(gjv, o, NULL, idx);
431
432         return &i->gopt;
433 }
434
435 static struct gopt *gopt_new_ullong(struct gopt_job_view *gjv,
436                                     struct fio_option *o, unsigned long long *p,
437                                     unsigned int idx)
438 {
439         struct gopt_int *i;
440
441         i = __gopt_new_int(gjv, o, p, idx);
442         return &i->gopt;
443 }
444
445 static void gopt_bool_toggled(GtkToggleButton *button, gpointer data)
446 {
447         struct gopt_bool *b = (struct gopt_bool *) data;
448         struct fio_option *o = &fio_options[b->gopt.opt_index];
449         gboolean set;
450
451         set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
452
453         if (o->inv_opt) {
454                 struct gopt *g_inv = o->inv_opt->gui_data;
455                 struct gopt_bool *b_inv = container_of(g_inv, struct gopt_bool, gopt);
456
457                 assert(o->type == o->inv_opt->type);
458
459                 g_signal_handler_block(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
460                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_inv->check), !set);
461                 g_signal_handler_unblock(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
462         }
463
464         gopt_set_children_visible(b->gopt.gjv, o, set);
465 }
466
467 static void gopt_bool_destroy(GtkWidget *w, gpointer data)
468 {
469         struct gopt_bool *b = (struct gopt_bool *) data;
470
471         free(b);
472         gtk_widget_destroy(w);
473 }
474
475 static struct gopt *gopt_new_bool(struct gopt_job_view *gjv,
476                                   struct fio_option *o, unsigned int *val,
477                                   unsigned int idx)
478 {
479         struct gopt_bool *b;
480         GtkWidget *label;
481         int defstate = 0;
482
483         b = malloc(sizeof(*b));
484         memset(b, 0, sizeof(*b));
485         b->gopt.box = gtk_hbox_new(FALSE, 3);
486         if (!o->lname)
487                 label = gtk_label_new(o->name);
488         else
489                 label = gtk_label_new(o->lname);
490
491         b->check = gtk_check_button_new();
492         gopt_mark_index(gjv, &b->gopt, idx);
493         if (val)
494                 defstate = *val;
495         else if (o->def && !strcmp(o->def, "1"))
496                 defstate = 1;
497
498         if (o->neg)
499                 defstate = !defstate;
500
501         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate);
502         b->gopt.sig_handler = g_signal_connect(G_OBJECT(b->check), "toggled", G_CALLBACK(gopt_bool_toggled), b);
503         g_signal_connect(G_OBJECT(b->check), "destroy", G_CALLBACK(gopt_bool_destroy), b);
504
505         gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
506         gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
507         return &b->gopt;
508 }
509
510 /*
511  * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values.
512  * If the max is made smaller than min, adjust min down.
513  * If the min is made larger than max, adjust the max.
514  */
515 static void range_value_changed(GtkSpinButton *spin, gpointer data)
516 {
517         struct gopt_range *r = (struct gopt_range *) data;
518         int changed = -1, i;
519         gint val, mval;
520
521         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
522                 if (GTK_SPIN_BUTTON(r->spins[i]) == spin) {
523                         changed = i;
524                         break;
525                 }
526         }
527
528         assert(changed != -1);
529
530         /*
531          * Min changed
532          */
533         if (changed == 0 || changed == 2) {
534                 GtkWidget *mspin = r->spins[changed + 1];
535
536                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
537                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
538                 if (val > mval)
539                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
540         } else {
541                 GtkWidget *mspin = r->spins[changed - 1];
542
543                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
544                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
545                 if (val < mval)
546                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
547         }
548 }
549
550 static void gopt_range_destroy(GtkWidget *w, gpointer data)
551 {
552         struct gopt_range *r = (struct gopt_range *) data;
553
554         free(r);
555         gtk_widget_destroy(w);
556 }
557
558 static struct gopt *gopt_new_int_range(struct gopt_job_view *gjv,
559                                        struct fio_option *o, unsigned int **ip,
560                                        unsigned int idx)
561 {
562         struct gopt_range *r;
563         gint maxval, defval;
564         GtkWidget *label;
565         guint interval;
566         int i;
567
568         r = malloc(sizeof(*r));
569         memset(r, 0, sizeof(*r));
570         r->gopt.box = gtk_hbox_new(FALSE, 3);
571         gopt_mark_index(gjv, &r->gopt, idx);
572         if (!o->lname)
573                 label = gtk_label_new(o->name);
574         else
575                 label = gtk_label_new(o->lname);
576
577         maxval = o->maxval;
578         if (!maxval)
579                 maxval = INT_MAX;
580
581         defval = 0;
582         if (o->def) {
583                 long long val;
584
585                 check_str_bytes(o->def, &val, NULL);
586                 defval = val;
587         }
588
589         interval = 1.0;
590         if (o->interval)
591                 interval = o->interval;
592
593         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
594                 r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval);
595                 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID);
596                 if (ip)
597                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), *ip[i]);
598                 else
599                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), defval);
600
601                 gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0);
602                 g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r);
603         }
604
605         gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
606         g_signal_connect(G_OBJECT(r->gopt.box), "destroy", G_CALLBACK(gopt_range_destroy), r);
607         return &r->gopt;
608 }
609
610 static void gopt_str_val_destroy(GtkWidget *w, gpointer data)
611 {
612         struct gopt_str_val *g = (struct gopt_str_val *) data;
613
614         free(g);
615         gtk_widget_destroy(w);
616 }
617
618 static void gopt_str_val_spin_wrapped(GtkSpinButton *spin, gpointer data)
619 {
620         struct gopt_str_val *g = (struct gopt_str_val *) data;
621         unsigned int val;
622         GtkAdjustment *adj;
623         gint index;
624
625         adj = gtk_spin_button_get_adjustment(spin);
626         val = gtk_adjustment_get_value(adj);
627
628         /*
629          * Can't rely on exact value, as fast changes increment >= 1
630          */
631         if (!val) {
632                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
633                 if (index + 1 <= g->maxindex) {
634                         val = 1;
635                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), ++index);
636                 } else
637                         val = 1023;
638                 gtk_spin_button_set_value(spin, val);
639         } else {
640                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
641                 if (index) {
642                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), --index);
643                         gtk_spin_button_set_value(spin, 1023);
644                 } else
645                         gtk_spin_button_set_value(spin, 0);
646         }
647 }
648
649 static struct gopt *gopt_new_str_val(struct gopt_job_view *gjv,
650                                      struct fio_option *o,
651                                      unsigned long long *p, unsigned int idx)
652 {
653         struct gopt_str_val *g;
654         const gchar *postfix[] = { "B", "KB", "MB", "GB", "PB", "TB", "" };
655         GtkWidget *label;
656         int i;
657
658         g = malloc(sizeof(*g));
659         memset(g, 0, sizeof(*g));
660         g->gopt.box = gtk_hbox_new(FALSE, 3);
661         if (!o->lname)
662                 label = gtk_label_new(o->name);
663         else
664                 label = gtk_label_new(o->lname);
665         gopt_mark_index(gjv, &g->gopt, idx);
666
667         g->spin = gtk_spin_button_new_with_range(0.0, 1023.0, 1.0);
668         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(g->spin), GTK_UPDATE_IF_VALID);
669         gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), 0);
670         gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(g->spin), 1);
671         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->spin, FALSE, FALSE, 0);
672         g_signal_connect(G_OBJECT(g->spin), "wrapped", G_CALLBACK(gopt_str_val_spin_wrapped), g);
673
674         g->combo = gtk_combo_box_new_text();
675         i = 0;
676         while (strlen(postfix[i])) {
677                 gtk_combo_box_append_text(GTK_COMBO_BOX(g->combo), postfix[i]);
678                 i++;
679         }
680         g->maxindex = i - 1;
681         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), 0);
682         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->combo, FALSE, FALSE, 0);
683         gtk_box_pack_start(GTK_BOX(g->gopt.box), label, FALSE, FALSE, 3);
684
685         g_signal_connect(G_OBJECT(g->gopt.box), "destroy", G_CALLBACK(gopt_str_val_destroy), g);
686         return &g->gopt;
687 }
688
689 static void gopt_add_option(struct gopt_job_view *gjv, GtkWidget *hbox,
690                             struct fio_option *o, unsigned int opt_index,
691                             struct thread_options *to)
692 {
693         struct gopt *go = NULL;
694
695         switch (o->type) {
696         case FIO_OPT_STR_VAL: {
697                 unsigned long long *ullp = NULL;
698
699                 if (o->off1)
700                         ullp = td_var(to, o->off1);
701
702                 go = gopt_new_str_val(gjv, o, ullp, opt_index);
703                 break;
704                 }
705         case FIO_OPT_STR_VAL_TIME: {
706                 unsigned long long *ullp = NULL;
707
708                 if (o->off1)
709                         ullp = td_var(to, o->off1);
710
711                 go = gopt_new_ullong(gjv, o, ullp, opt_index);
712                 break;
713                 }
714         case FIO_OPT_INT: {
715                 unsigned int *ip = NULL;
716
717                 if (o->off1)
718                         ip = td_var(to, o->off1);
719
720                 go = gopt_new_int(gjv, o, ip, opt_index);
721                 break;
722                 }
723         case FIO_OPT_STR_SET:
724         case FIO_OPT_BOOL: {
725                 unsigned int *ip = NULL;
726
727                 if (o->off1)
728                         ip = td_var(to, o->off1);
729
730                 go = gopt_new_bool(gjv, o, ip, opt_index);
731                 break;
732                 }
733         case FIO_OPT_STR: {
734                 if (o->posval[0].ival) {
735                         unsigned int *ip = NULL;
736
737                         if (o->off1)
738                                 ip = td_var(to, o->off1);
739
740                         go = gopt_new_combo_int(gjv, o, ip, opt_index);
741                 } else {
742                         /* TODO: usually ->cb, or unsigned int pointer */
743                         go = gopt_new_str_store(gjv, o, NULL, opt_index);
744                 }
745
746                 break;
747                 }
748         case FIO_OPT_STR_STORE: {
749                 char *text = NULL;
750
751                 if (o->off1) {
752                         char **p = td_var(to, o->off1);
753                         text = *p;
754                 }
755
756                 if (!o->posval[0].ival) {
757                         go = gopt_new_str_store(gjv, o, text, opt_index);
758                         break;
759                 }
760
761                 go = gopt_new_combo_str(gjv, o, text, opt_index);
762                 break;
763                 }
764         case FIO_OPT_STR_MULTI:
765                 go = gopt_new_str_multi(gjv, o, opt_index);
766                 break;
767         case FIO_OPT_RANGE: {
768                 unsigned int *ip[4] = { td_var(to, o->off1),
769                                         td_var(to, o->off2),
770                                         td_var(to, o->off3),
771                                         td_var(to, o->off4) };
772
773                 go = gopt_new_int_range(gjv, o, ip, opt_index);
774                 break;
775                 }
776         /* still need to handle this one */
777         case FIO_OPT_FLOAT_LIST:
778                 break;
779         case FIO_OPT_DEPRECATED:
780                 break;
781         default:
782                 printf("ignore type %u\n", o->type);
783                 break;
784         }
785
786         if (go) {
787                 GtkWidget *dest;
788
789                 if (o->help)
790                         gtk_widget_set_tooltip_text(go->box, o->help);
791
792                 go->opt_type = o->type;
793                 o->gui_data = go;
794
795                 dest = gopt_get_group_frame(gjv, hbox, o->group);
796                 if (!dest)
797                         gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5);
798                 else
799                         gtk_box_pack_start(GTK_BOX(dest), go->box, FALSE, FALSE, 5);
800         }
801 }
802
803 static void gopt_add_options(struct gopt_job_view *gjv,
804                              struct thread_options *to)
805 {
806         GtkWidget *hbox = NULL;
807         int i;
808
809         /*
810          * First add all options
811          */
812         for (i = 0; fio_options[i].name; i++) {
813                 struct fio_option *o = &fio_options[i];
814                 unsigned int mask = o->category;
815                 struct opt_group *og;
816
817                 while ((og = opt_group_from_mask(&mask)) != NULL) {
818                         GtkWidget *vbox = gjv->vboxes[ffz(~og->mask)];
819
820                         hbox = gtk_hbox_new(FALSE, 3);
821                         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
822                         gopt_add_option(gjv, hbox, o, i, to);
823                 }
824         }
825 }
826
827 static GtkWidget *gopt_add_tab(GtkWidget *notebook, const char *name)
828 {
829         GtkWidget *box, *vbox, *scroll;
830
831         scroll = gtk_scrolled_window_new(NULL, NULL);
832         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
833         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
834
835         vbox = gtk_vbox_new(FALSE, 3);
836         box = gtk_hbox_new(FALSE, 0);
837         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
838         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
839         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scroll, gtk_label_new(name));
840         return vbox;
841 }
842
843 static GtkWidget *gopt_add_group_tab(GtkWidget *notebook, struct opt_group *og)
844 {
845         return gopt_add_tab(notebook, og->name);
846 }
847
848 static void gopt_add_group_tabs(GtkWidget *notebook, struct gopt_job_view *gjv)
849 {
850         struct opt_group *og;
851         unsigned int i;
852
853         i = 0;
854         do {
855                 unsigned int mask = (1U << i);
856
857                 og = opt_group_from_mask(&mask);
858                 if (!og)
859                         break;
860                 gjv->vboxes[i] = gopt_add_group_tab(notebook, og);
861                 i++;
862         } while (1);
863 }
864
865 void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc)
866 {
867         GtkWidget *dialog, *notebook, *topnotebook, *vbox;
868         struct gfio_client_options *gco;
869         struct thread_options *o;
870         struct flist_head *entry;
871         struct gopt_job_view *gjv;
872         FLIST_HEAD(gjv_list);
873
874         /*
875          * Just choose the first item, we need to make each options
876          * entry the main notebook, with the below options view as
877          * a sub-notebook
878          */
879         assert(!flist_empty(&gc->o_list));
880
881         dialog = gtk_dialog_new_with_buttons("Fio options",
882                         GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
883                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
884                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
885
886         gtk_widget_set_size_request(GTK_WIDGET(dialog), 1024, 768);
887
888         topnotebook = gtk_notebook_new();
889         gtk_notebook_set_scrollable(GTK_NOTEBOOK(topnotebook), 1);
890         gtk_notebook_popup_enable(GTK_NOTEBOOK(topnotebook));
891         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), topnotebook, TRUE, TRUE, 5);
892
893         flist_for_each(entry, &gc->o_list) {
894                 const char *name;
895
896                 gco = flist_entry(entry, struct gfio_client_options, list);
897                 o = &gco->o;
898                 name = o->name;
899                 if (!name || !strlen(name))
900                         name = "Default job";
901
902                 vbox = gopt_add_tab(topnotebook, name);
903
904                 notebook = gtk_notebook_new();
905                 gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
906                 gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
907                 gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 5);
908
909                 gjv = calloc(1, sizeof(*gjv));
910                 INIT_FLIST_HEAD(&gjv->list);
911                 flist_add_tail(&gjv->list, &gjv_list);
912                 gopt_add_group_tabs(notebook, gjv);
913                 gopt_add_options(gjv, o);
914         }
915
916         gtk_widget_show_all(dialog);
917
918         gtk_dialog_run(GTK_DIALOG(dialog));
919
920         gtk_widget_destroy(dialog);
921
922         while (!flist_empty(&gjv_list)) {
923                 gjv = flist_entry(gjv_list.next, struct gopt_job_view, list);
924                 flist_del(&gjv->list);
925                 free(gjv);
926         }
927 }
928
929 /*
930  * Build n-ary option dependency tree
931  */
932 void gopt_init(void)
933 {
934         int i;
935
936         gopt_dep_tree = g_node_new(NULL);
937
938         for (i = 0; fio_options[i].name; i++) {
939                 struct fio_option *o = &fio_options[i];
940                 GNode *node, *nparent;
941
942                 /*
943                  * Insert node with either the root parent, or an
944                  * option parent.
945                  */
946                 node = g_node_new(o);
947                 nparent = gopt_dep_tree;
948                 if (o->parent) {
949                         struct fio_option *parent;
950
951                         parent = fio_option_find(o->parent);
952                         nparent = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
953                         if (!nparent) {
954                                 log_err("fio: did not find parent %s for opt %s\n", o->name, o->parent);
955                                 nparent = gopt_dep_tree;
956                         }
957                 }
958
959                 g_node_insert(nparent, -1, node);
960         }
961 }
962
963 void gopt_exit(void)
964 {
965         g_node_destroy(gopt_dep_tree);
966         gopt_dep_tree = NULL;
967 }