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