ref: 037bc7b4328948994f8b8fa2be487aac1cca835f
dir: /sys/src/libcontrol/control.c/
#include <u.h> #include <libc.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, uint 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 *str; Rune buf[2][20], *rp; cs = v; threadsetname("controlsetthread 0x%p", cs); 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: _ctlcontrol(cs, str); free(str); break; case AExit: threadexits(nil); } } } Control* _createctl(Controlset *cs, char *type, uint 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); 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(uint n) { void *p; p = mallocz(n, 1); if(p == nil) ctlerror("control allocation failed: %r"); return p; } void* ctlrealloc(void *p, uint 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]; strtol will stop at closing ] */ t++; cp->iargs[i] = strtol(t, 0, 0); } 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; seek(_ctlsnarffd, 0, 0); while((n = 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 = 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; } 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; Alt alts[3]; cs = v; threadsetname("resizethread 0x%p", cs); 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; ulong 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 = 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); }