jehanne/sys/src/lib/control/group.c

747 lines
19 KiB
C
Raw Permalink Normal View History

/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <u.h>
#include <lib9.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>
#include "group.h"
static int debug = 0;
static int debugm = 0;
static int debugr = 0;
enum{
EAdd,
EBorder,
EBordercolor,
EFocus,
EHide,
EImage,
ERect,
ERemove,
EReveal,
ESeparation,
EShow,
ESize,
};
static char *cmds[] = {
[EAdd] = "add",
[EBorder] = "border",
[EBordercolor] = "bordercolor",
[EFocus] = "focus",
[EHide] = "hide",
[EImage] = "image",
[ERect] = "rect",
[ERemove] = "remove",
[EReveal] = "reveal",
[ESeparation] = "separation",
[EShow] = "show",
[ESize] = "size",
};
static void boxboxresize(Group*, Rectangle);
static void columnresize(Group*, Rectangle);
static void groupctl(Control *c, CParse *cp);
static void groupfree(Control*);
static void groupmouse(Control *, Mouse *);
static void groupsize(Control *c);
static void removegroup(Group*, int);
static void rowresize(Group*, Rectangle);
static void stackresize(Group*, Rectangle);
static void
groupinit(Group *g)
{
g->bordercolor = _getctlimage("black");
g->image = _getctlimage("white");
g->border = 0;
g->mansize = 0;
g->separation = 0;
g->selected = -1;
g->lastkid = -1;
g->kids = nil;
g->separators = nil;
g->nkids = 0;
g->nseparators = 0;
g->ctl = groupctl;
g->mouse = groupmouse;
g->exit = groupfree;
}
static void
groupctl(Control *c, CParse *cp)
{
int cmd, i, n;
Rectangle r;
Group *g;
g = (Group*)c;
cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
switch(cmd){
case EAdd:
for (i = 1; i < cp->nargs; i++){
c = controlcalled(cp->args[i]);
if (c == nil)
ctlerror("%q: no such control: %s", g->name, cp->args[i]);
_ctladdgroup(g, c);
}
if (g->setsize)
g->setsize((Control*)g);
break;
case EBorder:
_ctlargcount(g, cp, 2);
if(cp->iargs[1] < 0)
ctlerror("%q: bad border: %c", g->name, cp->str);
g->border = cp->iargs[1];
break;
case EBordercolor:
_ctlargcount(g, cp, 2);
_setctlimage(g, &g->bordercolor, cp->args[1]);
break;
case EFocus:
/* ignore focus change */
break;
case EHide:
_ctlargcount(g, cp, 1);
for (i = 0; i < g->nkids; i++)
if (g->kids[i]->ctl)
_ctlprint(g->kids[i], "hide");
g->hidden = 1;
break;
case EImage:
_ctlargcount(g, cp, 2);
_setctlimage(g, &g->image, cp->args[1]);
break;
case ERect:
_ctlargcount(g, cp, 5);
r.min.x = cp->iargs[1];
r.min.y = cp->iargs[2];
r.max.x = cp->iargs[3];
r.max.y = cp->iargs[4];
if(Dx(r)<=0 || Dy(r)<=0)
ctlerror("%q: bad rectangle: %s", g->name, cp->str);
g->rect = r;
r = insetrect(r, g->border);
if (g->nkids == 0)
return;
switch(g->type){
case Ctlboxbox:
boxboxresize(g, r);
break;
case Ctlcolumn:
columnresize(g, r);
break;
case Ctlrow:
rowresize(g, r);
break;
case Ctlstack:
stackresize(g, r);
break;
}
break;
case ERemove:
_ctlargcount(g, cp, 2);
for (n = 0; n < g->nkids; n++)
if (strcmp(cp->args[1], g->kids[n]->name) == 0)
break;
if (n == g->nkids)
ctlerror("%s: remove nonexistent control: %q", g->name, cp->args[1]);
removegroup(g, n);
if (g->setsize)
g->setsize((Control*)g);
break;
case EReveal:
g->hidden = 0;
if (debugr) fprint(2, "reveal %s\n", g->name);
if (g->type == Ctlstack){
if (cp->nargs == 2){
if (cp->iargs[1] < 0 || cp->iargs[1] >= g->nkids)
ctlerror("%s: control out of range: %q", g->name, cp->str);
g->selected = cp->iargs[1];
}else
_ctlargcount(g, cp, 1);
for (i = 0; i < g->nkids; i++)
if (g->kids[i]->ctl){
if (g->selected == i){
if (debugr) fprint(2, "reveal %s: reveal kid %s\n", g->name, g->kids[i]->name);
_ctlprint(g->kids[i], "reveal");
}else{
if (debugr) fprint(2, "reveal %s: hide kid %s\n", g->name, g->kids[i]->name);
_ctlprint(g->kids[i], "hide");
}
}
break;
}
_ctlargcount(g, cp, 1);
if (debug) fprint(2, "reveal %s: border %R/%d\n", g->name, g->rect, g->border);
border(g->screen, g->rect, g->border, g->bordercolor->image, g->bordercolor->image->r.min);
r = insetrect(g->rect, g->border);
if (debug) fprint(2, "reveal %s: draw %R\n", g->name, r);
draw(g->screen, r, g->image->image, nil, g->image->image->r.min);
for (i = 0; i < g->nkids; i++)
if (g->kids[i]->ctl)
_ctlprint(g->kids[i], "reveal");
break;
case EShow:
_ctlargcount(g, cp, 1);
if (g->hidden)
break;
// pass it on to the kiddies
if (debug) fprint(2, "show %s: border %R/%d\n", g->name, g->rect, g->border);
border(g->screen, g->rect, g->border, g->bordercolor->image, g->bordercolor->image->r.min);
r = insetrect(g->rect, g->border);
if (debug) fprint(2, "show %s: draw %R\n", g->name, r);
draw(g->screen, r, g->image->image, nil, g->image->image->r.min);
for (i = 0; i < g->nkids; i++)
if (g->kids[i]->ctl){
if (debug) fprint(2, "show %s: kid %s: %q\n", g->name, g->kids[i]->name, cp->str);
_ctlprint(g->kids[i], "show");
}
flushimage(display, 1);
break;
case ESize:
r.max = Pt(_Ctlmaxsize, _Ctlmaxsize);
if (g->type == Ctlboxbox)
_ctlargcount(g, cp, 5);
switch(cp->nargs){
default:
ctlerror("%s: args of %q", g->name, cp->str);
case 1:
/* recursively set size */
g->mansize = 0;
if (g->setsize)
g->setsize((Control*)g);
break;
case 5:
_ctlargcount(g, cp, 5);
r.max.x = cp->iargs[3];
r.max.y = cp->iargs[4];
/* fall through */
case 3:
r.min.x = cp->iargs[1];
r.min.y = cp->iargs[2];
if(r.min.x<=0 || r.min.y<=0 || r.max.x<=0 || r.max.y<=0 || r.max.x < r.min.x || r.max.y < r.min.y)
ctlerror("%q: bad sizes: %s", g->name, cp->str);
g->size = r;
g->mansize = 1;
break;
}
break;
case ESeparation:
if (g->type != Ctlstack){
_ctlargcount(g, cp, 2);
if(cp->iargs[1] < 0)
ctlerror("%q: illegal value: %c", g->name, cp->str);
g->separation = cp->iargs[1];
break;
}
// fall through for Ctlstack
default:
ctlerror("%q: unrecognized message '%s'", g->name, cp->str);
break;
}
}
static void
groupfree(Control *c)
{
Group *g;
g = (Group*)c;
_putctlimage(g->bordercolor);
free(g->kids);
}
static void
groupmouse(Control *c, Mouse *m)
{
Group *g;
int i, lastkid;
g = (Group*)c;
if (g->type == Ctlstack){
i = g->selected;
if (i >= 0 && g->kids[i]->mouse &&
( ( ((m->buttons == 0) || (g->lastbut == 0)) &&
ptinrect(m->xy, g->kids[i]->rect) ) ||
( ((m->buttons != 0) || (g->lastbut != 0)) &&
(g->lastkid == i) ) ) ) {
if (debugm) fprint(2, "groupmouse %s mouse kid %s i=%d lastkid=%d buttons=%d lastbut=%d inrect=%d\n",
g->name, g->kids[i]->name, i, g->lastkid, m->buttons, g->lastbut,
ptinrect(m->xy, g->kids[i]->rect) ? 1 : 0);
(g->kids[i]->mouse)(g->kids[i], m);
g->lastkid = i;
g->lastbut = m->buttons;
} else {
if (debugm) fprint(2, "groupmouse %s skip kid %s i=%d lastkid=%d buttons=%d lastbut=%d inrect=%d\n",
g->name, g->kids[i]->name, i, g->lastkid, m->buttons, g->lastbut,
ptinrect(m->xy, g->kids[i]->rect) ? 1 : 0);
}
return;
}
lastkid = -1;
for(i=0; i<g->nkids; i++) {
if(g->kids[i]->mouse &&
( ( ((m->buttons == 0) || (g->lastbut == 0)) &&
ptinrect(m->xy, g->kids[i]->rect) ) ||
( ((m->buttons != 0) || (g->lastbut != 0)) &&
(g->lastkid == i) ) ) ) {
if (debugm) fprint(2, "groupmouse %s mouse kid %s i=%d lastkid=%d buttons=%d lastbut=%d inrect=%d\n",
g->name, g->kids[i]->name, i, g->lastkid, m->buttons, g->lastbut,
ptinrect(m->xy, g->kids[i]->rect) ? 1 : 0);
(g->kids[i]->mouse)(g->kids[i], m);
lastkid = i;
} else {
if (debugm) fprint(2, "groupmouse %s skip kid %s i=%d lastkid=%d buttons=%d lastbut=%d inrect=%d\n",
g->name, g->kids[i]->name, i, g->lastkid, m->buttons, g->lastbut,
ptinrect(m->xy, g->kids[i]->rect) ? 1 : 0);
}
}
g->lastkid = lastkid;
g->lastbut = m->buttons;
#ifdef notdef
if(m->buttons == 0){
/* buttons now up */
g->lastbut = 0;
return;
}
if(g->lastbut == 0 && m->buttons != 0){
/* button went down, start tracking border */
switch(g->stacking){
default:
return;
case Vertical:
p = Pt(m->xy.x, middle_of_border.y);
p0 = Pt(g->r.min.x, m->xy.y);
p1 = Pt(g->r.max.x, m->xy.y);
break;
case Horizontal:
p = Pt(middle_of_border.x, m->xy.y);
p0 = Pt(m->xy.x, g->r.min.y);
p1 = Pt(m->xy.x, g->r.max.y);
break;
}
// setcursor();
oi = nil;
} else if (g->lastbut != 0 && s->m.buttons != 0){
/* button is down, keep tracking border */
if(!eqpt(s->m.xy, p)){
p = onscreen(s->m.xy);
r = canonrect(Rpt(p0, p));
if(Dx(r)>5 && Dy(r)>5){
i = allocwindow(wscreen, r, Refnone, 0xEEEEEEFF); /* grey */
freeimage(oi);
if(i == nil)
goto Rescue;
oi = i;
border(i, r, Selborder, red, ZP);
flushimage(display, 1);
}
}
} else if (g->lastbut != 0 && s->m.buttons == 0){
/* button went up, resize kiddies */
}
g->lastbut = s->m.buttons;
#endif
}
static void
activategroup(Control *c, int act)
{
int i;
Group *g;
g = (Group*)c;
for (i = 0; i < g->nkids; i++)
if (act)
activate(g->kids[i]);
else
deactivate(g->kids[i]);
}
Control *
createrow(Controlset *cs, char *name)
{
Control *c;
c = _createctl(cs, "row", sizeof(Group), name);
groupinit((Group*)c);
c->setsize = groupsize;
c->activate = activategroup;
return c;
}
Control *
createcolumn(Controlset *cs, char *name)
{
Control *c;
c = _createctl(cs, "column", sizeof(Group), name);
groupinit((Group*)c);
c->setsize = groupsize;
c->activate = activategroup;
return c;
}
Control *
createboxbox(Controlset *cs, char *name)
{
Control *c;
c = _createctl(cs, "boxbox", sizeof(Group), name);
groupinit((Group*)c);
c->activate = activategroup;
return c;
}
Control *
createstack(Controlset *cs, char *name)
{
Control *c;
c = _createctl(cs, "stack", sizeof(Group), name);
groupinit((Group*)c);
c->setsize = groupsize;
return c;
}
void
_ctladdgroup(Control *c, Control *q)
{
Group *g = (Group*)c;
g->kids = ctlrealloc(g->kids, sizeof(Group*)*(g->nkids+1));
g->kids[g->nkids++] = q;
}
static void
removegroup(Group *g, int n)
{
int i;
if (g->selected == n)
g->selected = -1;
else if (g->selected > n)
g->selected--;
for (i = n+1; i < g->nkids; i++)
g->kids[i-1] = g->kids[i];
g->nkids--;
}
static void
groupsize(Control *c)
{
Rectangle r;
int i;
Control *q;
Group *g;
g = (Group*)c;
assert(g->type == Ctlcolumn || g->type == Ctlrow || g->type == Ctlstack);
if (g->mansize) return;
r = Rect(1, 1, 1, 1);
if (debug) fprint(2, "groupsize %q\n", g->name);
for (i = 0; i < g->nkids; i++){
q = g->kids[i];
if (q->setsize)
q->setsize(q);
if (q->size.min.x == 0 || q->size.min.y == 0 || q->size.max.x == 0 || q->size.max.y == 0)
ctlerror("%q: bad size %R", q->name, q->size);
if (debug) fprint(2, "groupsize %q: [%d %q]: %R\n", g->name, i, q->name, q->size);
switch(g->type){
case Ctlrow:
if (i)
r.min.x += q->size.min.x + g->border;
else
r.min.x = q->size.min.x;
if (i)
r.max.x += q->size.max.x + g->border;
else
r.max.x = q->size.max.x;
if (r.min.y < q->size.min.y) r.min.y = q->size.min.y;
if (r.max.y < q->size.max.y) r.max.y = q->size.max.y;
break;
case Ctlcolumn:
if (r.min.x < q->size.min.x) r.min.x = q->size.min.x;
if (r.max.x < q->size.max.x) r.max.x = q->size.max.x;
if (i)
r.min.y += q->size.min.y + g->border;
else
r.min.y = q->size.min.y;
if (i)
r.max.y += q->size.max.y + g->border;
else
r.max.y = q->size.max.y;
break;
case Ctlstack:
if (r.min.x < q->size.min.x) r.min.x = q->size.min.x;
if (r.max.x < q->size.max.x) r.max.x = q->size.max.x;
if (r.min.y < q->size.min.y) r.min.y = q->size.min.y;
if (r.max.y < q->size.max.y) r.max.y = q->size.max.y;
break;
}
}
g->size = rectaddpt(r, Pt(g->border, g->border));
if (debug) fprint(2, "groupsize %q: %R\n", g->name, g->size);
}
static void
boxboxresize(Group *g, Rectangle r)
{
int rows, cols, ht, wid, i, hpad, wpad;
Rectangle rr;
if(debug) fprint(2, "boxboxresize %q %R (%d×%d) min/max %R separation %d\n", g->name, r, Dx(r), Dy(r), g->size, g->separation);
ht = 0;
for(i=0; i<g->nkids; i++){
if (g->kids[i]->size.min.y > ht)
ht = g->kids[i]->size.min.y;
}
if (ht == 0)
ctlerror("boxboxresize: height");
rows = Dy(r) / (ht+g->separation);
hpad = (Dy(r) % (ht+g->separation)) / g->nkids;
cols = (g->nkids+rows-1)/rows;
wid = Dx(r) / cols - g->separation;
for(i=0; i<g->nkids; i++){
if (g->kids[i]->size.max.x < wid)
wid = g->kids[i]->size.max.x;
}
for(i=0; i<g->nkids; i++){
if (g->kids[i]->size.min.x > wid)
wid = g->kids[i]->size.min.x;
}
if (wid > Dx(r) / cols)
ctlerror("can't fit controls in boxbox");
wpad = (Dx(r) % (wid+g->separation)) / g->nkids;
rr = rectaddpt(Rect(0,0,wid, ht), addpt(r.min, Pt(g->separation/2, g->separation/2)));
if(debug) fprint(2, "boxboxresize rows %d, cols %d, wid %d, ht %d, wpad %d, hpad %d\n", rows, cols, wid, ht, wpad, hpad);
for(i=0; i<g->nkids; i++){
if(debug) fprint(2, " %d %q: %R (%d×%d)\n", i, g->kids[i]->name, rr, Dx(rr), Dy(rr));
_ctlprint(g->kids[i], "rect %R",
rectaddpt(rr, Pt((wpad+wid+g->separation)*(i/rows), (hpad+ht+g->separation)*(i%rows))));
}
g->nseparators = rows + cols - 2;
g->separators = realloc(g->separators, g->nseparators*sizeof(Rectangle));
rr = r;
rr.max.y = rr.min.y + g->separation+hpad;
for (i = 1; i < rows; i++){
g->separators[i-1] = rectaddpt(rr, Pt(0, (hpad+ht+g->separation)*i-g->separation-hpad));
if(debug) fprint(2, "row separation %d [%d]: %R\n", i, i-1, rectaddpt(rr, Pt(0, (hpad+ht+g->separation)*i-g->separation)));
}
rr = r;
rr.max.x = rr.min.x + g->separation+wpad;
for (i = 1; i < cols; i++){
g->separators[i+rows-2] = rectaddpt(rr, Pt((wpad+wid+g->separation)*i-g->separation-wpad, 0));
if(debug) fprint(2, "col separation %d [%d]: %R\n", i, i+rows-2, rectaddpt(rr, Pt((wpad+wid+g->separation)*i-g->separation, 0)));
}
}
static void
columnresize(Group *g, Rectangle r)
{
int x, y, *d, *p, i, j, t;
Rectangle rr;
Control *q;
x = Dx(r);
y = Dy(r);
if(debug) fprint(2, "columnresize %q %R (%d×%d) min/max %R separation %d\n", g->name, r, Dx(r), Dy(r), g->size, g->separation);
if (x < g->size.min.x) {
werrstr("resize %s: too narrow: need %d, have %d", g->name, g->size.min.x, x);
r.max.x = r.min.x + g->size.min.x;
}
if (y < g->size.min.y) {
werrstr("resize %s: too short: need %d, have %d", g->name, g->size.min.y, y);
r.max.y = r.min.y + g->size.min.y;
y = Dy(r);
}
d = ctlmalloc(g->nkids*sizeof(int));
p = ctlmalloc(g->nkids*sizeof(int));
if(debug) fprint(2, "kiddies: ");
for (i = 0; i < g->nkids; i++) {
q = g->kids[i];
if(debug) fprint(2, "[%q]: %d⋯%d\t", q->name, q->size.min.y, q->size.max.y);
d[i] = q->size.min.y;
y -= d[i];
p[i] = q->size.max.y - q->size.min.y;
}
if(debug) fprint(2, "\n");
y -= (g->nkids-1) * g->separation;
if(y < 0){
if (debug) fprint(2, "columnresize: y == %d\n", y);
y = 0;
}
if (y >= g->size.max.y - g->size.min.y) {
// all rects can be maximum width
for (i = 0; i < g->nkids; i++)
d[i] += p[i];
y -= g->size.max.y - g->size.min.y;
} else {
// rects can't be max width, divide up the rest
j = y;
for (i = 0; i < g->nkids; i++) {
t = p[i] * y/(g->size.max.y - g->size.min.y);
d[i] += t;
j -= t;
}
d[0] += j;
y = 0;
}
g->nseparators = g->nkids-1;
g->separators = realloc(g->separators, g->nseparators*sizeof(Rectangle));
j = 0;
rr = r;
for (i = 0; i < g->nkids; i++) {
q = g->kids[i];
if (i < g->nkids - 1){
g->separators[i].min.x = r.min.x;
g->separators[i].max.x = r.max.x;
}
t = y / (g->nkids - i);
y -= t;
j += t/2;
rr.min.y = r.min.y + j;
if (i)
g->separators[i-1].max.y = rr.min.y;
j += d[i];
rr.max.y = r.min.y + j;
if (i < g->nkids - 1)
g->separators[i].min.y = rr.max.y;
j += g->separation + t - t/2;
_ctlprint(q, "rect %R", rr);
if(debug) fprint(2, " %d %q: %R (%d×%d)\n", i, q->name, rr, Dx(rr), Dy(rr));
}
free(d);
free(p);
}
static void
rowresize(Group *g, Rectangle r)
{
int x, y, *d, *p, i, j, t;
Rectangle rr;
Control *q;
x = Dx(r);
y = Dy(r);
if(debug) fprint(2, "rowresize %q %R (%d×%d), separation %d\n", g->name, r, Dx(r), Dy(r), g->separation);
if (x < g->size.min.x) {
werrstr("resize %s: too narrow: need %d, have %d", g->name, g->size.min.x, x);
r.max.x = r.min.x + g->size.min.x;
x = Dx(r);
}
if (y < g->size.min.y) {
werrstr("resize %s: too short: need %d, have %d", g->name, g->size.min.y, y);
r.max.y = r.min.y + g->size.min.y;
}
d = ctlmalloc(g->nkids*sizeof(int));
p = ctlmalloc(g->nkids*sizeof(int));
if(debug) fprint(2, "kiddies: ");
for (i = 0; i < g->nkids; i++) {
q = g->kids[i];
if(debug) fprint(2, "[%q]: %d⋯%d\t", q->name, q->size.min.x, q->size.max.x);
d[i] = q->size.min.x;
x -= d[i];
p[i] = q->size.max.x - q->size.min.x;
}
if(debug) fprint(2, "\n");
x -= (g->nkids-1) * g->separation;
if(x < 0){
if (debug) fprint(2, "rowresize: x == %d\n", x);
x = 0;
}
if (x >= g->size.max.x - g->size.min.x) {
if (debug) fprint(2, "max: %d > %d - %d", x, g->size.max.x, g->size.min.x);
// all rects can be maximum width
for (i = 0; i < g->nkids; i++)
d[i] += p[i];
x -= g->size.max.x - g->size.min.x;
} else {
if (debug) fprint(2, "divvie up: %d < %d - %d", x, g->size.max.x, g->size.min.x);
// rects can't be max width, divide up the rest
j = x;
for (i = 0; i < g->nkids; i++) {
t = p[i] * x/(g->size.max.x - g->size.min.x);
d[i] += t;
j -= t;
}
d[0] += j;
x = 0;
}
j = 0;
g->nseparators = g->nkids-1;
g->separators = realloc(g->separators, g->nseparators*sizeof(Rectangle));
rr = r;
for (i = 0; i < g->nkids; i++) {
q = g->kids[i];
if (i < g->nkids - 1){
g->separators[i].min.y = r.min.y;
g->separators[i].max.y = r.max.y;
}
t = x / (g->nkids - i);
x -= t;
j += t/2;
rr.min.x = r.min.x + j;
if (i)
g->separators[i-1].max.x = rr.min.x;
j += d[i];
rr.max.x = r.min.x + j;
if (i < g->nkids - 1)
g->separators[i].min.x = rr.max.x;
j += g->separation + t - t/2;
_ctlprint(q, "rect %R", rr);
if(debug) fprint(2, " %d %q: %R (%d×%d)\n", i, q->name, rr, Dx(rr), Dy(rr));
}
free(d);
free(p);
}
static void
stackresize(Group *g, Rectangle r)
{
int x, y, i;
Control *q;
x = Dx(r);
y = Dy(r);
if(debug) fprint(2, "stackresize %q %R (%d×%d)\n", g->name, r, Dx(r), Dy(r));
if (x < g->size.min.x){
werrstr("resize %s: too narrow: need %d, have %d", g->name, g->size.min.x, x);
return;
}
if (y < g->size.min.y){
werrstr("resize %s: too short: need %d, have %d", g->name, g->size.min.y, y);
return;
}
if (x > g->size.max.x) {
x = (x - g->size.max.x)/2;
r.min.x += x;
r.max.x -= x;
}
if (y > g->size.max.y) {
y = (y - g->size.max.y)/2;
r.min.y += y;
r.max.y -= y;
}
for (i = 0; i < g->nkids; i++){
q = g->kids[i];
if(debug) fprint(2, " %d %q: %R (%d×%d)\n", i, q->name, r, Dx(r), Dy(r));
}
for (i = 0; i < g->nkids; i++){
q = g->kids[i];
_ctlprint(q, "rect %R", r);
}
}