Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.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);
        }
}