87d92e696815bd00609ef7d8442eff9a97a49f54
[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 void gopt_str_multi_destroy(GtkWidget *w, gpointer data)
341 {
342         struct gopt_str_multi *m = (struct gopt_str_multi *) data;
343
344         free(m);
345         gtk_widget_destroy(w);
346 }
347
348 static struct gopt *gopt_new_str_multi(struct gopt_job_view *gjv,
349                                        struct fio_option *o, unsigned int idx)
350 {
351         struct gopt_str_multi *m;
352         struct value_pair *vp;
353         GtkWidget *frame, *hbox;
354         int i;
355
356         m = calloc(1, sizeof(*m));
357         m->gopt.box = gtk_hbox_new(FALSE, 3);
358         gopt_mark_index(gjv, &m->gopt, idx, GOPT_STR_MULTI);
359
360         if (!o->lname)
361                 frame = gtk_frame_new(o->name);
362         else
363                 frame = gtk_frame_new(o->lname);
364         gtk_box_pack_start(GTK_BOX(m->gopt.box), frame, FALSE, FALSE, 3);
365
366         hbox = gtk_hbox_new(FALSE, 3);
367         gtk_container_add(GTK_CONTAINER(frame), hbox);
368
369         i = 0;
370         vp = &o->posval[0];
371         while (vp->ival) {
372                 m->checks[i] = gtk_check_button_new_with_label(vp->ival);
373                 gtk_widget_set_tooltip_text(m->checks[i], vp->help);
374                 gtk_box_pack_start(GTK_BOX(hbox), m->checks[i], FALSE, FALSE, 3);
375                 g_signal_connect(G_OBJECT(m->checks[i]), "toggled", G_CALLBACK(gopt_str_multi_toggled), m);
376                 vp++;
377                 i++;
378         }
379
380         g_signal_connect(G_OBJECT(m->gopt.box), "destroy", G_CALLBACK(gopt_str_multi_destroy), m);
381         return &m->gopt;
382 }
383
384 static void gopt_int_changed(GtkSpinButton *spin, gpointer data)
385 {
386         struct gopt_int *i = (struct gopt_int *) data;
387         struct fio_option *o = &fio_options[i->gopt.opt_index];
388         GtkAdjustment *adj;
389         int value, delta;
390
391         gopt_changed(&i->gopt);
392
393         adj = gtk_spin_button_get_adjustment(spin);
394         value = gtk_adjustment_get_value(adj);
395         delta = value - i->lastval;
396         i->lastval = value;
397
398         if (o->inv_opt) {
399                 struct gopt *b_inv = o->inv_opt->gui_data;
400                 struct gopt_int *i_inv = container_of(b_inv, struct gopt_int, gopt);
401                 int cur_val;
402
403                 assert(o->type == o->inv_opt->type);
404
405                 cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin));
406                 cur_val -= delta;
407                 g_signal_handler_block(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
408                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val);
409                 g_signal_handler_unblock(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
410         }
411 }
412
413 static void gopt_int_destroy(GtkWidget *w, gpointer data)
414 {
415         struct gopt_int *i = (struct gopt_int *) data;
416
417         free(i);
418         gtk_widget_destroy(w);
419 }
420
421 static struct gopt_int *__gopt_new_int(struct gopt_job_view *gjv,
422                                        struct fio_option *o,
423                                        unsigned long long *p, unsigned int idx)
424 {
425         unsigned long long defval;
426         struct gopt_int *i;
427         guint maxval, interval;
428         GtkWidget *label;
429
430         i = calloc(1, sizeof(*i));
431         i->gopt.box = gtk_hbox_new(FALSE, 3);
432         if (!o->lname)
433                 label = gtk_label_new(o->name);
434         else
435                 label = gtk_label_new(o->lname);
436
437         maxval = o->maxval;
438         if (!maxval)
439                 maxval = UINT_MAX;
440
441         defval = 0;
442         if (p)
443                 defval = *p;
444         else if (o->def) {
445                 long long val;
446
447                 check_str_bytes(o->def, &val, NULL);
448                 defval = val;
449         }
450
451         interval = 1.0;
452         if (o->interval)
453                 interval = o->interval;
454
455         i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval);
456         gopt_mark_index(gjv, &i->gopt, idx, GOPT_INT);
457         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
458         gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), defval);
459         i->lastval = defval;
460         i->gopt.sig_handler = g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i);
461         g_signal_connect(G_OBJECT(i->spin), "destroy", G_CALLBACK(gopt_int_destroy), i);
462
463         gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
464         gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
465
466         return i;
467 }
468
469 static struct gopt *gopt_new_int(struct gopt_job_view *gjv,
470                                  struct fio_option *o, unsigned int *ip,
471                                  unsigned int idx)
472 {
473         unsigned long long ullp;
474         struct gopt_int *i;
475
476         if (ip) {
477                 ullp = *ip;
478                 i = __gopt_new_int(gjv, o, &ullp, idx);
479         } else
480                 i = __gopt_new_int(gjv, o, NULL, idx);
481
482         return &i->gopt;
483 }
484
485 static struct gopt *gopt_new_ullong(struct gopt_job_view *gjv,
486                                     struct fio_option *o, unsigned long long *p,
487                                     unsigned int idx)
488 {
489         struct gopt_int *i;
490
491         i = __gopt_new_int(gjv, o, p, idx);
492         return &i->gopt;
493 }
494
495 static void gopt_bool_toggled(GtkToggleButton *button, gpointer data)
496 {
497         struct gopt_bool *b = (struct gopt_bool *) data;
498         struct fio_option *o = &fio_options[b->gopt.opt_index];
499         gboolean set;
500
501         gopt_changed(&b->gopt);
502
503         set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
504
505         if (o->inv_opt) {
506                 struct gopt *g_inv = o->inv_opt->gui_data;
507                 struct gopt_bool *b_inv = container_of(g_inv, struct gopt_bool, gopt);
508
509                 assert(o->type == o->inv_opt->type);
510
511                 g_signal_handler_block(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
512                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_inv->check), !set);
513                 g_signal_handler_unblock(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
514         }
515
516         gopt_set_children_visible(b->gopt.gjv, o, set);
517 }
518
519 static void gopt_bool_destroy(GtkWidget *w, gpointer data)
520 {
521         struct gopt_bool *b = (struct gopt_bool *) data;
522
523         free(b);
524         gtk_widget_destroy(w);
525 }
526
527 static struct gopt *gopt_new_bool(struct gopt_job_view *gjv,
528                                   struct fio_option *o, unsigned int *val,
529                                   unsigned int idx)
530 {
531         struct gopt_bool *b;
532         GtkWidget *label;
533         int defstate = 0;
534
535         b = calloc(1, sizeof(*b));
536         b->gopt.box = gtk_hbox_new(FALSE, 3);
537         if (!o->lname)
538                 label = gtk_label_new(o->name);
539         else
540                 label = gtk_label_new(o->lname);
541
542         b->check = gtk_check_button_new();
543         gopt_mark_index(gjv, &b->gopt, idx, GOPT_BOOL);
544         if (val)
545                 defstate = *val;
546         else if (o->def && !strcmp(o->def, "1"))
547                 defstate = 1;
548
549         if (o->neg)
550                 defstate = !defstate;
551
552         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate);
553         b->gopt.sig_handler = g_signal_connect(G_OBJECT(b->check), "toggled", G_CALLBACK(gopt_bool_toggled), b);
554         g_signal_connect(G_OBJECT(b->check), "destroy", G_CALLBACK(gopt_bool_destroy), b);
555
556         gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
557         gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
558         return &b->gopt;
559 }
560
561 /*
562  * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values.
563  * If the max is made smaller than min, adjust min down.
564  * If the min is made larger than max, adjust the max.
565  */
566 static void range_value_changed(GtkSpinButton *spin, gpointer data)
567 {
568         struct gopt_range *r = (struct gopt_range *) data;
569         int changed = -1, i;
570         gint val, mval;
571
572         gopt_changed(&r->gopt);
573
574         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
575                 if (GTK_SPIN_BUTTON(r->spins[i]) == spin) {
576                         changed = i;
577                         break;
578                 }
579         }
580
581         assert(changed != -1);
582
583         /*
584          * Min changed
585          */
586         if (changed == 0 || changed == 2) {
587                 GtkWidget *mspin = r->spins[changed + 1];
588
589                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
590                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
591                 if (val > mval)
592                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
593         } else {
594                 GtkWidget *mspin = r->spins[changed - 1];
595
596                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
597                 mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
598                 if (val < mval)
599                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
600         }
601 }
602
603 static void gopt_range_destroy(GtkWidget *w, gpointer data)
604 {
605         struct gopt_range *r = (struct gopt_range *) data;
606
607         free(r);
608         gtk_widget_destroy(w);
609 }
610
611 static struct gopt *gopt_new_int_range(struct gopt_job_view *gjv,
612                                        struct fio_option *o, unsigned int **ip,
613                                        unsigned int idx)
614 {
615         struct gopt_range *r;
616         gint maxval, defval;
617         GtkWidget *label;
618         guint interval;
619         int i;
620
621         r = calloc(1, sizeof(*r));
622         r->gopt.box = gtk_hbox_new(FALSE, 3);
623         gopt_mark_index(gjv, &r->gopt, idx, GOPT_RANGE);
624         if (!o->lname)
625                 label = gtk_label_new(o->name);
626         else
627                 label = gtk_label_new(o->lname);
628
629         maxval = o->maxval;
630         if (!maxval)
631                 maxval = INT_MAX;
632
633         defval = 0;
634         if (o->def) {
635                 long long val;
636
637                 check_str_bytes(o->def, &val, NULL);
638                 defval = val;
639         }
640
641         interval = 1.0;
642         if (o->interval)
643                 interval = o->interval;
644
645         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
646                 r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval);
647                 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID);
648                 if (ip)
649                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), *ip[i]);
650                 else
651                         gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), defval);
652
653                 gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0);
654                 g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r);
655         }
656
657         gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
658         g_signal_connect(G_OBJECT(r->gopt.box), "destroy", G_CALLBACK(gopt_range_destroy), r);
659         return &r->gopt;
660 }
661
662 static void gopt_str_val_destroy(GtkWidget *w, gpointer data)
663 {
664         struct gopt_str_val *g = (struct gopt_str_val *) data;
665
666         free(g);
667         gtk_widget_destroy(w);
668 }
669
670 static void gopt_str_val_spin_wrapped(GtkSpinButton *spin, gpointer data)
671 {
672         struct gopt_str_val *g = (struct gopt_str_val *) data;
673         unsigned int val;
674         GtkAdjustment *adj;
675         gint index;
676
677         adj = gtk_spin_button_get_adjustment(spin);
678         val = gtk_adjustment_get_value(adj);
679
680         /*
681          * Can't rely on exact value, as fast changes increment >= 1
682          */
683         if (!val) {
684                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
685                 if (index + 1 <= g->maxindex) {
686                         val = 1;
687                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), ++index);
688                 } else
689                         val = 1023;
690                 gtk_spin_button_set_value(spin, val);
691         } else {
692                 index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
693                 if (index) {
694                         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), --index);
695                         gtk_spin_button_set_value(spin, 1023);
696                 } else
697                         gtk_spin_button_set_value(spin, 0);
698         }
699 }
700
701 static void gopt_str_val_changed(GtkSpinButton *spin, gpointer data)
702 {
703         struct gopt_str_val *g = (struct gopt_str_val *) data;
704
705         gopt_changed(&g->gopt);
706 }
707
708 static struct gopt *gopt_new_str_val(struct gopt_job_view *gjv,
709                                      struct fio_option *o,
710                                      unsigned long long *p, unsigned int idx)
711 {
712         struct gopt_str_val *g;
713         const gchar *postfix[] = { "B", "KB", "MB", "GB", "PB", "TB", "" };
714         unsigned long long val;
715         GtkWidget *label;
716         int i;
717
718         g = calloc(1, sizeof(*g));
719         g->gopt.box = gtk_hbox_new(FALSE, 3);
720         if (!o->lname)
721                 label = gtk_label_new(o->name);
722         else
723                 label = gtk_label_new(o->lname);
724         gopt_mark_index(gjv, &g->gopt, idx, GOPT_STR_VAL);
725
726         g->spin = gtk_spin_button_new_with_range(0.0, 1023.0, 1.0);
727         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(g->spin), GTK_UPDATE_IF_VALID);
728         gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), 0);
729         gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(g->spin), 1);
730         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->spin, FALSE, FALSE, 0);
731         g_signal_connect(G_OBJECT(g->spin), "wrapped", G_CALLBACK(gopt_str_val_spin_wrapped), g);
732         g_signal_connect(G_OBJECT(g->spin), "changed", G_CALLBACK(gopt_str_val_changed), g);
733
734         g->combo = gtk_combo_box_text_new();
735         i = 0;
736         while (strlen(postfix[i])) {
737                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(g->combo), postfix[i]);
738                 i++;
739         }
740         g->maxindex = i - 1;
741         gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), 0);
742         gtk_box_pack_start(GTK_BOX(g->gopt.box), g->combo, FALSE, FALSE, 0);
743         gtk_box_pack_start(GTK_BOX(g->gopt.box), label, FALSE, FALSE, 3);
744
745         /*
746          * Set the value
747          */
748         if (p) {
749                 val = *p;
750                 i = 0;
751                 do {
752                         if (!val || (val % 1024))
753                                 break;
754
755                         i++;
756                         val /= 1024;
757                 } while (1);
758
759                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), val);
760                 gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), i);
761         }
762
763         g_signal_connect(G_OBJECT(g->combo), "changed", G_CALLBACK(gopt_str_val_changed), g);
764
765         g_signal_connect(G_OBJECT(g->gopt.box), "destroy", G_CALLBACK(gopt_str_val_destroy), g);
766         return &g->gopt;
767 }
768
769 static void gopt_add_option(struct gopt_job_view *gjv, GtkWidget *hbox,
770                             struct fio_option *o, unsigned int opt_index,
771                             struct thread_options *to)
772 {
773         struct gopt *go = NULL;
774
775         switch (o->type) {
776         case FIO_OPT_STR_VAL: {
777                 unsigned long long *ullp = NULL;
778
779                 if (o->off1)
780                         ullp = td_var(to, o->off1);
781
782                 go = gopt_new_str_val(gjv, o, ullp, opt_index);
783                 break;
784                 }
785         case FIO_OPT_STR_VAL_TIME: {
786                 unsigned long long *ullp = NULL;
787
788                 if (o->off1)
789                         ullp = td_var(to, o->off1);
790
791                 go = gopt_new_ullong(gjv, o, ullp, opt_index);
792                 break;
793                 }
794         case FIO_OPT_INT: {
795                 unsigned int *ip = NULL;
796
797                 if (o->off1)
798                         ip = td_var(to, o->off1);
799
800                 go = gopt_new_int(gjv, o, ip, opt_index);
801                 break;
802                 }
803         case FIO_OPT_STR_SET:
804         case FIO_OPT_BOOL: {
805                 unsigned int *ip = NULL;
806
807                 if (o->off1)
808                         ip = td_var(to, o->off1);
809
810                 go = gopt_new_bool(gjv, o, ip, opt_index);
811                 break;
812                 }
813         case FIO_OPT_STR: {
814                 if (o->posval[0].ival) {
815                         unsigned int *ip = NULL;
816
817                         if (o->off1)
818                                 ip = td_var(to, o->off1);
819
820                         go = gopt_new_combo_int(gjv, o, ip, opt_index);
821                 } else {
822                         /* TODO: usually ->cb, or unsigned int pointer */
823                         go = gopt_new_str_store(gjv, o, NULL, opt_index);
824                 }
825
826                 break;
827                 }
828         case FIO_OPT_STR_STORE: {
829                 char *text = NULL;
830
831                 if (o->off1) {
832                         char **p = td_var(to, o->off1);
833                         text = *p;
834                 }
835
836                 if (!o->posval[0].ival) {
837                         go = gopt_new_str_store(gjv, o, text, opt_index);
838                         break;
839                 }
840
841                 go = gopt_new_combo_str(gjv, o, text, opt_index);
842                 break;
843                 }
844         case FIO_OPT_STR_MULTI:
845                 go = gopt_new_str_multi(gjv, o, opt_index);
846                 break;
847         case FIO_OPT_RANGE: {
848                 unsigned int *ip[4] = { td_var(to, o->off1),
849                                         td_var(to, o->off2),
850                                         td_var(to, o->off3),
851                                         td_var(to, o->off4) };
852
853                 go = gopt_new_int_range(gjv, o, ip, opt_index);
854                 break;
855                 }
856         /* still need to handle this one */
857         case FIO_OPT_FLOAT_LIST:
858                 break;
859         case FIO_OPT_DEPRECATED:
860                 break;
861         default:
862                 printf("ignore type %u\n", o->type);
863                 break;
864         }
865
866         if (go) {
867                 GtkWidget *dest;
868
869                 if (o->help)
870                         gtk_widget_set_tooltip_text(go->box, o->help);
871
872                 o->gui_data = go;
873
874                 dest = gopt_get_group_frame(gjv, hbox, o->group);
875                 if (!dest)
876                         gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5);
877                 else
878                         gtk_box_pack_start(GTK_BOX(dest), go->box, FALSE, FALSE, 5);
879         }
880 }
881
882 static void gopt_add_options(struct gopt_job_view *gjv,
883                              struct thread_options *to)
884 {
885         GtkWidget *hbox = NULL;
886         int i;
887
888         /*
889          * First add all options
890          */
891         for (i = 0; fio_options[i].name; i++) {
892                 struct fio_option *o = &fio_options[i];
893                 unsigned int mask = o->category;
894                 struct opt_group *og;
895
896                 while ((og = opt_group_from_mask(&mask)) != NULL) {
897                         GtkWidget *vbox = gjv->vboxes[ffz(~og->mask)];
898
899                         hbox = gtk_hbox_new(FALSE, 3);
900                         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
901                         gopt_add_option(gjv, hbox, o, i, to);
902                 }
903         }
904 }
905
906 static GtkWidget *gopt_add_tab(GtkWidget *notebook, const char *name)
907 {
908         GtkWidget *box, *vbox, *scroll;
909
910         scroll = gtk_scrolled_window_new(NULL, NULL);
911         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
912         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
913
914         vbox = gtk_vbox_new(FALSE, 3);
915         box = gtk_hbox_new(FALSE, 0);
916         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
917         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
918         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scroll, gtk_label_new(name));
919         return vbox;
920 }
921
922 static GtkWidget *gopt_add_group_tab(GtkWidget *notebook, struct opt_group *og)
923 {
924         return gopt_add_tab(notebook, og->name);
925 }
926
927 static void gopt_add_group_tabs(GtkWidget *notebook, struct gopt_job_view *gjv)
928 {
929         struct opt_group *og;
930         unsigned int i;
931
932         i = 0;
933         do {
934                 unsigned int mask = (1U << i);
935
936                 og = opt_group_from_mask(&mask);
937                 if (!og)
938                         break;
939                 gjv->vboxes[i] = gopt_add_group_tab(notebook, og);
940                 i++;
941         } while (1);
942 }
943
944 static void gopt_handle_str_multi_changed(struct gopt_job_view *gjv,
945                                           struct gopt_str_multi *m,
946                                           struct fio_option *o)
947 {
948         unsigned int *ip = td_var(gjv->o, o->off1);
949         struct value_pair *vp;
950         gboolean set;
951         guint val = 0;
952         int i;
953
954         i = 0;
955         vp = &o->posval[0];
956         while (vp->ival) {
957                 if (!m->checks[i])
958                         break;
959                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->checks[i]));
960                 if (set) {
961                         if (vp->or)
962                                 val |= vp->oval;
963                         else
964                                 val = vp->oval;
965                 }
966                 i++;
967                 vp++;
968         }
969
970         if (o->off1)
971                 *ip = val;
972 }
973
974 static void gopt_handle_range_changed(struct gopt_job_view *gjv,
975                                       struct gopt_range *r,
976                                       struct fio_option *o)
977 {
978         unsigned int *ip[4] = { td_var(gjv->o, o->off1),
979                                 td_var(gjv->o, o->off2),
980                                 td_var(gjv->o, o->off3),
981                                 td_var(gjv->o, o->off4) };
982         gint val;
983         int i;
984
985         for (i = 0; i < GOPT_RANGE_SPIN; i++) {
986                 val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[i]));
987                 *ip[i] = val;
988         }
989 }
990
991 static void gopt_handle_str_val_changed(struct gopt_job_view *gjv,
992                                         struct gopt_str_val *s,
993                                         struct fio_option *o)
994 {
995         unsigned long long *ullp = td_var(gjv->o, o->off1);
996         GtkAdjustment *adj;
997         gint index;
998
999         if (!ullp)
1000                 return;
1001
1002         /*
1003          * Numerical value
1004          */
1005         adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(s->spin));
1006         *ullp = gtk_adjustment_get_value(adj);
1007
1008         /*
1009          * Multiplier
1010          */
1011         index = gtk_combo_box_get_active(GTK_COMBO_BOX(s->combo));
1012         while (index--)
1013                 *ullp *= 1024ULL;
1014 }
1015
1016 static void gopt_handle_str_changed(struct gopt_job_view *gjv,
1017                                     struct gopt_str *s, struct fio_option *o)
1018 {
1019         char **p = td_var(gjv->o, o->off1);
1020
1021         if (*p)
1022                 free(*p);
1023
1024         *p = strdup(gtk_entry_get_text(GTK_ENTRY(s->entry)));
1025 }
1026
1027 static void gopt_handle_bool_changed(struct gopt_job_view *gjv,
1028                                      struct gopt_bool *b, struct fio_option *o)
1029 {
1030         unsigned int *ip = td_var(gjv->o, o->off1);
1031         gboolean set;
1032
1033         set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
1034         *ip = set;
1035 }
1036
1037 static void gopt_handle_int_changed(struct gopt_job_view *gjv,
1038                                     struct gopt_int *i, struct fio_option *o)
1039 {
1040         unsigned int *ip = td_var(gjv->o, o->off1);
1041         GtkAdjustment *adj;
1042         guint val;
1043
1044         adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(i->spin));
1045         val = gtk_adjustment_get_value(adj);
1046         *ip = val;
1047 }
1048
1049 static void gopt_handle_combo_str_changed(struct gopt_job_view *gjv,
1050                                           struct gopt_combo *c,
1051                                           struct fio_option *o)
1052 {
1053         char **p = td_var(gjv->o, o->off1);
1054
1055         if (*p)
1056                 free(*p);
1057
1058         *p = strdup(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(c->combo)));
1059 }
1060
1061 static void gopt_handle_combo_int_changed(struct gopt_job_view *gjv,
1062                                           struct gopt_combo *c,
1063                                           struct fio_option *o)
1064 {
1065         unsigned int *ip = td_var(gjv->o, o->off1);
1066         gint index;
1067
1068         index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
1069         *ip = o->posval[index].oval;
1070 }
1071
1072 static void gopt_handle_changed(struct gopt *gopt)
1073 {
1074         struct fio_option *o = &fio_options[gopt->opt_index];
1075         struct gopt_job_view *gjv = gopt->gjv;
1076
1077         switch (gopt->opt_type) {
1078         case GOPT_COMBO_INT: {
1079                 struct gopt_combo *c;
1080
1081                 c = container_of(gopt, struct gopt_combo, gopt);
1082                 gopt_handle_combo_int_changed(gjv, c, o);
1083                 break;
1084                 }
1085         case GOPT_COMBO_STR: {
1086                 struct gopt_combo *c;
1087
1088                 c = container_of(gopt, struct gopt_combo, gopt);
1089                 gopt_handle_combo_str_changed(gjv, c, o);
1090                 break;
1091                 }
1092         case GOPT_INT: {
1093                 struct gopt_int *i;
1094
1095                 i = container_of(gopt, struct gopt_int, gopt);
1096                 gopt_handle_int_changed(gjv, i, o);
1097                 break;
1098                 }
1099         case GOPT_BOOL: {
1100                 struct gopt_bool *b;
1101
1102                 b = container_of(gopt, struct gopt_bool, gopt);
1103                 gopt_handle_bool_changed(gjv, b, o);
1104                 break;
1105                 }
1106         case GOPT_STR: {
1107                 struct gopt_str *s;
1108
1109                 s = container_of(gopt, struct gopt_str, gopt);
1110                 gopt_handle_str_changed(gjv, s, o);
1111                 break;
1112                 }
1113         case GOPT_STR_VAL: {
1114                 struct gopt_str_val *s;
1115
1116                 s = container_of(gopt, struct gopt_str_val, gopt);
1117                 gopt_handle_str_val_changed(gjv, s, o);
1118                 break;
1119                 }
1120         case GOPT_RANGE: {
1121                 struct gopt_range *r;
1122
1123                 r = container_of(gopt, struct gopt_range, gopt);
1124                 gopt_handle_range_changed(gjv, r, o);
1125                 break;
1126                 }
1127         case GOPT_STR_MULTI: {
1128                 struct gopt_str_multi *m;
1129
1130                 m = container_of(gopt, struct gopt_str_multi, gopt);
1131                 gopt_handle_str_multi_changed(gjv, m, o);
1132                 break;
1133                 }
1134         default:
1135                 log_err("gfio: bad option type %s/%d\n", gopt->opt_type);
1136                 return;
1137         }
1138
1139         g_object_unref(G_OBJECT(gopt->box));
1140 }
1141
1142 static void gopt_handle_changed_options(struct gopt_job_view *gjv)
1143 {
1144         struct gopt *gopt;
1145
1146         while (!flist_empty(&gjv->changed_list)) {
1147                 gopt = flist_entry(gjv->changed_list.next, struct gopt, changed_list);
1148                 flist_del(&gopt->changed_list);
1149                 gopt_handle_changed(gopt);
1150         }
1151 }
1152
1153 void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc)
1154 {
1155         GtkWidget *dialog, *notebook, *topnotebook, *vbox;
1156         struct gfio_client_options *gco;
1157         struct thread_options *o;
1158         struct flist_head *entry;
1159         struct gopt_job_view *gjv;
1160         FLIST_HEAD(gjv_list);
1161
1162         /*
1163          * Just choose the first item, we need to make each options
1164          * entry the main notebook, with the below options view as
1165          * a sub-notebook
1166          */
1167         assert(!flist_empty(&gc->o_list));
1168
1169         dialog = gtk_dialog_new_with_buttons("Fio options",
1170                         GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
1171                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1172                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1173
1174         gtk_widget_set_size_request(GTK_WIDGET(dialog), 1024, 768);
1175
1176         topnotebook = gtk_notebook_new();
1177         gtk_notebook_set_scrollable(GTK_NOTEBOOK(topnotebook), 1);
1178         gtk_notebook_popup_enable(GTK_NOTEBOOK(topnotebook));
1179         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1180         gtk_box_pack_start(GTK_BOX(vbox), topnotebook, TRUE, TRUE, 5);
1181
1182         flist_for_each(entry, &gc->o_list) {
1183                 const char *name;
1184
1185                 gco = flist_entry(entry, struct gfio_client_options, list);
1186                 o = &gco->o;
1187                 name = o->name;
1188                 if (!name || !strlen(name))
1189                         name = "Default job";
1190
1191                 vbox = gopt_add_tab(topnotebook, name);
1192
1193                 notebook = gtk_notebook_new();
1194                 gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
1195                 gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
1196                 gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 5);
1197
1198                 gjv = calloc(1, sizeof(*gjv));
1199                 INIT_FLIST_HEAD(&gjv->list);
1200                 INIT_FLIST_HEAD(&gjv->changed_list);
1201                 gjv->o = o;
1202                 flist_add_tail(&gjv->list, &gjv_list);
1203                 gopt_add_group_tabs(notebook, gjv);
1204                 gopt_add_options(gjv, o);
1205         }
1206
1207         gtk_widget_show_all(dialog);
1208
1209         gtk_dialog_run(GTK_DIALOG(dialog));
1210
1211         while (!flist_empty(&gjv_list)) {
1212                 gjv = flist_entry(gjv_list.next, struct gopt_job_view, list);
1213
1214                 gopt_handle_changed_options(gjv);
1215
1216                 flist_del(&gjv->list);
1217                 free(gjv);
1218         }
1219
1220         gtk_widget_destroy(dialog);
1221 }
1222
1223 /*
1224  * Build n-ary option dependency tree
1225  */
1226 void gopt_init(void)
1227 {
1228         int i;
1229
1230         gopt_dep_tree = g_node_new(NULL);
1231
1232         for (i = 0; fio_options[i].name; i++) {
1233                 struct fio_option *o = &fio_options[i];
1234                 GNode *node, *nparent;
1235
1236                 /*
1237                  * Insert node with either the root parent, or an
1238                  * option parent.
1239                  */
1240                 node = g_node_new(o);
1241                 nparent = gopt_dep_tree;
1242                 if (o->parent) {
1243                         struct fio_option *parent;
1244
1245                         parent = fio_option_find(o->parent);
1246                         nparent = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
1247                         if (!nparent) {
1248                                 log_err("fio: did not find parent %s for opt %s\n", o->name, o->parent);
1249                                 nparent = gopt_dep_tree;
1250                         }
1251                 }
1252
1253                 g_node_insert(nparent, -1, node);
1254         }
1255 }
1256
1257 void gopt_exit(void)
1258 {
1259         g_node_destroy(gopt_dep_tree);
1260         gopt_dep_tree = NULL;
1261 }