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

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


# Generic panel coordination
# One process (per tree) coordinates operations within the tree, so that
# there are no races.
# A tree operation operates on a subtree, identified by its root panel.
# The operation blocks the subtree while it is being executed by a helper process.
# A operation can proceed if
#	1. no inner panel of its subtree is blocked by a previous operation
#	2. the root of its subtree is not within a blocked subtree
# When it cannot proceed, it is queued along with the panel causing it to block
# When an operation completes, we process its queued operations one by one
# unless they block again.

# Tree operations that may take a long time are performed by auxiliary
# processes, represented by Xops (to be cached and reused).
# Channels of type ref Panel are also cached by means of get/putpchan.

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

Menu: import menus;
Ptag, Pline, Pedit, Psync, Pdead, Pdirty, Pshown, Predraw, Phide, Qatom,
Pbusy, Pdirties, Pmore, Playout, Qrow, Qcol, nth,
intag, Panel: import wpanel;
Cpointer, Tagwid, Taght, Inset, setcursor, drawtag,
getfont, readsnarf, cookclick, Arrow, Drag: import gui;


# The following code is support to reuse processes
# heavily spawned by the tree.

# tree operations performed by auxiliary processes
Xop: adt {
	f:	ref fn(x: ref Xop);
	t:	ref Tree;
	rp:	ref Panel;
	op:	ref Treeop;
	dc:	chan of ref Panel;
	rc:	chan of ref Panel;
	d:	ref Dir;			# for update tree rec.
};

xc: chan of ref Xop;

pchans: list of chan of ref Panel;
getpchan(): chan of ref Panel
{
	c: chan of ref Panel;
	if (pchans != nil){
		c = hd pchans;
		pchans = tl pchans;
	} else
		c = chan of ref Panel;
	return c;
}

putpchan(c: chan of ref Panel)
{
	pchans = c::pchans;
}

init(d: Livedat)
{
	dat = d;
	initmods();
	xc = chan of ref Xop;
	spawn xctlproc();
}

xctlproc()
{
	tprocs: list of chan of ref Xop;
	tprocs = nil;
	nprocs := 0;
	idlec := chan of chan of ref Xop;
	for(;;){
		alt {
		x := <- xc =>
			tpc: chan of ref Xop;
			if (tprocs != nil){
				tpc = hd tprocs;
				tprocs = tl tprocs;
			} else {
				tpc = chan of ref Xop;
				spawn xproc(tpc, idlec);
				if (debug['P'])
					fprint(stderr, "%d tree procs\n", nprocs);
			}
			tpc <-= x;
		tpc := <- idlec =>
			tprocs = tpc::tprocs;
		}
	}
}

xproc(tpc: chan of ref Xop, idlec: chan of chan of ref Xop)
{
	for(;;){
		x := <-tpc;
		x.f(x);
		if (x.rc != nil)
			x.rc <-= x.rp;
		if (x.dc != nil)
			x.dc <-= x.rp;
		idlec <-= tpc;
	}
}


Tree.path(t: self ref Tree, p: ref Panel): string
{
	l := len t.slash.path;
	path := p.path[l:];
	if (path == "")
		path = "/";
	return path;
}

notshown(p: ref Panel)
{
	p.flags &= ~Pshown;
	if (p.rowcol)
	for (i := 0; i < len p.child; i++)
		if (p.child[i] != nil)
			notshown(p.child[i]);
}



showtree(p: ref Panel, force: int)
{
	mustdraw := p.flags&Predraw;
	p.flags &= ~Predraw;
	if (p.rect.dx() <= Inset || p.rect.dy() <= Inset || (p.flags&Phide)){
		notshown(p);
		return;
	}
	p.flags |= Pshown;
	force |= ! p.rect.eq(p.orect);
	if (p.rowcol != Qatom){
		for (i := 0; i < len p.child; i++){
			if (p.child[i] == nil)
				panic("showtree bug");
			showtree(p.child[i], force);
		}
		p.draw();
	} else
		if (force || mustdraw){
			if (debug['L'])
			fprint(stderr, "show: %s\t[%d %d %d %d] drw%d frz%d dp=%d\n", p.name,
				p.rect.min.x, p.rect.min.y, p.rect.max.x, p.rect.max.y, mustdraw, force, p.depth);
			p.draw();	# must draw tag as well
		}
}

showtreeproc(x: ref Xop)
{
	force := x.t == nil;	# kludge.
	showtree(x.rp, force);
}

synctree(x: ref Xop)
{
	p := x.rp;
	if (p.rowcol != Qatom){
		nc := getpchan();
		for (i := 0; i < len p.child; i++){
			xr := ref Xop(synctree, nil, p.child[i], nil, nc, nil, nil);
			xc <-= xr;
		}
		for (i = 0; i < len p.child; i++)
			<-nc;
		putpchan(nc);
	} else if (p.flags&Psync){
		if (debug['P'])
			fprint(stderr, "sync: %s\n", p.path);
		p.sync();
		p.flags &= ~Psync;
	}
	if (x.dc != nil)
		x.dc <-= p;
}

synctreeproc(x: ref Xop)
{
	xr := ref Xop(synctree, nil, x.rp, nil, nil, nil, nil);
	synctree(xr);
}

Tree.sync(t: self ref Tree, path: string)
{
	rc := chan[1] of ref Panel;
	t.opc <-= ref Treeop.Sync(rc, path);
}

orderchildren(p: ref Panel)
{
	# len p.order < len p.child if the child is being created
	# len p.order > len p.child if the child is being removed
	ochild := p.child;
	n := len ochild;
	if (n < len p.order)
		n = len p.order;
	nchild := array[n] of ref Panel;
	pos := 0;
	for (ol := p.order; ol != nil; ol = tl ol)
		for (i := 0; i < len ochild; i++)
			if (ochild[i] != nil && hd ol == ochild[i].name){
				nchild[pos++] = ochild[i];
				ochild[i] = nil;
				break;
			}
	for (i = 0; i < len ochild; i++)
		if (ochild[i] != nil)
			nchild[pos++] = ochild[i];
	p.child = nchild[0:pos];
}

sameqid(q1: Qid, q2: Qid, vers: int): int
{
	if (vers)
		return q1.path == q2.path && q1.vers == q2.vers;
	else
		return q1.path == q2.path;
}

readnew(fname: string, q: Qid): (array of byte, ref Qid)
{
	# Is this better? For Op should be. But not clear.
	(e, d) := stat(fname);
	if (e < 0)
		return (nil, nil);
	if (sameqid(d.qid, q, 1))
		return (nil, ref q);
	q = d.qid;

	fd := open(fname, OREAD);
	if (fd == nil)
		return (nil, nil);
	data := readfile(fd);
	if (data == nil)
		return (nil, nil);
	else
		return (data, ref q);
}

# We try hard to read concurrently as much as we can, to fold the latencies
# of multiple file RPCs into a single one, if possible.
# Update data and readctl are executed concurrently, as are updatetree calls
# for childs of containers. Channels are used to wait for update completions.

updatedata(p: ref Panel)
{
	fname := p.path + "/data";
	(data, q) := readnew(fname, p.dqid);
	if (q == nil)
		fprint(stderr, "o/live: updatedata: %r\n");
	else if (data != nil){
		p.dqid = *q;
		if (debug['P']){
			dd := data;
			if (len dd > 10)
				dd = dd[0:10];
			fprint(stderr, "o/live: %s: updatedata [%s...] q v%x\n", p.path, string dd, p.qid.vers);
		}
		p.update(data);
	}
}

# updating ctls might require current data, for pctl() ops.
# we read concurrently, but updatectl once data is current.
readctl(x: ref Xop)
{
	p := x.rp;
	fname := p.path + "/ctl";
	(data, q) := readnew(fname, p.cqid);
	if (p.ctls != nil)
		panic("readctl: ctls is not nil; others using it?");
	if (q == nil){
		fprint(stderr, "o/live: readctl: %r\n");
	} else if (data != nil){
		p.cqid = *q;
		p.ctls = string data;
	}
}

updatectl(p: ref Panel)
{
	(nil, ctls) := tokenize(p.ctls, "\n");
	for(; ctls != nil; ctls = tl ctls)
		p.ctl(hd ctls);
	p.ctls = nil;
}

packchildren(p: ref Panel)
{
	i,j : int;
	for (i = j = 0; j < len p.child; j++)
		if ((p.child[i] = p.child[j]) != nil)
			i++;
	p.child = p.child[0:i];
}

#This should order childs using the order attribute. So that we can use
#the child array to iterate over children in order.
updatetree(x: ref Xop)
{
	p := x.rp;
	d := x.d;
	if (d == nil){
		(e, dd) := stat(p.path);
		if (e >= 0)
			d = ref dd;
	}
	if (d == nil){
		if (debug['P'])
			fprint(stderr, "o/live: updatetree: %s: %r\n", p.path);
		return;
	}
	if (sameqid(p.qid, d.qid, 1)){
		if (debug['P']>1)
			fprint(stderr, "\tsameqid: %s: v%x\n", p.path, p.qid.vers);
		return;
	}
	p.qid = d.qid;


	cc:= getpchan();
	xc <-= ref Xop(readctl, nil, p, nil, cc, nil, nil);
	if (p.parent != nil && p.rowcol == Qatom){
		updatedata(p);
		<-cc;
		putpchan(cc);
		updatectl(p);
	} else {
		fd := open(p.path, OREAD);
		if (fd == nil){
			if (debug['P'])
				fprint(stderr, "o/live: update: %s: %r\n", p.path);
			<-cc;	# discard ctls
			putpchan(cc);
			return;
		}
		(dirs, n) := readdir->readall(fd, Readdir->NONE);
		fd = nil;
		<- cc;
		putpchan(cc);
		if (n < 0)
			return;
		updatectl(p);
		for (j := 0; j < len p.child; j++)
			p.child[j].flags |= Pdead;
		nc := 0;
		ec:= getpchan();
		for (i := 0; i < n; i++){
			d = dirs[i];
			if (d.name == "ctl" || d.name == "data" || d.name == "image")
				continue;
			for (j = 0; j < len p.child; j++)
				if (sameqid(d.qid, p.child[j].qid, 0))
					break;
			np: ref Panel;
			if (j == len p.child)
				np = Panel.new(d.name, p);
			else {
				np = p.child[j];
				np.flags &= ~Pdead;
			}
			nc++;
			xc <-= ref Xop(updatetree, nil, np, nil, ec, nil, d);			
		}
		while(nc-- > 0)	# wait for children updates
			<-ec;
		putpchan(ec);
		for(j = 0; j < len p.child; j++)
			if (p.child[j].flags&Pdead){
				p.child[j].term();
				p.child[j] = nil;		# release panel
			}
		packchildren(p);
		orderchildren(p);
	}
}

updatetreeproc(x: ref Xop)
{
	rx := ref Xop(updatetree, nil, x.rp, nil, nil, nil, nil);
	updatetree(rx);
	tagtree(x.t.slash);
	showtree(x.rp, 0);
}

Tree.update(t: self ref Tree, path: string)
{
	if (debug['P'] > 1)
		fprint(stderr, "o/live: updatetree %s\n", path);
	rc := getpchan();
	t.opc <-= ref Treeop.Update(rc, path);
	<-rc;
	putpchan(rc);
}

mousetreeproc(x: ref Xop)
{
	pick op := x.op {
	Mouse =>
		x.rp.mouse(op.m, op.mc);
	}
}

Tree.mouse(t: self ref Tree, m: ref Cpointer, mc: chan of ref Cpointer): ref Panel
{
	rc := getpchan();
	t.opc <-= ref Treeop.Mouse(rc, nil, m, mc);
	p := <-rc;
	putpchan(rc);
	return p;
}

kbdtreeproc(x: ref Xop)
{
	pick op := x.op {
	Kbd =>
		x.rp.kbd(op.r);
	}
}

Tree.kbd(t: self ref Tree, r: int): ref Panel
{
	rc := getpchan();
	t.opc <-= ref Treeop.Kbd(rc, nil, r);
	p := <-rc;
	putpchan(rc);
	return p;
}

tabs(n: int): string
{
	s := "";
	for (i := 0; i < n; i++)
		s += "  ";
	return s;
}

dumptree(p: ref Panel, d: int)
{

	fprint(stderr, "%s%s\n", tabs(d), p.text());
	if (p.rowcol != Qatom)
		for (i := 0; i < len p.child; i++)
				dumptree(p.child[i], d+1);
}

checkpath(path: string): string
{
	if (path == nil)
		path = "/";
	if (path[0] != '/')
		panic("Tree: relative path");
	return path;
}

Tree.dump(t: self ref Tree, path: string)
{
	path = checkpath(path);
	rc := getpchan();
	t.opc <-= ref Treeop.Dump(rc, path);
	<-rc;
	putpchan(rc);
}

# locates a point, stops if any from "/" to the panel is busy
ptwalktree(p: ref Panel, pt: Point, atomok: int): ref Panel
{
	if (p.flags&Pdead)	# let the caller check it out.
		return p;
	if (p.flags&Pbusy)
		return nil;
	if(pt.in(p.rect) && p.rowcol != Qatom)
		for (i := 0; i < len p.child; i++){
			np := p.child[i];
			if (np.flags&Phide)
				continue;
			if (pt.in(np.rect) && (atomok || np.rowcol != Qatom))
				return ptwalktree(np, pt, atomok);
		}
	return p;
}

Tree.ptwalk(t: self ref Tree, pt: Point, atomok: int): ref Panel
{
	rc := getpchan();
	t.opc <-= ref Treeop.Ptwalk(rc, "/", pt, atomok);
	p := <-rc;
	putpchan(rc);
	return p;
}

walktree(fp: ref Panel, elems: list of string): ref Panel
{
	if (len elems == 0)
		return fp;
	if (hd elems == "/")
		return walktree(fp, tl elems);
	for (i := 0; i < len fp.child; i++)
		if (fp.child[i] != nil && fp.child[i].name == hd elems)
			return walktree(fp.child[i], tl elems);
	return nil;
}

Tree.walk(t: self ref Tree, path: string): ref Panel
{
	path = checkpath(path);
	rc := getpchan();
	t.opc <-= ref Treeop.Walk(rc, path);
	p := <-rc;
	putpchan(rc);
	if (p == nil)
		fprint(stderr, "o/live: Tree.walk: %s: not found\n", path);
	return p;
}

# Would be more efficient to compute this as we change the tree
# But it's more simple to get it correct this way.
tagtree(fp: ref Panel): (int, int)
{
	dirties := (fp.flags&Pdirty);
	more := (fp.flags&Phide);

	fp.nshown = 0;
	for (i := 0; i < len fp.child; i++)
		if (fp.child[i] != nil){
			(cd, cm) := tagtree(fp.child[i]);
			dirties |= cd;
			more |= cm;
			if (!(fp.child[i].flags&Phide))
				fp.nshown++;
		}
	if (fp.rowcol){
		if (dirties)
			fp.flags |= Pdirties;
		else
			fp.flags &= ~Pdirties;
		if (more)
			fp.flags |= Pmore;
		else
			fp.flags &= ~Pmore;
	}
#	if (od != dirties || om != more)
	if (fp.flags&Pshown)
	if (fp.flags&Ptag)
		drawtag(fp);
	return (dirties, more);
}

Tree.tags(t: self ref Tree, path: string)
{
	path = checkpath(path);
	rc := getpchan();
	t.opc <-= ref Treeop.Tags(rc, path);
	<-rc;
	putpchan(rc);
}

flagsons(p: ref Panel, set: int, clr: int, first: int, last: int, excl: ref Panel)
{
	if (first < last)
	for (i := 0; i < len p.child; i++){
		np := p.child[i];
		if (np != nil)
		if (--first < 0 && --last >= 0 && np != excl){
			old := np.flags;
			if ((old&Phide) && (clr&Phide)){
				np.ctl("show");
				np.fsctl("show\n", 1);
			}
			if (!(old&Phide) && (set&Phide)){
				np.ctl("hide");
				np.fsctl("hide\n", 1);
			}
		}
	}
}

Tree.size(t: self ref Tree, path: string, op: int)
{
	path = checkpath(path);
	rc := getpchan();
	t.opc <-= ref Treeop.Size(rc, path, op);
	<-rc;
	putpchan(rc);
}

Tree.layout(t: self ref Tree, path: string)
{
	if (path == nil)
		path = t.path(t.dslash);
	path = checkpath(path);
	rc := getpchan();
	t.opc <-= ref Treeop.Layout(rc, path);
	<-rc;
	putpchan(rc);
}

Tree.image(t: self ref Tree, path: string)
{
	if (path == nil)
		path = t.path(t.dslash);
	path = checkpath(path);
	rc := getpchan();
	t.opc <-= ref Treeop.Image(rc, path);
	<-rc;
	putpchan(rc);
}

paneltop(p: ref Panel): ref Panel
{
	for(;;){
		if (p.parent == nil)
			return p;
		if (p.flags&Playout)
			return p;
		p = p.parent;
	}
}

unblock(l: list of string, p: string): list of string
{
	nl: list of string;
	for(; l != nil; l = tl l){
		if (hd l != p)
			nl = hd l::nl;
	}
	return nl;
}

isblocked(path: string, bl: list of string): int
{
	for(; bl != nil; bl = tl bl)
		if (path == hd bl || isprefix(path, hd bl) || isprefix(hd bl, path))
			return 1;
	return 0;
}

release(hl: list of ref Treeop, bl: list of string): (list of ref Treeop, list of ref Treeop)
{
	nhl: list of ref Treeop;
	nrl: list of ref Treeop;

	for(; hl != nil; hl = tl hl){
		if (isblocked((hd hl).path, bl))
			nhl = hd hl::nhl;
		else
			nrl = hd hl::nrl;
	}
	return (nrl, nhl);
}

nofocus(t: ref Tree, p: ref Panel)
{
	t.donec <-= p;
}

treeproc(t: ref Tree)
{
	blocked: list of string;		# list of blocked paths for subtrees
	onhold: list of ref Treeop;		# ops waiting because of blocked subtrees
	released: list of ref Treeop;	# were on hold, attend before t.opc
	blkd: int;
	t.focus = nil;
	t.lastxy = Point(0,0);
Loop:
	for(;;){
		o: ref Treeop;
		if (released != nil){
			o = hd released;
			released = tl released;
		}
		if (o == nil){
			alt {
			o = <-t.opc =>
				;
			p := <-t.donec =>
				p.flags &= ~Pbusy;
				blocked = unblock(blocked, p.path);
				(released, onhold) = release(onhold, blocked);
				continue Loop;
			}
		}

		# Mouse ops may draw menus and bars that require the
		# screen to be quiet. Other operations do not.
		if (tagof(o) == tagof(Treeop.Mouse))
			blkd = isblocked(t.dslash.path, blocked);
		else
			blkd = isblocked(o.path, blocked);
		if (blkd){
			onhold = o::onhold;
			continue Loop;
		}

		elems: list of string;
		elems = nil;
		if (o.path != nil)
			elems = names->elements(o.path);
		if (elems != nil)
			elems = tl elems;	# get rid of "/"

		# 1. Walk to panel
		rp: ref Panel;

		# In general, walktree blocks while a subtree is busy
		# but Mouse, Kbd, and Ptwalk try to be optimistic, and
		# only block while the particular panel is busy (due to itself
		# or due to an ancestor). But operation would proceed when
		# if an inner panel is marked as busy.
		pick op := o {
		Mouse =>
			t.lastxy = op.m.xy;
			rp = ptwalktree(t.dslash, op.m.xy, 1);
			if (rp != nil)
				t.focus = rp;
		Kbd =>
			if (t.focus == nil)
				t.focus = ptwalktree(t.dslash, t.lastxy, 1);
			rp = t.focus;
		Ptwalk =>
			rp = ptwalktree(t.dslash, op.pt, op.atomok);
		* =>
			rp = walktree(t.slash, elems);
			if (rp == nil){
				if (debug['P'])
					fprint(stderr, "walktree: %s: no panel\n", o.path);
				o.rc <-= nil;
				continue Loop;
			}
		}
		if (rp == nil){				# Mouse, Kbd, or Ptwalk
			onhold = o::onhold;		# got their panel busy.
			continue Loop;			# Try later.
		}
		if (rp != nil && (rp.flags&Pdead)){
			o.rc <-= nil;
			continue Loop;
		}
		# 2. Perform operation
		pick op := o {
		Ptwalk =>
			if (!op.atomok)
				rp = paneltop(rp);
			o.rc <-= rp;
		Mouse =>
			t.focus = rp;
			# mouse on tags and columns may lead to tree operations.
			# we can't set the involved panels busy or we'll deadlock.
			# in any case, the request does not complete until the mouse op is done.
			if (rp.rowcol != Qatom || ((rp.flags&Ptag) && intag(rp, op.m.xy)))
				xc <-= ref Xop(mousetreeproc, t, rp, op, nil, o.rc, nil);
			else {
				rp.flags |= Pbusy;
				blocked = rp.path::blocked;
				xc <-= ref Xop(mousetreeproc, t, rp, op, t.donec, o.rc, nil);
			}
		Kbd =>
			if (rp.rowcol == Qatom){
				rp.flags |= Pbusy;
				blocked = rp.path::blocked;
				xc <-= ref Xop(kbdtreeproc, t, rp, op, t.donec, o.rc, nil);
			} else {
				xc <-= ref Xop(kbdtreeproc, t, rp, op, nil, nil, nil);
				o.rc <-= rp;
			}
		Walk =>
			o.rc <-= rp;
		Update =>
			rp.flags |= Pbusy;
			blocked = rp.path::blocked;
			xc <-= ref Xop(updatetreeproc, t, rp, op, t.donec, o.rc, nil);
		Sync =>
			rp.flags |= Pbusy;
			blocked = rp.path::blocked;
			xc <-= ref Xop(synctreeproc, t, rp, op, t.donec, o.rc, nil);
		Layout =>
			force := t.dslash != rp;
			t.dslash = rp;
			rp.flags |= Pbusy;
			blocked = rp.path::blocked;
			layoutm->layout(rp);
			# kludge: nil tree means force
			tt := t;
			if (force)
				tt = nil;
			xc <-= ref Xop(showtreeproc, tt, rp, op, t.donec, o.rc, nil);
		Size =>
			case op.op {
			Min =>
				# Either shrink to one inner, or
				# flag one more (or just one) as not hidden
				if (rp.nshown == len rp.child)
					flagsons(rp, Phide, 0, 1, len rp.child+1, nil);
				else {
					flagsons(rp, 0, Phide, rp.nshown, rp.nshown+1, nil);
					flagsons(rp, Phide, 0, rp.nshown+1, len rp.child+1, nil);
				}
			Max =>
				if ((fp := rp.parent) != nil && fp != rp)
					flagsons(fp, Phide, 0, 0, len fp.child + 1, rp);
			Full =>
				flagsons(rp, 0, Phide, 0, len rp.child + 1, nil);
			}
			o.rc <-= rp;
		Image =>
			# write panel image to its image file
			fprint(stderr, "o/live: bug: write image requested. Not implemented.\n");
			o.rc <-= rp;
		Tags =>
			tagtree(rp);
			o.rc <-= rp;
		Dump =>
			dumptree(rp, 0);
			o.rc <-= rp;
		}
	}
}

Tree.start(omero: string, name: string): ref Tree
{
	rootdir := names->rooted(omero, name);
	slash := Panel.new(name, nil);
	if (slash == nil){
		werrstr("bad panel type");
		return nil;
	}
	slash.path = rootdir;
	opc := chan of ref Treeop;
	donec := getpchan();
	t := ref Tree(omero, slash, slash, opc, donec, nil, (0,0));
	spawn treeproc(t);
	return t;
}

Tree.terminate(t: self ref Tree)
{
	t.opc <-= nil;
}

drag(t: ref Tree, p: ref Panel, m: ref Cpointer, mc: chan of ref Cpointer, innerok: int)
{
	setcursor(Drag);
	b := m.buttons;
	while(m.buttons == b){
		m = <-mc;
	}
	if (m.buttons != 0){
		do {
			m = <-mc;
		} while(m.buttons != 0);
		return;
	}
	np := t.ptwalk(m.xy, innerok);
	if (innerok && np.rowcol == Qatom)
		np = np.parent;
	if (np != nil && np != p){
		# pos = insertpoint(np, m->xy);
		l := len t.omero;
		path := np.path[l:];
		if (copying)
			p.fsctl(sprint("copyto %s\n", path), 1);
		else
			p.fsctl(sprint("moveto %s\n", path), 1);
		copying = 0;
	}
	setcursor(Arrow);
}

copying := 0;

wcmd(t: ref Tree, p: ref Panel, c: string)
{
	
	case c {
	"Col" =>
		p.ctl("col");
		p.fsctl("col\n", 1);
		t.layout(nil);
	"Row" =>
		p.ctl("row");
		p.fsctl("row\n", 1);
		t.layout(nil);
	"Del" =>
		p.fsctl("exec Del\n", 1);
	"Hide" =>
		p.ctl("hide");
		p.fsctl("hide\n", 1);
		t.tags("/");
		t.layout(nil);
	"Min" =>
		t.size(t.path(p), Min);
		t.tags("/");
		t.layout(nil);
	"Max" =>
		t.size(t.path(p), Max);
		t.tags("/");
		t.layout(nil);
	"Full" =>
		t.size(t.path(p), Full);
		t.tags("/");
		t.layout(nil);
	"Copy" =>
		copying = 1;
		pt := p.rect.min;
		pt.add((Inset+Tagwid/2, Inset+Taght/2));
		win.wmctl("ptr " + string pt.x + " " + string pt.y);
	}
	if (debug['P'])
		t.dump("/");
}

tagmenu: ref Menu;
rowtagmenu: ref Menu;
coltagmenu: ref Menu;
minrowtagmenu: ref Menu;
mincoltagmenu: ref Menu;

tagmouse(t: ref Tree, p: ref Panel, m: ref Cpointer, mc: chan of ref Cpointer)
{
	if (m.buttons == 0)
		return;
	if (tagmenu == nil){
		tagmenu = Menu.new(array[] of {"Hide", "Del", "Copy"});
		rowtagmenu = Menu.new(array [] of {"Min", "Hide", "Col", "Copy", "Del", "Max"});
		coltagmenu = Menu.new(array [] of {"Min", "Hide", "Row", "Copy", "Del", "Max"});
		minrowtagmenu = Menu.new(array [] of {"Full", "Hide", "Col", "Copy", "Del", "Max"});
		mincoltagmenu = Menu.new(array [] of {"Full", "Hide", "Row", "Copy", "Del", "Max"});
	}
	menu: ref Menu;
	case p.rowcol {
	Qrow =>
		if (p.nshown != len p.child)
			menu = minrowtagmenu;
		else
			menu = rowtagmenu;
	Qcol =>
		if (p.nshown != len p.child)
			menu = mincoltagmenu;
		else
			menu = coltagmenu;
	* => menu = tagmenu;
	}
	case m.buttons {
	4 =>
		m = <-mc;
		if (m.buttons == 0){
			copying = 0;
			opt := menu.run(m, mc);
			if (opt != nil)
				wcmd(t, p, opt);
		}
	1 =>
		m = <-mc;
		if (intag(p, m.xy) && m.buttons == 1){
			drag(t, p, m, mc, 0);
	}
	2 =>
		m = <-mc;
		if (intag(p, m.xy) && m.buttons == 0)
			t.size(t.path(p), Max);
	}
}

panelmouse(t: ref Tree, p: ref Panel, m: ref Cpointer, mc: chan of ref Cpointer): int
{
	if ((p.flags&Ptag) && intag(p, m.xy)){
		tagmouse(t, p, m, mc);
		return 1;
	}
	case m.buttons {
	4 =>
		if (cookclick(m, mc))
			p.fsctl(sprint("look %s\n", p.name), 1);
		return 1;
	2 =>
		if (cookclick(m, mc))
			p.fsctl(sprint("exec %s\n", p.name), 1);
		return 1;
	* =>
		return 0;
	}
}

panelkbd(nil: ref Tree, p: ref Panel, r: int)
{
	if (r == Keyboard->Del)
		p.fsctl("interrupt\n", 1);
	else if (p.flags&Pedit)
		p.fsctl(sprint("keys %c\n", r), 1);
}

flagredraw(fp: ref Panel)
{
	fp.flags |= Predraw;
	for (i := 0; i < len fp.child; i++)
		if (fp.child[i] != nil)
			flagredraw(fp.child[i]);
}

# The FS should never report certain attributes for certain panels.
# In any case, we process most of them here, and most panels may
# call this function to deal with their ctls.
panelctl(nil: ref Tree, p: ref Panel, s: string): int
{
	(nargs, args) := tokenize(s, " \t\n");
	if (nargs < 1)
		return -1;
	case hd args {
	"row" =>
		if (p.rowcol != Qrow)
			flagredraw(p);
		p.rowcol = Qrow;
	"col" =>
		if (p.rowcol != Qcol)
			flagredraw(p);
		p.rowcol = Qcol;
	"appl" =>
		# arg ignored
		p.flags &= ~Playout;
	"layout" =>
		p.flags |= Playout;
	"hide" =>
		p.flags |= Phide;
		# must make the rectangle void, otherwise, if a show ctl
		# leaves the same rectangle Predraw would not be set
		p.rect = Rect((0, 0), (0, 0));
	"show" =>
		p.flags &= ~Phide;
		flagredraw(p);
	"tag" =>
		if (!(p.flags&Ptag))
			p.flags |= Predraw;
		p.flags |= Ptag;
	"notag" =>
		if (p.flags&Ptag)
			p.flags |= Predraw;
		p.flags &= ~Ptag;
	"dirty" =>
		# containers should never get here
		p.flags |= Pdirty;
	"clean" =>
		# containers should never get here
		p.flags &= ~Pdirty;
	"font" =>
		o := p.font;
		# only text panels should get here
		p.font = getfont(nth(args, 1));
		if (o != p.font)
			p.flags |= Predraw;
	"space" =>
		# only containers should get here
		o := p.space;
		p.space = int nth(args, 1);
		if (o != p.space)
			p.flags |= Predraw;
	"order" =>
		if (tl args != p.order)
			flagredraw(p);
		p.order = tl args;
	* =>
		return -1;
	}
	return 0;
}

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.