code: 9ferno

ref: 600bbfe4aaa9ad0f73d8d73eef1b7670e5f7d3a3
dir: /appl/collab/clients/poller.b/

View raw version
implement Poller;

include "sys.m";
	sys: Sys;

include "draw.m";
	draw: Draw;
	Rect, Point: import draw;

include "tk.m";
	tk: Tk;

include "tkclient.m";
	tkclient: Tkclient;

include "dialog.m";
	dialog: Dialog;

include "arg.m";

Poller: module
{
	init:	fn(nil: ref Draw->Context, nil: list of string);
};

Maxanswer: con 4;	# Tk below isn't parametrised, but could be

contents := array[] of {
	"frame .f",
	"frame .f.n",
	"label .f.l -anchor nw -text {Number of answers: }",
	"radiobutton .f.n.a2 -text {2} -variable nanswer -value 2",
	"radiobutton .f.n.a3 -text {3} -variable nanswer -value 3",
	"radiobutton .f.n.a4 -text {4} -variable nanswer -value 4",
	"pack .f.n.a2 .f.n.a3 .f.n.a4 -side left",

	"frame .f.b",
	"button .f.b.start -text {Start} -command {send cmd start}",
	"button .f.b.stop -text {Stop} -state disabled -command {send cmd stop}",
	"pack .f.b.start .f.b.stop -side left",

	"canvas .f.c -height 230 -width 200",

	"pack .f.l -side top -fill x",
	"pack .f.n -side top -fill x",
	"pack .f.b -side top -fill x -expand 1",
	"pack .f.c -side top -pady 2",
	"pack .f -side top -fill both -expand 1",
};

dbcontents := array[] of {
	"text .f.t -state disabled -wrap word -height 4h -yscrollcommand {.f.sb set}",	# message log
	"scrollbar .f.sb -orient vertical -command {.f.t yview}",
	"pack .f.sb -side left -fill y",
	"pack .f.t -side left -fill both",
};

Bar: adt {
	frame:	ref Tk->Toplevel;
	canvas:	string;
	border:	string;
	inside:	string;
	label:	string;
	r:	Rect;
	v:	real;

	draw:	fn(nil: self ref Bar);
};

usage()
{
	sys->fprint(sys->fildes(2), "usage: poller [-d] [servicedir] pollname\n");
	raise "fail:usage";
}

init(ctxt: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;
	dialog = load Dialog Dialog->PATH;

	arg := load Arg Arg->PATH;
	if(arg == nil){
		sys->fprint(sys->fildes(2), "poller: can't load %s: %r\n", Arg->PATH);
		raise "fail:load";
	}
	arg->init(args);
	debug := 0;
	while((ch := arg->opt()) != 0)
		case ch {
		'd' =>
			debug = 1;
		* =>
			usage();
		}
	args = arg->argv();
	arg = nil;
	if(len args < 1)
		usage();
	sys->pctl(Sys->NEWPGRP, nil);

	servicedir := "/n/remote/services";
	if(len args == 2)
		(servicedir, args) = (hd args, tl args);
	pollname := hd args;

	(cfd, dir, emsg) := opensvc(servicedir, "mpx", pollname);
	if(cfd == nil){
		sys->fprint(sys->fildes(2), "poller: can't access polling station %s: %s\n", pollname, emsg);
		raise "fail:error";
	}
	fd := sys->open(dir+"/root", Sys->ORDWR);
	if(fd == nil){
		sys->fprint(sys->fildes(2), "poller: can't open %s/root: %r\n", dir);
		raise "fail:open";
	}

	tkclient->init();
	dialog->init();
	(frame, wmctl) := tkclient->toplevel(ctxt, nil, sys->sprint("Poller: %s", pollname), Tkclient->Appl);
	cmd := chan of string;
	tk->namechan(frame, cmd, "cmd");
	tkcmds(frame, contents);
	if(debug)
		tkcmds(frame, dbcontents);
	tkcmd(frame, "pack propagate . 0");
	fittoscreen(frame);
	tk->cmd(frame, "update");
	tkclient->onscreen(frame, nil);
	tkclient->startinput(frame, "kbd"::"ptr"::nil);

	bars := mkbars(frame, ".f.c", Maxanswer);
	count: array of int;

	in := chan of string;
	spawn reader(fd, in);
	first := 1;
	qno := 0;
	nanswer := 0;
	opt := 0;
	total := 0;
	for(;;)
		alt{
		s := <-frame.ctxt.kbd =>
			tk->keyboard(frame, s);
		s := <-frame.ctxt.ptr =>
			tk->pointer(frame, *s);
		s := <-frame.ctxt.ctl or
		s = <-frame.wreq or
		s = <-wmctl =>
			tkclient->wmctl(frame, s);

		c := <-cmd =>
			if(fd == nil){
				dialog->prompt(ctxt, frame.image, "error -fg red", "Error", "Lost connection to polling station", 0, "Dismiss"::nil);
				break;
			}
			case c {
			"start" =>
				s := tkcmd(frame, "variable nanswer");
				if(s == nil || s[0] == '!'){
					dialog->prompt(ctxt, frame.image, "error -fg red", "Error", "Please select number of answers", 0, "Ok"::nil);
					break;
				}
				nanswer = int s;
				count = array[Maxanswer] of {* => 0};
				total = 0;
				qno++;
				#opt = (int tkcmd(frame, "variable none") << 1) | int tkcmd(frame, "variable all");
				tkcmd(frame, ".f.b.start configure -state disabled");
				tkcmd(frame, ".f.b.stop configure -state normal");
				if(sys->fprint(fd, "poll %d %d %d", qno, nanswer, opt) <= 0)
					sys->fprint(sys->fildes(2), "poller: write error: %r\n");
			"stop" =>
				tkcmd(frame, ".f.b.stop configure -state disabled");
				tkcmd(frame, "update");
				if(sys->fprint(fd, "stop %d", qno) <= 0)
					sys->fprint(sys->fildes(2), "poller: write error: %r\n");
				# stop ...
				tkcmd(frame, ".f.b.start configure -state normal");
			}
			tk->cmd(frame, "update");

		s := <-in =>
			if(s != nil){
				if(debug){
					t := s;
					if(!first)
						t = "\n"+t;
					first = 0;
					tkcmd(frame, ".f.t insert end '" + t);
					tkcmd(frame, ".f.t see end");
					tkcmd(frame, "update");
				}
				r := getresult(s, qno);
				if(r < 0)
					break;
				if(r >= 0 && r < len count){
					count[r]++;
					total++;
					for(i:=0; i < len count; i++){
						bars[i].v = real count[i]/real total;
						bars[i].draw();
					}
					tk->cmd(frame, "update");
				}
				#sys->print("%d %d\n", qno, r);
			}else
				fd = nil;
		}
}

mkbars(t: ref Tk->Toplevel, canvas: string, nbars: int): array of ref Bar
{
	x := 0;
	a := array[nbars] of ref Bar;
	for(i := 0; i < nbars; i++){
		b := ref Bar(t, canvas, nil, nil, nil, Rect((x,2),(x+20,202)), 0.0);
		b.border = tkcmd(t, sys->sprint("%s create rectangle %d %d %d %d",
			canvas, b.r.min.x,b.r.min.y,b.r.max.x,b.r.max.y));
		r := b.r.inset(1);
		b.inside = tkcmd(t, sys->sprint("%s create rectangle %d %d %d %d -fill red",
			canvas, r.max.x, r.max.y,r.max.x,r.max.y));
		b.label = tkcmd(t, sys->sprint("%s create text %d %d -justify center -anchor n -text '0%%",
			canvas, (r.min.x+r.max.x)/2, r.max.y+4));
		a[i] = b;
		x += 50;
	}
	tk->cmd(t, "update");
	return a;
}

Bar.draw(b: self ref Bar)
{
	r := b.r.inset(2);
	y := r.max.y - int (b.v * real r.dy());
	tkcmd(b.frame, sys->sprint("%s coords %s %d %d %d %d",
		b.canvas, b.inside, r.min.x, y, r.max.x, r.max.y));
	tkcmd(b.frame, sys->sprint("%s itemconfigure %s -text '%.0f%%",
		b.canvas, b.label, b.v*100.0));
}

getresult(msg: string, qno: int): int
{
	(nf, flds) := sys->tokenize(msg, " ");
	if(nf < 5 || hd flds == "error:")
		return -1;	# not of interest
	op := hd tl tl flds;
	flds = tl tl tl flds;
	if(op != "m")
		return -1; # not a message from leaf
	if(len flds < 3)
		return -1;	# bad format
	flds = tl flds;	# ignore user name
	if(int hd flds != qno)
		return -1;	# not current question
	result := hd tl flds;
	if(result[0] >= 'A' && result[0] <= 'D')
		return result[0]-'A';
	return -1;
}
	
reader(fd: ref Sys->FD, c: chan of string)
{
	buf := array[Sys->ATOMICIO] of byte;
	while((n := sys->read(fd, buf, len buf)) > 0)
		c <-= string buf[0:n];
	if(n < 0)
		c <-= sys->sprint("error: %r");
	c <-= nil;
}

opensvc(dir: string, svc: string, name: string): (ref Sys->FD, string, string)
{
	ctlfd := sys->open(dir+"/ctl", Sys->ORDWR);
	if(ctlfd == nil)
		return (nil, nil, sys->sprint("can't open %s/ctl: %r", dir));
	if(sys->fprint(ctlfd, "%s %s", svc, name) <= 0)
		return (nil, nil, sys->sprint("can't access %s service %s: %r", svc, name));
	buf := array [32] of byte;
	sys->seek(ctlfd, big 0, Sys->SEEKSTART);
	n := sys->read(ctlfd, buf, len buf);
	if (n <= 0)
		return (nil, nil, sys->sprint("%s/ctl: protocol error: %r", dir));
	return (ctlfd, dir+"/"+string buf[0:n], nil);
}

tkcmds(t: ref Tk->Toplevel, cmds: array of string)
{
	for (i := 0; i < len cmds; i++)
		tkcmd(t, cmds[i]);
}

tkcmd(t: ref Tk->Toplevel, cmd: string): string
{
	s := tk->cmd(t, cmd);
	if (s != nil && s[0] == '!')
		sys->fprint(sys->fildes(2), "poller: tk error: %s [%s]\n", s, cmd);
	return s;
}

fittoscreen(win: ref Tk->Toplevel)
{
	if (win.image == nil || win.image.screen == nil)
		return;
	r := win.image.screen.image.r;
	scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
	bd := int tkcmd(win, ". cget -bd");
	winsize := Point(int tkcmd(win, ". cget -actwidth") + bd * 2, int tkcmd(win, ". cget -actheight") + bd * 2);
	if (winsize.x > scrsize.x)
		tkcmd(win, ". configure -width " + string (scrsize.x - bd * 2));
	if (winsize.y > scrsize.y)
		tkcmd(win, ". configure -height " + string (scrsize.y - bd * 2));
	actr: Rect;
	actr.min = Point(int tkcmd(win, ". cget -actx"), int tkcmd(win, ". cget -acty"));
	actr.max = actr.min.add((int tkcmd(win, ". cget -actwidth") + bd*2,
				int tkcmd(win, ". cget -actheight") + bd*2));
	(dx, dy) := (actr.dx(), actr.dy());
	if (actr.max.x > r.max.x)
		(actr.min.x, actr.max.x) = (r.min.x - dx, r.max.x - dx);
	if (actr.max.y > r.max.y)
		(actr.min.y, actr.max.y) = (r.min.y - dy, r.max.y - dy);
	if (actr.min.x < r.min.x)
		(actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
	if (actr.min.y < r.min.y)
		(actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
	tkcmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
}