# 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;
}
|