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

822 lines
16 KiB
C

/*
* 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 <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>
static int debug = 0;
enum /* alts */
{
AKey,
AMouse,
ACtl,
AExit,
NALT
};
static Controlset **controlset;
int ncontrolset;
int ctldeletequits;
char *alignnames[Nalignments] = {
[Aupperleft] = "upperleft",
[Auppercenter] = "uppercenter",
[Aupperright] = "upperright",
[Acenterleft] = "centerleft",
[Acenter] = "center",
[Acenterright] = "centerright",
[Alowerleft] = "lowerleft",
[Alowercenter] = "lowercenter",
[Alowerright] = "lowerright",
};
char *ctltypenames[Ntypes] = {
[Ctlunknown] = "unknown",
[Ctlbox] = "box",
[Ctlbutton] = "button",
[Ctlentry] = "entry",
[Ctlkeyboard] = "keyboard",
[Ctllabel] = "label",
[Ctlmenu] = "menu",
[Ctlradio] = "radio",
[Ctlscribble] = "scribble",
[Ctlslider] = "slider",
[Ctltabs] = "tabs",
[Ctltext] = "text",
[Ctltextbutton] = "textbutton",
[Ctltextbutton3] = "textbutton3",
[Ctlgroup] = "group", // divider between controls and metacontrols
[Ctlboxbox] = "boxbox",
[Ctlcolumn] = "column",
[Ctlrow] = "row",
[Ctlstack] = "stack",
[Ctltab] = "tab",
};
static void _ctlcmd(Controlset*, char*);
static void _ctlcontrol(Controlset*, char*);
static char*
_mkctlcmd(Control *c, char *fmt, va_list arg)
{
char *name, *p, *both;
name = quotestrdup(c->name);
if(name == nil)
ctlerror("quotestrdup in ctlprint failed");
p = vsmprint(fmt, arg);
if(p == nil){
free(name);
ctlerror("vsmprint1 in ctlprint failed");
}
both = ctlmalloc(strlen(name)+strlen(p)+2);
strcpy(both, name);
strcat(both, " ");
strcat(both, p);
free(name);
free(p);
return both;
}
int
ctlprint(Control *c, char *fmt, ...)
{
int n;
char *p;
va_list arg;
va_start(arg, fmt);
p = _mkctlcmd(c, fmt, arg);
va_end(arg);
n = sendp(c->controlset->ctl, p);
yield();
return n;
}
void
_ctlprint(Control *c, char *fmt, ...)
{
char *p;
va_list arg;
va_start(arg, fmt);
p = _mkctlcmd(c, fmt, arg);
va_end(arg);
_ctlcmd(c->controlset, p);
free(p);
}
int
_ctllookup(char *s, char *tab[], int ntab)
{
int i;
for(i=0; i<ntab; i++)
if(tab[i] != nil && strcmp(s, tab[i]) == 0)
return i;
return -1;
}
static Control*
_newcontrol(Controlset *cs, uint32_t n, char *name, char *type)
{
Control *c;
for(c=cs->controls; c; c=c->next)
if(strcmp(c->name, name) == 0){
werrstr("control %q already defined", name);
return nil;
}
c = ctlmalloc(n);
c->screen = cs->screen;
c->name = ctlstrdup(name);
c->type = _ctllookup(type, ctltypenames, Ntypes);
if (c->type < 0)
ctlerror("unknown type: %s", type);
c->event = chancreate(sizeof(char*), 64);
c->data = chancreate(sizeof(char*), 0);
c->size = Rect(1, 1, _Ctlmaxsize, _Ctlmaxsize);
c->hidden = 0;
c->ctl = nil;
c->mouse = nil;
c->key = nil;
c->exit = nil;
c->setsize = nil;
c->controlset = cs;
c->next = cs->controls;
cs->controls = c;
return c;
}
static void
controlsetthread(void *v)
{
Controlset *cs;
Mouse mouse;
Control *f;
int prevbut, n, i;
Alt alts[NALT+1];
char tmp[64], *str;
Rune buf[2][20], *rp;
cs = v;
snprint(tmp, sizeof tmp, "controlsetthread 0x%p", cs);
threadsetname(tmp);
alts[AKey].c = cs->kbdc;
alts[AKey].v = &rp;
alts[AKey].op = CHANRCV;
alts[AMouse].c = cs->mousec;
alts[AMouse].v = &mouse;
alts[AMouse].op = CHANRCV;
alts[ACtl].c = cs->ctl;
alts[ACtl].v = &str;
alts[ACtl].op = CHANRCV;
alts[AExit].c = cs->csexitc;
alts[AExit].v = nil;
alts[AExit].op = CHANRCV;
alts[NALT].op = CHANEND;
cs->focus = nil;
prevbut=0;
n = 0;
for(;;){
/* toggle so we can receive in one buffer while client processes the other */
alts[AKey].v = buf[n];
rp = buf[n];
n = 1-n;
switch(alt(alts)){
case AKey:
if(ctldeletequits && rp[0]=='\177')
ctlerror("delete");
for(i=1; i<nelem(buf[0])-1; i++)
if(nbrecv(cs->kbdc, rp+i) <= 0)
break;
rp[i] = L'\0';
if(cs->focus && cs->focus->key)
cs->focus->key(cs->focus, rp);
break;
case AMouse:
/* is this a focus change? */
if(prevbut) /* don't change focus if button was down */
goto Send;
if(cs->focus!=nil && cs->focus->hidden == 0 && ptinrect(mouse.xy, cs->focus->rect))
goto Send;
if(cs->clicktotype == 0)
goto Change;
/* click to type: only change if button is down */
if(mouse.buttons == 0)
goto Send;
Change:
/* change of focus */
if(cs->focus != nil)
_ctlprint(cs->focus, "focus 0");
cs->focus = nil;
for(f=cs->actives; f!=nil; f=f->nextactive)
if(f->hidden == 0 && f->mouse && ptinrect(mouse.xy, f->rect)){
cs->focus = f;
_ctlprint(f, "focus 1");
if (f->mouse) {
if (debug) fprint(2, "f->mouse %s\n", f->name);
f->mouse(f, &mouse);
}
break;
}
Send:
if(cs->focus && cs->focus->mouse) {
if (debug) fprint(2, "cs->focus->mouse %s\n", cs->focus->name);
cs->focus->mouse(cs->focus, &mouse);
}
prevbut=mouse.buttons;
break;
case ACtl:
if(str != nil){
_ctlcontrol(cs, str);
free(str);
str = nil;
}
break;
case AExit:
threadexits(nil);
}
}
}
Control*
_createctl(Controlset *cs, char *type, uint32_t size, char *name)
{
Control *c;
c = _newcontrol(cs, size, name, type);
if(c == nil)
ctlerror("can't create %s control %q: %r", type, name);
return c;
}
void
closecontrol(Control *c)
{
Control *prev, *p;
if(c == nil)
return;
if (c == c->controlset->focus)
c->controlset->focus = nil;
if(c->exit)
c->exit(c);
prev = nil;
for(p=c->controlset->controls; p; p=p->next){
if(p == c)
break;
prev = p;
}
if(p == nil)
ctlerror("closecontrol: no such control %q %p\n", c->name, c);
if(prev == nil)
c->controlset->controls = c->next;
else
prev->next = c->next;
/* is it active? if so, delete from active list */
prev = nil;
for(p=c->controlset->actives; p; p=p->nextactive){
if(p == c)
break;
prev = p;
}
if(p != nil){
if(prev == nil)
c->controlset->actives = c->nextactive;
else
prev->nextactive = c->nextactive;
}
if(!c->wevent)
chanfree(c->event);
if(!c->wdata)
chanfree(c->data);
free(c->name);
free(c->format);
free(c);
}
Control*
controlcalled(char *name)
{
Control *c;
int i;
for(i=0; i<ncontrolset; i++)
for(c=controlset[i]->controls; c; c=c->next)
if(strcmp(c->name, name) == 0)
return c;
return nil;
}
void
ctlerror(char *fmt, ...)
{
va_list arg;
char buf[256];
va_start(arg, fmt);
vfprint(2, fmt, arg);
va_end(arg);
jehanne_write(2, "\n", 1);
threadexitsall(buf);
}
Rune*
_ctlrunestr(char *s)
{
Rune *r, *ret;
ret = r = ctlmalloc((utflen(s)+1)*sizeof(Rune));
while(*s != '\0')
s += chartorune(r++, s);
*r = L'\0';
return ret;
}
char*
_ctlstrrune(Rune *r)
{
char *s;
s = ctlmalloc(runestrlen(r)*UTFmax+1);
sprint(s, "%S", r);
return s;
}
void*
ctlmalloc(uint32_t n)
{
void *p;
p = mallocz(n, 1);
if(p == nil)
ctlerror("control allocation failed: %r");
return p;
}
void*
ctlrealloc(void *p, uint32_t n)
{
p = realloc(p, n);
if(p == nil)
ctlerror("control reallocation failed: %r");
return p;
}
char*
ctlstrdup(char *s)
{
char *t;
t = strdup(s);
if(t == nil)
ctlerror("control strdup(%q) failed: %r", s);
return t;
}
static void
ctokenize(char *s, CParse *cp)
{
snprint(cp->str, sizeof cp->str, "%s", s);
cp->args = cp->pargs;
cp->nargs = tokenize(s, cp->args, nelem(cp->pargs));
}
static int
ctlparse(CParse *cp, char *s, int hasreceiver)
{
int i;
char *t;
/* keep original string for good error messages */
strncpy(cp->str, s, sizeof cp->str);
cp->str[sizeof cp->str - 1] = '\0';
ctokenize(s, cp);
if(cp->nargs == 0)
return -1;
/* strip leading sender name if present */
cp->sender = nil;
i = strlen(cp->args[0])-1;
if(cp->args[0][i] == ':'){
cp->sender = cp->args[0];
cp->sender[i] = '\0';
cp->args++;
cp->nargs--;
}
if(hasreceiver){
if(cp->nargs-- == 0)
return -1;
cp->receiver = *cp->args++;
}else
cp->receiver = nil;
for(i=0; i<cp->nargs; i++){
t = cp->args[i];
while(*t == '[') /* %R gives [0 0] [1 1]; atoi will stop at closing ] */
t++;
cp->iargs[i] = atoi(t);
}
return cp->nargs;
}
void
_ctlargcount(Control *c, CParse *cp, int n)
{
if(cp->nargs != n)
ctlerror("%q: wrong argument count in '%s'", c->name, cp->str);
}
static void
_ctlcmd(Controlset *cs, char*s)
{
CParse cp;
char *rcvrs[32];
int ircvrs[32], n, i, hit;
Control *c;
// fprint(2, "_ctlcmd: %s\n", s);
cp.args = cp.pargs;
if (ctlparse(&cp, s, 1) < 0)
ctlerror("bad command string: %q", cp.str);
if (cp.nargs == 0 && strcmp(cp.receiver, "sync") == 0){
chanprint(cs->data, "sync");
return;
}
if (cp.nargs == 0)
ctlerror("no command in command string: %q", cp.str);
n = tokenize(cp.receiver, rcvrs, nelem(rcvrs));
// lookup type names: a receiver can be a named type or a named control
for (i = 0; i < n; i++)
ircvrs[i] = _ctllookup(rcvrs[i], ctltypenames, Ntypes);
for(c = cs->controls; c != nil; c = c->next){
/* if a control matches on more than one receiver element,
* make sure it gets processed once; hence loop through controls
* in the outer loop
*/
hit = 0;
for (i = 0; i < n; i++)
if(strcmp(c->name, rcvrs[i]) == 0 || c->type == ircvrs[i])
hit++;
if (hit && c->ctl)
c->ctl(c, &cp);
}
}
static void
_ctlcontrol(Controlset *cs, char *s)
{
char *lines[16];
int i, n;
char *l;
// fprint(2, "_ctlcontrol: %s\n", s);
n = gettokens(s, lines, nelem(lines), "\n");
for(i=0; i<n; i++){
l = lines[i];
while(*l==' ' || *l=='\t')
l++;
if(*l != '\0')
_ctlcmd(cs, l);
}
}
Rune*
_ctlgetsnarf(void)
{
int i, n;
char *sn, buf[512];
Rune *snarf;
if(_ctlsnarffd < 0)
return nil;
sn = nil;
i = 0;
sys_seek(_ctlsnarffd, 0, 0);
while((n = jehanne_read(_ctlsnarffd, buf, sizeof buf)) > 0){
sn = ctlrealloc(sn, i+n+1);
memmove(sn+i, buf, n);
i += n;
sn[i] = 0;
}
snarf = nil;
if(i > 0){
snarf = _ctlrunestr(sn);
free(sn);
}
return snarf;
}
void
_ctlputsnarf(Rune *snarf)
{
int fd, i, n, nsnarf;
if(_ctlsnarffd<0 || snarf[0]==0)
return;
fd = sys_open("/dev/snarf", OWRITE);
if(fd < 0)
return;
nsnarf = runestrlen(snarf);
/* snarf buffer could be huge, so fprint will truncate; do it in blocks */
for(i=0; i<nsnarf; i+=n){
n = nsnarf-i;
if(n >= 256)
n = 256;
if(fprint(fd, "%.*S", n, snarf+i) < 0)
break;
}
sys_close(fd);
}
int
_ctlalignment(char *s)
{
int i;
i = _ctllookup(s, alignnames, Nalignments);
if (i < 0)
ctlerror("unknown alignment: %s", s);
return i;
}
Point
_ctlalignpoint(Rectangle r, int dx, int dy, int align)
{
Point p;
p = r.min; /* in case of trouble */
switch(align%3){
case 0: /* left */
p.x = r.min.x;
break;
case 1: /* center */
p.x = r.min.x+(Dx(r)-dx)/2;
break;
case 2: /* right */
p.x = r.max.x-dx;
break;
}
switch((align/3)%3){
case 0: /* top */
p.y = r.min.y;
break;
case 1: /* center */
p.y = r.min.y+(Dy(r)-dy)/2;
break;
case 2: /* bottom */
p.y = r.max.y - dy;
break;
}
return p;
}
void
controlwire(Control *cfrom, char *name, Channel *chan)
{
Channel **p;
p = nil;
if(strcmp(name, "event") == 0){
p = &cfrom->event;
cfrom->wevent = 1;
}else if(strcmp(name, "data") == 0){
p = &cfrom->data;
cfrom->wdata = 1;
}else
ctlerror("%q: unknown controlwire channel %s", cfrom->name, name);
chanfree(*p);
*p = chan;
}
void
_ctlfocus(Control *me, int set)
{
Controlset *cs;
cs = me->controlset;
if(set){
if(cs->focus == me)
return;
if(cs->focus != nil)
_ctlprint(cs->focus, "focus 0");
cs->focus = me;
}else{
if(cs->focus != me)
return;
cs->focus = nil;
}
}
static void
resizethread(void *v)
{
Controlset *cs;
char buf[64];
Alt alts[3];
cs = v;
snprint(buf, sizeof buf, "resizethread0x%p", cs);
threadsetname(buf);
alts[0].c = cs->resizec;
alts[0].v = nil;
alts[0].op = CHANRCV;
alts[1].c = cs->resizeexitc;
alts[1].v = nil;
alts[1].op = CHANRCV;
alts[2].op = CHANEND;
for(;;){
switch(alt(alts)){
case 0:
resizecontrolset(cs);
break;
case 1:
return;
}
}
}
void
activate(Control *a)
{
Control *c;
for(c=a->controlset->actives; c; c=c->nextactive)
if(c == a)
ctlerror("%q already active\n", a->name);
if (a->activate){
a->activate(a, 1);
return;
}
/* prepend */
a->nextactive = a->controlset->actives;
a->controlset->actives = a;
}
void
deactivate(Control *a)
{
Control *c, *prev;
/* if group, first deactivate kids, then self */
if (a->activate){
a->activate(a, 0);
return;
}
prev = nil;
for(c=a->controlset->actives; c; c=c->nextactive){
if(c == a){
if(a->controlset->focus == a)
a->controlset->focus = nil;
if(prev != nil)
prev->nextactive = a->nextactive;
else
a->controlset->actives = a->nextactive;
return;
}
prev = c;
}
ctlerror("%q not active\n", a->name);
}
static struct
{
char *name;
uint32_t color;
}coltab[] = {
"red", DRed,
"green", DGreen,
"blue", DBlue,
"cyan", DCyan,
"magenta", DMagenta,
"yellow", DYellow,
"paleyellow", DPaleyellow,
"darkyellow", DDarkyellow,
"darkgreen", DDarkgreen,
"palegreen", DPalegreen,
"medgreen", DMedgreen,
"darkblue", DDarkblue,
"palebluegreen", DPalebluegreen,
"paleblue", DPaleblue,
"bluegreen", DBluegreen,
"greygreen", DGreygreen,
"palegreygreen", DPalegreygreen,
"yellowgreen", DYellowgreen,
"medblue", DMedblue,
"greyblue", DGreyblue,
"palegreyblue", DPalegreyblue,
"purpleblue", DPurpleblue,
nil, 0
};
void
initcontrols(void)
{
int i;
Image *im;
quotefmtinstall();
namectlimage(display->opaque, "opaque");
namectlimage(display->transparent, "transparent");
namectlimage(display->white, "white");
namectlimage(display->black, "black");
for(i=0; coltab[i].name!=nil; i++){
im = allocimage(display, Rect(0,0,1,1), RGB24, 1, coltab[i].color);
namectlimage(im, coltab[i].name);
}
namectlfont(font, "font");
_ctlsnarffd = sys_open("/dev/snarf", OREAD);
}
Controlset*
newcontrolset(Image *im, Channel *kbdc, Channel *mousec, Channel *resizec)
{
Controlset *cs;
if(im == nil)
im = screen;
if((mousec==nil && resizec!=nil) || (mousec!=nil && resizec==nil))
ctlerror("must specify either or both of mouse and resize channels");
cs = ctlmalloc(sizeof(Controlset));
cs->screen = im;
if(kbdc == nil){
cs->keyboardctl = initkeyboard(nil);
if(cs->keyboardctl == nil)
ctlerror("can't initialize keyboard: %r");
kbdc = cs->keyboardctl->c;
}
cs ->kbdc = kbdc;
if(mousec == nil){
cs->mousectl = initmouse(nil, im);
if(cs->mousectl == nil)
ctlerror("can't initialize mouse: %r");
mousec = cs->mousectl->c;
resizec = cs->mousectl->resizec;
}
cs->mousec = mousec;
cs->resizec = resizec;
cs->ctl = chancreate(sizeof(char*), 64); /* buffer to prevent deadlock */
cs->data = chancreate(sizeof(char*), 0);
cs->resizeexitc = chancreate(sizeof(int), 0);
cs->csexitc = chancreate(sizeof(int), 0);
threadcreate(resizethread, cs, 32*1024);
threadcreate(controlsetthread, cs, 32*1024);
controlset = ctlrealloc(controlset, (ncontrolset+1)*sizeof(Controlset*));
controlset[ncontrolset++] = cs;
return cs;
}
void
closecontrolset(Controlset *cs)
{
int i;
sendul(cs->resizeexitc, 0);
chanfree(cs->resizeexitc);
sendul(cs->csexitc, 0);
chanfree(cs->csexitc);
chanfree(cs->ctl);
chanfree(cs->data);
for(i=0; i<ncontrolset; i++)
if(cs == controlset[i]){
memmove(controlset+i, controlset+i+1, (ncontrolset-(i+1))*sizeof(Controlset*));
ncontrolset--;
goto Found;
}
if(i == ncontrolset)
ctlerror("closecontrolset: control set not found");
Found:
while(cs->controls != nil)
closecontrol(cs->controls);
}