shithub: neindaw

ref: 8747033fb429a67053e229b30c323ce1c5a25a54
dir: neindaw/cfg/cfg.c

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <thread.h>
#include <microui.h>
#include "common.h"

enum {
	Hasclone = 1<<0,
};

typedef struct UI UI;

struct UI {
	int type;
	const char *label;
	int index;

	uvlong qidpath;

	int ivalue;
	double value;
	double init;
	double min;
	double max;
	double step;

	int ctl;
	int flags;

	char *group;
	char *name;
	char *unit;

	char *path;
	UI **child;
	int numchild;

	/* µui stuff goes here */
	mu_Container *win;
	int show;
};

int mainstacksize = 32768;

static char **dirs;
static UI **top;
static int numtop;
static int winopt;

static int
readmeta(UI *ui, char *path)
{
	Biobuf *b;
	char *s, *k;
	int index;

	if ((b = Bopen(path, OREAD)) == nil)
		return -1;

	ui->group = ui->name = ui->unit = nil;
	ui->index = -1;
	for (; (s = Brdstr(b, '\n', 1)) != nil; free(s)) {
		if ((k = strtok(s, "\t")) == nil)
			continue;
		if (strcmp(k, "group") == 0) /* FIXME more than one group? */
			ui->group = strdup(strtok(nil, "\t"));
		else if (strcmp(k, "name") == 0)
			ui->name = strdup(strtok(nil, "\t"));
		else if (strcmp(k, "unit") == 0)
			ui->unit = strdup(strtok(nil, "\t"));
		else if ((index = strtol(k, &k, 10)) >= 0 && *k == 0)
			ui->index = index;
	}

	Bterm(b);
	return 0;
}

static int
readctl(UI *ui)
{
	Biobuf b;
	char *s, *k;
	int i;

	ui->type = -1;
	ui->show = 1; /* show everything by default */
	seek(ui->ctl, 0, 0);
	Binit(&b, ui->ctl, OREAD);

	if ((s = Brdstr(&b, '\n', 1)) != nil && (k = strtok(s, "\t")) != nil) {
		for (i = 0; i < nelem(uitypenames); i++) {
			if (strcmp(k, uitypenames[i]) == 0) {
				ui->type = i;
				break;
			}
		}

		switch (ui->type) {
		case UITGroup:
		case UIHGroup:
		case UIVGroup:
			close(ui->ctl);
			ui->ctl = -1;
			break;
		case UIButton:
		case UICheckBox:
			ui->value = atof(strtok(nil, "\t"));
			ui->ivalue = !!ui->value;
			break;
		case UIVSlider:
		case UIHSlider:
		case UINEntry:
			ui->value = atof(strtok(nil, "\t"));
			ui->init = atof(strtok(nil, "\t"));
			ui->min = atof(strtok(nil, "\t"));
			ui->max = atof(strtok(nil, "\t"));
			ui->step = atof(strtok(nil, "\t"));
			break;
		case UIHBarGraph:
		case UIVBarGraph:
			ui->min = atof(strtok(nil, "\t"));
			ui->max = atof(strtok(nil, "\t"));
			break;
		}
	}
	free(s);

	Bterm(&b);
	return 0;
}

static UI *
newui(char *path)
{
	UI *ui;
	Dir *dirs, *d;
	char *s, *name, tmp[64];
	long i, n;
	int f;

	/* FIXME perhaps a prefix common for all dirs should be removed too */
	path = cleanname(path);

	if ((f = open(path, OREAD)) < 0)
		return nil;
	ui = calloc(1, sizeof(*ui));
	ui->path = strdup(path);
	ui->ctl = -1;

	if ((s = strrchr(ui->path, '/')) == nil && fd2path(f, tmp, sizeof(tmp)) == 0) {
		if ((s = strrchr(tmp, '/')) == nil)
			s = path;
	}
	ui->label = strdup(s != nil ? s + 1 : ui->path);

	n = dirreadall(f, &dirs);
	close(f);
	if (n < 0)
		fprint(2, "newui: %s: %r", s);

	for (i = 0; i < n; i++) {
		name = dirs[i].name;
		s = smprint("%s/%s", path, name);
		if (strcmp(name, "metadata") == 0) {
			readmeta(ui, s);
		} else if (strcmp(name, "clone") == 0) {
			ui->flags |= Hasclone;
		} else if (strcmp(name, "ctl") == 0) {
			ui->ctl = open(s, ORDWR);
			if (readctl(ui) == 0 && ui->ctl >= 0 && (d = dirfstat(ui->ctl)) != nil) {
				ui->qidpath = d->qid.path;
				free(d);
			}
		} else if (dirs[i].mode & DMDIR) {
			ui->child = realloc(ui->child, (ui->numchild+1) * sizeof(*ui->child));
			ui->child[ui->numchild++] = newui(s);
		}
		free(s);
	}
	free(dirs);

	return ui;
}

static void
process_ui(UI *w)
{
	UI *c, *slider;
	double v;
	mu_Id id;
	int i, n, widths[32], maxwidth, x, dt;
	char tmp[256];

	dt = stringwidth(mu_style.font, ": ");

	slider = nil;
	for (i = 0; i < w->numchild; i++) {
		c = w->child[i];

		if (slider != nil && c->type != UIVSlider && c->type != UIHSlider) {
			widths[0] = -1;
			mu_layout_row(1, widths, 0);
			slider = nil;
		}

		switch (c->type) {
		case UIHGroup: /* FIXME use mu_layout_row/mu_layout_begin_column here */
		case UITGroup: /* FIXME no tgroup in µui yet */
		case UIVGroup:
			if (mu_header(&c->show, c->label))
				process_ui(c);
			break;
		case UIButton:
			if (mu_button(c->label) & MU_RES_SUBMIT) {
				if (write(c->ctl, "1", 1) > 0)
					c->value = 1;
			} else if (c->value) {
				id = mu_get_id(c->label, strlen(c->label));
				if (mu_ctx.focus != id || mu_ctx.mouse_down != MU_MOUSE_LEFT) {
					if (write(c->ctl, "0", 1) > 0)
						c->value = 0;
				}
			}
			break;
		case UICheckBox:
			if (mu_checkbox(&c->ivalue, c->label) & MU_RES_CHANGE && write(c->ctl, c->ivalue ? "1" : "0", 1) < 0)
				c->value = c->ivalue;
			break;
		case UIVSlider: /* FIXME no vslider in µui yet */
		case UIHSlider:
			if (slider == nil) {
				slider = c;
				maxwidth = 0;
				for (n = i; n < w->numchild; n++) {
					if (w->child[n]->type != UIVSlider && w->child[n]->type != UIHSlider)
						break;
					if ((x = stringwidth(mu_style.font, w->child[n]->label)) > maxwidth)
						maxwidth = x;
				}
				widths[0] = maxwidth + dt;
				widths[1] = -1;
				mu_layout_row(2, widths, 0);
			}

			snprint(tmp, sizeof(tmp), "%s: ", c->label);
			mu_label(tmp);
			mu_push_id(&c, sizeof(c));
			v = c->value;
			snprint(tmp, sizeof(tmp), "%%g%s", c->unit == nil ? "" : c->unit);
			if (mu_slider_ex(&v, c->min, c->max, c->step, tmp, MU_OPT_ALIGNCENTER) & MU_RES_CHANGE) {
				n = snprint(tmp, sizeof(tmp), "%g", v);
				if (write(c->ctl, tmp, n) > 0)
					c->value = v;
			}
			mu_pop_id();
		case UINEntry:
			break;
		case UIHBarGraph:
		case UIVBarGraph:
			/* FIXME */
			break;
		}
	}
}

static void
process_frame(void)
{
	UI *prev, *ui, *ch;
	char tmp[128], *s;
	int i, j;

	mu_begin();
	for (i = 0, prev = nil; i < numtop; i++) {
		ui = top[i];
		if ((ui->flags & Hasclone) == 0) {
			if (mu_begin_window_ex(ui->win, ui->label, winopt)) {
				if (prev != nil && (winopt & MU_OPT_AUTOSIZE))
					ui->win->rect.x = prev->win->rect.x + prev->win->rect.w;
				prev = ui;
				process_ui(ui);
				mu_end_window();
				continue;
			}
		}

		for (j = 0; j < ui->numchild; j++) {
			s = ui->child[j]->path;
			ch = ui->child[j]->child[0];

			snprint(tmp, sizeof(tmp), "[%s] %s", s, ch->label);

			if (mu_begin_window_ex(ch->win, tmp, winopt)) {
				if (prev != nil && (winopt & MU_OPT_AUTOSIZE))
					ch->win->rect.x = prev->win->rect.x + prev->win->rect.w;
				process_ui(ch);
				mu_end_window();
			}
			prev = ch;
		}
	}
	mu_end();
}

static void
diradd(char *path)
{
	UI *ui, *ch;
	mu_Container *w;
	int i;

	top = realloc(top, (numtop+1) * sizeof(*top));
	ui = newui(path);
	top[numtop++] = ui;
	if ((ui->flags & Hasclone) == 0) {
		ui->win = w = calloc(1, sizeof(*w));
		mu_init_window(w, 0);
		w->rect = mu_rect(10 + 210, 10, 200, 400);
		return;
	}

	for (i = 0; i < ui->numchild; i++) {
		if (ui->child[i]->numchild < 1)
			continue;

		ch = ui->child[i]->child[0];
		ch->win = w = calloc(1, sizeof(*w));

		mu_init_window(w, 0);
		w->rect = mu_rect(10 + i*210, 10, 200, 400);
	}
}

static void
usage(void)
{
	print("usage: %s [DIR]...\n", argv0);
	threadexitsall("usage");
}

void
threadmain(int argc, char **argv)
{
	char *s;
	Biobuf *snarf;
	Mousectl *mctl;
	Keyboardctl *kctl;
	Rune key;
	Mouse m;
	Alt a[] = {
		{ nil, &m, CHANRCV },
		{ nil, nil, CHANRCV },
		{ nil, &key, CHANRCV },
		{ nil, nil, CHANEND },
	};
	char *curdir[] = {".", nil};
	int oldbuttons, b, nkey, gotevent, i;
	char text[5];

	ARGBEGIN{
	default:
		usage();
	}ARGEND

	dirs = *argv == nil ? curdir : argv;
	top = nil;
	for (i = 0; dirs[i] != nil; i++)
		diradd(dirs[i]);

	if (initdraw(nil, nil, "daw/cfg") < 0)
		sysfatal("initdraw: %r");
	if ((mctl = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if ((kctl = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");

	a[0].c = mctl->c;
	a[1].c = mctl->resizec;
	a[2].c = kctl->c;

	srand(time(0));
	threadsetname("daw/cfg");

	mu_init();
	mu_style.font = font;
	mu_style.size.y = font->height - 6;
	mu_style.title_height = mu_style.size.y + 12;

	winopt = MU_OPT_AUTOSIZE_H;
	/* FIXME that's how much it takes for the sizing to kick in :( */
	for (i = 0; i < 3; i++)
		process_frame();
	winopt &= ~MU_OPT_AUTOSIZE_H;

	oldbuttons = 0;
	for (;;) {
		process_frame();
		if (mu_render())
			flushimage(display, 1);

		gotevent = 1;
		switch (alt(a)) {
		case 0: /* mouse */
			m.xy.x -= screen->r.min.x;
			m.xy.y -= screen->r.min.y;
			mu_input_mousemove(m.xy.x, m.xy.y);
			if ((b = (m.buttons & 1)) != (oldbuttons & 1))
				(b ? mu_input_mousedown : mu_input_mouseup)(m.xy.x, m.xy.y, MU_MOUSE_LEFT);
			else if ((b = (m.buttons & 2)) != (oldbuttons & 2))
				(b ? mu_input_mousedown : mu_input_mouseup)(m.xy.x, m.xy.y, MU_MOUSE_MIDDLE);
			else if ((b = (m.buttons & 4)) != (oldbuttons & 4))
				(b ? mu_input_mousedown : mu_input_mouseup)(m.xy.x, m.xy.y, MU_MOUSE_RIGHT);
			if (m.buttons == 5 && (snarf = Bopen("/dev/snarf", OREAD)) != nil) {
				if ((s = Brdstr(snarf, 0, 1)) != nil) {
					mu_input_text(s);
					free(s);
				}
				Bterm(snarf);
			}
			oldbuttons = m.buttons;
			break;

		case 1: /* resize */
			getwindow(display, Refnone);
			break;

		case 2: /* keyboard */
			nkey = -1;
			switch (key) {
			case Kdel: goto end;
			case Kshift: nkey = MU_KEY_SHIFT; break;
			case Kbs: nkey = MU_KEY_BACKSPACE; break;
			case '\n': nkey = MU_KEY_RETURN; break;
			case Knack: nkey = MU_KEY_NACK; break;
			case Kleft: nkey = MU_KEY_LEFT; break;
			case Kright: nkey = MU_KEY_RIGHT; break;
			case Kesc: mu_set_focus(0); break;
			default:
				if (key < 0xf000 || key > 0xffff) {
					memset(text, 0, sizeof(text));
					if (runetochar(text, &key) > 0)
						mu_input_text(text);
				}
				break;
			}
			if (nkey >= 0) {
				mu_input_keydown(nkey);
				mu_input_keyup(nkey);
			}
			break;

		default:
			gotevent = 0;
			break;
		}

		if (gotevent)
			process_frame();
	}

end:
	threadexitsall(nil);
}