Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/octopus/port/live/owptext.b.frame

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#
# Text and frame management needs a rewrite.
# 

implement Pimpl;
include "mods.m";
mods, debug, win, tree: import dat;

frame: Framem;

Menu: import menus;
setcursor, Waiting, Arrow,
getfont, Cpointer, cols, panelback, maxpt, cookclick, drawtag,
BACK,TEXT, readsnarf, Inset, terminate, writesnarf, readfile, 
SET, CLEAR, SHAD, BORD, CMtriple, CMdouble : import gui;
Ptag, Pline, Pedit, Pshown, Psync, Pdead, Pdirty, Ptbl, Predraw,
intag, escape, unescape, nth, Panel: import wpanel;
panelctl, panelkbd, panelmouse, tagmouse, Tree: import wtree;
Frame: import frame;
fixpos,  dtxt, strstr, Maxline, Blks, strchr, Str: import tblks;
Edit, Edits: import tundo;
Ptext: import text;
usage: import arg;

panels: array of ref Ptext;
textmenu: ref Menu;

# To allow search from tag lines, anytime a text is selected it becomes
# an argument for search, open, etc. The argument is void when they
# keyboard is used or a single click is made.
seltext: string;

pimpl(p: ref Panel): ref Ptext
{
	if (p.implid < 0 || p.implid > len panels || panels[p.implid] == nil)
		panic("draw: bug: no impl");
	return panels[p.implid];
}

init(d: Livedat): string
{
	prefixes = list of {"text:", "button:", "label:", "tag:", "tbl:"};
	dat = d;
	initmods();
	frame = load Framem Framem->PATH;
	if (frame == nil)
		return sprint("loading %s: %r", Framem->PATH);
	dat = d;
	str = load String String->PATH;
	if (str == nil)
		return sprint("loading %s: %r", String->PATH);
	text = load Text Text->PATH;
	if (text == nil)
		return sprint("loading %s: %r", Text->PATH);
	tblks = load Tblks Tblks->PATH;
	if (tblks == nil)
		return sprint("loading %s: %r", Tblks->PATH);
	tundo= load Tundo Tundo->PATH;
	if (tundo == nil)
		return sprint("loading %s: %r", Tundo->PATH);
	frame->init(d);
	tblks->init(sys, str, err, debug['T']);
	tundo->init(sys, err, debug['T']);
	text->init(sys, str, frame, err, tblks, tundo, debug['T']);
	return nil;
}

settextsize(p: ref Panel)
{
	pi := pimpl(p);
	pi.rect = p.rect;
	if (!(p.flags&Pline)){
		pi.rect.min.y += 1;
	}
	p.maxsz.y = 0;
	s := pi.blks.pack();
	if (p.flags&Pline){
		p.minsz.y = p.font.height; # up/down margins
		if (!(p.flags&Pedit) && len s.s > 0)
			p.minsz.x = p.font.width(s.s);
	} else {
		p.minsz = Point(p.font.height*2,p.font.height*2);
		p.minsz.y += 1;
		if (pi.nlines == 0)
			p.maxsz.y = 0;
		else {
			n := pi.nlines + 1;
			if (n < 3)
				n = 3;
			p.maxsz.y =  p.font.height * n;
			p.maxsz.y += 1;
		}
	}
	pi.nrows = pi.rect.dy() / p.font.height;
	pi.ncols = pi.rect.dx() / p.font.width("M");
}

pinit(p: ref Panel)
{
	if (tree == nil)
		tree = dat->tree;
	if (textmenu == nil)
		textmenu = Menu.new(array[] of {"Exec", "Find", "Put", "Apply", "Paste",  "Cut", "Close", "Open"});
	for (i := 0; i < len panels; i++)
		if (panels[i] == nil)
			break;
	if (i == len panels){
		npanels := array[i+16] of ref Ptext;
		npanels[0:] = panels;
		panels = npanels;
	}
	p.implid = i;
	pi := panels[i] = Ptext.new();
	pi.mlast = 0; # Set Exec by default
	p.flags &= ~(Pline|Pedit);
	pi.tab = 4;
	if (len p.name > 3 && p.name[0:3] == "tbl"){
		p.flags |= Ptbl|Pedit;
		p.wants = Point(1,1);
		pi.tabtext = "[empty]";
		pi.mlast = 5; # Open by default
	} else if (len p.name > 4 && p.name[0:4] == "text"){
		p.flags |= Pedit;
		p.wants = Point(1,1);
		pi.mlast = 2; # Find
	} else if (len p.name > 3 && p.name[0:3] == "tag"){
		p.flags |= Pline|Pedit;
		p.wants = Point(1,0);
	} else {
		p.flags |= Pline;
	}
	if (p.flags&Pline)
		p.font = getfont("B");
	else
		p.font = getfont("R");
	s := "";
	if (p.flags&Pline){
		(nil, n) := splitl(p.name, ":");
		if (n != nil){
			n = n[1:];
			(s, nil) = splitl(n, ".");
			if (s == nil)
				s = nil;
		}
	}
	pi.blks = Blks.new(s);
	settextsize(p);
}

pterm(p: ref Panel)
{
	if (p.implid != -1){
		pi := pimpl(p);
		panels[p.implid] = nil; 
		p.implid = -1;
		pi.term();
	}
}

dirty(p: ref Panel, set: int)
{
	if (set)
		p.flags |= Psync;
	else
		p.flags &= ~Psync;
	if (p.flags&Pline)	# lines do not get dirty
		return;
	old := p.flags;
	if (set)
		p.flags |= Pdirty;
	else
		p.flags &= ~Pdirty;
	if (old != p.flags){
		if (set)
			p.fsctl("dirty\n");
		else
			p.fsctl("clean\n");
		spawn tree.tags("/"); # we might get here called from tree; avoid deadlock.
	}
}

filltext(p: ref Panel)
{
	nl := "\n";
	pi := pimpl(p);
	(i, n) := pi.blks.seek(pi.froff + pi.f.nchars);
	b := pi.blks.b;
	for(; i < len b && b[i] != nil && !pi.f.lastlinefull; i++){
		pi.f.insert(b[i].s[n:], pi.f.nchars);
		n = 0;
	}
	# Frame may not clear the right of the last line. BUG?
	# we force that by inserting a fake \n
	if (!pi.f.lastlinefull){
		pi.f.insert(nl, pi.f.nchars);
		pi.f.delete(pi.f.nchars-1, pi.f.nchars);
	}
	# clear the unused part
	pt := pi.rect.min;
	pt.y += pi.f.nlines * p.font.height;
	wr := Rect(pt, pi.rect.max);
	cback := panelback(p);
	if (pi.f.ticked && pi.f.p0 == pi.f.p1 && pi.f.p0 == pi.f.nchars){
		# avoid clearing the tick
		hidesel(p);
		win.image.draw(wr, cback, nil, (0,0));
		drawsel(p);
	} else
		win.image.draw(wr, cback, nil, (0,0));
}

hidesel(p: ref Panel)
{
	pi := pimpl(p);
	pi.f.drawsel(pi.f.ptofchar(pi.f.p0), pi.f.p0, pi.f.p1, 0);
}

drawsel(p: ref Panel)
{
	pi := pimpl(p);
	f := pi.f;
	ss := fixpos(f.nchars, pi.ss - pi.froff);
	se := fixpos(f.nchars, pi.se - pi.froff);
	if(f.ticked && ss == f.p0 && se == f.p1)	# it's already there.
		return;
	# This code below was taken from acme's
	#
	if(f.p1<=ss || se<=f.p0 || ss==se || f.p1==f.p0){
		# no overlap or too easy to bother trying
		if (f.p0 != f.p1 || !(p.flags&Pedit))
			f.drawsel(f.ptofchar(f.p0), f.p0, f.p1, 0);
		if (ss != se || (p.flags&Pedit))
			f.drawsel(f.ptofchar(ss), ss, se, 1);
	} else {
		if(ss < f.p0){
			# extend selection backwards
			f.drawsel(f.ptofchar(ss), ss, f.p0, 1);
		}else if(ss > f.p0){
			# trim first part of selection 
			f.drawsel(f.ptofchar(f.p0), f.p0, ss, 0);
		}
		if(se > f.p1){
			# extend selection forwards
			f.drawsel(f.ptofchar(f.p1), f.p1, se, 1);
		}else if(se < f.p1){
			# trim last part of selection
			f.drawsel(f.ptofchar(se), se, f.p1, 0);
		}
	}
	f.p0 = ss;
	f.p1 = se;
}

tab(p: ref Panel)
{
	pi := pimpl(p);
	(n, toks) := tokenize(pi.tabtext, "\t\n");
	if (n <= 0)
		return;
	wl := array[n] of string;
	for (i := 0; i < n; i++){
		wl[i] = hd toks;
		toks = tl toks;
	}
	mint := p.font.width("0");
	maxtab := 3 * mint;
	maxt := maxtab;
	pi.f.maxtab = 3 * mint;
	colw := 0;
	for(i=0; i<n; i++){
		w := p.font.width(wl[i]);
		if(maxt-w%maxt < mint)
			w += mint;
		if(w % maxt)
			w += maxt-(w%maxt);
		if(w > colw)
			colw = w;
	}
	ncol := 1;
	if (colw != 0)
		ncol = pi.rect.dx()/colw;
	if (ncol < 1)
		ncol = 1;
	nrow := (n+ncol-1)/ncol;

	ns := "";
	for(i=0; i<nrow; i++){
		for(j:=i; j<n; j+=nrow){
			ns += wl[j];
			if(j+nrow >= n)
				break;
			w := p.font.width(wl[j]);
			if(maxt-w%maxt < mint){
				ns += "\t";
				w += mint;
			}
			do{
				ns += "\t";
				w += maxt-(w%maxt);
			}while(w < colw);
		}
		ns += "\n";
	}
	pi.blks = Blks.new(ns);
	l := len ns;
	pi.s0 = fixpos(pi.s0, l);
	pi.ss = fixpos(pi.ss, l);
	pi.se = fixpos(pi.se, l);
	pi.froff = fixpos(pi.froff, l);
	pi.mark = fixpos(pi.mark, l);
}

drawmark(img: ref Image, ur: Rect, cback: ref Image)
{
	lr := ur;
#	lr.max.x = lr.min.x + ur.dx()/3;
#	img.draw(lr, cback, nil, (0,0));
#	lr.min.x = lr.max.x;
	lr.max.x = lr.min.x + ur.dx()/3;
	img.draw(lr, cols[TEXT], nil, (0,0));
	lr.min.x = lr.max.x;
	lr.max.x = ur.max.x;
	img.draw(lr, cback, nil, (0,0));
}

drawmarks(img: ref Image, pi: ref Ptext, ht: int, prect: Rect, cback: ref Image)
{
	ur := prect;
	ur.max.y = ur.min.y+1;
	if (pi.froff == 0)
		drawmark(img, ur, cback);
	else
		img.draw(ur, cback, nil, (0,0));
	if (pi.f.nlines + 1 < pi.f.maxlines){	# leave one line for typing
		dr := prect;
		dr.min.y += (pi.f.nlines+1)*ht + 2;
		dr.max.y = dr.min.y + 1;
		if (dr.max.y <= prect.max.y && dr.min.y > prect.min.y)
			drawmark(img, dr, cback);
	} 
}

pdraw(p: ref Panel)
{
	if (!(p.flags&Pshown))
		return;
	w := dat->win;
	pi := pimpl(p);
	pi.rect = p.rect;
	if (!(p.flags&Pline)){
		pi.rect.min.y += 1;
	}
	if (pi.f != nil){
		hidesel(p);
		pi.f.clear(0);
	} else
		pi.f = Frame.new();
	fcols := gui->cols[0:gui->NCOL];
	cback := panelback(p);
	fcols[BACK] = cback;
	pi.f.init(pi.rect, p.font, w.image, fcols);
	fcols = nil;
	if (p.flags&Ptbl)
		tab(p);
	else
		pi.f.maxtab = pi.tab * p.font.width("0");
	filltext(p);
	drawsel(p);
	settextsize(p);
	if (p.flags&Ptag)
		drawtag(p);
	if (!(p.flags&Pline))
		drawmarks(win.image, pi, p.font.height, p.rect, cback);
}

stringlines(s: string): int
{
	nc := n := 0;
	for (i := 0; i < len s; i++)
		if (s[i] == '\n' || nc == Maxline){
			n++;
			nc = 0;
		} else
			nc++;
	return n;
}

# could put the update as a new del+ins event, but how would we
# sync our current ongoing edit?
# The safe thing to do is to discard any edits and start again.
# The application knows best.
pupdate(p: ref Panel, d: array of byte)
{
	pi := pimpl(p);
	s := string d;
	pi.blks = Blks.new(s);
	pi.edits = Edits.new();
	if (p.flags&Ptbl)
		pi.tabtext = s;
	pi.nlines = stringlines(s);
	pi.froff = fixpos(len s, pi.froff);
	pi.mark = fixpos(len s, pi.mark);
	pos := 0;
	if ( (p.flags&Pline) || !(p.flags&Pedit))
		pos= len s;
	else
		pos = fixpos(len s, pi.s0);
	pi.ss = pi.se = pi.s0 = pos;
	p.flags |= Predraw;
}

syncchk(p: ref Panel)
{
	if (debug['T']){
		# double check that we are really synced with the FS.
		fname := p.path + "/data";
		fd := open(fname, OREAD);
		data := readfile(fd);
		if (data != nil){
			pi := pimpl(p);
			ftext := string data;
			s := pi.blks.pack().s;
			if (s != ftext){
				pi.dump();
				l := len s;
				if (l > len ftext)
					l = len ftext;
				for (i := 0; i < l - 1 && s[i] == ftext[i]; i++)
					;
				if (i > 15)
					i -= 15;
				else
					i = 0;
				sj := len s - 1;
				fj := len ftext - 1;
				while(sj > 0 && fj > 0 && s[sj] == ftext[fj]){
					sj--; fj--;
				}
				fprint(stderr, "after sync text differs: pos %d:\n", i);
				fprint(stderr, "text[%s]\n\nfile[%s]\n\n", s[i:sj], ftext[i:fj]);
				panic("insdel bug");
			}
		}
	}
}

syncedits(p: ref Panel, edits: list of ref Edit)
{
	ctlstr := "";
	for(; edits != nil; edits = tl edits){
		ev : string;
		pick e := hd edits {
		Ins =>
			ev = sprint("ins %d %s\n", e.pos, e.s);
		Del =>
			ev = sprint("del %d %d\n", e.pos, len e.s);
		}
		ctlstr += escape(ev);
	}
	# fsctl would scape our \n's, and we want a single write for the entire
	# set of updates, if feasible.
	if (ctlstr != ""){
		fname := p.path + "/ctl";
		fd := open(fname, OWRITE);
		if (fd != nil){
			if (debug['E'])
				fprint(stderr, "fsctl: %s: [%s]\n", p.path, ctlstr);
			seek(fd, big 0, 2);
			data := array of byte ctlstr;
			write(fd, data, len data);
		}
	}
	p.flags &= ~Psync;	# we're synced now.
}

applyedit(p: ref Panel, e: ref Edit): int
{
	pi := pimpl(p);
	if (e != nil)
	pick ep := e {
	Ins =>
		pi.ins(ep.s, ep.pos);
		pi.f.insert(ep.s, ep.pos-pi.froff);
		return ep.pos + len ep.s;
	Del =>
		pi.del(len ep.s, ep.pos);
		pi.f.delete(ep.pos - pi.froff, ep.pos - pi.froff + len ep.s);
		filltext(p);
		return ep.pos;
	}
	return -1;
}

undo(p: ref Panel): int
{
	pi := pimpl(p);
	e := pi.edits.undo();
	return applyedit(p, e);
}

redo(p: ref Panel): int
{
	pi := pimpl(p);
	e := pi.edits.redo();
	return applyedit(p, e);
}

ins(p: ref Panel, s: string, pos: int)
{
	pi := pimpl(p);
	pi.ins(s, pos);
	if (pi.edits.ins(s, pos) < 0){
		syncedits(p, pi.edits.sync());
		if (pi.edits.ins(s, pos) < 0){
			pi.dump();
			panic(sprint("text: ins [%s] %d failed", s, pos));
		}
	}
}

del(p: ref Panel, n: int, pos: int): string
{
	pi := pimpl(p);
	ds := pi.del(n, pos);
	if (pi.edits.del(ds, pos) < 0){
		syncedits(p, pi.edits.sync());
		if (pi.edits.del(ds, pos) < 0){
			pi.dump();
			panic(sprint("text: del [%s] %d failed", ds, pos));
		}
	}
	return ds;
}

pctl(p: ref Panel, s: string)
{
	pi := pimpl(p);
	odirty := p.flags&Pdirty;
	if (panelctl(tree, p, s) < 0){
		(nargs, args) := tokenize(s, " \t\n");
		if (nargs > 0)
		case hd args {
		"sel" =>
			pi.ss = int nth(args, 1);
			pi.se = int nth(args, 2);
			if (pi.se < pi.ss)
				pi.se = pi.ss;
			pi.s0 = pi.ss;
		"mark" =>
			pi.mark = int nth(args, 1);
		"tab" =>
			pi.tab = int nth(args, 1);
			if (pi.tab < 3)
				pi.tab = 3;
			if (pi.tab > 10)
				pi.tab = 10;
		* =>
			; # ignore others
		}
	}
	if (odirty && !(p.flags&Pdirty)){
		pi.edits.cpos = pi.edits.pos;
		# potential race here: User puts, editor issues a clean ctl,
		# and the user adds an edit in the mean while.
		# If this happens, we could just set Pdirty again.
		# But let see if the race is a real one.
		eds := pi.edits.sync();
		if (eds != nil)
			panic("text pctl: clean while dirty edits");
	}
}

# Any of:
# o/mero: /path/to/panel ins pos str
# o/mero: /path/to/panel del pos n
pevent(p: ref Panel, ev: string)
{
	pi := pimpl(p);
	n := strchr(ev, ' ');
	ev = ev[n+1:];
	n = strchr(ev, ' ');
	ev = ev[n+1:];		# ins|del ...
	if (len ev < 5){
		fprint(stderr, "o/live: pevent: bad event\n");
		return;
	}
	op := ev[0:3];
	ev = ev[3+1:];		# pos ...
	n = strchr(ev, ' ');
	if (n < 0){
		fprint(stderr, "o/live: pevent: short event\n");
		return;
	}
	pos := int ev[0:n];
	ev = ev[n+1:];		# n|str
	if (len ev == 0)
		return;
	ev = ev[0:len ev -1];	# remove \n
	if (op == "del"){
		n = int ev;
		del(p, n, pos);
		pi.f.delete(pos, pos+n);
		filltext(p);
	} else {
		s := unescape(ev);
		ins(p, s, pos);
		# pdraw should refill the frame. (?)
		if (p.flags&Pline)
			pos += len s;
	}
	pi.ss = pi.se = pi.s0 = pos;	# that's the
	p.flags |= Pdirty;			# best we can do.
	pdraw(p);
}

writepsel(p: ref Panel)
{
	fd := open("/dev/sel", OWRITE|OTRUNC);
	if (fd != nil)
		fprint(fd, "%s\n", p.path);
}

movetoshow(p: ref Panel, pos: int): int
{
	pi := pimpl(p);
	if (pos < pi.froff || (pos >= pi.froff + pi.f.nchars && pi.f.nlines == pi.f.maxlines)){
		pi.gotopos(pos);
		pi.froff = pi.scroll(-pi.f.nlines/2);
		return 1;
	}
	return 0;
}

cut(p: ref Panel, putsnarf: int): int
{
	pi := pimpl(p);
	if (pi.ss == pi.se)
		return 0;
	p0 := pi.ss - pi.froff;
	p1 := pi.se - pi.froff;
	s := del(p, pi.se - pi.ss, pi.ss);
	pi.f.delete(p0, p1);
	pi.se = pi.s0 = pi.ss;
	filltext(p);
	if (putsnarf){
		writesnarf(s);
		writepsel(p);
	}
	return 1;
}

paste(p: ref Panel, pos: int): int
{
	pi := pimpl(p);
	s := readsnarf();
	if (len s > 0){
		ins(p, s, pos);
		pi.f.insert(s, pos - pi.froff);
		pi.ss = pi.s0 = pos;
		pi.se = pos + len s;
		drawsel(p);
		writepsel(p);
	}
	return 1;
}

lastcmd: string;

exec(p: ref Panel, pos: int)
{
	pi := pimpl(p);
	s := pi.blks.pack();
	(ws, we) := pi.wordat(pos, 1, 1);
	c := s.s[ws:we];
	lastcmd = c;
	if (c == "Exit")
		terminate();
	pi.ss = pi.s0 = ws;
	pi.se = we;
	drawsel(p);
	p.fsctl("exec " + c + "\n");
}

look(p: ref Panel, pos: int)
{
	pi := pimpl(p);
	s := pi.blks.pack();
	if (seltext != nil){
		p.fsctl("look " + seltext + "\n");
		return;
	}
	(ws, we) := pi.wordat(pos, 1, 1);
	c := s.s[ws:we];
	pi.ss = pi.s0 = ws;
	pi.se = we;
	drawsel(p);
	p.fsctl("look " + c + "\n");
}

apply(p: ref Panel, nil: int)
{
	if (lastcmd != nil){
		writepsel(p);
		p.fsctl("apply " + lastcmd + "\n");
	}
}

search(p: ref Panel, pos: int)
{
	ws, we: int;
	txt: string;
	pi := pimpl(p);
	s := pi.blks.pack();
	if (seltext == nil){
		(ws, we) = pi.wordat(pos, 0, 1);
		txt = s.s[ws:we];
	} else {
		we = pos+1;
		txt = seltext;
	}
	i := strstr(s.s[we:], txt);
	if (i < 0)
		i = strstr(s.s, txt);
	else
		i += we;
	if (i < 0)
		return;
	if (debug['T'])
		fprint(stderr, "search: %s: pos %d\n", dtxt(txt), i);
	pi.ss = pi.s0 = i;
	pi.se = pi.ss+ len txt;
	if (movetoshow(p, pi.ss))
		pdraw(p);
	else
		drawsel(p);
	pt := pi.f.ptofchar(pi.f.p0);
	pt.add(pi.rect.min);
	pt.add((p.font.width(txt[0:1])-2, p.font.height -2));
	win.wmctl("ptr " + string pt.x + " " + string pt.y);
}

frscroll(p: ref Panel, nlines: int)
{
	pi := pimpl(p);
	if (nlines == 0){
		return;
	}
	noff := pi.scroll(nlines);
	if (noff < pi.froff){
		s := pi.blks.pack().s;
		s = s[noff:pi.froff];
		pi.f.insert(s, 0);
		pi.froff = noff;
	} else if (noff > pi.froff){
		if (pi.f.nchars > 0)
		if (noff < pi.froff + pi.f.nchars)
			pi.f.delete(0, noff - pi.froff);
		else if (pi.f.nchars > 0)
			pi.f.delete(0, pi.f.nchars - 1);
		pi.froff = noff;
		filltext(p);
	}
}

select(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer): ref Cpointer
{
	pi := pimpl(p);
	b := m.buttons;
	s := pi.blks.pack();
	if (m.flags&(CMdouble|CMtriple)){
		long := m.flags&CMtriple;
		(ws, we) := pi.wordat(pos, long, 0);
		if (debug['T'])
			fprint(stderr, "getword: pos %d long %d: %d %d %s\n",
				pos, long, ws, we, dtxt(s.s[ws:we]));
		pi.ss = pi.s0 = ws;
		pi.se = we;
		writepsel(p);
		drawsel(p);
		do {
			m = <-mc;
		} while (m.buttons == b);
	} else {
		pi.s0 = pos;
		m = pi.f.select(m, mc, frscroll, p);
		if (pi.f.p0 + pi.froff < pi.s0){
			pi.ss = pi.f.p0 + pi.froff;
			pi.se = pi.s0;
		} else {
			pi.ss = pi.s0;
			pi.se = pi.f.p1 + pi.froff;
		}
		if (!(p.flags&Pedit) && pi.ss == pi.se)
			hidesel(p);
		else
			drawsel(p);
	}
	if (pi.ss == pi.se)
		seltext = nil;
	else
		seltext = s.s[pi.ss:pi.se];
	return m;
}

pmouse1(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer)
{
	pi := pimpl(p);
	m = select(p, pos, m, mc);
	case m.buttons {
	0 =>
		return;
	3 =>
		if (cut(p, 1))
			dirty(p, 1);
	5 =>
		if (cut(p, 0))
			pos = pi.ss;
		if (paste(p, pos))
			dirty(p, 1);
	}
	while(m.buttons)
		m = <-mc;
}

pmouse2(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer)
{
	pi := pimpl(p);
	if (pos >= pi.ss && pos < pi.se){
		m = <-mc;
		if (m.buttons == 0)
			exec(p, pos);
	} else {
		m = select(p, pos, m, mc);
		if (m.buttons == 0)
			exec(p, pos);
	}
	while(m.buttons)
		m = <-mc;
}

# To avoid blinking, we use double buffering during
# scroll operations. This is because we redraw the whole frame,
# and do not shift rectangles around. This makes it easier to draw
# overlayed scroll bars.

scrdraw(p: ref Panel, i: ref Image): ref Image
{
	pi := pimpl(p);
	r := Rect((0,0), (p.rect.dx(), p.rect.dy()));
	if (i == nil)
		i = win.display.newimage(r, win.image.chans, 0, Draw->White);
	fcols := gui->cols[0:gui->NCOL];
	cback := panelback(p);
	fcols[BACK] = cback;
	i.draw(i.r, cback, nil, (0,0));
	pi.f.clear(0);
	fr := r;
	if (!(p.flags&Pline)){
		fr.min.y += 1;
	}
	pi.f.init(fr, p.font, i, fcols);
	if (p.flags&Ptbl)
		pi.f.maxtab = 3 * p.font.width("0");
	else
		pi.f.maxtab = pi.tab * p.font.width("0");
	filltext(p);
	drawsel(p);
	if (!(p.flags&Pline))
		drawmarks(i, pi, p.font.height, r, cback);
	drawscrollbar(p, i);
	win.image.draw(p.rect, i, nil, (0,0));
	return i;
}

scrdone(p: ref Panel)
{
	pi := pimpl(p);
	pi.f.clear(0);
	pi.f = nil;
	pdraw(p);
}

drawscrollbar(p: ref Panel, i: ref Image)
{
	Barwid: con 25;
	Barht: con 120;

	pi := pimpl(p);
	bar, r: Rect;
	bar.max.x = r.max.x = i.r.max.x - Inset - 3;
	bar.min.x = r.min.x = r.max.x - Barwid;
	r.min.y = i.r.min.y + Inset;
	ysz := Barht;
	r.max.y = r.min.y + ysz;
	if (r.max.y + 3 > i.r.max.y){
		r.max.y = i.r.max.y - 3;
		ysz = r.dy();
	}
	i.draw(r.addpt((2,2)), cols[TEXT], cols[SHAD], (0,0));
	i.draw(r, cols[CLEAR], cols[SHAD], (0,0));
	l := len pi.blks.b[0].s;
	y0 := dy := 0;
	if (l > 0){
		y0 = ysz * pi.froff / l;
		dy = ysz * pi.f.nchars / l;
		if (dy < 3)
			dy = 3;
	} else {
		y0 = 0;
		dy = r.dy();
	}
	bar.min.y = r.min.y + y0;
	bar.max.y = bar.min.y + dy;
	if (bar.max.y > r.max.y)
		bar.max.y = r.max.y;
	if (bar.min.y > bar.max.y - 2)
		bar.min.y = bar.max.y - 2;
	i.draw(bar.addpt((3,3)), cols[TEXT], cols[SHAD], (0,0));
	i.draw(bar, cols[SET], nil, (0,0));
	i.border(r, 1, cols[BORD], (0,0));
}

jumpscale(p: ref Panel, xy: Point): real
{
	pi := pimpl(p);
	s := pi.blks.pack();
	dy := xy.y - pi.rect.min.y;
	if (dy > pi.rect.max.y - xy.y)
		dy = pi.rect.max.y - xy.y;
	dc := len s.s - pi.froff;
	if (dc < pi.froff)
		dc = pi.froff;
	if (dy < 1)
		dy = 1;
	return (real dc) / (real dy);
}

pmouse3scrl(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer)
{
	pi := pimpl(p);
	xy := m.xy;
	jfactor := real 0;
	jy := xy.y;
	old := pi.froff;
	i := scrdraw(p, nil);
	b := m.buttons;
	do {
		if (jfactor <= real 0.00001){
			pos = pi.froff;
			jfactor = jumpscale(p, xy);
		}
		jpos := pos + int (real (xy.y - jy) * jfactor);
		if (jpos == old || !pi.gotopos(jpos))
			; # sys->sleep(10);
		else {
			old = jpos;
			scrdraw(p, i);
		}
		more := 0;
		m = <- mc;
		do {
			alt {
			m = <-mc =>
				more=1;
			* =>
				more = 0;
			}
		} while(more && m.buttons == b);
		xy = m.xy;
	} while(m.buttons);
	scrdone(p);
}

pmouse3(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer)
{
	pi := pimpl(p);
	m = <- mc;
	case m.buttons {
	0 =>
		textmenu.last = pi.mlast;
		cmd := textmenu.run(m, mc);
		if (debug['T'])
			fprint(stderr, "pmouse3: cmd: %s at %d\n", cmd, pos);
		case cmd {
		"scroll" =>
			pmouse3scrl(p, pos, m, mc);
		"Find" =>
			search(p, pos);
		"Open" =>
			look(p, pos);
		"Exec" =>
			exec(p, pos);
		"Apply" =>
			apply(p, pos);
		"Cut" =>
			if (cut(p, 1))
				dirty(p, 1);
			textmenu.last = 3;
		"Paste" =>
			if (paste(p, pos))
				dirty(p, 1);
		* =>
			p.fsctl("exec " + cmd + "\n");
		}
		pi.mlast = textmenu.last;
	4 =>
		pmouse3scrl(p, pos, m, mc);
	* =>
		do
			m = <-mc;
		while (m.buttons);
	}
}

pmouse(p: ref Panel, m: ref Cpointer, mc: chan of ref Cpointer)
{
	if ((p.flags&Ptag) && intag(p, m.xy)){
		tagmouse(tree, p, m, mc);
		return;
	}
	pi := pimpl(p);
	# mouse movement syncs any pending edit.
	eds := pi.edits.sync();
	if (eds != nil){
		syncedits(p, eds);
		syncchk(p);
	}
	pos := pi.froff + pi.f.charofpt(m.xy);
	case m.buttons {
	1 =>
		# For tbls, this should probably allow drag&drop
		pmouse1(p, pos, m, mc);
		if (debug['T'] > 1)
			pi.dump();
	2 =>
		pmouse2(p, pos, m, mc);
		if (debug['T'] > 1)
			pi.dump();
	4 =>
		pmouse3(p, pos, m, mc);
		if (debug['T'] > 1)
			pi.dump();
	}
	drawsel(p);
}

pkbd(p: ref Panel, k: int)
{
	Killword:	con 16r17;	# C-w
	pi := pimpl(p);
	pos := pi.ss;
	s := "";
	s[0] = k;
	if (!(p.flags&Pedit)){
		p.fsctl("keys " + s + "\n");
		return;
	}
	case k {
	'\b' =>
		if (!cut(p, 1) && pos > 0){
			pos--;
			if (movetoshow(p, pos))
				pdraw(p);
			del(p, 1, pos);
			if (pos < pi.froff | pos > pi.froff + pi.f.nchars + 1)
				panic("pos del bug");
			pi.f.delete(pos-pi.froff, pos-pi.froff+1);
			pi.ss = pi.se = pi.s0 = pos;
		}
		dirty(p, 1);
		filltext(p);
	Killword =>
		(ws, we) := pi.wordat(pos, 1, 1);
		if (ws != we){
			pos = ws;
			n := we - ws;
			if (movetoshow(p, pos))
				pdraw(p);
			del(p, n, pos);
			if (pos < pi.froff | pos > pi.froff + pi.f.nchars + 1)
				panic("pos killword bug");
			pi.f.delete(pos-pi.froff, pos+n-pi.froff);
			pi.ss = pi.se = pi.s0 = pos;
		}
	Keyboard->Del =>
		p.fsctl("interrupt\n");
	Keyboard->Left or Keyboard->Right=>
		if (k == Keyboard->Left)
			pos = undo(p);
		else
			pos = redo(p);
		if (pos >= 0){
			dirty(p, pi.edits.pos != pi.edits.cpos);
			pi.ss = pi.se = pi.s0 = pos;
			if (movetoshow(p, pos))
				pdraw(p);
			else
				drawsel(p);
		}
	Keyboard->Up or Keyboard->Down =>
		if (p.flags&Pline)
			return;
		n := pi.f.nlines / 3;
		if (n < 2)
			n = 2;
		if (k == Keyboard->Up)
			n = -n;
		noff := pi.scroll(n);
		if (noff != pi.froff){
			pi.froff = noff;
			pdraw(p);
		}
	Keyboard->Esc =>
		if (pi.s0 < pos){
			pi.ss = pi.s0;
			pi.se = pos;
		} else {
			pi.ss = pos;
			pi.se = pi.s0;
		}
		drawsel(p);
	* =>
		if (k == '\n' && (p.flags&Pline)){
			if (pi.s0 < pos){
				pi.ss = pi.s0;
				pi.se = pos;
			} else {
				pi.ss = pos;
				pi.se = pi.s0;
			}
			drawsel(p);
			exec(p, pos);
		} else {
			cut(p, 1);
			if (!(p.flags&Pline) && movetoshow(p, pos))
				pdraw(p);
			ins(p, s, pos);
			pi.f.insert(s, pos - pi.froff);
			pi.ss = pi.se = pos+1;
			if (pos >= pi.froff + pi.f.nchars - 2 && !(p.flags&Pline)){
				noff := pi.scroll(pi.f.nlines/3);
				if (noff != pi.froff){
					pi.froff = noff;
					pdraw(p);
				}
			}
			dirty(p, 1);
			if ((p.flags&Pedit) && (k == '\n' || (p.flags&Pline)))
				settextsize(p);
		}
	}
	if (debug['T'] > 1)
		pi.dump();
}

psync(p: ref Panel)
{
fprint(stderr, "owptext: sync called\n");

	pi := pimpl(p);
	fd := open(p.path + "/data", OWRITE|OTRUNC);
	if (fd == nil)
		return;
	s := pi.blks.pack();
	data := array of byte s.s;
	if (write(fd, data, len data) != len data)
		fprint(stderr, "o/live: sync %s: %r\n", p.path);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.