code: purgatorio

ref: 42dfac6916ebbdac65cbec8b3e1a80c3ee41423c
dir: /appl/wm/samtk.b/

View raw version
implement Samtk;

include "sys.m";
sys: Sys;
sprint, FD: import sys;

include "draw.m";
draw:	Draw;

include "samterm.m";
Context, Flayer, Text, Section: import Samterm;

include "tkclient.m";

include "samtk.m";

ctxt: ref Context;

tk:	Tk;
tkclient:	Tkclient;

tksam1 := array[] of {
	"frame .w",
	"scrollbar .w.s -command {send scroll}",
	"text .w.t -width 80w -height 8h",
	"pack .w.s -side left -fill y",
	"pack .w.t -fill both -expand 1",
	"pack .Wm_t -fill x",
	"pack .w -fill both -expand 1",
	"pack propagate . 0",
};

tkwork1 := array[] of {
	"frame .w",
	"scrollbar .w.s -command {send scroll}",
	"text .w.t -width 80w -height 20h",
	"pack .w.s -side left -fill y",
	"pack .w.t -fill both -expand 1",
	"pack .Wm_t -fill x",
	"pack .w -fill both -expand 1",
	"pack propagate . 0",
};

tkcmdlist := array[] of {
	"bind .w.t <Key> {send keys {%A}}",
	"bind .w.t <Key-\b> {send keys {%A}}",
	"bind .w.s <ButtonRelease-1> +{send scroll %s %b %y}",
	"bind .w.t <ButtonPress-1> +{send button1 %s %b %x %y}",
	"bind .w.t <ButtonRelease-1> +{send button1 %s %b %x %y}",
	"bind .w.t <Double-ButtonPress-1> {send button1 2 %b %x %y}",
	"bind .w.t <Double-ButtonRelease-1> {send button1 3 %b %x %y}",
	"bind .w.t <ButtonPress-2> {.m2 post %x %y; grab set .m2}",
	"bind .w.t <ButtonPress-3> {.m3 post %x %y; grab set .m3}",
	"bind . <Configure> {send titlesel resize}",
	"focus .w.t",
	"update"
};

menuidx := array[2] of {"0","0"};

init(c: ref Context)
{
	ctxt = c;
	sys = load Sys Sys->PATH;
	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;

	tkclient = load Tkclient Tkclient->PATH;
	tkclient->init();

	scrollpos = scrolllines = 0;
}

x := 10;
y := 10;

newflayer(tag, tp: int): ref Flayer
{
	if (ctxt.which != nil) {
		tk->cmd(ctxt.which.t,
			".Wm_t.title configure -background blue; update");
	}
	(t, cmdc) := tkclient->toplevel(ctxt.ctxt.screen, "-borderwidth 1 -relief raised", "SamTerm", Tkclient->Appl);
	tk->cmd(t, ". configure -x "+string x+" -y "+string y+"; update");

	if (x == 10 && y == 10) {
		y = 200;
	} else {
		x += 40;
		y += 40;
	}

	n := chanadd();
	ctxt.titlesel[n] = cmdc;
	tk->namechan(t, ctxt.menu3sel[n], "menu3");
	tk->namechan(t, ctxt.menu2sel[n], "menu2");
	tk->namechan(t, ctxt.buttonsel[n], "button1");
	tk->namechan(t, ctxt.keysel[n], "keys");
	tk->namechan(t, ctxt.scrollsel[n], "scroll");
	tk->namechan(t, ctxt.titlesel[n], "titlesel");

	lines: int;
	if (tp) {
		lines = 8;
		tkclient->tkcmds(t, tksam1);
		mkmenu2c(t);
	} else {
		lines = 20;
		tkclient->tkcmds(t, tkwork1);
		mkmenu2(t);
	}
	mkmenu3(t);
	tkclient->tkcmds(t, tkcmdlist);

	f := ref Flayer(
		tag,		# tag
		t,		# t
		"SamTerm",	# tkwin
		(0, 0),		# scope
		(0, 0),		# dot
		int tk->cmd(t, ".w.t cget actwidth"),		# screen width
		int tk->cmd(t, ".w.t cget actheight") / lines,	# lineheigth
		lines,		# lines
		(0, 1),		# scrollbar
		-1		# typepoint
	);
	ctxt.flayers[n] = f;
	return f;
}

menu2str := array [] of {
	"cut",
	"paste",
	"snarf",
	"look",
#	"exch",
	"send",		# storage for last pattern
};

menu3str := array [] of {
	"new",
	"zerox",
	"close",
	"write",
};

mkmenu2c(t: ref Tk->Toplevel)
{
	menus := array [NMENU2+1] of string;

	menus[0] = "menu .m2";
	for (i := 0; i < NMENU2; i++) {
		menus[i+1] = addmenuitem(2, "menu2", menu2str[i]);
	}
	tkclient->tkcmds(t, menus);
}

mkmenu2(t: ref Tk->Toplevel)
{
	menus := array [NMENU2+1] of string;

	menus[0] = "menu .m2";
	for (i := 0; i < NMENU2-1; i++) {
		menus[i+1] = addmenuitem(2, "menu2", menu2str[i]);
	}
	menus[NMENU2] = addmenuitem(2, "edit", "/");
	tkclient->tkcmds(t, menus);
}

mkmenu3(t: ref Tk->Toplevel)
{
	menus := array [NMENU3+len ctxt.menus+1] of string;

	menus[0] = "menu .m3";
	for (i := 0; i < NMENU3; i++) {
		menus[i+1] = addmenuitem(3, "menu3", menu3str[i]);
	}
	for (i = 0; i < len ctxt.menus; i++) {
		menus[i+NMENU3+1] = addmenuitem(3, "menu3", ctxt.menus[i].name);
	}
	tkclient->tkcmds(t, menus);
}

addmenuitem(d: int, m, s: string): string
{
	return sprint(".m%d add command -text %s -command {send %s %s}",
		d, s, m, s);
}

menuins(pos: int, s: string)
{
	for (i := 0; i < len ctxt.flayers; i++)
	   tk->cmd(ctxt.flayers[i].t,
	      sprint(".m3 insert %d command -text %s -command {send menu3 %s}",
		pos + NMENU3, s, s));
}

menudel(pos: int)
{
	for (i := 0; i < len ctxt.flayers; i++)
	    tk->cmd(ctxt.flayers[i].t, sprint(".m3 delete %d", pos + NMENU3));
}

hsetpat(s: string)
{
	for (i := 0; i < len ctxt.flayers; i++) {
	    fl := ctxt.flayers[i];
	    if (fl.tag != ctxt.cmd.tag) {
		tk->cmd(fl.t, ".m2 entryconfigure "
		        + string Search
		        + " -command {send menu2 search} -text '/" + s);
	    }
	}
}

lastsearchstring := "//";

setmenu(num : int,c : string){
		fl := ctxt.flayers[num];
		(nil, l) := sys->tokenize(c, " ");
		x1 := int hd l - 50;
		y1 := int hd tl l - int tk->cmd(fl.t, ".m"+string num+" yposition "+menuidx[num-2]) 
								- 10;
		tk->cmd(fl.t, ".m"+string num+" activate "+menuidx[num-2]+
			"; .m"+string num+" post "+string x1+" "+string y1+
			"; grab set .m"+string num+"; update");
}

titlectl(win: int, menu: string)
{
	tkclient->wmctl(ctxt.flayers[win].t, menu);
}

flraise(t: ref Text, fl: ref Flayer)
{
	nfls: list of ref Flayer;

	nfls = nil;
	t.flayers = fl :: dellist(t.flayers, fl);
	tk->cmd(fl.t, "raise .; focus .w.t; update");
}

dellist(fls: list of ref Flayer, fl: ref Flayer): list of ref Flayer
{
	if (fls == nil) return nil;
	if (hd fls == fl) return dellist(tl fls, fl);
	return hd fls :: dellist(tl fls, fl);
}

append(fls: list of ref Flayer, fl: ref Flayer): list of ref Flayer
{
	if (fls == nil) return fl :: nil;
	return hd fls :: append(tl fls, fl);
}

focus(fl: ref Flayer)
{
	tk->cmd(fl.t, "focus .w.t; update");
}

newcur(t: ref Text, fl: ref Flayer)
{
	if (ctxt.which == fl) return;
	flraise(t, fl);
	ctxt.which = fl;
	if (t != ctxt.cmd)
		ctxt.work = fl;
}

settitle(t: ref Text, s: string)
{
	sd := "";
	sz := "";
	if (t.state & Samterm->Dirty) sd = " (Dirty)";
	if (t != ctxt.cmd && (t.state & Samterm->LDirty)) sd = " (Modified)";
	if (len t.flayers > 1) sz = " (Zeroxed)";
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		fl.tkwin = s;
		tkclient->settitle(fl.t, s + sd + sz);
		tk->cmd(fl.t, "update");
	}
}

resize(fl: ref Flayer)
{
	fl.lines = int tk->cmd(fl.t, ".w.t cget actheight") / fl.lineheigth;
}

allflayers(s: string)
{
	for (i := 0; i < len ctxt.texts; i++)
		for (fls := ctxt.texts[i].flayers; fls != nil; fls = tl fls) {
			fl := hd fls;
			tk->cmd(fl.t, s);
		}
}

setdot(fl: ref Flayer, l1, l2: int)
{
	tk->cmd(fl.t, ".w.t tag remove sel 0.0 end");

	fl.dot.first = l1;
	fl.dot.last = l2;
	if (l2 <= fl.scope.first)
		tk->cmd(fl.t, ".w.t mark set insert 0.0");
	else if (fl.scope.last <= l1)
		tk->cmd(fl.t, ".w.t mark set insert end");
	else {
		tk->cmd(fl.t, sprint(".w.t mark set insert 0.0+%dchars",
				l1-fl.scope.first));
		if (l1 != l2)
			tk->cmd(fl.t, sprint(".w.t tag add sel 0.0+%dchars 0.0+%dchars",
				l1-fl.scope.first,
				l2-fl.scope.first));
	}
	tk->cmd(fl.t, "update");
}

panic(s: string)
{
	stderr := sys->fildes(2);
	sys->fprint(stderr, "Panic: %s\n", s);
	f := sys->sprint("#p/%d/ctl", ctxt.pgrp);
	if ((fd := sys->open(f, sys->OWRITE)) != nil)
		sys->write(fd, array of byte "killgrp\n", 8);
	exit;
}

whichmenu(tag: int): int
{
	for (i := 0; i < len ctxt.menus; i++)
		if (ctxt.menus[i].tag == tag)
			return i;
	return -1;
}

whichtext(tag: int): int
{
	for (i := 0; i < len ctxt.texts; i++)
		if (ctxt.texts[i].tag == tag)
			return i;
	return -1;
}

setscrollbar(t: ref Text, fl: ref Flayer)
{
	ll := real t.nrunes;
	f1 := 0.0; f2 := 1.0;
	if (ll != 0.0) {
		f1 = real fl.scope.first / ll;
		if (fl.scope.last > t.nrunes)
			f2 = 1.0;
		else
			f2 = real fl.scope.last / ll;
	}
	fl.scrollbar = fl.scope;
	tk->cmd(fl.t, sprint(".w.s set %f %f; update", f1, f2));
}

buttonselect(fl: ref Flayer, s: string): int
{
	tag := fl.tag;
	if ((i := whichtext(tag)) < 0) panic("buttonselect: whichtext");
	t := ctxt.texts[i];

	(n, l) := sys->tokenize(s, " ");
	if (n != 4) panic("buttonselect");

	# ignore mouse down -- wait for mouse up
	if (hd l == "1" || hd l == "3") return 0;

	if (ctxt.which != fl) {
		if (ctxt.menus[i].text != ctxt.cmd)
			ctxt.work = fl;
		newcur(t, fl);
#		setdot(fl, fl.dot.first, fl.dot.first);
		return 0;
	}

	if (hd l == "2") {
		# Double click
		l = tl tl l;
		s = tk->cmd(fl.t, ".w.t index @" + hd l + "," + hd tl l);
		fl.dot.first = fl.dot.last = coord2pos(t, fl, s);
		return 1;
	}

	rg := tk->cmd(fl.t, ".w.t tag ranges sel");
	if (rg == "") {
		# Nothing selected, find insertion point
		l = tl tl l;
		s = tk->cmd(fl.t, ".w.t index @" + hd l + "," + hd tl l);
		fl.dot.first = fl.dot.last = coord2pos(t, fl, s);
	} else {
		(n, l) = sys->tokenize(rg, " ");
		#if (n == 4 && hd tl l == hd tl tl l)
		#	lst := hd tl tl tl l;
		#else if (n != 2) panic("buttonselect: tag ranges");
		#else lst = hd tl l;
		# We only have one contiguous selection, so, take the
		# first as dot.first and the last as dot.last
		fst:=hd l;
		lst:=fst;
		while(l!=nil){
			lst=hd l;
			l = tl l;
		}
		fl.dot.first = coord2pos(t, fl, fst);
		fl.dot.last = coord2pos(t, fl, lst);
		tk->cmd(fl.t, ".w.t mark set insert " + fst);
		tk->cmd(fl.t, "update");
	}
	return 0;
}

coord2pos(t: ref Text, fl: ref Flayer, s: string): int
{
	x, y: int;

	(n, l) := sys->tokenize(s, ".");
	if (n != 2) panic("coord2pos");
	y = (int hd l) - 1;
	x = int hd tl l;
	if (x == 0 && y == 0) return fl.scope.first;
	first := fl.scope.first;
	for (scts := t.sects; scts != nil; scts = tl scts) {
		sct := hd scts;
		if (first >= sct.nrunes) {
			first -= sct.nrunes;
			continue;
		}
		if (first > 0) i := first; else i = 0;
		while (i < len sct.text) {
			if (y) {
				if (sct.text[i++] == '\n') y--;
			} else {
				if (x <= 1)
					return fl.scope.first - first + i + x;
				if (sct.text[i++] == '\n') panic("coord2pos");
				x--;
			}
		}
		if (len sct.text < sct.nrunes) panic("coord2pos: hole");
		first -= sct.nrunes;
	}
	if (x <= 0 && y == 0) return t.nrunes;
	panic("coord2pos: can't find");
	return(-1);
}

scrollpos, scrolllines: int;

scroll(fl: ref Flayer, s: string): (int, int)
{
	tag := fl.tag;
	if ((i := whichtext(tag)) < 0) panic("scroll: whichtext");
	t := ctxt.texts[i];
	(n, l) := sys->tokenize(s, " ");
	height := fl.scrollbar.last - fl.scrollbar.first;
	length := t.nrunes;
	case (hd l) {
	"0" =>
		if (n != 3) panic("scroll: format");
		return (scrollpos, scrolllines);
	"moveto" =>
		if (n != 2) panic("scroll: format");
		f := real hd tl l;
		if (f < 0.0) f = 0.0;
		if (f > 1.0) f = 1.0;
		scrollpos = int (f * real length) - height/2;
		scrolllines = 1;
	"scroll" =>
		if (n != 3) panic("scroll: format");
		l = tl l;
		n = int hd l;
		case(hd tl l) {
		"page" =>
			if (n < 0) {
				scrollpos = fl.scrollbar.first;
				scrolllines = fl.lines;
				break;
			}
			scrollpos = fl.scrollbar.last;
			scrolllines = 0;
		"unit" =>
			if (n < 0) {
				scrollpos = fl.scrollbar.first - 1;
				scrolllines = 1;
				break;
			}
			(p, q) := rasplines(t.sects, fl.scrollbar.first, 1);
			if (p > 0) {
				scrollpos = p;
				scrolllines = 0;
			} else {
				scrollpos = fl.scrollbar.first;
				scrolllines = 0;
			}
		}
	* =>
		panic("scroll: input");
	}
	if (scrollpos > length)
		scrollpos = length;
	if (scrollpos < 0) {
		scrollpos = 0;
		scrolllines = 0;
	}
	if (length != 0)
		tk->cmd(fl.t, sprint(".w.s set %f %f",
			real scrollpos / real length,
			real (scrollpos + height) / real length));
	else
		tk->cmd(fl.t, ".w.s set 0.0 1.0");
	tk->cmd(fl.t, "update");
	return (-1, -1);
}

flclear(fl: ref Flayer)
{
	tk->cmd(fl.t, ".w.t delete 0.0 end");
	tk->cmd(fl.t, "update");
}

flinsert(fl: ref Flayer, l: int, s: string)
{
	offset := l-fl.scope.first;
	tk->cmd(fl.t, ".w.t insert 0.0+" + string offset + "chars '" + s);
	setdot(fl, fl.dot.first, fl.dot.last);
}

fldelexcess(fl: ref Flayer)
{
	tk->cmd(fl.t, ".w.t delete " + string (fl.lines+1) + ".0 end");
}

fldelete(fl: ref Flayer, l1, l2: int)
{
	s: string;
	if (l1 <= fl.scope.first) {
		if (l2 >= fl.scope.last) {
			s = sprint(".w.t delete 0.0 end");
			fl.scope.first = fl.scope.last = l1;
		} else {
			s = sprint(".w.t delete 0.0 0.0+%dchars",
				l2 - fl.scope.first);
			fl.scope.last -= l2 - l1;
			fl.scope.first = l1;
		}
	} else {
		if (l2 >= fl.scope.last) {
			s = sprint(".w.t delete 0.0+%dchars end",
				l1 - fl.scope.first);
			fl.scope.last = l1;
		} else {
			s = sprint(".w.t delete 0.0+%dchars 0.0+%dchars",
				l1 - fl.scope.first, l2 - fl.scope.first);
			fl.scope.last -= l2 - l1;	
		}
	}
	if (fl.dot.first >= l2) fl.dot.first -= l2-l1;
	else if (fl.dot.first > l1) fl.dot.first = l1;
	if (fl.dot.last >= l2) fl.dot.last -= l2-l1;
	else if (fl.dot.last > l1) fl.dot.last = l1;
	tk->cmd(fl.t, s);
	setdot(fl, fl.dot.first, fl.dot.last);
	tk->cmd(fl.t, "update");
}

# Calculate position forward or backward nlines lines from pos.
# If lines > 0 count forward, if lines < 0 count backward.\
# Returns a pair, (position, nlines).  Nlines is the remaining
# number of lines to be found.  If non-zero, beginning or end of
# rasp was encountered while still counting, or a hole was
# encountered.  In the former case, position will be 0 or nrunes,
# in the latter case, position will be set to -1.
# To search to the beginning of the current line, set nlines to -1;

rasplines(scts: list of ref Section, pos, nlines: int): (int, int)
{
	p, i: int;
	if (nlines < 0) {
		if (scts != nil) {
			sct := hd scts; scts = tl scts;
			if (pos > sct.nrunes) {
				(p, nlines) =
				    rasplines(scts, pos - sct.nrunes, nlines);
				if (p < 0) return (p, nlines);
				pos = p + sct.nrunes;
				if (nlines == 0) return (pos, 0);
			}
			if (pos > len sct.text) return (-1, nlines);
			for (p = pos-1; p >= 0; p--) {
				if (sct.text[p] == '\n') nlines++;
				if (nlines == 0) return (p+1, 0);
			}
		}
		return (0, nlines);
	} else {
		p = 0;
		while (scts != nil) {
			sct := hd scts; scts = tl scts;
			if (pos < sct.nrunes) {
				for (i = pos; i < len sct.text; i++) {
					if (sct.text[i] == '\n') nlines--;
					if (nlines == 0) return (p+i+1, 0);
				}
				if (i < sct.nrunes) return (-1, nlines);
			}
			pos -= sct.nrunes;
			if (pos < 0) pos = 0;
			p += sct.nrunes;
		}
		return (p, nlines);
	}
}

chanadd(): int
{
	l := len ctxt.flayers;

	keysel := array [l+1] of chan of string;
	keysel[0:] = ctxt.keysel;
	keysel[l] = chan of string;
	ctxt.keysel = keysel;
	scrollsel := array [l+1] of chan of string;
	scrollsel[0:] = ctxt.scrollsel;
	scrollsel[l] = chan of string;
	ctxt.scrollsel = scrollsel;
	buttonsel := array [l+1] of chan of string;
	buttonsel[0:] = ctxt.buttonsel;
	buttonsel[l] = chan of string;
	ctxt.buttonsel = buttonsel;
	menu2sel := array [l+1] of chan of string;
	menu2sel[0:] = ctxt.menu2sel;
	menu2sel[l] = chan of string;
	ctxt.menu2sel = menu2sel;
	menu3sel := array [l+1] of chan of string;
	menu3sel[0:] = ctxt.menu3sel;
	menu3sel[l] = chan of string;
	ctxt.menu3sel = menu3sel;
	titlesel := array [l+1] of chan of string;
	titlesel[0:] = ctxt.titlesel;
	titlesel[l] = chan of string;
	ctxt.titlesel = titlesel;
	flayers := array [l+1] of ref Flayer;
	flayers[0:] = ctxt.flayers;
	flayers[l] = nil;
	ctxt.flayers = flayers;
	return l;
}

chandel(n: int)
{
	l := len ctxt.flayers;
	if (n >= l)
		panic("chandel");

	keysel := array [l-1] of chan of string;
	keysel[0:] = ctxt.keysel[0:n];
	keysel[n:] = ctxt.keysel[n+1:];
	ctxt.keysel = keysel;
	scrollsel := array [l-1] of chan of string;
	scrollsel[0:] = ctxt.scrollsel[0:n];
	scrollsel[n:] = ctxt.scrollsel[n+1:];
	ctxt.scrollsel = scrollsel;
	buttonsel := array [l-1] of chan of string;
	buttonsel[0:] = ctxt.buttonsel[0:n];
	buttonsel[n:] = ctxt.buttonsel[n+1:];
	ctxt.buttonsel = buttonsel;
	menu2sel := array [l-1] of chan of string;
	menu2sel[0:] = ctxt.menu2sel[0:n];
	menu2sel[n:] = ctxt.menu2sel[n+1:];
	ctxt.menu2sel = menu2sel;
	menu3sel := array [l-1] of chan of string;
	menu3sel[0:] = ctxt.menu3sel[0:n];
	menu3sel[n:] = ctxt.menu3sel[n+1:];
	ctxt.menu3sel = menu3sel;
	titlesel := array [l-1] of chan of string;
	titlesel[0:] = ctxt.titlesel[0:n];
	titlesel[n:] = ctxt.titlesel[n+1:];
	ctxt.titlesel = titlesel;
	flayers := array [l-1] of ref Flayer;
	flayers[0:] = ctxt.flayers[0:n];
	flayers[n:] = ctxt.flayers[n+1:];
	ctxt.flayers = flayers;
}