Add support for inverse options

Options that are tied to each other, meaning that if one is
incremented by X, the other is decremented by X automatically.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/goptions.c b/goptions.c
index 6484197..547d9cf 100644
--- a/goptions.c
+++ b/goptions.c
@@ -24,6 +24,8 @@
 
 struct gopt_int {
 	struct gopt gopt;
+	unsigned int lastval;
+	unsigned int in_change;
 	GtkWidget *spin;
 };
 
@@ -160,6 +162,7 @@
 
 	gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0);
+	o->gui_data = s;
 	return &s->gopt;
 }
 
@@ -191,6 +194,7 @@
 	gtk_box_pack_start(GTK_BOX(c->gopt.box), c->combo, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(c->gopt.box), label, FALSE, FALSE, 0);
 
+	o->gui_data = c;
 	return c;
 }
 
@@ -276,8 +280,32 @@
 {
 	struct gopt_int *i = (struct gopt_int *) data;
 	struct fio_option *o = &fio_options[i->gopt.opt_index];
+	GtkAdjustment *adj;
+	int value, delta;
 
-	printf("int %s changed\n", o->name);
+	adj = gtk_spin_button_get_adjustment(spin);
+	value = gtk_adjustment_get_value(adj);
+	delta = value - i->lastval;
+	i->lastval = value;
+
+	if (o->inv_opt) {
+		struct gopt_int *i_inv = o->inv_opt->gui_data;
+		int cur_val;
+
+		/*
+		 * Don't recourse into notify changes. Is there a better
+		 * way than this? We essentially want to block the update
+		 * signal while we perform the below set_value().
+		 */
+		if (i_inv->in_change)
+			return;
+
+		i->in_change = 1;
+		cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin));
+		cur_val -= delta;
+		gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val);
+		i->in_change = 0;
+	}
 }
 
 static struct gopt_int *__gopt_new_int(struct fio_option *o, unsigned long long *p,
@@ -317,11 +345,13 @@
 	gopt_mark_index(&i->gopt, idx);
 	gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), defval);
+	i->lastval = defval;
 	g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i);
 
 	gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
 
+	o->gui_data = i;
 	return i;
 }
 
@@ -386,6 +416,7 @@
 
 	gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
+	o->gui_data = b;
 	return &b->gopt;
 }
 
@@ -475,6 +506,7 @@
 	}
 
 	gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
+	o->gui_data = r;
 	return &r->gopt;
 }
 
@@ -587,6 +619,9 @@
 	GtkWidget *hbox = NULL;
 	int i;
 
+	/*
+	 * First add all options
+	 */
 	for (i = 0; fio_options[i].name; i++) {
 		struct fio_option *o = &fio_options[i];
 		unsigned int mask = o->category;
diff --git a/options.c b/options.c
index 8681c21..635779d 100644
--- a/options.c
+++ b/options.c
@@ -2124,6 +2124,7 @@
 		.help	= "Percentage of mixed workload that is reads",
 		.def	= "50",
 		.interval = 5,
+		.inverse = "rwmixwrite",
 		.category = FIO_OPT_C_IO,
 		.group	= FIO_OPT_G_RWMIX,
 	},
@@ -2136,6 +2137,7 @@
 		.help	= "Percentage of mixed workload that is writes",
 		.def	= "50",
 		.interval = 5,
+		.inverse = "rwmixread",
 		.category = FIO_OPT_C_IO,
 		.group	= FIO_OPT_G_RWMIX,
 	},
diff --git a/parse.c b/parse.c
index c1fed56..545c3de 100644
--- a/parse.c
+++ b/parse.c
@@ -833,9 +833,8 @@
 		return 1;
 	}
 
-	if (!handle_option(*o, post, data)) {
+	if (!handle_option(*o, post, data))
 		return 0;
-	}
 
 	log_err("fio: failed parsing %s\n", input);
 	return 1;
@@ -1063,8 +1062,11 @@
 
 	dprint(FD_PARSE, "init options\n");
 
-	for (o = &options[0]; o->name; o++)
+	for (o = &options[0]; o->name; o++) {
 		option_init(o);
+		if (o->inverse)
+			o->inv_opt = find_option(options, o->inverse);
+	}
 }
 
 void options_free(struct fio_option *options, void *data)
diff --git a/parse.h b/parse.h
index 030a180..fe3b6bc 100644
--- a/parse.h
+++ b/parse.h
@@ -62,10 +62,13 @@
 	struct value_pair posval[PARSE_MAX_VP];/* possible values */
 	const char *parent;		/* parent option */
 	int hide;			/* hide if parent isn't set */
+	const char *inverse;		/* if set, apply opposite action to this option */
+	struct fio_option *inv_opt;	/* cached lookup */
 	int (*verify)(struct fio_option *, void *);
 	const char *prof_name;		/* only valid for specific profile */
 	unsigned int category;		/* what type of option */
 	unsigned int group;		/* who to group with */
+	void *gui_data;
 };
 
 typedef int (str_cb_fn)(void *, char *);