README: update/add mintty issue links
[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 "gerror.h"
13 #include "parse.h"
14 #include "optgroup.h"
15
16 struct gopt {
17         GtkWidget *box;
18         unsigned int opt_index;
19         unsigned int opt_type;
20         gulong sig_handler;
21         struct gopt_job_view *gjv;
22         struct flist_head changed_list;
23 };
24
25 struct gopt_combo {
26         struct gopt gopt;
27         GtkWidget *combo;
28 };
29
30 struct gopt_int {
31         struct gopt gopt;
32         unsigned long long lastval;
33         GtkWidget *spin;
34 };
35
36 struct gopt_bool {
37         struct gopt gopt;
38         GtkWidget *check;
39 };
40
41 struct gopt_str {
42         struct gopt gopt;
43         GtkWidget *entry;
44 };
45
46 struct gopt_str_val {
47         struct gopt gopt;
48         GtkWidget *spin;
49         GtkWidget *combo;
50         unsigned int maxindex;
51 };
52
53 #define GOPT_RANGE_SPIN 4
54
55 struct gopt_range {
56         struct gopt gopt;
57         GtkWidget *spins[GOPT_RANGE_SPIN];
58 };
59
60 struct gopt_str_multi {
61         struct gopt gopt;
62         GtkWidget *checks[PARSE_MAX_VP];
63 };
64
65 enum {
66         GOPT_COMBO_INT = 1,
67         GOPT_COMBO_STR,
68         GOPT_INT,
69         GOPT_BOOL,
70         GOPT_STR,
71         GOPT_STR_VAL,
72         GOPT_RANGE,
73         GOPT_STR_MULTI,
74 };
75
76 struct gopt_frame_widget {
77         GtkWidget *vbox[2];
78         unsigned int nr;
79 };
80
81 struct gopt_job_view {
82         struct gopt_frame_widget g_widgets[__FIO_OPT_G_NR];
83         GtkWidget *vboxes[__FIO_OPT_C_NR];
84         struct gopt *gopts[FIO_MAX_OPTS];
85         GtkWidget *dialog;
86         GtkWidget *job_combo;
87         struct gfio_client *client;
88         struct flist_head changed_list;
89         struct thread_options *o;
90         int in_job_switch;
91 };
92
93 static GNode *gopt_dep_tree;
94
95 static GtkWidget *gopt_get_group_frame(struct gopt_job_view *gjv,
96                                        GtkWidget *box, uint64_t groupmask)
97 {
98         uint64_t mask, group;
99         const struct opt_group *og;
100         GtkWidget *frame, *hbox;
101         struct gopt_frame_widget *gfw;
102
103         if (!groupmask)
104                 return 0;
105
106         mask = groupmask;
107         og = opt_group_cat_from_mask(&mask);
108         if (!og)
109                 return NULL;
110
111         group = ffz64(~groupmask);
112         gfw = &gjv->g_widgets[group];
113         if (!gfw->vbox[0]) {
114                 frame = gtk_frame_new(og->name);
115                 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3);
116                 hbox = gtk_hbox_new(FALSE, 0);
117                 gtk_container_add(GTK_CONTAINER(frame), hbox);
118                 gfw->vbox[0] = gtk_vbox_new(TRUE, 5);
119                 gfw->vbox[1] = gtk_vbox_new(TRUE, 5);
120                 gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[0], TRUE, TRUE, 5);
121                 gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[1], TRUE, TRUE, 5);
122         }
123
124         hbox = gtk_hbox_new(FALSE, 3);
125         gtk_box_pack_start(GTK_BOX(gfw->vbox[gfw->nr++ & 1]), hbox, FALSE, FALSE, 5);
126         return hbox;
127 }
128
129 /*
130  * Mark children as invisible, if needed.
131  */
132 static void gopt_set_children_visible(struct gopt_job_view *gjv,
133                                       struct fio_option *parent,
134                                       gboolean visible)
135 {
136         GNode *child, *node;
137
138         if (parent->hide_on_set)
139                 visible = !visible;
140
141         node = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
142         child = g_node_first_child(node);
143         while (child) {
144                 struct fio_option *o = child->data;
145                 struct gopt *g = o->gui_data;
146                 GtkWidget *widget = g->box;
147
148                 /*
149                  * Recurse into child, if it also has children
150                  */
151                 if (g_node_n_children(child))
152                         gopt_set_children_visible(gjv, o, visible);
153
154                 gtk_widget_set_sensitive(widget, visible);
155                 child = g_node_next_sibling(child);
156         }
157 }
158
159 static void gopt_mark_index(struct gopt_job_view *gjv, struct gopt *gopt,
160                             unsigned int idx, int type)
161 {
162         INIT_FLIST_HEAD(&gopt->changed_list);
163
164         assert(!gjv->gopts[idx]);
165         gopt->opt_index = idx;
166         gopt->opt_type = type;
167         gopt->gjv = gjv;
168         gjv->gopts[idx] = gopt;
169 }
170
171 static void gopt_dialog_update_apply_button(struct gopt_job_view *gjv)
172 {
173         GtkDialog *dialog = GTK_DIALOG(gjv->dialog);
174         gboolean set;
175
176         set = !flist_empty(&gjv->changed_list);
177         gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_APPLY, set);
178
179         if (set) {
180                 gtk_widget_set_sensitive(gjv->job_combo, 0);
181                 gtk_widget_set_tooltip_text(gjv->job_combo, "Apply option changes before switching to a new job");
182         } else {
183                 gtk_widget_set_sensitive(gjv->job_combo, 1);
184                 gtk_widget_set_tooltip_text(gjv->job_combo, "Change current job");
185         }
186 }
187
188 static void gopt_changed(struct gopt *gopt)
189 {
190         struct gopt_job_view *gjv = gopt->gjv;
191
192         if (gjv->in_job_switch)
193                 return;
194
195         /*
196          * Add to changed list. This also prevents the option from being
197          * freed when the widget is destroyed.
198          */
199         if (flist_empty(&gopt->changed_list)) {
200                 flist_add_tail(&gopt->changed_list, &gjv->changed_list);
201                 gopt_dialog_update_apply_button(gjv);
202         }
203 }
204
205 static void gopt_str_changed(GtkEntry *entry, gpointer data)
206 {
207         struct gopt_str *s = (struct gopt_str *) data;
208         struct fio_option *o = &fio_options[s->gopt.opt_index];
209         const gchar *text;
210         int set;
211
212         gopt_changed(&s->gopt);
213
214         text = gtk_entry_get_text(GTK_ENTRY(s->entry));
215         set = strcmp(text, "") != 0;
216
217         gopt_set_children_visible(s->gopt.gjv, o, set);
218 }
219
220 static void gopt_str_destroy(GtkWidget *w, gpointer data)
221 {
222         struct gopt_str *s = (struct gopt_str *) data;
223
224         free(s);
225         gtk_widget_destroy(w);
226 }
227
228 static void gopt_str_store_set_val(struct gopt_str *s, const char *text)
229 {
230         if (text)
231                 gtk_entry_set_text(GTK_ENTRY(s->entry), text);
232 }
233
234 static struct gopt *gopt_new_str_store(struct gopt_job_view *gjv,
235                                        struct fio_option *o, const char *text,
236                                        unsigned int idx)
237 {
238         struct gopt_str *s;
239         GtkWidget *label;
240
241         s = calloc(1, sizeof(*s));
242
243         s->gopt.box = gtk_hbox_new(FALSE, 3);
244         if (!o->lname)
245                 label = gtk_label_new(o->name);
246         else
247                 label = gtk_label_new(o->lname);
248
249         s->entry = gtk_entry_new();
250         gopt_mark_index(gjv, &s->gopt, idx, GOPT_STR);
251         gtk_editable_set_editable(GTK_EDITABLE(s->entry), 1);
252
253         if (text)
254                 gopt_str_store_set_val(s, text);
255         else if (o->def)
256                 gopt_str_store_set_val(s, o->def);
257
258         s->gopt.sig_handler = g_signal_connect(G_OBJECT(s->entry), "changed", G_CALLBACK(gopt_str_changed), s);
259         g_signal_connect(G_OBJECT(s->entry), "destroy", G_CALLBACK(gopt_str_destroy), s);
260
261         gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0);
262         gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0);
263         return &s->gopt;
264 }
265
266 static void gopt_combo_changed(GtkComboBox *box, gpointer data)
267 {
268         struct gopt_combo *c = (struct gopt_combo *) data;
269         struct fio_option *o = &fio_options[c->gopt.opt_index];
270         unsigned int index;
271
272         gopt_changed(&c->gopt);
273
274         index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
275
276         gopt_set_children_visible(c->gopt.gjv, o, index);
277 }
278
279 static void gopt_combo_destroy(GtkWidget *w, gpointer data)
280 {
281         struct gopt_combo *c = (struct gopt_combo *) data;
282
283         free(c);
284         gtk_widget_destroy(w);
285 }
286
287 static struct gopt_combo *__gopt_new_combo(struct gopt_job_view *gjv,
288                                            struct fio_option *o,
289                                            unsigned int idx, int type)
290 {
291         struct gopt_combo *c;
292         GtkWidget *label;
293
294         c = calloc(1, sizeof(*c));
295
296         c->gopt.box = gtk_hbox_new(FALSE, 3);
297         if (!o->lname)
298                 label = gtk_label_new(o->name);
299         else
300                 label = gtk_label_new(o->lname);
301
302         c->combo = gtk_combo_box_text_new();
303         gopt_mark_index(gjv, &c->gopt, idx, type);
304         g_signal_connect(G_OBJECT(c->combo), "destroy", G_CALLBACK(gopt_combo_destroy), c);
305
306         gtk_box_pack_start(GTK_BOX(c->gopt.box), c->combo, FALSE, FALSE, 0);
307         gtk_box_pack_start(GTK_BOX(c->gopt.box), label, FALSE, FALSE, 0);
308
309         return c;
310 }
311
312 static void gopt_combo_str_set_val(struct gopt_combo *c, const char *text)
313 {
314         struct fio_option *o = &fio_options[c->gopt.opt_index];
315         struct value_pair *vp;
316         int i;
317
318         i = 0;
319         vp = &o->posval[0];
320         while (vp->ival) {
321                 if (!strcmp(vp->ival, text)) {
322                         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), i);
323                         break;
324                 }
325                 vp++;
326                 i++;
327         }
328 }
329
330 static struct gopt *gopt_new_combo_str(struct gopt_job_view *gjv,
331                                        struct fio_option *o, const char *text,
332                                        unsigned int idx)
333 {
334         struct gopt_combo *c;
335         struct value_pair *vp;
336         int i, active = 0;
337
338         c = __gopt_new_combo(gjv, o, idx, GOPT_COMBO_STR);
339
340         i = 0;
341         vp = &o->posval[0];
342         while (vp->ival) {
343                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c->combo), vp->ival);
344                 if (o->def && !strcmp(vp->ival, o->def))
345                         active = i;
346                 vp++;
347                 i++;
348         }
349
350         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
351         if (text)
352                 gopt_combo_str_set_val(c, text);
353         c->gopt.sig_handler = g_signal_connect(G_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
354         return &c->gopt;
355 }
356
357 static void gopt_combo_int_set_val(struct gopt_combo *c, unsigned int ip)
358 {
359         struct fio_option *o = &fio_options[c->gopt.opt_index];
360         struct value_pair *vp;
361         int i;
362
363         i = 0;
364         vp = &o->posval[0];
365         while (vp->ival) {
366                 if (vp->oval == ip) {
367                         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), i);
368                         break;
369                 }
370                 vp++;
371                 i++;
372         }
373 }
374
375 static struct gopt *gopt_new_combo_int(struct gopt_job_view *gjv,
376                                        struct fio_option *o, unsigned int *ip,
377                                        unsigned int idx)
378 {
379         struct gopt_combo *c;
380         struct value_pair *vp;
381         int i, active = 0;
382
383         c = __gopt_new_combo(gjv, o, idx, GOPT_COMBO_INT);
384
385         i = 0;
386         vp = &o->posval[0];
387         while (vp->ival) {
388                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c->combo), vp->ival);
389                 if (ip && vp->oval == *ip)
390                         active = i;
391                 vp++;
392                 i++;
393         }
394
395         gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
396         if (ip)
397                 gopt_combo_int_set_val(c, *ip);
398         c->gopt.sig_handler = g_signal_connect(G_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
399         return &c->gopt;
400 }
401
402 static void gopt_str_multi_toggled(GtkToggleButton *button, gpointer data)
403 {
404         struct gopt_str_multi *m = (struct gopt_str_multi *) data;
405
406         gopt_changed(&m->gopt);
407 }
408
409 static void gopt_str_multi_destroy(GtkWidget *w, gpointer data)
410 {
411         struct gopt_str_multi *m = (struct gopt_str_multi *) data;
412
413         free(m);
414         gtk_widget_destroy(w);
415 }
416
417 static void gopt_str_multi_set_val(struct gopt_str_multi *m, int val)
418 {
419 }
420
421 static struct gopt *gopt_new_str_multi(struct gopt_job_view *gjv,
422                                        struct fio_option *o, unsigned int idx)
423 {
424         struct gopt_str_multi *m;
425         struct value_pair *vp;
426         GtkWidget *frame, *hbox;
427         int i;
428
429         m = calloc(1, sizeof(*m));
430         m->gopt.box = gtk_hbox_new(FALSE, 3);
431         gopt_mark_index(gjv, &m->gopt, idx, GOPT_STR_MULTI);
432
433         if (!o->lname)
434                 frame = gtk_frame_new(o->name);
435         else
436                 frame = gtk_frame_new(o->lname);
437         gtk_box_pack_start(GTK_BOX(m->gopt.box), frame, FALSE, FALSE, 3);
438
439         hbox = gtk_hbox_new(FALSE, 3);
440         gtk_container_add(GTK_CONTAINER(frame), hbox);
441
442         i = 0;
443         vp = &o->posval[0];
444         while (vp->ival) {
445                 m->checks[i] = gtk_check_button_new_with_label(vp->ival);
446                 gtk_widget_set_tooltip_text(m->checks[i], vp->help);
447                 gtk_box_pack_start(GTK_BOX(hbox), m->checks[i], FALSE, FALSE, 3);
448                 g_signal_connect(G_OBJECT(m->checks[i]), "toggled", G_CALLBACK(gopt_str_multi_toggled), m);
449                 vp++;
450                 i++;
451         }
452
453         gopt_str_multi_set_val(m, 0);
454         g_signal_connect(G_OBJECT(m->gopt.box), "destroy", G_CALLBACK(gopt_str_multi_destroy), m);
455         return &m->gopt;
456 }
457
458 static void gopt_int_changed(GtkSpinButton *spin, gpointer data)
459 {
460         struct gopt_int *i = (struct gopt_int *) data;
461         struct fio_option *o = &fio_options[i->gopt.opt_index];
462         GtkAdjustment *adj;
463         int value, delta;
464
465         gopt_changed(&i->gopt);
466
467         adj = gtk_spin_button_get_adjustment(spin);
468         value = gtk_adjustment_get_value(adj);
469         delta = value - i->lastval;
470         i->lastval = value;
471
472         if (o->inv_opt) {
473                 struct gopt *b_inv = o->inv_opt->gui_data;
474                 struct gopt_int *i_inv = container_of(b_inv, struct gopt_int, gopt);
475                 int cur_val;
476
477                 assert(o->type == o->inv_opt->type);
478
479                 cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin));
480                 cur_val -= delta;
481                 g_signal_handler_block(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
482                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val);
483                 g_signal_handler_unblock(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
484         }
485 }
486
487 static void gopt_int_destroy(GtkWidget *w, gpointer data)
488 {
489         struct gopt_int *i = (struct gopt_int *) data;
490
491         free(i);
492         gtk_widget_destroy(w);
493 }
494
495 static void gopt_int_set_val(struct gopt_int *i, unsigned long long p)
496 {
497         gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), p);
498         i->lastval = p;
499 }
500
501 static struct gopt_int *__gopt_new_int(struct gopt_job_view *gjv,
502                                        struct fio_option *o,
503                                        unsigned long long *p, unsigned int idx)
504 {
505         unsigned long long defval;
506         struct gopt_int *i;
507         guint maxval, interval;
508         GtkWidget *label;
509
510         i = calloc(1, sizeof(*i));
511         i->gopt.box = gtk_hbox_new(FALSE, 3);
512         if (!o->lname)
513                 label = gtk_label_new(o->name);
514         else
515                 label = gtk_label_new(o->lname);
516
517         maxval = o->maxval;
518         if (!maxval)
519                 maxval = UINT_MAX;
520
521         defval = 0;
522         if (p)
523                 defval = *p;
524         else if (o->def) {
525                 long long val;
526
527                 check_str_bytes(o->def, &val, o);
528                 defval = val;
529         }
530
531         interval = 1.0;
532         if (o->interval)
533                 interval = o->interval;
534
535         i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval);
536         gopt_mark_index(gjv, &i->gopt, idx, GOPT_INT);
537         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
538         if (p)
539                 gopt_int_set_val(i, *p);
540         else
541                 gopt_int_set_val(i, defval);
542         i->gopt.sig_handler = g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i);
543         g_signal_connect(G_OBJECT(i->spin), "destroy", G_CALLBACK(gopt_int_destroy), i);
544
545         gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
546         gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
547
548         return i;
549 }
550
551 static struct gopt *gopt_new_int(struct gopt_job_view *gjv,
552                                  struct fio_option *o, unsigned int *ip,
553                                  unsigned int idx)
554 {
555         unsigned long long ullp;
556         struct gopt_int *i;
557
558         if (ip) {
559                 ullp = *ip;
560                 i = __gopt_new_int(gjv, o, &ullp, idx);
561         } else
562                 i = __gopt_new_int(gjv, o, NULL, idx);
563
564         return &i->gopt;
565 }
566
567 static struct gopt *gopt_new_ullong(struct gopt_job_view *gjv,
568                                     struct fio_option *o, unsigned long long *p,
569                                     unsigned int idx)
570 {
571         struct gopt_int *i;
572
573         i = __gopt_new_int(gjv, o, p, idx);
574         return &i->gopt;
575 }
576
577 static void gopt_bool_toggled(GtkToggleButton *button, gpointer data)
578 {
579         struct gopt_bool *b = (struct gopt_bool *) data;
580         struct fio_option *o = &fio_options[b->gopt.opt_index];
581         gboolean set;
582
583         gopt_changed(&b->gopt);
584
585         set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
586
587         if (o->inv_opt) {
588                 struct gopt *g_inv = o->inv_opt->gui_data;
589                 struct gopt_bool *b_inv = container_of(g_inv, struct gopt_bool, gopt);
590
591                 assert(o->type == o->inv_opt->type);
592
593                 g_signal_handler_block(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
594                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_inv->check), !set);
595                 g_signal_handler_unblock(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
596         }
597
598         gopt_set_children_visible(b->gopt.gjv, o, set);
599 }
600
601 static void gopt_bool_destroy(GtkWidget *w, gpointer data)
602 {
603         struct gopt_bool *b = (struct gopt_bool *) data;
604
605         free(b);
606         gtk_widget_destroy(w);
607 }
608
609 static void gopt_bool_set_val(struct gopt_bool *b, unsigned int val)
610 {
611         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), val);
612 }
613
614 static struct gopt *gopt_new_bool(struct gopt_job_view *gjv,
615                                   struct fio_option *o, unsigned int *val,
616                                   unsigned int idx)
617 {
618         struct gopt_bool *b;
619         GtkWidget *label;
620         int defstate = 0;
621
622         b = calloc(1, sizeof(*b));
623         b->gopt.box = gtk_hbox_new(FALSE, 3);
624         if (!o->lname)
625                 label = gtk_label_new(o->name);
626         else
627                 label = gtk_label_new(o->lname);
628
629         b->check = gtk_check_button_new();
630         gopt_mark_index(gjv, &b->gopt, idx, GOPT_BOOL);
631         if (o->def && !strcmp(o->def, "1"))
632                 defstate = 1;
633
634         if (o->neg)
635                 defstate = !defstate;
636
637         if (val)
638                 gopt_bool_set_val(b, *val);
639         else
640                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate);
641         b->gopt.sig_handler = g_signal_connect(G_OBJECT(b->check), "toggled", G_CALLBACK(gopt_bool_toggled), b);
642         g_signal_connect(G_OBJECT(b->check), "destroy", G_CALLBACK(gopt_bool_destroy), b);
643
644         gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
645         gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
646         return &b->gopt;
647 }
648
649 /*
650  * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values.
651  * If the max is made smaller than min, adjust min down.
652  * If the min is made larger than max, adjust the max.
653  */
654 static void range_value_changed(GtkSpinButton *spin, gpointer data)
655 {
656         struct gopt_range *r = (struct gopt_range *) data;
657         int changed = -1, i;
658         gint val, mval;
659
660         gopt_changed(&r->gopt);
661
662         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
663                 if (GTK_SPIN_BUTTON(r->spins[i]) == spin) {
664                         changed = i;
665                         break;
666                 }
667         }
668
669         assert(changed != -1);
670
671         /*
672          * Min changed
673          */
674         if (changed == 0 || changed == 2) {
675                 GtkWidget *mspin = r->spins[changed + 1];
676
677                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
678                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
679                 if (val > mval)
680                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
681         } else {
682                 GtkWidget *mspin = r->spins[changed - 1];
683
684                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
685                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
686                 if (val < mval)
687                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
688         }
689 }
690
691 static void gopt_range_destroy(GtkWidget *w, gpointer data)
692 {
693         struct gopt_range *r = (struct gopt_range *) data;
694
695         free(r);
696         gtk_widget_destroy(w);
697 }
698
699 static void gopt_int_range_set_val(struct gopt_range *r, unsigned int *vals)
700 {
701         int i;
702
703         for (i = 0; i < GOPT_RANGE_SPIN; i++)
704                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), vals[i]);
705 }
706
707 static struct gopt *gopt_new_int_range(struct gopt_job_view *gjv,
708                                        struct fio_option *o, unsigned int **ip,
709                                        unsigned int idx)
710 {
711         struct gopt_range *r;
712         GtkWidget *label;
713         guint interval;
714         unsigned int defvals[GOPT_RANGE_SPIN];
715         gint maxval;
716         int i;
717
718         r = calloc(1, sizeof(*r));
719         r->gopt.box = gtk_hbox_new(FALSE, 3);
720         gopt_mark_index(gjv, &r->gopt, idx, GOPT_RANGE);
721         if (!o->lname)
722                 label = gtk_label_new(o->name);
723         else
724                 label = gtk_label_new(o->lname);
725
726         maxval = o->maxval;
727         if (!maxval)
728                 maxval = INT_MAX;
729
730         memset(defvals, 0, sizeof(defvals));
731         if (o->def) {
732                 long long val;
733
734                 check_str_bytes(o->def, &val, o);
735                 for (i = 0; i < GOPT_RANGE_SPIN; i++)
736                         defvals[i] = val;
737         }
738
739         interval = 1.0;
740         if (o->interval)
741                 interval = o->interval;
742
743         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
744                 r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval);
745                 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID);
746                 gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0);
747         }
748
749         if (ip)
750                 gopt_int_range_set_val(r, *ip);
751         else
752                 gopt_int_range_set_val(r, defvals);
753
754         for (i = 0; i < GOPT_RANGE_SPIN; i++)
755                 g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r);
756
757         gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
758         g_signal_connect(G_OBJECT(r->gopt.box), "destroy", G_CALLBACK(gopt_range_destroy), r);
759         return &r->gopt;
760 }
761
762 static void gopt_str_val_destroy(GtkWidget *w, gpointer data)
763 {
764         struct gopt_str_val *g = (struct gopt_str_val *) data;
765
766         free(g);
767         gtk_widget_destroy(w);
768 }
769
770 static void gopt_str_val_spin_wrapped(GtkSpinButton *spin, gpointer data)
771 {
772         struct gopt_str_val *g = (struct gopt_str_val *) data;
773         unsigned int val;
774         GtkAdjustment *adj;
775         gint index;
776
777         adj = gtk_spin_button_get_adjustment(spin);
778         val = gtk_adjustment_get_value(adj);
779
780         /*
781          * Can't rely on exact value, as fast changes increment >= 1
782          */
783         if (!val) {
784                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
785                 if (index + 1 <= g->maxindex) {
786                         val = 1;
787                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), ++index);
788                 } else
789                         val = 1023;
790                 gtk_spin_button_set_value(spin, val);
791         } else {
792                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
793                 if (index) {
794                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), --index);
795                         gtk_spin_button_set_value(spin, 1023);
796                 } else
797                         gtk_spin_button_set_value(spin, 0);
798         }
799 }
800
801 static void gopt_str_val_changed(GtkSpinButton *spin, gpointer data)
802 {
803         struct gopt_str_val *g = (struct gopt_str_val *) data;
804
805         gopt_changed(&g->gopt);
806 }
807
808 static void gopt_str_val_set_val(struct gopt_str_val *g, unsigned long long val)
809 {
810         int i = 0;
811
812         do {
813                 if (!val || (val % 1024))
814                         break;
815
816                 i++;
817                 val /= 1024;
818         } while (1);
819
820         gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), val);
821         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), i);
822 }
823
824 static struct gopt *gopt_new_str_val(struct gopt_job_view *gjv,
825                                      struct fio_option *o,
826                                      unsigned long long *p, unsigned int idx)
827 {
828         struct gopt_str_val *g;
829         const gchar *postfix[] = { "B", "KiB", "MiB", "GiB", "PiB", "PiB", "" };
830         GtkWidget *label;
831         int i;
832
833         g = calloc(1, sizeof(*g));
834         g->gopt.box = gtk_hbox_new(FALSE, 3);
835         if (!o->lname)
836                 label = gtk_label_new(o->name);
837         else
838                 label = gtk_label_new(o->lname);
839         gopt_mark_index(gjv, &g->gopt, idx, GOPT_STR_VAL);
840
841         g->spin = gtk_spin_button_new_with_range(0.0, 1023.0, 1.0);
842         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(g->spin), GTK_UPDATE_IF_VALID);
843         gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), 0);
844         gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(g->spin), 1);
845         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->spin, FALSE, FALSE, 0);
846         g_signal_connect(G_OBJECT(g->spin), "wrapped", G_CALLBACK(gopt_str_val_spin_wrapped), g);
847         g_signal_connect(G_OBJECT(g->spin), "changed", G_CALLBACK(gopt_str_val_changed), g);
848
849         g->combo = gtk_combo_box_text_new();
850         i = 0;
851         while (strlen(postfix[i])) {
852                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(g->combo), postfix[i]);
853                 i++;
854         }
855         g->maxindex = i - 1;
856         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), 0);
857         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->combo, FALSE, FALSE, 0);
858         gtk_box_pack_start(GTK_BOX(g->gopt.box), label, FALSE, FALSE, 3);
859
860         if (p)
861                 gopt_str_val_set_val(g, *p);
862
863         g_signal_connect(G_OBJECT(g->combo), "changed", G_CALLBACK(gopt_str_val_changed), g);
864
865         g_signal_connect(G_OBJECT(g->gopt.box), "destroy", G_CALLBACK(gopt_str_val_destroy), g);
866         return &g->gopt;
867 }
868
869 static void gopt_set_option(struct gopt_job_view *gjv, struct fio_option *o,
870                             struct gopt *gopt, struct thread_options *to)
871 {
872         switch (o->type) {
873         case FIO_OPT_STR_VAL: {
874                 unsigned long long *ullp = NULL;
875                 struct gopt_str_val *g;
876
877                 if (o->off1)
878                         ullp = td_var(to, o, o->off1);
879
880                 g = container_of(gopt, struct gopt_str_val, gopt);
881                 if (ullp)
882                         gopt_str_val_set_val(g, *ullp);
883                 break;
884                 }
885         case FIO_OPT_STR_VAL_TIME: {
886                 unsigned long long *ullp = NULL;
887                 struct gopt_int *i;
888
889                 if (o->off1)
890                         ullp = td_var(to, o, o->off1);
891
892                 i = container_of(gopt, struct gopt_int, gopt);
893                 if (ullp)
894                         gopt_int_set_val(i, *ullp);
895                 break;
896                 }
897         case FIO_OPT_INT:
898                 if (o->posval[0].ival) {
899                         unsigned int *ip = NULL;
900                         struct gopt_combo *c;
901
902                         if (o->off1)
903                                 ip = td_var(to, o, o->off1);
904
905                         c = container_of(gopt, struct gopt_combo, gopt);
906                         if (ip)
907                                 gopt_combo_int_set_val(c, *ip);
908                 } else {
909                         unsigned int *ip = NULL;
910                         struct gopt_int *i;
911
912                         if (o->off1)
913                                 ip = td_var(to, o, o->off1);
914
915                         i = container_of(gopt, struct gopt_int, gopt);
916                         if (ip)
917                                 gopt_int_set_val(i, *ip);
918                 }
919                 break;
920         case FIO_OPT_STR_SET:
921         case FIO_OPT_BOOL: {
922                 unsigned int *ip = NULL;
923                 struct gopt_bool *b;
924
925                 if (o->off1)
926                         ip = td_var(to, o, o->off1);
927
928                 b = container_of(gopt, struct gopt_bool, gopt);
929                 if (ip)
930                         gopt_bool_set_val(b, *ip);
931                 break;
932                 }
933         case FIO_OPT_STR: {
934                 if (o->posval[0].ival) {
935                         unsigned int *ip = NULL;
936                         struct gopt_combo *c;
937
938                         if (o->off1)
939                                 ip = td_var(to, o, o->off1);
940
941                         c = container_of(gopt, struct gopt_combo, gopt);
942                         if (ip)
943                                 gopt_combo_int_set_val(c, *ip);
944                 } else {
945                         struct gopt_str *s;
946                         char *text = NULL;
947
948                         if (o->off1) {
949                                 char **p = td_var(to, o, o->off1);
950
951                                 text = *p;
952                         }
953
954                         s = container_of(gopt, struct gopt_str, gopt);
955                         gopt_str_store_set_val(s, text);
956                 }
957
958                 break;
959                 }
960         case FIO_OPT_STR_STORE: {
961                 struct gopt_combo *c;
962                 char *text = NULL;
963
964                 if (o->off1) {
965                         char **p = td_var(to, o, o->off1);
966                         text = *p;
967                 }
968
969                 if (!o->posval[0].ival) {
970                         struct gopt_str *s;
971
972                         s = container_of(gopt, struct gopt_str, gopt);
973                         gopt_str_store_set_val(s, text);
974                         break;
975                 }
976
977                 c = container_of(gopt, struct gopt_combo, gopt);
978                 if (text)
979                         gopt_combo_str_set_val(c, text);
980                 break;
981                 }
982         case FIO_OPT_STR_MULTI:
983                 /* HANDLE ME */
984                 break;
985         case FIO_OPT_RANGE: {
986                 struct gopt_range *r;
987                 unsigned int *ip[4] = { td_var(to, o, o->off1),
988                                         td_var(to, o, o->off2),
989                                         td_var(to, o, o->off3),
990                                         td_var(to, o, o->off4) };
991
992                 r = container_of(gopt, struct gopt_range, gopt);
993                 gopt_int_range_set_val(r, *ip);
994                 break;
995                 }
996         /* still need to handle this one */
997         case FIO_OPT_FLOAT_LIST:
998                 break;
999         case FIO_OPT_DEPRECATED:
1000                 break;
1001         default:
1002                 printf("ignore type %u\n", o->type);
1003                 break;
1004         }
1005 }
1006
1007 static void gopt_add_option(struct gopt_job_view *gjv, GtkWidget *hbox,
1008                             struct fio_option *o, unsigned int opt_index,
1009                             struct thread_options *to)
1010 {
1011         struct gopt *go = NULL;
1012
1013         switch (o->type) {
1014         case FIO_OPT_STR_VAL: {
1015                 unsigned long long *ullp = NULL;
1016
1017                 if (o->off1)
1018                         ullp = td_var(to, o, o->off1);
1019
1020                 go = gopt_new_str_val(gjv, o, ullp, opt_index);
1021                 break;
1022                 }
1023         case FIO_OPT_STR_VAL_TIME: {
1024                 unsigned long long *ullp = NULL;
1025
1026                 if (o->off1)
1027                         ullp = td_var(to, o, o->off1);
1028
1029                 go = gopt_new_ullong(gjv, o, ullp, opt_index);
1030                 break;
1031                 }
1032         case FIO_OPT_INT:
1033                 if (o->posval[0].ival) {
1034                         unsigned int *ip = NULL;
1035
1036                         if (o->off1)
1037                                 ip = td_var(to, o, o->off1);
1038
1039                         go = gopt_new_combo_int(gjv, o, ip, opt_index);
1040                 } else {
1041                         unsigned int *ip = NULL;
1042
1043                         if (o->off1)
1044                                 ip = td_var(to, o, o->off1);
1045
1046                         go = gopt_new_int(gjv, o, ip, opt_index);
1047                 }
1048                 break;
1049         case FIO_OPT_STR_SET:
1050         case FIO_OPT_BOOL: {
1051                 unsigned int *ip = NULL;
1052
1053                 if (o->off1)
1054                         ip = td_var(to, o, o->off1);
1055
1056                 go = gopt_new_bool(gjv, o, ip, opt_index);
1057                 break;
1058                 }
1059         case FIO_OPT_STR: {
1060                 if (o->posval[0].ival) {
1061                         unsigned int *ip = NULL;
1062
1063                         if (o->off1)
1064                                 ip = td_var(to, o, o->off1);
1065
1066                         go = gopt_new_combo_int(gjv, o, ip, opt_index);
1067                 } else {
1068                         /* TODO: usually ->cb, or unsigned int pointer */
1069                         go = gopt_new_str_store(gjv, o, NULL, opt_index);
1070                 }
1071
1072                 break;
1073                 }
1074         case FIO_OPT_STR_STORE: {
1075                 char *text = NULL;
1076
1077                 if (o->off1) {
1078                         char **p = td_var(to, o, o->off1);
1079                         text = *p;
1080                 }
1081
1082                 if (!o->posval[0].ival) {
1083                         go = gopt_new_str_store(gjv, o, text, opt_index);
1084                         break;
1085                 }
1086
1087                 go = gopt_new_combo_str(gjv, o, text, opt_index);
1088                 break;
1089                 }
1090         case FIO_OPT_STR_MULTI:
1091                 go = gopt_new_str_multi(gjv, o, opt_index);
1092                 break;
1093         case FIO_OPT_RANGE: {
1094                 unsigned int *ip[4] = { td_var(to, o, o->off1),
1095                                         td_var(to, o, o->off2),
1096                                         td_var(to, o, o->off3),
1097                                         td_var(to, o, o->off4) };
1098
1099                 go = gopt_new_int_range(gjv, o, ip, opt_index);
1100                 break;
1101                 }
1102         /* still need to handle this one */
1103         case FIO_OPT_FLOAT_LIST:
1104                 break;
1105         case FIO_OPT_DEPRECATED:
1106                 break;
1107         default:
1108                 printf("ignore type %u\n", o->type);
1109                 break;
1110         }
1111
1112         if (go) {
1113                 GtkWidget *dest;
1114
1115                 if (o->help)
1116                         gtk_widget_set_tooltip_text(go->box, o->help);
1117
1118                 o->gui_data = go;
1119
1120                 dest = gopt_get_group_frame(gjv, hbox, o->group);
1121                 if (!dest)
1122                         gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5);
1123                 else
1124                         gtk_box_pack_start(GTK_BOX(dest), go->box, FALSE, FALSE, 5);
1125         }
1126 }
1127
1128 static void gopt_add_options(struct gopt_job_view *gjv,
1129                              struct thread_options *to)
1130 {
1131         GtkWidget *hbox = NULL;
1132         int i;
1133
1134         /*
1135          * First add all options
1136          */
1137         for (i = 0; fio_options[i].name; i++) {
1138                 struct fio_option *o = &fio_options[i];
1139                 uint64_t mask = o->category;
1140                 const struct opt_group *og;
1141
1142                 while ((og = opt_group_from_mask(&mask)) != NULL) {
1143                         GtkWidget *vbox = gjv->vboxes[ffz64(~og->mask)];
1144
1145                         hbox = gtk_hbox_new(FALSE, 3);
1146                         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1147                         gopt_add_option(gjv, hbox, o, i, to);
1148                 }
1149         }
1150 }
1151
1152 static void gopt_set_options(struct gopt_job_view *gjv,
1153                              struct thread_options *to)
1154 {
1155         int i;
1156
1157         for (i = 0; fio_options[i].name; i++) {
1158                 struct fio_option *o = &fio_options[i];
1159                 struct gopt *gopt = gjv->gopts[i];
1160
1161                 gopt_set_option(gjv, o, gopt, to);
1162         }
1163 }
1164
1165 static GtkWidget *gopt_add_tab(GtkWidget *notebook, const char *name)
1166 {
1167         GtkWidget *box, *vbox, *scroll;
1168
1169         scroll = gtk_scrolled_window_new(NULL, NULL);
1170         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1171         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1172
1173         vbox = gtk_vbox_new(FALSE, 3);
1174         box = gtk_hbox_new(FALSE, 0);
1175         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1176         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1177         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scroll, gtk_label_new(name));
1178         return vbox;
1179 }
1180
1181 static GtkWidget *gopt_add_group_tab(GtkWidget *notebook,
1182                                      const struct opt_group *og)
1183 {
1184         return gopt_add_tab(notebook, og->name);
1185 }
1186
1187 static void gopt_add_group_tabs(GtkWidget *notebook, struct gopt_job_view *gjv)
1188 {
1189         const struct opt_group *og;
1190         unsigned int i;
1191
1192         i = 0;
1193         do {
1194                 uint64_t mask = (1ULL << i);
1195
1196                 og = opt_group_from_mask(&mask);
1197                 if (!og)
1198                         break;
1199                 gjv->vboxes[i] = gopt_add_group_tab(notebook, og);
1200                 i++;
1201         } while (1);
1202 }
1203
1204 static void gopt_handle_str_multi_changed(struct gopt_job_view *gjv,
1205                                           struct gopt_str_multi *m,
1206                                           struct fio_option *o)
1207 {
1208         unsigned int *ip = td_var(gjv->o, o, o->off1);
1209         struct value_pair *vp;
1210         gboolean set;
1211         guint val = 0;
1212         int i;
1213
1214         i = 0;
1215         vp = &o->posval[0];
1216         while (vp->ival) {
1217                 if (!m->checks[i])
1218                         break;
1219                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->checks[i]));
1220                 if (set) {
1221                         if (vp->orval)
1222                                 val |= vp->oval;
1223                         else
1224                                 val = vp->oval;
1225                 }
1226                 i++;
1227                 vp++;
1228         }
1229
1230         if (o->off1)
1231                 *ip = val;
1232 }
1233
1234 static void gopt_handle_range_changed(struct gopt_job_view *gjv,
1235                                       struct gopt_range *r,
1236                                       struct fio_option *o)
1237 {
1238         unsigned int *ip[4] = { td_var(gjv->o, o, o->off1),
1239                                 td_var(gjv->o, o, o->off2),
1240                                 td_var(gjv->o, o, o->off3),
1241                                 td_var(gjv->o, o, o->off4) };
1242         gint val;
1243         int i;
1244
1245         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
1246                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[i]));
1247                 *ip[i] = val;
1248         }
1249 }
1250
1251 static void gopt_handle_str_val_changed(struct gopt_job_view *gjv,
1252                                         struct gopt_str_val *s,
1253                                         struct fio_option *o)
1254 {
1255         unsigned long long *ullp = td_var(gjv->o, o, o->off1);
1256         GtkAdjustment *adj;
1257         gint index;
1258
1259         if (!ullp)
1260                 return;
1261
1262         /*
1263          * Numerical value
1264          */
1265         adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(s->spin));
1266         *ullp = gtk_adjustment_get_value(adj);
1267
1268         /*
1269          * Multiplier
1270          */
1271         index = gtk_combo_box_get_active(GTK_COMBO_BOX(s->combo));
1272         while (index--)
1273                 *ullp *= 1024ULL;
1274 }
1275
1276 static void gopt_handle_str_changed(struct gopt_job_view *gjv,
1277                                     struct gopt_str *s, struct fio_option *o)
1278 {
1279         char **p = td_var(gjv->o, o, o->off1);
1280
1281         if (*p)
1282                 free(*p);
1283
1284         *p = strdup(gtk_entry_get_text(GTK_ENTRY(s->entry)));
1285 }
1286
1287 static void gopt_handle_bool_changed(struct gopt_job_view *gjv,
1288                                      struct gopt_bool *b, struct fio_option *o)
1289 {
1290         unsigned int *ip = td_var(gjv->o, o, o->off1);
1291         gboolean set;
1292
1293         set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
1294         *ip = set;
1295 }
1296
1297 static void gopt_handle_int_changed(struct gopt_job_view *gjv,
1298                                     struct gopt_int *i, struct fio_option *o)
1299 {
1300         unsigned int *ip = td_var(gjv->o, o, o->off1);
1301         GtkAdjustment *adj;
1302         guint val;
1303
1304         adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(i->spin));
1305         val = gtk_adjustment_get_value(adj);
1306         *ip = val;
1307 }
1308
1309 static void gopt_handle_combo_str_changed(struct gopt_job_view *gjv,
1310                                           struct gopt_combo *c,
1311                                           struct fio_option *o)
1312 {
1313         char **p = td_var(gjv->o, o, o->off1);
1314
1315         if (*p)
1316                 free(*p);
1317
1318         *p = strdup(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(c->combo)));
1319 }
1320
1321 static void gopt_handle_combo_int_changed(struct gopt_job_view *gjv,
1322                                           struct gopt_combo *c,
1323                                           struct fio_option *o)
1324 {
1325         unsigned int *ip = td_var(gjv->o, o, o->off1);
1326         gint index;
1327
1328         index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
1329         *ip = o->posval[index].oval;
1330 }
1331
1332 static void gopt_handle_changed(struct gopt *gopt)
1333 {
1334         struct fio_option *o = &fio_options[gopt->opt_index];
1335         struct gopt_job_view *gjv = gopt->gjv;
1336
1337         switch (gopt->opt_type) {
1338         case GOPT_COMBO_INT: {
1339                 struct gopt_combo *c;
1340
1341                 c = container_of(gopt, struct gopt_combo, gopt);
1342                 gopt_handle_combo_int_changed(gjv, c, o);
1343                 break;
1344                 }
1345         case GOPT_COMBO_STR: {
1346                 struct gopt_combo *c;
1347
1348                 c = container_of(gopt, struct gopt_combo, gopt);
1349                 gopt_handle_combo_str_changed(gjv, c, o);
1350                 break;
1351                 }
1352         case GOPT_INT: {
1353                 struct gopt_int *i;
1354
1355                 i = container_of(gopt, struct gopt_int, gopt);
1356                 gopt_handle_int_changed(gjv, i, o);
1357                 break;
1358                 }
1359         case GOPT_BOOL: {
1360                 struct gopt_bool *b;
1361
1362                 b = container_of(gopt, struct gopt_bool, gopt);
1363                 gopt_handle_bool_changed(gjv, b, o);
1364                 break;
1365                 }
1366         case GOPT_STR: {
1367                 struct gopt_str *s;
1368
1369                 s = container_of(gopt, struct gopt_str, gopt);
1370                 gopt_handle_str_changed(gjv, s, o);
1371                 break;
1372                 }
1373         case GOPT_STR_VAL: {
1374                 struct gopt_str_val *s;
1375
1376                 s = container_of(gopt, struct gopt_str_val, gopt);
1377                 gopt_handle_str_val_changed(gjv, s, o);
1378                 break;
1379                 }
1380         case GOPT_RANGE: {
1381                 struct gopt_range *r;
1382
1383                 r = container_of(gopt, struct gopt_range, gopt);
1384                 gopt_handle_range_changed(gjv, r, o);
1385                 break;
1386                 }
1387         case GOPT_STR_MULTI: {
1388                 struct gopt_str_multi *m;
1389
1390                 m = container_of(gopt, struct gopt_str_multi, gopt);
1391                 gopt_handle_str_multi_changed(gjv, m, o);
1392                 break;
1393                 }
1394         default:
1395                 log_err("gfio: bad option type: %d\n", gopt->opt_type);
1396                 break;
1397         }
1398 }
1399
1400 static void gopt_report_update_status(struct gopt_job_view *gjv)
1401 {
1402         struct gfio_client *gc = gjv->client;
1403         char tmp[80];
1404
1405         sprintf(tmp, "\nCompleted with error: %d\n", gc->update_job_status);
1406         gfio_report_info(gc->ge->ui, "Update job", tmp);
1407 }
1408
1409 static int gopt_handle_changed_options(struct gopt_job_view *gjv)
1410 {
1411         struct gfio_client *gc = gjv->client;
1412         struct flist_head *entry;
1413         uint64_t waitid = 0;
1414         struct gopt *gopt;
1415         int ret;
1416
1417         flist_for_each(entry, &gjv->changed_list) {
1418                 gopt = flist_entry(entry, struct gopt, changed_list);
1419                 gopt_handle_changed(gopt);
1420         }
1421
1422         gc->update_job_status = 0;
1423         gc->update_job_done = 0;
1424
1425         ret = fio_client_update_options(gc->client, gjv->o, &waitid);
1426         if (ret)
1427                 goto done;
1428
1429         ret = fio_client_wait_for_reply(gc->client, waitid);
1430         if (ret)
1431                 goto done;
1432
1433         assert(gc->update_job_done);
1434         if (gc->update_job_status)
1435                 goto done;
1436
1437         while (!flist_empty(&gjv->changed_list)) {
1438                 gopt = flist_first_entry(&gjv->changed_list, struct gopt, changed_list);
1439                 flist_del_init(&gopt->changed_list);
1440         }
1441
1442 done:
1443         gopt_dialog_update_apply_button(gjv);
1444         return ret;
1445 }
1446
1447 static gint gopt_dialog_cancel(gint response)
1448 {
1449         switch (response) {
1450         case GTK_RESPONSE_NONE:
1451         case GTK_RESPONSE_REJECT:
1452         case GTK_RESPONSE_DELETE_EVENT:
1453         case GTK_RESPONSE_CANCEL:
1454         case GTK_RESPONSE_NO:
1455                 return 1;
1456         default:
1457                 return 0;
1458         }
1459 }
1460
1461 static gint gopt_dialog_done(gint response)
1462 {
1463         switch (response) {
1464         case GTK_RESPONSE_ACCEPT:
1465         case GTK_RESPONSE_OK:
1466         case GTK_RESPONSE_YES:
1467                 return 1;
1468         default:
1469                 return 0;
1470         }
1471 }
1472
1473 static void gopt_handle_option_dialog(struct gopt_job_view *gjv)
1474 {
1475         gint response;
1476
1477         do {
1478                 response = gtk_dialog_run(GTK_DIALOG(gjv->dialog));
1479
1480                 if (gopt_dialog_cancel(response) ||
1481                     gopt_dialog_done(response))
1482                         break;
1483
1484                 /*
1485                  * Apply
1486                  */
1487                 gopt_handle_changed_options(gjv);
1488                 gopt_report_update_status(gjv);
1489         } while (1);
1490
1491         if (gopt_dialog_cancel(response))
1492                 return;
1493
1494         gopt_handle_changed_options(gjv);
1495 }
1496
1497 static void gopt_job_changed(GtkComboBox *box, gpointer data)
1498 {
1499         struct gopt_job_view *gjv = (struct gopt_job_view *) data;
1500         struct gfio_client_options *gco = NULL;
1501         struct gfio_client *gc = gjv->client;
1502         struct flist_head *entry;
1503         gchar *job;
1504
1505         /*
1506          * The switch act should be sensitized appropriately, so that we
1507          * never get here with modified options.
1508          */
1509         if (!flist_empty(&gjv->changed_list)) {
1510                 gfio_report_info(gc->ge->ui, "Internal Error", "Modified options on job switch.\nThat should not be possible!\n");
1511                 return;
1512         }
1513
1514         job = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gjv->job_combo));
1515         flist_for_each(entry, &gc->o_list) {
1516                 const char *name;
1517
1518                 gco = flist_entry(entry, struct gfio_client_options, list);
1519                 name = gco->o.name;
1520                 if (!name || !strlen(name))
1521                         name = "Default job";
1522
1523                 if (!strcmp(name, job))
1524                         break;
1525
1526                 gco = NULL;
1527         }
1528
1529         if (!gco) {
1530                 gfio_report_info(gc->ge->ui, "Internal Error", "Could not find job description.\nThat should not be possible!\n");
1531                 return;
1532         }
1533
1534         gjv->in_job_switch = 1;
1535         gopt_set_options(gjv, &gco->o);
1536         gjv->in_job_switch = 0;
1537 }
1538
1539 void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc)
1540 {
1541         GtkWidget *dialog, *notebook, *vbox, *topvbox, *combo;
1542         struct gfio_client_options *gco;
1543         struct flist_head *entry;
1544         struct gopt_job_view *gjv;
1545
1546         dialog = gtk_dialog_new_with_buttons("Fio options",
1547                         GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
1548                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1549                         GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
1550                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1551
1552         combo = gtk_combo_box_text_new();
1553         flist_for_each(entry, &gc->o_list) {
1554                 struct thread_options *o;
1555                 const char *name;
1556
1557                 gco = flist_entry(entry, struct gfio_client_options, list);
1558                 o = &gco->o;
1559                 name = o->name;
1560                 if (!name || !strlen(name))
1561                         name = "Default job";
1562
1563                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), name);
1564         }
1565         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1566
1567         gtk_widget_set_size_request(GTK_WIDGET(dialog), 1024, 768);
1568
1569         topvbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1570         gtk_box_pack_start(GTK_BOX(topvbox), combo, FALSE, FALSE, 5);
1571
1572         vbox = gtk_vbox_new(TRUE, 5);
1573         gtk_box_pack_start(GTK_BOX(topvbox), vbox, TRUE, TRUE, 5);
1574
1575         notebook = gtk_notebook_new();
1576         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
1577         gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
1578         gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 5);
1579
1580         gjv = calloc(1, sizeof(*gjv));
1581         INIT_FLIST_HEAD(&gjv->changed_list);
1582         gco = flist_first_entry(&gc->o_list, struct gfio_client_options, list);
1583         gjv->o = &gco->o;
1584         gjv->dialog = dialog;
1585         gjv->client = gc;
1586         gjv->job_combo = combo;
1587         gopt_add_group_tabs(notebook, gjv);
1588         gopt_add_options(gjv, &gco->o);
1589         gopt_dialog_update_apply_button(gjv);
1590
1591         g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(gopt_job_changed), gjv);
1592
1593         gtk_widget_show_all(dialog);
1594
1595         gopt_handle_option_dialog(gjv);
1596
1597         gtk_widget_destroy(dialog);
1598         free(gjv);
1599 }
1600
1601 /*
1602  * Build n-ary option dependency tree
1603  */
1604 void gopt_init(void)
1605 {
1606         int i;
1607
1608         gopt_dep_tree = g_node_new(NULL);
1609
1610         for (i = 0; fio_options[i].name; i++) {
1611                 struct fio_option *o = &fio_options[i];
1612                 GNode *node, *nparent;
1613
1614                 /*
1615                  * Insert node with either the root parent, or an
1616                  * option parent.
1617                  */
1618                 node = g_node_new(o);
1619                 nparent = gopt_dep_tree;
1620                 if (o->parent) {
1621                         struct fio_option *parent;
1622
1623                         parent = fio_option_find(o->parent);
1624                         nparent = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
1625                         if (!nparent) {
1626                                 log_err("fio: did not find parent %s for opt %s\n", o->name, o->parent);
1627                                 nparent = gopt_dep_tree;
1628                         }
1629                 }
1630
1631                 g_node_insert(nparent, -1, node);
1632         }
1633 }
1634
1635 void gopt_exit(void)
1636 {
1637         g_node_destroy(gopt_dep_tree);
1638         gopt_dep_tree = NULL;
1639 }