code: purgatorio

ref: 94443daf8e248e65afc8d3f17f26efea22748b51
dir: /appl/charon/gui.b/

View raw version
# Gui implementation for running under wm (tk window manager)
implement Gui;

include "common.m";
include "tk.m";
include "tkclient.m";

include "dialog.m";
	dialog: Dialog;

sys: Sys;

D: Draw;
	Font,Point, Rect, Image, Screen, Display: import D;

CU: CharonUtils;

E: Events;
	Event: import E;

tk: Tk;

tkclient: Tkclient;

WINDOW, CTLS, PROG, STATUS, BORDER, EXIT: con 1 << iota;
REQD: con ~0;

cfg := array[] of {
	(REQD,	"entry .ctlf.url -bg white -font /fonts/lucidasans/unicode.7.font -height 16"),
	(REQD,	"button .ctlf.back -bd 1 -command {send gctl back} -state disabled -text {back} -font /fonts/lucidasans/unicode.7.font"),
	(REQD,	"button .ctlf.stop -bd 1 -command {send gctl stop} -state disabled -text {stop} -font /fonts/lucidasans/unicode.7.font"),
	(REQD,	"button .ctlf.fwd -bd 1 -command {send gctl fwd} -state disabled -text {next} -font /fonts/lucidasans/unicode.7.font"),
	(REQD,	"label .status.status -bd 1 -font /fonts/lucidasans/unicode.6.font -height 14 -anchor w"),
	(REQD,	"button .ctlf.exit -bd 1 -bitmap exit.bit -command {send wm_title exit}"),
	(REQD,	"frame .f -bd 0"),
	(BORDER,	".f configure -bd 2 -relief sunken"),
	(CTLS|EXIT,	"frame .ctlf"),
	(STATUS,	"frame .status -bd 0"),
	(STATUS,	"frame .statussep -bg black -height 1"),
	(STATUS,	"button .status.snarf -text snarf -command {send gctl snarfstatus} -font /fonts/charon/plain.small.font"),

	(CTLS,	"bind .ctlf.url <Key-\n> {send gctl go}"),
	(CTLS,	"bind .ctlf.url <Key-\u0003> {send gctl copyurl}"),
	(CTLS,	"bind .ctlf.url <Key-\u0016> {send gctl pasteurl}"),

#	(PROG,	"canvas .prog -bd 0 -height 20"),
#	(PROG,	"bind .prog <ButtonPress-1> {send gctl b1p %X %Y}"),
	(CTLS,	"pack .ctlf.back .ctlf.stop .ctlf.fwd -side left -anchor w -fill y"),
	(CTLS,	"pack .ctlf.url -side left -padx 2 -fill x -expand 1"),
	(EXIT,	"pack .ctlf.exit -side right -anchor e"),
	(CTLS|EXIT,	"pack .ctlf -side top -fill x"),
	(REQD,	"pack .f -side top -fill both -expand 1"),
#	(PROG,	"pack .prog -side bottom -fill x"),
	(STATUS,	"pack .status.snarf -side right"),
	(STATUS,	"pack .status.status -side right -fill x -expand 1"),
	(STATUS,	"pack .statussep -side top -fill x"),
	(STATUS,	"pack .status -side bottom -fill x"),
	(CTLS|EXIT,	"pack propagate .ctlf 0"),
	(STATUS,		"pack propagate .status 0"),
};

framebinds := array[] of {
	"bind .f <Key> {send gctl k %s}",
	"bind .f <FocusOut> {send gctl focusout}",
	"bind .f <ButtonPress-1> {grab set .f;send gctl b1p %X %Y}",
	"bind .f <Double-ButtonPress-1> {send gctl b1p %X %Y}",
	"bind .f <ButtonRelease-1> {grab release .f;send gctl b1r %X %Y}",
	"bind .f <Motion-Button-1> {send gctl b1d %X %Y}",
	"bind .f <ButtonPress-2> {send gctl b2p %X %Y}",
	"bind .f <Double-ButtonPress-2> {send gctl b2p %X %Y}",
	"bind .f <ButtonRelease-2> {send gctl b2r %X %Y}",
	"bind .f <Motion-Button-2> {send gctl b2d %X %Y}",
	"bind .f <ButtonPress-3> {send gctl b3p %X %Y}",
	"bind .f <Double-ButtonPress-3> {send gctl b3p %X %Y}",
	"bind .f <ButtonRelease-3> {send gctl b3r %X %Y}",
	"bind .f <Motion-Button-3> {send gctl b3d %X %Y}",
	"bind .f <Motion> {send gctl m %X %Y}",
};

tktop: ref Tk->Toplevel;
mousegrabbed := 0;
offset: Point;
ZP: con Point(0,0);
popup: ref Popup;
popuptk: ref Tk->Toplevel;
gctl: chan of string;
drawctxt: ref Draw->Context;

realwin: ref Draw->Image;
mask: ref Draw->Image;

init(ctxt: ref Draw->Context, cu: CharonUtils): ref Draw->Context
{
	sys = load Sys Sys->PATH;
	D = load Draw Draw->PATH;
	CU = cu;
	E = cu->E;
	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;
	if(tkclient == nil)
		raise sys->sprint("EXInternal: can't load module Tkclient: %r");
	tkclient->init();

	wmctl: chan of string;
	buttons := parsebuttons((CU->config).buttons);
	winopts := parsewinopts((CU->config).framework);

	(tktop, wmctl) = tkclient->toplevel(ctxt, "", (CU->config).wintitle, buttons);

	ctxt = tktop.ctxt.ctxt;
	drawctxt = ctxt;
	display = ctxt.display;

	gctl = chan of string;
	tk->namechan(tktop, gctl, "gctl");
	tk->cmd(tktop, "pack propagate . 0");
	filtertkcmds(tktop, winopts, cfg);
	tkcmds(tktop, framebinds);
	w := (CU->config).defaultwidth;
	h := (CU->config).defaultheight;
	tk->cmd(tktop, ". configure -width " + string w + " -height " + string h);
	tk->cmd(tktop, "update");
	tkclient->onscreen(tktop, nil);
	tkclient->startinput(tktop, "kbd"::"ptr"::nil);
	makewins();
	mask = display.opaque;
	progress = chan of Progressmsg;
	pidc := chan of int;
	spawn progmon(pidc);
	<- pidc;
	spawn evhandle(tktop, wmctl, E->evchan);
	return ctxt;
}

parsebuttons(s: string): int
{
	b := 0;
	(nil, toks) := sys->tokenize(s, ",");
	for (;toks != nil; toks = tl toks) {
		case hd toks {
		"help" =>
			b |= Tkclient->Help;
		"resize" =>
			b |= Tkclient->Resize;
		"hide" =>
			b |= Tkclient->Hide;
		"plain" =>
			b = Tkclient->Plain;
		}
	}
	return b | Tkclient->Help;
}

parsewinopts(s: string): int
{
	b := WINDOW;
	(nil, toks) := sys->tokenize(s, ",");
	for (;toks != nil; toks = tl toks) {
		case hd toks {
		"status" =>
			b |= STATUS;
		"controls" or "ctls" =>
			b |= CTLS;
		"progress" or "prog" =>
			b |= PROG;
		"border" =>
			b |= BORDER;
		"exit" =>
			b |= EXIT;
		"all" =>
			# note: "all" doesn't include 'EXIT' !
			b |= WINDOW | STATUS | CTLS | PROG | BORDER;
		}
	}
	return b;
}

filtertkcmds(top: ref Tk->Toplevel, filter: int, cmds: array of (int, string))
{
	for (i := 0; i < len cmds; i++) {
		(val, cmd) := cmds[i];
		if (val & filter) {
			if ((e := tk->cmd(top, cmd)) != nil && e[0] == '!')
				sys->print("tk error on '%s': %s\n", cmd, e);
		}
	}
}

tkcmds(top: ref Tk->Toplevel, cmds: array of string)
{
	for (i := 0; i < len cmds; i++)
		if ((e := tk->cmd(top, cmds[i])) != nil && e[0] == '!')
			sys->print("tk error on '%s': %s\n", cmds[i], e);
}

clientr(t: ref Tk->Toplevel, wname: string): Rect
{
	bd := int tk->cmd(t, wname + " cget -borderwidth");
	x := bd + int tk->cmd(t, wname + " cget -actx");
	y := bd + int tk->cmd(t, wname + " cget -acty");
	w := int tk->cmd(t, wname + " cget -actwidth");
	h := int tk->cmd(t, wname + " cget -actheight");
	return Rect((x,y),(x+w,y+h));
}

progmon(pidc: chan of int)
{
	pidc <-= sys->pctl(0, nil);
	for (;;) {
		msg := <- progress;
#prprog(msg);
		# just handle stop button for now
		if (msg.bsid == -1) {
			case (msg.state) {
			Pstart =>	stopbutton(1);
			* =>		stopbutton(0);
			}
		}
	}
}

st2s := array [] of {
	Punused => "unused",
	Pstart => "start",
	Pconnected => "connected",
	Psslconnected => "sslconnected",
	Phavehdr => "havehdr",
	Phavedata => "havedata",
	Pdone => "done",
	Perr => "error",
	Paborted => "aborted",
};

prprog(m:Progressmsg)
{
	sys->print("%d %s %d%% %s\n", m.bsid, st2s[m.state], m.pcnt, m.s);
}


r2s(r: Rect): string
{
	return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
}

winpos(t: ref Tk->Toplevel): Point
{
	return (int tk->cmd(t, ". cget -actx"), int tk->cmd(t, ". cget -acty"));
}

evhandle(t: ref Tk->Toplevel, wmctl: chan of string, evchan: chan of ref Event)
{
	for(;;) {
		ev: ref Event = nil;
		dismisspopup := 1;
		alt {
		s := <-gctl =>
			(nil, l) := sys->tokenize(s, " ");
			case hd l {
			"focusout" =>
				ev = ref Event.Elostfocus;
			"b1p" or "b1r" or "b1d" or 
			"b2p" or "b2r" or "b2d" or 
			"b3p" or "b3r" or "b3d" or 
			"m" =>
				l = tl l;
				pt := Point(int hd l, int hd tl l);
				pt = pt.sub(offset);
				mtype := s2mtype(s);
				dismisspopup = 0;
				if(mtype == E->Mlbuttondown) {
					tk->cmd(t, "focus .f");
					pu := popup;
					if (pu != nil && !pu.r.contains(pt))
						dismisspopup = 1;
					pu = nil;
				}
				ev = ref Event.Emouse(pt, mtype);
			"k" =>
				dismisspopup = 0;
				k := int hd tl l;
				if(k != 0)
					ev = ref Event.Ekey(k);
			"back" =>
				ev = ref Event.Eback;
			"stop" =>
				ev = ref Event.Estop;
			"fwd" =>
				ev = ref Event.Efwd;
			"go" =>
				url := tk->cmd(tktop, ".ctlf.url get");
				if (url != nil)
					ev = ref Event.Ego(url, nil, 0, E->EGnormal);
			"copyurl" =>
				url := tk->cmd(tktop, ".ctlf.url get");
				snarfput(url);
			"pasteurl" =>
				url := tk->quote(tkclient->snarfget());
				tk->cmd(tktop, ".ctlf.url delete 0 end");
				tk->cmd(tktop, ".ctlf.url insert end " + url);
				tk->cmd(tktop, "update");
			"snarfstatus" =>
				url := tk->cmd(tktop, ".status.status cget -text");
				tkclient->snarfput(url);
			}
		s := <-t.ctxt.ctl or
		s = <-t.wreq or
		s = <-wmctl =>
			case s {
			"exit" =>
				hidewins();
				ev = ref Event.Equit(0);
			"task" =>
				if (cancelpopup())
					evchan <-= ref Event.Edismisspopup;
				tkclient->wmctl(t, s);
				if(tktop.image == nil)
					realwin = nil;
			"help" =>
				ev = ref Event.Ego((CU->config).helpurl, nil, 0, E->EGnormal);
			* =>
				if (s[0] == '!' && cancelpopup())
					evchan <-= ref Event.Edismisspopup;
				oldimg := t.image;
				e := tkclient->wmctl(t, s);
				if(s[0] == '!' && e == nil){
					if(t.image != oldimg){
						oldimg = nil;
						makewins();
						ev = ref Event.Ereshape(mainwin.r);
					}
					offset = tk->rect(tktop, ".f", 0).min;
				}
			}
		s := <-t.ctxt.kbd =>
			tk->keyboard(t, s);
		s := <-t.ctxt.ptr =>
			tk->pointer(t, *s);
		}
		if (dismisspopup) {
			if (cancelpopup()) {
				evchan <-= ref Event.Edismisspopup;
			}
		}
		if (ev != nil)
			evchan <-= ev;
	}
}

s2mtype(s: string): int
{
	mtype := E->Mmove;
	if(s[0] == 'm')
		mtype = E->Mmove;
	else {
		case s[1] {
		'1' =>
			case s[2] {
			'p' => mtype = E->Mlbuttondown;
			'r' => mtype = E->Mlbuttonup;
			'd' => mtype = E->Mldrag;
			}
		'2' =>
			case s[2] {
			'p' => mtype = E->Mmbuttondown;
			'r' => mtype = E->Mmbuttonup;
			'd' => mtype = E->Mmdrag;
			}
		'3' =>
			case s[2] {
			'p' => mtype = E->Mrbuttondown;
			'r' => mtype = E->Mrbuttonup;
			'd' => mtype = E->Mrdrag;
			}
		}
	}
	return mtype;
}

makewins()
{
	if(tktop.image == nil)
		return;
	screen := Screen.allocate(tktop.image, display.transparent, 0);
	offset = tk->rect(tktop, ".f", 0).min;
	r := tk->rect(tktop, ".f", Tk->Local);
	realwin = screen.newwindow(r, D->Refnone, D->White);
	realwin.origin(ZP, r.min);
	if(realwin == nil)
		raise sys->sprint("EXFatal: can't initialize windows: %r");

	mainwin = display.newimage(realwin.r, realwin.chans, 0, D->White);
	if(mainwin == nil)
		raise sys->sprint("EXFatal: can't initialize windows: %r");
}

hidewins()
{
	tk->cmd(tktop, ". unmap");
}

snarfput(s: string)
{
	tkclient->snarfput(s);
}

setstatus(s: string)
{
	tk->cmd(tktop, ".status.status configure -text " + tk->quote(s));
	tk->cmd(tktop, "update");
}

seturl(s: string)
{
	tk->cmd(tktop, ".ctlf.url delete 0 end");
	tk->cmd(tktop, ".ctlf.url insert 0 " + tk->quote(s));
	tk->cmd(tktop, "update");
}

auth(realm: string): (int, string, string)
{
	user := prompt(realm + " username?", nil).t1;
	passwd := prompt("password?", nil).t1;
	if(user == nil)
		return (0, nil, nil);
	return (1, user, passwd);
}

alert(msg: string)
{
sys->print("ALERT:%s\n", msg);
	return;
}

confirm(msg: string): int
{
sys->print("CONFIRM:%s\n", msg);
	return -1;
}

prompt(msg, dflt: string): (int, string)
{
	if(dialog == nil){
		dialog = load Dialog Dialog->PATH;
		dialog->init();
	}
	return (1, dialog->getstring(drawctxt, mainwin, msg));
	# return (-1, "");
}

stopbutton(enable: int)
{
	state: string;
	if (enable) {
		tk->cmd(tktop, ".ctlf.stop configure -bg red -activebackground red -activeforeground white");
		state = "normal";
	} else {
		tk->cmd(tktop, ".ctlf.stop configure -bg #dddddd");
		state = "disabled";
	}
	tk->cmd(tktop, ".ctlf.stop configure -state " + state + ";update");
}

backbutton(enable: int)
{
	state: string;
	if (enable) {
		tk->cmd(tktop, ".ctlf.back configure -bg lime -activebackground lime -activeforeground red");
		state = "normal";
	} else {
		tk->cmd(tktop, ".ctlf.back configure -bg #dddddd");
		state = "disabled";
	}
	tk->cmd(tktop, ".ctlf.back configure -state " + state + ";update");
}

fwdbutton(enable: int)
{
	state: string;
	if (enable) {
		tk->cmd(tktop, ".ctlf.fwd  configure -bg lime -activebackground lime -activeforeground red");
		state = "normal";
	} else {
		tk->cmd(tktop, ".ctlf.fwd configure -bg #dddddd");
		state = "disabled";
	}
	tk->cmd(tktop, ".ctlf.fwd configure -state " + state + ";update");
}

flush(r: Rect)
{
	if(realwin != nil) {
		oclipr := mainwin.clipr;
		mainwin.clipr = r;
		realwin.draw(r, mainwin, nil, r.min);
		mainwin.clipr = oclipr;
	}
}

clientfocus()
{
	tk->cmd(tktop, "focus .f");
	tk->cmd(tktop, "update");
}

exitcharon()
{
	hidewins();
	E->evchan <-= ref Event.Equit(0);
}

getpopup(r: Rect): ref Popup
{
	return nil;
#	cancelpopup();
##	img := screen.newwindow(r, D->White);
#	img := display.newimage(r, screen.image.chans, 0, D->White);
#	if (img == nil)
#		return nil;
#	winr := r.addpt(offset);	# race for offset
#
#	pos := "-x " + string winr.min.x + " -y " + string winr.min.y;
#	(top, nil) := tkclient->toplevel(drawctxt, pos, nil, Tkclient->Plain);
#	tk->namechan(top, gctl, "gctl");
#	tk->cmd(top, "frame .f -bd 0 -bg white -width " + string r.dx() + " -height " + string r.dy());
#	tkcmds(top, framebinds);
#	tk->cmd(top, "pack .f; update");
#	tkclient->onscreen(tktop, "onscreen");
#	tkclient->startinput(tktop, "kbd"::"ptr"::nil);
#	win := screen.newwindow(winr, D->Refbackup, D->White);
#	if (win == nil)
#		return nil;
#	win.origin(r.min, winr.min);
#
#	popuptk = top;
#	popup = ref Popup(r, img, win);
## XXXX need to start a thread to feed mouse/kbd events from popup,
## but we need to know when to tear it down.
#	return popup;
}

cancelpopup(): int
{
	popuptk = nil;
	pu := popup;
	if (pu == nil)
		return 0;
	pu.image = nil;
	pu.window = nil;
	pu = nil;
	popup = nil;
	return 1;
}

Popup.flush(p: self ref Popup, r: Rect)
{
	win := p.window;
	img := p.image;
	if (win != nil && img != nil)
		win.draw(r, img, nil, r.min);
}