plan9front/sys/src/libcontrol/group.c

738 lines
19 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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