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