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