ref: d4124d48f9c7e85fdaa50640035080174c94196e
dir: /sys/src/libcontrol/group.c/
#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); } }