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