8798b39eb83f3b6ccf27f87ca886627be71f70dc
[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 fio_option *parent,
114                                       gboolean visible)
115 {
116         GNode *child, *node;
117
118         if (parent->hide_on_set)
119                 visible = !visible;
120
121         node = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
122         child = g_node_first_child(node);
123         while (child) {
124                 struct fio_option *o = child->data;
125                 struct gopt *g = o->gui_data;
126                 struct gopt_job_view *gjv = g->gjv;
127
128                 /*
129                  * Recurse into child, if it also has children
130                  */
131                 if (g_node_n_children(child))
132                         gopt_set_children_visible(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         gopt_set_children_visible(o, set);
151 }
152
153 static void gopt_mark_index(struct gopt_job_view *gjv, struct gopt *gopt,
154                             unsigned int idx)
155 {
156         assert(!gjv->widgets[idx]);
157         gopt->opt_index = idx;
158         gopt->gjv = gjv;
159         gjv->widgets[idx] = gopt->box;
160 }
161
162 static void gopt_str_destroy(GtkWidget *w, gpointer data)
163 {
164         struct gopt_str *s = (struct gopt_str *) data;
165
166         free(s);
167         gtk_widget_destroy(w);
168 }
169
170 static struct gopt *gopt_new_str_store(struct gopt_job_view *gjv,
171                                        struct fio_option *o, const char *text,
172                                        unsigned int idx)
173 {
174         struct gopt_str *s;
175         GtkWidget *label;
176
177         s = malloc(sizeof(*s));
178         memset(s, 0, sizeof(*s));
179
180         s->gopt.box = gtk_hbox_new(FALSE, 3);
181         if (!o->lname)
182                 label = gtk_label_new(o->name);
183         else
184                 label = gtk_label_new(o->lname);
185
186         s->entry = gtk_entry_new();
187         gopt_mark_index(gjv, &s->gopt, idx);
188         if (text)
189                 gtk_entry_set_text(GTK_ENTRY(s->entry), text);
190         gtk_entry_set_editable(GTK_ENTRY(s->entry), 1);
191
192         if (o->def)
193                 gtk_entry_set_text(GTK_ENTRY(s->entry), o->def);
194
195         s->gopt.sig_handler = g_signal_connect(GTK_OBJECT(s->entry), "changed", G_CALLBACK(gopt_str_changed), s);
196         g_signal_connect(GTK_OBJECT(s->entry), "destroy", G_CALLBACK(gopt_str_destroy), s);
197
198         gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0);
199         gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0);
200         return &s->gopt;
201 }
202
203 static void gopt_combo_changed(GtkComboBox *box, gpointer data)
204 {
205         struct gopt_combo *c = (struct gopt_combo *) data;
206         struct fio_option *o = &fio_options[c->gopt.opt_index];
207         unsigned int index;
208
209         index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
210         gopt_set_children_visible(o, index);
211 }
212
213 static void gopt_combo_destroy(GtkWidget *w, gpointer data)
214 {
215         struct gopt_combo *c = (struct gopt_combo *) data;
216
217         free(c);
218         gtk_widget_destroy(w);
219 }
220
221 static struct gopt_combo *__gopt_new_combo(struct gopt_job_view *gjv,
222                                            struct fio_option *o,
223                                            unsigned int idx)
224 {
225         struct gopt_combo *c;
226         GtkWidget *label;
227
228         c = malloc(sizeof(*c));
229         memset(c, 0, sizeof(*c));
230
231         c->gopt.box = gtk_hbox_new(FALSE, 3);
232         if (!o->lname)
233                 label = gtk_label_new(o->name);
234         else
235                 label = gtk_label_new(o->lname);
236
237         c->combo = gtk_combo_box_new_text();
238         gopt_mark_index(gjv, &c->gopt, idx);
239         g_signal_connect(GTK_OBJECT(c->combo), "destroy", G_CALLBACK(gopt_combo_destroy), c);
240
241         gtk_box_pack_start(GTK_BOX(c->gopt.box), c->combo, FALSE, FALSE, 0);
242         gtk_box_pack_start(GTK_BOX(c->gopt.box), label, FALSE, FALSE, 0);
243
244         return c;
245 }
246
247 static struct gopt *gopt_new_combo_str(struct gopt_job_view *gjv,
248                                        struct fio_option *o, const char *text,
249                                        unsigned int idx)
250 {
251         struct gopt_combo *c;
252         struct value_pair *vp;
253         int i, active = 0;
254
255         c = __gopt_new_combo(gjv, o, idx);
256
257         i = 0;
258         vp = &o->posval[0];
259         while (vp->ival) {
260                 gtk_combo_box_append_text(GTK_COMBO_BOX(c->combo), vp->ival);
261                 if (o->def && !strcmp(vp->ival, o->def))
262                         active = i;
263                 if (text && !strcmp(vp->ival, text))
264                         active = i;
265                 vp++;
266                 i++;
267         }
268
269         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
270         c->gopt.sig_handler = g_signal_connect(GTK_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
271         return &c->gopt;
272 }
273
274 static struct gopt *gopt_new_combo_int(struct gopt_job_view *gjv,
275                                        struct fio_option *o, unsigned int *ip,
276                                        unsigned int idx)
277 {
278         struct gopt_combo *c;
279         struct value_pair *vp;
280         int i, active = 0;
281
282         c = __gopt_new_combo(gjv, o, idx);
283
284         i = 0;
285         vp = &o->posval[0];
286         while (vp->ival) {
287                 gtk_combo_box_append_text(GTK_COMBO_BOX(c->combo), vp->ival);
288                 if (ip && vp->oval == *ip)
289                         active = i;
290                 vp++;
291                 i++;
292         }
293
294         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
295         c->gopt.sig_handler = g_signal_connect(GTK_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
296         return &c->gopt;
297 }
298
299 static struct gopt *gopt_new_str_multi(struct gopt_job_view *gjv,
300                                        struct fio_option *o, unsigned int idx)
301 {
302         struct gopt_str_multi *m;
303         struct value_pair *vp;
304         GtkWidget *frame, *hbox;
305         int i;
306
307         m = malloc(sizeof(*m));
308         memset(m, 0, sizeof(*m));
309         m->gopt.box = gtk_hbox_new(FALSE, 3);
310         gopt_mark_index(gjv, &m->gopt, idx);
311
312         if (!o->lname)
313                 frame = gtk_frame_new(o->name);
314         else
315                 frame = gtk_frame_new(o->lname);
316         gtk_box_pack_start(GTK_BOX(m->gopt.box), frame, FALSE, FALSE, 3);
317
318         hbox = gtk_hbox_new(FALSE, 3);
319         gtk_container_add(GTK_CONTAINER(frame), hbox);
320
321         i = 0;
322         vp = &o->posval[0];
323         while (vp->ival) {
324                 m->checks[i] = gtk_check_button_new_with_label(vp->ival);
325                 gtk_widget_set_tooltip_text(m->checks[i], vp->help);
326                 gtk_box_pack_start(GTK_BOX(hbox), m->checks[i], FALSE, FALSE, 3);
327                 vp++;
328         }
329
330         return &m->gopt;
331 }
332
333 static void gopt_int_changed(GtkSpinButton *spin, gpointer data)
334 {
335         struct gopt_int *i = (struct gopt_int *) data;
336         struct fio_option *o = &fio_options[i->gopt.opt_index];
337         GtkAdjustment *adj;
338         int value, delta;
339
340         adj = gtk_spin_button_get_adjustment(spin);
341         value = gtk_adjustment_get_value(adj);
342         delta = value - i->lastval;
343         i->lastval = value;
344
345         if (o->inv_opt) {
346                 struct gopt *b_inv = o->inv_opt->gui_data;
347                 struct gopt_int *i_inv = container_of(b_inv, struct gopt_int, gopt);
348                 int cur_val;
349
350                 assert(o->type == o->inv_opt->type);
351
352                 cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin));
353                 cur_val -= delta;
354                 g_signal_handler_block(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
355                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val);
356                 g_signal_handler_unblock(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
357         }
358 }
359
360 static void gopt_int_destroy(GtkWidget *w, gpointer data)
361 {
362         struct gopt_int *i = (struct gopt_int *) data;
363
364         free(i);
365         gtk_widget_destroy(w);
366 }
367
368 static struct gopt_int *__gopt_new_int(struct gopt_job_view *gjv,
369                                        struct fio_option *o,
370                                        unsigned long long *p, unsigned int idx)
371 {
372         unsigned long long defval;
373         struct gopt_int *i;
374         guint maxval, interval;
375         GtkWidget *label;
376
377         i = malloc(sizeof(*i));
378         memset(i, 0, sizeof(*i));
379         i->gopt.box = gtk_hbox_new(FALSE, 3);
380         if (!o->lname)
381                 label = gtk_label_new(o->name);
382         else
383                 label = gtk_label_new(o->lname);
384
385         maxval = o->maxval;
386         if (!maxval)
387                 maxval = UINT_MAX;
388
389         defval = 0;
390         if (p)
391                 defval = *p;
392         else if (o->def) {
393                 long long val;
394
395                 check_str_bytes(o->def, &val, NULL);
396                 defval = val;
397         }
398
399         interval = 1.0;
400         if (o->interval)
401                 interval = o->interval;
402
403         i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval);
404         gopt_mark_index(gjv, &i->gopt, idx);
405         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
406         gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), defval);
407         i->lastval = defval;
408         i->gopt.sig_handler = g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i);
409         g_signal_connect(G_OBJECT(i->spin), "destroy", G_CALLBACK(gopt_int_destroy), i);
410
411         gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
412         gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
413
414         return i;
415 }
416
417 static struct gopt *gopt_new_int(struct gopt_job_view *gjv,
418                                  struct fio_option *o, unsigned int *ip,
419                                  unsigned int idx)
420 {
421         unsigned long long ullp;
422         struct gopt_int *i;
423
424         if (ip) {
425                 ullp = *ip;
426                 i = __gopt_new_int(gjv, o, &ullp, idx);
427         } else
428                 i = __gopt_new_int(gjv, o, NULL, idx);
429
430         return &i->gopt;
431 }
432
433 static struct gopt *gopt_new_ullong(struct gopt_job_view *gjv,
434                                     struct fio_option *o, unsigned long long *p,
435                                     unsigned int idx)
436 {
437         struct gopt_int *i;
438
439         i = __gopt_new_int(gjv, o, p, idx);
440         return &i->gopt;
441 }
442
443 static void gopt_bool_toggled(GtkToggleButton *button, gpointer data)
444 {
445         struct gopt_bool *b = (struct gopt_bool *) data;
446         struct fio_option *o = &fio_options[b->gopt.opt_index];
447         gboolean set;
448
449         set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
450
451         if (o->inv_opt) {
452                 struct gopt *g_inv = o->inv_opt->gui_data;
453                 struct gopt_bool *b_inv = container_of(g_inv, struct gopt_bool, gopt);
454
455                 assert(o->type == o->inv_opt->type);
456
457                 g_signal_handler_block(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
458                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_inv->check), !set);
459                 g_signal_handler_unblock(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
460         }
461
462         gopt_set_children_visible(o, set);
463 }
464
465 static void gopt_bool_destroy(GtkWidget *w, gpointer data)
466 {
467         struct gopt_bool *b = (struct gopt_bool *) data;
468
469         free(b);
470         gtk_widget_destroy(w);
471 }
472
473 static struct gopt *gopt_new_bool(struct gopt_job_view *gjv,
474                                   struct fio_option *o, unsigned int *val,
475                                   unsigned int idx)
476 {
477         struct gopt_bool *b;
478         GtkWidget *label;
479         int defstate = 0;
480
481         b = malloc(sizeof(*b));
482         memset(b, 0, sizeof(*b));
483         b->gopt.box = gtk_hbox_new(FALSE, 3);
484         if (!o->lname)
485                 label = gtk_label_new(o->name);
486         else
487                 label = gtk_label_new(o->lname);
488
489         b->check = gtk_check_button_new();
490         gopt_mark_index(gjv, &b->gopt, idx);
491         if (val)
492                 defstate = *val;
493         else if (o->def && !strcmp(o->def, "1"))
494                 defstate = 1;
495
496         if (o->neg)
497                 defstate = !defstate;
498
499         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate);
500         b->gopt.sig_handler = g_signal_connect(G_OBJECT(b->check), "toggled", G_CALLBACK(gopt_bool_toggled), b);
501         g_signal_connect(G_OBJECT(b->check), "destroy", G_CALLBACK(gopt_bool_destroy), b);
502
503         gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
504         gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
505         return &b->gopt;
506 }
507
508 /*
509  * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values.
510  * If the max is made smaller than min, adjust min down.
511  * If the min is made larger than max, adjust the max.
512  */
513 static void range_value_changed(GtkSpinButton *spin, gpointer data)
514 {
515         struct gopt_range *r = (struct gopt_range *) data;
516         int changed = -1, i;
517         gint val, mval;
518
519         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
520                 if (GTK_SPIN_BUTTON(r->spins[i]) == spin) {
521                         changed = i;
522                         break;
523                 }
524         }
525
526         assert(changed != -1);
527
528         /*
529          * Min changed
530          */
531         if (changed == 0 || changed == 2) {
532                 GtkWidget *mspin = r->spins[changed + 1];
533
534                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
535                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
536                 if (val > mval)
537                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
538         } else {
539                 GtkWidget *mspin = r->spins[changed - 1];
540
541                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
542                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
543                 if (val < mval)
544                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
545         }
546 }
547
548 static void gopt_range_destroy(GtkWidget *w, gpointer data)
549 {
550         struct gopt_range *r = (struct gopt_range *) data;
551
552         free(r);
553         gtk_widget_destroy(w);
554 }
555
556 static struct gopt *gopt_new_int_range(struct gopt_job_view *gjv,
557                                        struct fio_option *o, unsigned int **ip,
558                                        unsigned int idx)
559 {
560         struct gopt_range *r;
561         gint maxval, defval;
562         GtkWidget *label;
563         guint interval;
564         int i;
565
566         r = malloc(sizeof(*r));
567         memset(r, 0, sizeof(*r));
568         r->gopt.box = gtk_hbox_new(FALSE, 3);
569         gopt_mark_index(gjv, &r->gopt, idx);
570         if (!o->lname)
571                 label = gtk_label_new(o->name);
572         else
573                 label = gtk_label_new(o->lname);
574
575         maxval = o->maxval;
576         if (!maxval)
577                 maxval = INT_MAX;
578
579         defval = 0;
580         if (o->def) {
581                 long long val;
582
583                 check_str_bytes(o->def, &val, NULL);
584                 defval = val;
585         }
586
587         interval = 1.0;
588         if (o->interval)
589                 interval = o->interval;
590
591         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
592                 r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval);
593                 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID);
594                 if (ip)
595                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), *ip[i]);
596                 else
597                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), defval);
598
599                 gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0);
600                 g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r);
601         }
602
603         gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
604         g_signal_connect(G_OBJECT(r->gopt.box), "destroy", G_CALLBACK(gopt_range_destroy), r);
605         return &r->gopt;
606 }
607
608 static void gopt_str_val_destroy(GtkWidget *w, gpointer data)
609 {
610         struct gopt_str_val *g = (struct gopt_str_val *) data;
611
612         free(g);
613         gtk_widget_destroy(w);
614 }
615
616 static void gopt_str_val_spin_wrapped(GtkSpinButton *spin, gpointer data)
617 {
618         struct gopt_str_val *g = (struct gopt_str_val *) data;
619         unsigned int val;
620         GtkAdjustment *adj;
621         gint index;
622
623         adj = gtk_spin_button_get_adjustment(spin);
624         val = gtk_adjustment_get_value(adj);
625
626         /*
627          * Can't rely on exact value, as fast changes increment >= 1
628          */
629         if (!val) {
630                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
631                 if (index + 1 <= g->maxindex) {
632                         val = 1;
633                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), ++index);
634                 } else
635                         val = 1023;
636                 gtk_spin_button_set_value(spin, val);
637         } else {
638                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
639                 if (index) {
640                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), --index);
641                         gtk_spin_button_set_value(spin, 1023);
642                 } else
643                         gtk_spin_button_set_value(spin, 0);
644         }
645 }
646
647 static struct gopt *gopt_new_str_val(struct gopt_job_view *gjv,
648                                      struct fio_option *o,
649                                      unsigned long long *p, unsigned int idx)
650 {
651         struct gopt_str_val *g;
652         const gchar *postfix[] = { "B", "KB", "MB", "GB", "PB", "TB", "" };
653         GtkWidget *label;
654         int i;
655
656         g = malloc(sizeof(*g));
657         memset(g, 0, sizeof(*g));
658         g->gopt.box = gtk_hbox_new(FALSE, 3);
659         if (!o->lname)
660                 label = gtk_label_new(o->name);
661         else
662                 label = gtk_label_new(o->lname);
663         gopt_mark_index(gjv, &g->gopt, idx);
664
665         g->spin = gtk_spin_button_new_with_range(0.0, 1023.0, 1.0);
666         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(g->spin), GTK_UPDATE_IF_VALID);
667         gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), 0);
668         gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(g->spin), 1);
669         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->spin, FALSE, FALSE, 0);
670         g_signal_connect(G_OBJECT(g->spin), "wrapped", G_CALLBACK(gopt_str_val_spin_wrapped), g);
671
672         g->combo = gtk_combo_box_new_text();
673         i = 0;
674         while (strlen(postfix[i])) {
675                 gtk_combo_box_append_text(GTK_COMBO_BOX(g->combo), postfix[i]);
676                 i++;
677         }
678         g->maxindex = i - 1;
679         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), 0);
680         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->combo, FALSE, FALSE, 0);
681         gtk_box_pack_start(GTK_BOX(g->gopt.box), label, FALSE, FALSE, 3);
682
683         g_signal_connect(G_OBJECT(g->gopt.box), "destroy", G_CALLBACK(gopt_str_val_destroy), g);
684         return &g->gopt;
685 }
686
687 static void gopt_add_option(struct gopt_job_view *gjv, GtkWidget *hbox,
688                             struct fio_option *o, unsigned int opt_index,
689                             struct thread_options *to)
690 {
691         struct gopt *go = NULL;
692
693         switch (o->type) {
694         case FIO_OPT_STR_VAL: {
695                 unsigned long long *ullp = NULL;
696
697                 if (o->off1)
698                         ullp = td_var(to, o->off1);
699
700                 go = gopt_new_str_val(gjv, o, ullp, opt_index);
701                 break;
702                 }
703         case FIO_OPT_STR_VAL_TIME: {
704                 unsigned long long *ullp = NULL;
705
706                 if (o->off1)
707                         ullp = td_var(to, o->off1);
708
709                 go = gopt_new_ullong(gjv, o, ullp, opt_index);
710                 break;
711                 }
712         case FIO_OPT_INT: {
713                 unsigned int *ip = NULL;
714
715                 if (o->off1)
716                         ip = td_var(to, o->off1);
717
718                 go = gopt_new_int(gjv, o, ip, opt_index);
719                 break;
720                 }
721         case FIO_OPT_STR_SET:
722         case FIO_OPT_BOOL: {
723                 unsigned int *ip = NULL;
724
725                 if (o->off1)
726                         ip = td_var(to, o->off1);
727
728                 go = gopt_new_bool(gjv, o, ip, opt_index);
729                 break;
730                 }
731         case FIO_OPT_STR: {
732                 if (o->posval[0].ival) {
733                         unsigned int *ip = NULL;
734
735                         if (o->off1)
736                                 ip = td_var(to, o->off1);
737
738                         go = gopt_new_combo_int(gjv, o, ip, opt_index);
739                 } else {
740                         /* TODO: usually ->cb, or unsigned int pointer */
741                         go = gopt_new_str_store(gjv, o, NULL, opt_index);
742                 }
743
744                 break;
745                 }
746         case FIO_OPT_STR_STORE: {
747                 char *text = NULL;
748
749                 if (o->off1) {
750                         char **p = td_var(to, o->off1);
751                         text = *p;
752                 }
753
754                 if (!o->posval[0].ival) {
755                         go = gopt_new_str_store(gjv, o, text, opt_index);
756                         break;
757                 }
758
759                 go = gopt_new_combo_str(gjv, o, text, opt_index);
760                 break;
761                 }
762         case FIO_OPT_STR_MULTI:
763                 go = gopt_new_str_multi(gjv, o, opt_index);
764                 break;
765         case FIO_OPT_RANGE: {
766                 unsigned int *ip[4] = { td_var(to, o->off1),
767                                         td_var(to, o->off2),
768                                         td_var(to, o->off3),
769                                         td_var(to, o->off4) };
770
771                 go = gopt_new_int_range(gjv, o, ip, opt_index);
772                 break;
773                 }
774         /* still need to handle this one */
775         case FIO_OPT_FLOAT_LIST:
776                 break;
777         case FIO_OPT_DEPRECATED:
778                 break;
779         default:
780                 printf("ignore type %u\n", o->type);
781                 break;
782         }
783
784         if (go) {
785                 GtkWidget *dest;
786
787                 if (o->help)
788                         gtk_widget_set_tooltip_text(go->box, o->help);
789
790                 go->opt_type = o->type;
791                 o->gui_data = go;
792
793                 dest = gopt_get_group_frame(gjv, hbox, o->group);
794                 if (!dest)
795                         gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5);
796                 else
797                         gtk_box_pack_start(GTK_BOX(dest), go->box, FALSE, FALSE, 5);
798         }
799 }
800
801 static void gopt_add_options(struct gopt_job_view *gjv,
802                              struct thread_options *to)
803 {
804         GtkWidget *hbox = NULL;
805         int i;
806
807         gopt_dep_tree = g_node_new(NULL);
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 }