summaryrefslogtreecommitdiff
path: root/sys/src/libcontrol
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/libcontrol
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/libcontrol')
-rwxr-xr-xsys/src/libcontrol/box.c181
-rwxr-xr-xsys/src/libcontrol/button.c264
-rwxr-xr-xsys/src/libcontrol/cache.c168
-rwxr-xr-xsys/src/libcontrol/control.c809
-rwxr-xr-xsys/src/libcontrol/entry.c354
-rwxr-xr-xsys/src/libcontrol/group.c737
-rwxr-xr-xsys/src/libcontrol/group.h18
-rwxr-xr-xsys/src/libcontrol/keyboard.c536
-rwxr-xr-xsys/src/libcontrol/label.c204
-rwxr-xr-xsys/src/libcontrol/menu.c366
-rwxr-xr-xsys/src/libcontrol/mkfile37
-rwxr-xr-xsys/src/libcontrol/radiobutton.c192
-rwxr-xr-xsys/src/libcontrol/scribble.c325
-rwxr-xr-xsys/src/libcontrol/slider.c326
-rwxr-xr-xsys/src/libcontrol/tabs.c256
-rwxr-xr-xsys/src/libcontrol/text.c542
-rwxr-xr-xsys/src/libcontrol/textbutton.c344
-rwxr-xr-xsys/src/libcontrol/textbutton3.c393
18 files changed, 6052 insertions, 0 deletions
diff --git a/sys/src/libcontrol/box.c b/sys/src/libcontrol/box.c
new file mode 100755
index 000000000..64a16d877
--- /dev/null
+++ b/sys/src/libcontrol/box.c
@@ -0,0 +1,181 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Box Box;
+
+struct Box
+{
+ Control;
+ int border;
+ CImage *bordercolor;
+ CImage *image;
+ int align;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EHide,
+ EImage,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] ="bordercolor",
+ [EFocus] = "focus",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ nil
+};
+
+static void
+boxkey(Control *c, Rune *rp)
+{
+ Box *b;
+
+ b = (Box*)c;
+ chanprint(b->event, "%q: key 0x%x", b->name, rp[0]);
+}
+
+static void
+boxmouse(Control *c, Mouse *m)
+{
+ Box *b;
+
+ b = (Box*)c;
+ if (ptinrect(m->xy,b->rect))
+ chanprint(b->event, "%q: mouse %P %d %ld", b->name,
+ m->xy, m->buttons, m->msec);
+}
+
+static void
+boxfree(Control *c)
+{
+ _putctlimage(((Box*)c)->image);
+}
+
+static void
+boxshow(Box *b)
+{
+ Image *i;
+ Rectangle r;
+
+ if(b->hidden)
+ return;
+ if(b->border > 0){
+ border(b->screen, b->rect, b->border, b->bordercolor->image, ZP);
+ r = insetrect(b->rect, b->border);
+ }else
+ r = b->rect;
+ i = b->image->image;
+ /* BUG: ALIGNMENT AND CLIPPING */
+ draw(b->screen, r, i, nil, ZP);
+}
+
+static void
+boxctl(Control *c, CParse *cp)
+{
+ int cmd;
+ Rectangle r;
+ Box *b;
+
+ b = (Box*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", b->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(b, cp, 2);
+ b->align = _ctlalignment(cp->args[1]);
+ break;
+ case EBorder:
+ _ctlargcount(b, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", b->name, cp->str);
+ b->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->bordercolor, cp->args[1]);
+ break;
+ case EFocus:
+ _ctlargcount(b, cp, 2);
+ chanprint(b->event, "%q: focus %s", b->name, cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(b, cp, 1);
+ b->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->image, cp->args[1]);
+ break;
+ case ERect:
+ _ctlargcount(b, 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", b->name, cp->str);
+ b->rect = r;
+ break;
+ case EReveal:
+ _ctlargcount(b, cp, 1);
+ b->hidden = 0;
+ boxshow(b);
+ break;
+ case EShow:
+ _ctlargcount(b, cp, 1);
+ boxshow(b);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(b, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", b->name, cp->str);
+ b->size.min = r.min;
+ b->size.max = r.max;
+ break;
+ }
+}
+
+Control*
+createbox(Controlset *cs, char *name)
+{
+ Box *b;
+
+ b = (Box *)_createctl(cs, "box", sizeof(Box), name);
+ b->image = _getctlimage("white");
+ b->bordercolor = _getctlimage("black");
+ b->align = Aupperleft;
+ b->key = boxkey;
+ b->mouse = boxmouse;
+ b->ctl = boxctl;
+ b->exit = boxfree;
+ return (Control *)b;
+}
diff --git a/sys/src/libcontrol/button.c b/sys/src/libcontrol/button.c
new file mode 100755
index 000000000..1d6b23d73
--- /dev/null
+++ b/sys/src/libcontrol/button.c
@@ -0,0 +1,264 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Button Button;
+
+struct Button
+{
+ Control;
+ CImage *image;
+ CImage *mask;
+ CImage *light;
+ CImage *pale;
+ CImage *bordercolor;
+ int pressed;
+ int lastbut;
+ int lastshow;
+ int border;
+ int align;
+ int off;
+ int prepress;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFormat,
+ EHide,
+ EImage,
+ ELight,
+ EMask,
+ EPale,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EFocus] = "focus",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ELight] = "light",
+ [EMask] = "mask",
+ [EPale] = "pale",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [EValue] = "value",
+ nil
+};
+
+static void
+buttonfree(Control *c)
+{
+ Button *b;
+
+ b = (Button *)c;
+ _putctlimage(b->image);
+ _putctlimage(b->mask);
+ _putctlimage(b->light);
+ _putctlimage(b->pale);
+ _putctlimage(b->bordercolor);
+}
+
+static void
+buttonshow(Button *b)
+{
+ Rectangle r;
+
+ if (b->hidden)
+ return;
+ r = b->rect;
+ if(b->border > 0){
+ border(b->screen, r, b->border, b->bordercolor->image, ZP);
+ r = insetrect(b->rect, b->border);
+ }
+ draw(b->screen, r, b->image->image, nil, b->image->image->r.min);
+ if(b->off)
+ draw(b->screen, r, b->pale->image, b->mask->image, b->mask->image->r.min);
+ else if(b->pressed)
+ draw(b->screen, r, b->light->image, b->mask->image, b->mask->image->r.min);
+ b->lastshow = b->pressed;
+ flushimage(display, 1);
+}
+
+static void
+buttonmouse(Control *c, Mouse *m)
+{
+ Button *b;
+
+ b = (Button*)c;
+
+ if(m->buttons&7) {
+ if (ptinrect(m->xy,b->rect)) {
+ if (b->off) {
+ b->off = 0;
+ buttonshow(b);
+ }
+ } else {
+ if (!b->off) {
+ b->off = 1;
+ buttonshow(b);
+ }
+ }
+ }
+ if((m->buttons&7) != b->lastbut){
+ if(m->buttons & 7){
+ b->prepress = b->pressed;
+ if (b->pressed)
+ b->pressed = 0;
+ else
+ b->pressed = m->buttons & 7;
+ buttonshow(b);
+ }else /* generate event on button up */
+ if (ptinrect(m->xy,b->rect))
+ chanprint(b->event, b->format, b->name, b->pressed);
+ else {
+ b->off = 0;
+ b->pressed = b->prepress;
+ buttonshow(b);
+ }
+ }
+ b->lastbut = m->buttons & 7;
+}
+
+static void
+buttonctl(Control *c, CParse *cp)
+{
+ int cmd;
+ Rectangle r;
+ Button *b;
+
+ b = (Button*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", b->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(b, cp, 2);
+ b->align = _ctlalignment(cp->args[1]);
+ b->lastshow = -1; /* force redraw */
+ break;
+ case EBorder:
+ _ctlargcount(b, cp, 2);
+ b->border = cp->iargs[1];
+ b->lastshow = -1; /* force redraw */
+ break;
+ case EBordercolor:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->bordercolor, cp->args[1]);
+ b->lastshow = -1; /* force redraw */
+ break;
+ case EFocus:
+ /* ignore focus change */
+ break;
+ case EFormat:
+ _ctlargcount(b, cp, 2);
+ b->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(b, cp, 1);
+ b->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->image, cp->args[1]);
+ b->lastshow = -1; /* force redraw */
+ break;
+ case ELight:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->light, cp->args[1]);
+ b->lastshow = -1; /* force redraw */
+ break;
+ case EMask:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->mask, cp->args[1]);
+ b->lastshow = -1; /* force redraw */
+ break;
+ case EPale:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->pale, cp->args[1]);
+ b->lastshow = -1; /* force redraw */
+ break;
+ case ERect:
+ _ctlargcount(b, 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", b->name, cp->str);
+ b->rect = r;
+ b->lastshow = -1; /* force redraw */
+ break;
+ case EReveal:
+ _ctlargcount(b, cp, 1);
+ b->hidden = 0;
+ buttonshow(b);
+ break;
+ case EShow:
+ _ctlargcount(b, cp, 1);
+ buttonshow(b);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(b, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", b->name, cp->str);
+ b->size.min = r.min;
+ b->size.max = r.max;
+ break;
+ case EValue:
+ _ctlargcount(b, cp, 2);
+ if((cp->iargs[1]!=0) != b->pressed){
+ b->pressed ^= 1;
+ buttonshow(b);
+ }
+ break;
+ }
+}
+
+Control*
+createbutton(Controlset *cs, char *name)
+{
+ Button *b;
+ b = (Button*)_createctl(cs, "button", sizeof(Button), name);
+ b->image = _getctlimage("white");
+ b->mask = _getctlimage("opaque");
+ b->light = _getctlimage("yellow");
+ b->pale = _getctlimage("paleyellow");
+ b->bordercolor = _getctlimage("black");
+ b->format = ctlstrdup("%q: value %d");
+ b->lastshow = -1;
+ b->border = 0;
+ b->align = Aupperleft;
+ b->ctl = buttonctl;
+ b->mouse = buttonmouse;
+ b->key = nil;
+ b->exit = buttonfree;
+ b->off = 0;
+ b->prepress = 0;
+ return (Control*)b;
+}
diff --git a/sys/src/libcontrol/cache.c b/sys/src/libcontrol/cache.c
new file mode 100755
index 000000000..ab38d3084
--- /dev/null
+++ b/sys/src/libcontrol/cache.c
@@ -0,0 +1,168 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Cache Cache;
+
+struct Cache
+{
+ char *name;
+ CCache **cache;
+ int ncache;
+};
+
+static struct Cache imagecache = {"image"};
+static struct Cache fontcache = {"font"};
+
+static CCache*
+getcacheitem(Cache *c, char *name)
+{
+ int i;
+
+ for(i=0; i<c->ncache; i++)
+ if(c->cache[i]!=nil && strcmp(c->cache[i]->name, name)==0){
+ c->cache[i]->ref++;
+ return c->cache[i];
+ }
+ return nil;
+}
+
+static int
+namecacheitem(Cache *c, void *image, char *name)
+{
+ int i, free;
+ CCache *cc;
+
+ free = -1;
+ for(i=0; i<c->ncache; i++){
+ if(c->cache[i] == nil){
+ free = i;
+ continue;
+ }
+ if(strcmp(c->cache[i]->name, name) == 0){
+ werrstr("%s name %q already in use", c->name, name);
+ return -1;
+ }
+ }
+ cc = ctlmalloc(sizeof(CCache));
+ cc->image = image;
+ cc->name = ctlstrdup(name);
+ if(free >= 0){
+ cc->index = free;
+ c->cache[free] = cc;
+ }else{
+ cc->index = c->ncache;
+ c->cache = ctlrealloc(c->cache, (c->ncache+1)*sizeof(CCache*));
+ c->cache[c->ncache++] = cc;
+ }
+ cc->ref = 1;
+ return 1;
+}
+
+static int
+freecacheitem(Cache *c, char *name)
+{
+ CCache *cc;
+
+ cc = getcacheitem(c, name);
+ if(cc == nil){
+ werrstr("%s name %q not in use", c->name, name);
+ return -1;
+ }
+ cc->ref--; /* getcacheitem increments ref */
+ if(cc->ref-- == 1){
+ /* client must free object itself */
+ free(cc->name);
+ c->cache[cc->index] = nil;
+ free(cc);
+ }
+ return 0;
+}
+
+static void
+putcacheitem(CCache *cc)
+{
+ if(cc == nil)
+ return;
+ cc->ref--;
+}
+
+static void
+setcacheitemptr(Cache *c, Control *ctl, CCache **cp, char *s)
+{
+ CCache *ci;
+
+ ci = getcacheitem(c, s);
+ if(ci == nil)
+ ctlerror("%q: %s name %q not defined", ctl->name, c->name, s);
+ putcacheitem(*cp);
+ *cp = ci;
+}
+
+/* Images */
+
+CImage*
+_getctlimage(char *name)
+{
+ return getcacheitem(&imagecache, name);
+}
+
+void
+_putctlimage(CImage *c)
+{
+ putcacheitem(c);
+}
+
+int
+namectlimage(Image *image, char *name)
+{
+ return namecacheitem(&imagecache, image, name);
+}
+
+int
+freectlimage(char *name)
+{
+ return freecacheitem(&imagecache, name);
+}
+
+void
+_setctlimage(Control *c, CImage **cp, char *s)
+{
+ setcacheitemptr(&imagecache, c, cp, s);
+}
+
+/* Fonts */
+
+CFont*
+_getctlfont(char *name)
+{
+ return getcacheitem(&fontcache, name);
+}
+
+void
+_putctlfont(CFont *c)
+{
+ putcacheitem(c);
+}
+
+int
+namectlfont(Font *font, char *name)
+{
+ return namecacheitem(&fontcache, font, name);
+}
+
+int
+freectlfont(char *name)
+{
+ return freecacheitem(&fontcache, name);
+}
+
+void
+_setctlfont(Control *c, CFont **cp, char *s)
+{
+ setcacheitemptr(&fontcache, c, cp, s);
+}
diff --git a/sys/src/libcontrol/control.c b/sys/src/libcontrol/control.c
new file mode 100755
index 000000000..f30d6fe57
--- /dev/null
+++ b/sys/src/libcontrol/control.c
@@ -0,0 +1,809 @@
+#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 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:
+ _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]; 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;
+ 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;
+ 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;
+ 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);
+}
diff --git a/sys/src/libcontrol/entry.c b/sys/src/libcontrol/entry.c
new file mode 100755
index 000000000..0b786592d
--- /dev/null
+++ b/sys/src/libcontrol/entry.c
@@ -0,0 +1,354 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Entry Entry;
+
+struct Entry
+{
+ Control;
+ int border;
+ CFont *font;
+ CImage *image;
+ CImage *textcolor;
+ CImage *bordercolor;
+ Rune *text;
+ int ntext;
+ int cursor;
+ int align;
+ int hasfocus;
+ int lastbut;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EData,
+ EFocus,
+ EFont,
+ EFormat,
+ EHide,
+ EImage,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ ETextcolor,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EData] = "data",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [ETextcolor] = "textcolor",
+ [EValue] = "value",
+ nil
+};
+
+static void
+entryfree(Control *c)
+{
+ Entry *e;
+
+ e = (Entry *)c;
+ _putctlfont(e->font);
+ _putctlimage(e->image);
+ _putctlimage(e->textcolor);
+ _putctlimage(e->bordercolor);
+ free(e->text);
+}
+
+static Point
+entrypoint(Entry *e, int c)
+{
+ Point p;
+ Rectangle r;
+
+ r = e->rect;
+ if(e->border > 0)
+ r = insetrect(r, e->border);
+ p = _ctlalignpoint(r,
+ runestringnwidth(e->font->font, e->text, e->ntext),
+ e->font->font->height, e->align);
+ if(c > e->ntext)
+ c = e->ntext;
+ p.x += runestringnwidth(e->font->font, e->text, c);
+ return p;
+}
+
+static void
+entryshow(Entry *e)
+{
+ Rectangle r, dr;
+ Point p;
+
+ if (e->hidden)
+ return;
+ r = e->rect;
+ draw(e->screen, r, e->image->image, nil, e->image->image->r.min);
+ if(e->border > 0){
+ border(e->screen, r, e->border, e->bordercolor->image, e->bordercolor->image->r.min);
+ dr = insetrect(r, e->border);
+ }else
+ dr = r;
+ p = entrypoint(e, 0);
+ _string(e->screen, p, e->textcolor->image,
+ ZP, e->font->font, nil, e->text, e->ntext,
+ dr, nil, ZP, SoverD);
+ if(e->hasfocus){
+ p = entrypoint(e, e->cursor);
+ r.min = p;
+ r.max.x = p.x+1;
+ r.max.y = p.y+e->font->font->height;
+ if(rectclip(&r, dr))
+ draw(e->screen, r, e->textcolor->image, nil, ZP);
+ }
+ flushimage(display, 1);
+}
+
+static void
+entrysetpoint(Entry *e, Point cp)
+{
+ Point p;
+ int i;
+
+ if(!ptinrect(cp, insetrect(e->rect, e->border)))
+ return;
+ p = entrypoint(e, 0);
+ for(i=0; i<e->ntext; i++){
+ p.x += runestringnwidth(e->font->font, e->text+i, 1);
+ if(p.x > cp.x)
+ break;
+ }
+ e->cursor = i;
+ entryshow(e);
+}
+
+static void
+entrymouse(Control *c, Mouse *m)
+{
+ Entry *e;
+
+ e = (Entry*)c;
+ if(m->buttons==1 && e->lastbut==0)
+ entrysetpoint(e, m->xy);
+ e->lastbut = m->buttons;
+}
+
+static void
+entryctl(Control *c, CParse *cp)
+{
+ int cmd;
+ Rectangle r;
+ Entry *e;
+ Rune *rp;
+
+ e = (Entry*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", e->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(e, cp, 2);
+ e->align = _ctlalignment(cp->args[1]);
+ break;
+ case EBorder:
+ _ctlargcount(e, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", e->name, cp->str);
+ e->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(e, cp, 2);
+ _setctlimage(e, &e->bordercolor, cp->args[1]);
+ break;
+ case EData:
+ _ctlargcount(e, cp, 1);
+ chanprint(e->data, "%S", e->text);
+ break;
+ case EFocus:
+ _ctlargcount(e, cp, 2);
+ e->hasfocus = cp->iargs[1];
+ e->lastbut = 0;
+ entryshow(e);
+ break;
+ case EFont:
+ _ctlargcount(e, cp, 2);
+ _setctlfont(e, &e->font, cp->args[1]);
+ break;
+ case EFormat:
+ _ctlargcount(e, cp, 2);
+ e->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(e, cp, 1);
+ e->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(e, cp, 2);
+ _setctlimage(e, &e->image, cp->args[1]);
+ break;
+ case ERect:
+ _ctlargcount(e, 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", e->name, cp->str);
+ e->rect = r;
+ break;
+ case EReveal:
+ _ctlargcount(e, cp, 1);
+ e->hidden = 0;
+ entryshow(e);
+ break;
+ case EShow:
+ _ctlargcount(e, cp, 1);
+ entryshow(e);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(e, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", e->name, cp->str);
+ e->size.min = r.min;
+ e->size.max = r.max;
+ break;
+ case ETextcolor:
+ _ctlargcount(e, cp, 2);
+ _setctlimage(e, &e->textcolor, cp->args[1]);
+ break;
+ case EValue:
+ _ctlargcount(e, cp, 2);
+ rp = _ctlrunestr(cp->args[1]);
+ if(runestrcmp(rp, e->text) != 0){
+ free(e->text);
+ e->text = rp;
+ e->ntext = runestrlen(e->text);
+ e->cursor = e->ntext;
+ entryshow(e);
+ }else
+ free(rp);
+ break;
+ }
+}
+
+static void
+entrykey(Entry *e, Rune r)
+{
+ Rune *s;
+ int n;
+ char *p;
+
+ switch(r){
+ default:
+ e->text = ctlrealloc(e->text, (e->ntext+1+1)*sizeof(Rune));
+ memmove(e->text+e->cursor+1, e->text+e->cursor,
+ (e->ntext+1-e->cursor)*sizeof(Rune));
+ e->text[e->cursor++] = r;
+ e->ntext++;
+ break;
+ case L'\n': /* newline: return value */
+ p = _ctlstrrune(e->text);
+ chanprint(e->event, e->format, e->name, p);
+ free(p);
+ return;
+ case L'\b':
+ if(e->cursor > 0){
+ memmove(e->text+e->cursor-1, e->text+e->cursor,
+ (e->ntext+1-e->cursor)*sizeof(Rune));
+ e->cursor--;
+ e->ntext--;
+ }
+ break;
+ case Kright:
+ if(e->cursor < e->ntext)
+ e->cursor++;
+ break;
+ case Kleft:
+ if(e->cursor > 0)
+ e->cursor--;
+ break;
+ case 0x01: /* control A: beginning of line */
+ e->cursor = 0;
+ break;
+ case 0x05: /* control E: end of line */
+ e->cursor = e->ntext;
+ break;
+ case 0x15: /* control U: kill line */
+ e->cursor = 0;
+ e->ntext = 0;
+ break;
+ case 0x16: /* control V: paste (append snarf buffer) */
+ s = _ctlgetsnarf();
+ if(s != nil){
+ n = runestrlen(s);
+ e->text = ctlrealloc(e->text, (e->ntext+n+1)*sizeof(Rune));
+ memmove(e->text+e->cursor+n, e->text+e->cursor,
+ (e->ntext+1-e->cursor)*sizeof(Rune));
+ memmove(e->text+e->cursor, s, n*sizeof(Rune));
+ e->cursor += n;
+ e->ntext += n;
+ }
+ break;
+ }
+ e->text[e->ntext] = L'\0';
+}
+
+static void
+entrykeys(Control *c, Rune *rp)
+{
+ Entry *e;
+ int i;
+
+ e = (Entry *)c;
+ for(i=0; rp[i]!=L'\0'; i++)
+ entrykey(e, rp[i]);
+ entryshow(e);
+}
+
+Control*
+createentry(Controlset *cs, char *name)
+{
+ Entry *e;
+
+ e = (Entry*) _createctl(cs, "entry", sizeof(Entry), name);
+ e->text = ctlmalloc(sizeof(Rune));
+ e->ntext = 0;
+ e->image = _getctlimage("white");
+ e->textcolor = _getctlimage("black");
+ e->bordercolor = _getctlimage("black");
+ e->font = _getctlfont("font");
+ e->format = ctlstrdup("%q: value %q");
+ e->border = 0;
+ e->ctl = entryctl;
+ e->mouse = entrymouse;
+ e->key = entrykeys;
+ e->exit = entryfree;
+ return (Control *)e;
+}
diff --git a/sys/src/libcontrol/group.c b/sys/src/libcontrol/group.c
new file mode 100755
index 000000000..1d13f2f68
--- /dev/null
+++ b/sys/src/libcontrol/group.c
@@ -0,0 +1,737 @@
+#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);
+ }
+}
diff --git a/sys/src/libcontrol/group.h b/sys/src/libcontrol/group.h
new file mode 100755
index 000000000..b6a2fe7b8
--- /dev/null
+++ b/sys/src/libcontrol/group.h
@@ -0,0 +1,18 @@
+
+typedef struct Group Group;
+
+struct Group {
+ Control;
+ int lastbut;
+ int border;
+ int mansize; /* size was set manually */
+ int separation;
+ int selected;
+ int lastkid;
+ CImage *bordercolor;
+ CImage *image;
+ int nkids;
+ Control **kids; /* mallocated */
+ Rectangle *separators; /* mallocated */
+ int nseparators;
+};
diff --git a/sys/src/libcontrol/keyboard.c b/sys/src/libcontrol/keyboard.c
new file mode 100755
index 000000000..9bfc594e4
--- /dev/null
+++ b/sys/src/libcontrol/keyboard.c
@@ -0,0 +1,536 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Keyboard Keyboard;
+
+enum{
+ SRegular = 0,
+ SShift = 1,
+ SCaps = 2,
+ SMask = 3,
+ Nstate = 4,
+ SControl = 4,
+};
+
+struct Keyboard
+{
+ Control;
+ CImage *image;
+ CImage *mask;
+ CImage *light;
+ CImage *textcolor;
+ CImage *bordercolor;
+ CFont *font;
+ CFont *ctlfont;
+ Image *im[Nstate];
+ int border;
+ int lastbut;
+ int state;
+ char *key;
+};
+
+enum{
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFont,
+ EFormat,
+ EHide,
+ EImage,
+ ELight,
+ EMask,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+};
+
+static char *cmds[] = {
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ELight] = "light",
+ [EMask] = "mask",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ nil
+};
+
+enum
+{
+ Nrow = 5
+};
+
+static uchar wid [Nrow][16] = {
+ {16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 30, },
+ {24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, },
+ {32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, },
+ {40, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, },
+ {30, 30, 80, 40, 42, 24, },
+};
+
+static char *keyregular[Nrow] = {
+ "`\0001\0002\0003\0004\0005\0006\0007\0008\0009\0000\0-\0=\0\\\0<-\0\0",
+ "->\0q\0w\0e\0r\0t\0y\0u\0i\0o\0p\0[\0]\0Del\0\0",
+ "Caps\0a\0s\0d\0f\0g\0h\0j\0k\0l\0;\0'\0Enter\0\0",
+ "Shift\0z\0x\0c\0v\0b\0n\0m\0,\0.\0/\0Shift\0\0",
+ "Ctrl\0Alt\0 \0Scrib\0Menu\0Esc\0\0"
+};
+
+static char *keyshift[Nrow] = {
+ "~\0!\0@\0#\0$\0%\0^\0&\0*\0(\0)\0_\0+\0|\0<-\0\0",
+ "->\0Q\0W\0E\0R\0T\0Y\0U\0I\0O\0P\0{\0}\0Del\0\0",
+ "Caps\0A\0S\0D\0F\0G\0H\0J\0K\0L\0:\0\"\0Enter\0\0",
+ "Shift\0Z\0X\0C\0V\0B\0N\0M\0<\0>\0?\0Shift\0\0",
+ "Ctrl\0Alt\0 \0Scrib\0Menu\0Esc\0\0"
+};
+
+static char *keycaps[Nrow] = {
+ "`\0001\0002\0003\0004\0005\0006\0007\0008\0009\0000\0-\0=\0\\\0<-\0\0",
+ "->\0Q\0W\0E\0R\0T\0Y\0U\0I\0O\0P\0[\0]\0Del\0\0",
+ "Caps\0A\0S\0D\0F\0G\0H\0J\0K\0L\0;\0'\0Enter\0\0",
+ "Shift\0Z\0X\0C\0V\0B\0N\0M\0,\0.\0/\0Shift\0\0",
+ "Ctrl\0Alt\0 \0Scrib\0Menu\0Esc\0\0"
+};
+
+static char *keycapsshift[Nrow] = {
+ "~\0!\0@\0#\0$\0%\0^\0&\0*\0(\0)\0_\0+\0|\0<-\0\0",
+ "->\0q\0w\0e\0r\0t\0y\0u\0i\0o\0p\0{\0}\0Del\0\0",
+ "Caps\0a\0s\0d\0f\0g\0h\0j\0k\0l\0:\0\"\0Enter\0\0",
+ "Shift\0z\0x\0c\0v\0b\0n\0m\0<\0>\0?\0Shift\0\0",
+ "Ctrl\0Alt\0 \0Scrib\0Menu\0Esc\0\0"
+};
+
+struct{
+ char *name;
+ int val;
+}keytab[] = {
+ "Shift", 0,
+ "Ctrl", 0,
+ "Alt", 0,
+ "Caps", 0,
+ "Del", '\177',
+ "Enter", '\n',
+ "Esc", '\033',
+ "<-", '\b',
+ "->", '\t',
+ "Scrib", 0x10000,
+ "Menu", 0x10001,
+ nil, 0,
+};
+
+static char **keyset[Nstate] = {
+ keyregular,
+ keyshift,
+ keycaps,
+ keycapsshift,
+};
+
+static void keyboardshow(Keyboard*);
+static void keyup(Keyboard*, Point);
+static void keydown(Keyboard*, Point);
+static void keyresize(Keyboard*);
+
+static void
+keyboardmouse(Control *c, Mouse *m)
+{
+ Keyboard *k;
+
+ k = (Keyboard *)c;
+ if(m->buttons==1)
+ keydown(k, m->xy);
+ else if(k->lastbut==1 && m->buttons==0)
+ keyup(k, m->xy);
+ k->lastbut = m->buttons;
+}
+
+static void
+keyboardfree(Control *c)
+{
+ int i;
+ Keyboard *k;
+
+ k = (Keyboard *)c;
+ _putctlimage(k->image);
+ _putctlimage(k->mask);
+ _putctlimage(k->light);
+ _putctlimage(k->textcolor);
+ _putctlimage(k->bordercolor);
+ _putctlfont(k->font);
+ _putctlfont(k->ctlfont);
+ for(i=0; i<nelem(k->im); i++)
+ freeimage(k->im[i]);
+ free(k->format);
+}
+
+static int
+keyboardy(Keyboard *k, int row)
+{
+ int dy;
+
+ if(row >= Nrow)
+ return k->rect.max.y-k->border;
+ dy = Dy(k->rect)-2*k->border;
+ return k->rect.min.y+k->border+(row*dy+Nrow-1)/Nrow;
+}
+
+static char*
+whichkey(Keyboard *k, Point p, int *rowp, int *colp, Rectangle *rp)
+{
+ uchar *wp;
+ char *kp;
+ int row, col, dx, dy, x, n, maxx;
+ Rectangle r;
+
+ r = insetrect(k->rect, k->border);
+ if(!ptinrect(p, r))
+ return nil;
+ maxx = r.max.x;
+ dx = Dx(r);
+ dy = Dy(r);
+ row = (p.y - r.min.y)*Nrow/dy;
+ if(row >= Nrow)
+ row = Nrow-1;
+ r.min.y = keyboardy(k, row);
+ r.max.y = keyboardy(k, row+1);
+ x = r.min.x;
+ kp = keyset[k->state&SMask][row];
+ wp = wid[row];
+ for(col=0; *kp; col++,kp+=n+1){
+ n = strlen(kp);
+ r.min.x = x;
+ r.max.x = x + (wp[col]*dx+255)/256;
+ if(kp[n+1] == '\0')
+ r.max.x = maxx;
+ if(r.max.x > p.x)
+ break;
+ x = r.max.x;
+ }
+ *rp = insetrect(r, 1);
+ *rowp = row;
+ *colp = col;
+ return kp;
+}
+
+static Rectangle
+keyrect(Keyboard *k, int row, int col)
+{
+ uchar *wp;
+ char *kp;
+ int i, x, n, dx;
+ Rectangle r;
+ Point p;
+
+ r = insetrect(k->rect, k->border);
+ p = r.min;
+ dx = Dx(r);
+ r.min.y = keyboardy(k, row);
+ r.max.y = keyboardy(k, row+1);
+ x = r.min.x;
+ kp = keyset[0][row];
+ wp = wid[row];
+ for(i=0; *kp; i++,kp+=n+1){
+ n = strlen(kp);
+ r.min.x = x;
+ r.max.x = x + (wp[i]*dx+255)/256;
+ if(kp[n+1] == '\0')
+ r.max.x = p.x+dx;
+ if(i >= col)
+ break;
+ x = r.max.x;
+ }
+ return insetrect(r, 1);
+}
+
+static void
+keydraw(Keyboard *k, int state)
+{
+ Point p, q;
+ int row, col, x, dx, dy, nexty, n;
+ uchar *wp;
+ char *kp;
+ Rectangle r;
+ Font *f, *f1, *f2;
+ Image *im;
+
+ freeimage(k->im[state]);
+ k->im[state] = nil;
+ if(Dx(k->rect)-2*k->border <= 0)
+ return;
+
+ im = allocimage(display, k->rect, screen->chan, 0, ~0);
+ if(im == nil)
+ return;
+ k->im[state] = im;
+
+ r = insetrect(k->rect, k->border);
+ border(im, k->rect, k->border, k->bordercolor->image, ZP);
+ draw(im, r, k->image->image, nil, ZP);
+ dx = Dx(r);
+ dy = Dy(r);
+ p = r.min;
+ f1 = k->font->font;
+ f2 = k->ctlfont->font;
+ nexty = p.y;
+ for(row=0; row<Nrow; row++){
+ x = p.x;
+ kp = keyset[state][row];
+ wp = wid[row];
+ r.min.y = nexty;
+ nexty = keyboardy(k, row+1);
+ r.max.y = nexty;
+ for(col=0; *kp; col++,kp+=n+1){
+ r.min.x = x;
+ r.max.x = x + (wp[col]*dx+255)/256;
+ n = strlen(kp);
+ if(kp[n+1] == '\0')
+ r.max.x = p.x+dx;
+ if(row == Nrow-1)
+ r.max.y = p.y+dy;
+ if(n > 1)
+ f = f2;
+ else
+ f = f1;
+ q = _ctlalignpoint(r, stringnwidth(f, kp, n), f->height, Acenter);
+ _string(im, q, k->textcolor->image,
+ ZP, f, kp, nil, n, r,
+ nil, ZP, SoverD);
+ x = r.max.x;
+ if(kp[n+1])
+ draw(im, Rect(x, r.min.y, x+1, r.max.y),
+ k->textcolor->image, nil, ZP);
+ }
+ if(row != Nrow-1)
+ draw(im, Rect(p.x, r.max.y, p.x+dx, r.max.y+1),
+ k->textcolor->image, nil, ZP);
+ }
+}
+
+static void
+keyresize(Keyboard *k)
+{
+ int i;
+
+ for(i=0; i<Nstate; i++)
+ keydraw(k, i);
+}
+
+static void
+keyboardshow(Keyboard *k)
+{
+ Rectangle r;
+
+ if (k->hidden)
+ return;
+ if(k->im[0]==nil || !eqrect(k->im[0]->r, k->rect))
+ keyresize(k);
+ if(k->im[k->state&SMask] == nil)
+ return;
+ draw(k->screen, k->rect, k->im[k->state&SMask], nil, k->rect.min);
+ if(k->state & SShift){
+ r = keyrect(k, 3, 0);
+ draw(k->screen, r, k->light->image, k->mask->image, ZP);
+ r = keyrect(k, 3, 11);
+ draw(k->screen, r, k->light->image, k->mask->image, ZP);
+ }
+ if(k->state & SCaps){
+ r = keyrect(k, 2, 0);
+ draw(k->screen, r, k->light->image, k->mask->image, ZP);
+ }
+ if(k->state & SControl){
+ r = keyrect(k, 4, 0);
+ draw(k->screen, r, k->light->image, k->mask->image, ZP);
+ }
+ flushimage(display, 1);
+}
+
+static void
+keydown(Keyboard *k, Point p)
+{
+ int row, col;
+ Rectangle r;
+ char *s;
+
+ s = whichkey(k, p, &row, &col, &r);
+ if(s == k->key)
+ return;
+ keyboardshow(k);
+ if(s != nil)
+ draw(k->screen, r, k->light->image, k->mask->image, ZP);
+ flushimage(display, 1);
+ k->key = s;
+}
+
+static int
+keylookup(char *s)
+{
+ int i;
+
+ for(i=0; keytab[i].name; i++)
+ if(strcmp(s, keytab[i].name) == 0)
+ return keytab[i].val;
+ return s[0];
+}
+
+static void
+keyup(Keyboard *k, Point p)
+{
+ int row, col;
+ Rectangle r;
+ char *s;
+ int val;
+
+ s = whichkey(k, p, &row, &col, &r);
+ if(s == nil)
+ return;
+ val = keylookup(s);
+ if(k->state & SControl)
+ if(' '<val && val<0177)
+ val &= ~0x60;
+ if(strcmp(s, "Alt") == 0)
+ {;}
+ if(strcmp(s, "Ctrl") == 0){
+ k->state ^= SControl;
+ }else
+ k->state &= ~SControl;
+ if(strcmp(s, "Shift")==0 || strcmp(s, "Caps")==0){
+ if(strcmp(s, "Shift") == 0)
+ k->state ^= SShift;
+ if(strcmp(s, "Caps") == 0)
+ k->state ^= SCaps;
+ }else
+ k->state &= ~SShift;
+ keyboardshow(k);
+ if(val)
+ chanprint(k->event, k->format, k->name, val);
+ k->key = nil;
+}
+
+static void
+keyboardctl(Control *c, CParse *cp)
+{
+ int cmd;
+ Rectangle r;
+ Keyboard *k;
+
+ k = (Keyboard*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", k->name, cp->str);
+ break;
+ case EBorder:
+ _ctlargcount(k, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", k->name, cp->str);
+ k->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(k, cp, 2);
+ _setctlimage(k, &k->bordercolor, cp->args[1]);
+ break;
+ case EFocus:
+ /* ignore focus change */
+ break;
+ case EFont:
+ if(cp->nargs!=2 && cp->nargs!=3)
+ ctlerror("%q: bad font message '%s'", k->name, cp->str);
+ _setctlfont(k, &k->font, cp->args[1]);
+ if(cp->nargs == 3)
+ _setctlfont(k, &k->ctlfont, cp->args[2]);
+ else
+ _setctlfont(k, &k->ctlfont, cp->args[1]);
+ break;
+ case EFormat:
+ _ctlargcount(k, cp, 2);
+ k->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(k, cp, 1);
+ k->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(k, cp, 2);
+ _setctlimage(k, &k->image, cp->args[1]);
+ break;
+ case ELight:
+ _ctlargcount(k, cp, 2);
+ _setctlimage(k, &k->light, cp->args[1]);
+ break;
+ case EMask:
+ _ctlargcount(k, cp, 2);
+ _setctlimage(k, &k->mask, cp->args[1]);
+ break;
+ case ERect:
+ _ctlargcount(k, 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", k->name, cp->str);
+ k->rect = r;
+ keyboardshow(k);
+ break;
+ case EReveal:
+ _ctlargcount(k, cp, 1);
+ k->hidden = 0;
+ keyboardshow(k);
+ break;
+ case EShow:
+ _ctlargcount(k, cp, 1);
+ keyboardshow(k);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(k, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", k->name, cp->str);
+ k->size.min = r.min;
+ k->size.max = r.max;
+ break;
+ }
+}
+
+Control*
+createkeyboard(Controlset *cs, char *name)
+{
+ Keyboard *k;
+
+ k = (Keyboard *)_createctl(cs, "keyboard", sizeof(Keyboard), name);
+ k->image = _getctlimage("white");
+ k->mask = _getctlimage("opaque");
+ k->light = _getctlimage("yellow");
+ k->bordercolor = _getctlimage("black");
+ k->textcolor = _getctlimage("black");
+ k->font = _getctlfont("font");
+ k->ctlfont = _getctlfont("font");
+ k->format = ctlstrdup("%q: value 0x%x");
+ k->border = 0;
+ k->lastbut = 0;
+ k->key = nil;
+ k->state = SRegular;
+ k->ctl = keyboardctl;
+ k->mouse = keyboardmouse;
+ k->exit = keyboardfree;
+ k->size = Rect(246, 2 + 5 * (k->font->font->height + 1), 512, 256);
+ return k;
+}
diff --git a/sys/src/libcontrol/label.c b/sys/src/libcontrol/label.c
new file mode 100755
index 000000000..d924f6dd9
--- /dev/null
+++ b/sys/src/libcontrol/label.c
@@ -0,0 +1,204 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Label Label;
+
+struct Label
+{
+ Control;
+ int border;
+ CFont *font;
+ CImage *image;
+ CImage *textcolor;
+ CImage *bordercolor;
+ char *text;
+ int align;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFont,
+ EHide,
+ EImage,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ ETextcolor,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [ETextcolor] = "textcolor",
+ [EValue] = "value",
+ nil
+};
+
+static void labelshow(Label*);
+
+static void
+labelfree(Control *c)
+{
+ Label *l;
+
+ l = (Label*)c;
+ _putctlfont(l->font);
+ _putctlimage(l->image);
+ _putctlimage(l->textcolor);
+ _putctlimage(l->bordercolor);
+}
+
+
+static void
+labelshow(Label *l)
+{
+ Rectangle r;
+ Point p;
+
+ if (l->hidden)
+ return;
+ r = l->rect;
+ draw(l->screen, r, l->image->image, nil, l->image->image->r.min);
+ if(l->border > 0){
+ border(l->screen, r, l->border, l->bordercolor->image, l->bordercolor->image->r.min);
+ r = insetrect(r, l->border);
+ }
+ p = _ctlalignpoint(r,
+ stringwidth(l->font->font, l->text),
+ l->font->font->height, l->align);
+ _string(l->screen, p, l->textcolor->image,
+ ZP, l->font->font, l->text, nil, strlen(l->text),
+ r, nil, ZP, SoverD);
+ flushimage(display, 1);
+}
+
+static void
+labelctl(Control *c, CParse *cp)
+{
+ int cmd;
+ Rectangle r;
+ Label *l;
+
+ l = (Label*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", l->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(l, cp, 2);
+ l->align = _ctlalignment(cp->args[1]);
+ break;
+ case EBorder:
+ _ctlargcount(l, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", l->name, cp->str);
+ l->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(l, cp, 2);
+ _setctlimage(l, &l->bordercolor, cp->args[1]);
+ break;
+ case EFocus:
+ /* ignore focus change */
+ break;
+ case EFont:
+ _ctlargcount(l, cp, 2);
+ _setctlfont(l, &l->font, cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(l, cp, 1);
+ l->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(l, cp, 2);
+ _setctlimage(l, &l->image, cp->args[1]);
+ break;
+ case ERect:
+ _ctlargcount(l, 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", l->name, cp->str);
+ l->rect = r;
+ break;
+ case EReveal:
+ _ctlargcount(l, cp, 1);
+ l->hidden = 0;
+ labelshow(l);
+ break;
+ case EShow:
+ _ctlargcount(l, cp, 1);
+ labelshow(l);
+ /*
+ _ctlargcount(l, cp, 2);
+ _setctlimage(l, &l->textcolor, cp->args[1]);
+ */
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(l, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", l->name, cp->str);
+ l->size.min = r.min;
+ l->size.max = r.max;
+ break;
+ case ETextcolor:
+ _ctlargcount(l, cp, 2);
+ _setctlimage(l, &l->textcolor, cp->args[1]);
+ break;
+ case EValue:
+ _ctlargcount(l, cp, 2);
+ if(strcmp(cp->args[1], l->text) != 0){
+ free(l->text);
+ l->text = ctlstrdup(cp->args[1]);
+ labelshow(l);
+ }
+ break;
+ }
+}
+
+Control*
+createlabel(Controlset *cs, char *name)
+{
+ Label *l;
+
+ l = (Label*)_createctl(cs, "label", sizeof(Label), name);
+ l->text = ctlstrdup("");
+ l->image = _getctlimage("white");
+ l->textcolor = _getctlimage("black");
+ l->bordercolor = _getctlimage("black");
+ l->font = _getctlfont("font");
+ l->border = 0;
+ l->ctl = labelctl;
+ l->exit = labelfree;
+ return (Control *)l;
+}
diff --git a/sys/src/libcontrol/menu.c b/sys/src/libcontrol/menu.c
new file mode 100755
index 000000000..56b915a4c
--- /dev/null
+++ b/sys/src/libcontrol/menu.c
@@ -0,0 +1,366 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Menu0 Menu0; /* Menu is taken by mouse.h */
+
+struct Menu0
+{
+ Control;
+ CImage *image;
+ CImage *bordercolor;
+ CImage *textcolor;
+ CImage *selectcolor;
+ CImage *selecttextcolor;
+ CFont *font;
+ char **line;
+ int nline;
+ int border;
+ int align;
+ Image *window;
+ int visible; /* state of menu */
+ int selection; /* currently selected line; -1 == none */
+ int prevsel; /* previous selection */
+ int lastbut; /* previous state of mouse button */
+};
+
+enum{
+ EAdd,
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFont,
+ EFormat,
+ EHide,
+ EImage,
+ ERect,
+ EReveal,
+ ESelectcolor,
+ ESelecttextcolor,
+ EShow,
+ ESize,
+ ETextcolor,
+ EWindow,
+};
+
+static char *cmds[] = {
+ [EAdd] = "add",
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [ESelectcolor] = "selectcolor",
+ [ESelecttextcolor] = "selecttextcolor",
+ [EShow] = "show",
+ [ESize] = "size",
+ [ETextcolor] = "textcolor",
+ [EWindow] = "window",
+ nil
+};
+
+static void menushow(Menu0*);
+static void menuhide(Menu0*);
+
+static void
+menufree(Control *c)
+{
+ Menu0 *m;
+
+ m = (Menu0*)c;
+ _putctlfont(m->font);
+ _putctlimage(m->image);
+ _putctlimage(m->textcolor);
+ _putctlimage(m->bordercolor);
+ _putctlimage(m->selectcolor);
+ _putctlimage(m->selecttextcolor);
+}
+
+static void
+menushow(Menu0 *m)
+{
+ Rectangle r, clipr;
+ int i, dx, dy, w;
+ Font *f;
+ Point p, q;
+ Image *im, *c;
+
+ if(m->hidden || m->window == nil)
+ return;
+
+ m->visible = 1;
+ f = m->font->font;
+ draw(m->window, m->rect, m->image->image, nil, m->image->image->r.min);
+ if(m->border > 0)
+ border(m->window, m->rect, m->border, m->bordercolor->image, ZP);
+ /* text goes here */
+ dx = 0;
+ for(i=0; i<m->nline; i++){
+ w = stringwidth(f, m->line[i]);
+ if(dx < w)
+ dx = w;
+ }
+ dy = m->nline*f->height;
+ clipr = insetrect(m->rect, m->border);
+ p = _ctlalignpoint(clipr, dx, dy, m->align);
+ im = m->textcolor->image;
+// if(m->pressed)
+// im = m->pressedtextcolor->image;
+ for(i=0; i<m->nline; i++){
+ r.min = p;
+ r.max.x = p.x+dx;
+ r.max.y = p.y+f->height;
+ c = im;
+ if(i == m->selection){
+ draw(m->window, r, m->selectcolor->image, nil, ZP);
+ c = m->selecttextcolor->image;
+ }
+ q = _ctlalignpoint(r, stringwidth(f, m->line[i]), f->height, m->align%3);
+ _string(m->window, q, c,
+ ZP, f, m->line[i], nil, strlen(m->line[i]),
+ clipr, nil, ZP, SoverD);
+ p.y += f->height;
+ }
+// if(m->pressed)
+// draw(m->screen, m->rect, m->lighm->image, m->mask->image, m->mask->image->r.min);
+ flushimage(display, 1);
+}
+
+static Point
+menusize(Menu0 *m)
+{
+ int x, y;
+ int i;
+ Point p;
+ Font *f;
+
+ x = 0;
+ y = 0;
+ f = m->font->font;
+ for(i=0; i<m->nline; i++){
+ p = stringsize(f, m->line[i]);
+ if(p.x > x)
+ x = p.x;
+ y += f->height;
+ }
+
+ return Pt(x+2*m->border, y+2*m->border);
+}
+
+static void
+menuhide(Menu0 *m)
+{
+ freeimage(m->window);
+ m->window = nil;
+ m->rect.max.y = m->rect.min.y; /* go to zero size */
+ m->lastbut = 0;
+ m->visible = 0;
+ if(m->selection >= 0)
+ m->prevsel = m->selection;
+ m->selection = -1;
+ _ctlfocus(m, 0);
+}
+
+static void
+menutrack(Control *c, Mouse *ms)
+{
+ Rectangle r;
+ int s;
+ Menu0 *m;
+
+ m = (Menu0*)c;
+ if(m->window == nil)
+ return;
+ if(m->lastbut && ms->buttons==0){ /* menu was released */
+ chanprint(m->event, "%q: value %d", m->name, m->selection);
+ menuhide(m);
+ return;
+ }
+ m->lastbut = ms->buttons;
+ r = insetrect(m->rect, m->border);
+ if(!ptinrect(ms->xy, r))
+ s = -1;
+ else{
+ s = (ms->xy.y - r.min.y)/m->font->font->height;
+ if(s < 0 || s >= m->nline)
+ s = -1;
+ }
+ if(m->visible== 0 || s!=m->selection){
+ m->selection = s;
+ menushow(m);
+ }
+}
+
+static void
+menuctl(Control *c, CParse *cp)
+{
+ int up, cmd, h;
+ Rectangle r;
+ Menu0 *m;
+ Point diag;
+
+ m = (Menu0*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", m->name, cp->str);
+ break;
+ case EAdd:
+ _ctlargcount(m, cp, 2);
+ m->line = ctlrealloc(m->line, (m->nline+1)*sizeof(char*));
+ m->line[m->nline++] = ctlstrdup(cp->args[1]);
+ menushow(m);
+ break;
+ case EAlign:
+ _ctlargcount(m, cp, 2);
+ m->align = _ctlalignment(cp->args[1]);
+ menushow(m);
+ break;
+ case EBorder:
+ _ctlargcount(m, cp, 2);
+ m->border = cp->iargs[1];
+ menushow(m);
+ break;
+ case EBordercolor:
+ _ctlargcount(m, cp, 2);
+ _setctlimage(m, &m->bordercolor, cp->args[1]);
+ menushow(m);
+ break;
+ case EFocus:
+ _ctlargcount(m, cp, 2);
+ if(atoi(cp->args[1]) == 0)
+ menuhide(m);
+ break;
+ case EFont:
+ _ctlargcount(m, cp, 2);
+ _setctlfont(m, &m->font, cp->args[1]);
+ break;
+ case EFormat:
+ _ctlargcount(m, cp, 2);
+ m->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(m, cp, 1);
+ m->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(m, cp, 2);
+ _setctlimage(m, &m->image, cp->args[1]);
+ menushow(m);
+ break;
+ case ERect:
+ _ctlargcount(m, 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", m->name, cp->str);
+ m->rect = r;
+ menushow(m);
+ break;
+ case EReveal:
+ _ctlargcount(m, cp, 1);
+ m->hidden = 0;
+ menushow(m);
+ break;
+ case ESelectcolor:
+ _ctlargcount(m, cp, 2);
+ _setctlimage(m, &m->selectcolor, cp->args[1]);
+ menushow(m);
+ break;
+ case ESelecttextcolor:
+ _ctlargcount(m, cp, 2);
+ _setctlimage(m, &m->selecttextcolor, cp->args[1]);
+ menushow(m);
+ break;
+ case EShow:
+ _ctlargcount(m, cp, 1);
+ menushow(m);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(m, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", m->name, cp->str);
+ m->size.min = r.min;
+ m->size.max = r.max;
+ break;
+ case ETextcolor:
+ _ctlargcount(m, cp, 2);
+ _setctlimage(m, &m->textcolor, cp->args[1]);
+ menushow(m);
+ break;
+ case EWindow:
+ /* no args == toggle; otherwise 0 or 1 for state of window */
+ if(cp->nargs >= 2)
+ up = cp->iargs[1];
+ else
+ up = (m->window == nil);
+ if(!up){ /* take window down */
+ if(m->window)
+ menuhide(m);
+ break;
+ }
+ if(m->window != nil)
+ break;
+ diag = menusize(m);
+ m->rect.max.x = m->rect.min.x + diag.x;
+ m->rect.max.y = m->rect.min.y + diag.y;
+ m->window = allocwindow(_screen, m->rect, Refbackup, DWhite);
+ if(m->window == nil)
+ m->window = m->screen;
+ up = m->prevsel;
+ if(up<0 || up>=m->nline)
+ up = 0;
+ m->selection = up;
+ menushow(m);
+ h = m->font->font->height;
+ moveto(m->controlset->mousectl,
+ Pt(m->rect.min.x+Dx(m->rect)/2, m->rect.min.y+up*h+h/2));
+// _ctlfocus(m, 1);
+ break;
+ }
+}
+
+Control*
+createmenu(Controlset *cs, char *name)
+{
+ Menu0 *m;
+
+ m = (Menu0*)_createctl(cs, "menu", sizeof(Menu0), name);
+ m->font = _getctlfont("font");
+ m->image = _getctlimage("white");
+ m->textcolor = _getctlimage("black");
+ m->selectcolor = _getctlimage("yellow");
+ m->selecttextcolor = _getctlimage("black");
+ m->bordercolor = _getctlimage("black");
+ m->format = ctlstrdup("%q: value %d");
+ m->border = 0;
+ m->align = Aupperleft;
+ m->visible = 0;
+ m->window = nil;
+ m->lastbut = 0;
+ m->selection = -1;
+ m->mouse = menutrack;
+ m->ctl = menuctl;
+ m->exit = menufree;
+ return (Control *)m;
+}
diff --git a/sys/src/libcontrol/mkfile b/sys/src/libcontrol/mkfile
new file mode 100755
index 000000000..f86d47229
--- /dev/null
+++ b/sys/src/libcontrol/mkfile
@@ -0,0 +1,37 @@
+</$objtype/mkfile
+
+LIB=/$objtype/lib/libcontrol.a
+
+OFILES=\
+ box.$O\
+ button.$O\
+ cache.$O\
+ control.$O\
+ entry.$O\
+ group.$O\
+ keyboard.$O\
+ label.$O\
+ menu.$O\
+ radiobutton.$O\
+ scribble.$O\
+ slider.$O\
+ tabs.$O\
+ text.$O\
+ textbutton.$O\
+ textbutton3.$O\
+
+HFILES=/sys/include/draw.h\
+ /sys/include/mouse.h\
+ /sys/include/keyboard.h\
+ /sys/include/control.h\
+ group.h
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${LIB:/$objtype/%=/386/%}\
+
+</sys/src/cmd/mksyslib
+
+$OFILES: $HFILES
diff --git a/sys/src/libcontrol/radiobutton.c b/sys/src/libcontrol/radiobutton.c
new file mode 100755
index 000000000..133730a50
--- /dev/null
+++ b/sys/src/libcontrol/radiobutton.c
@@ -0,0 +1,192 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Radio Radio;
+
+struct Radio
+{
+ Control;
+ int value;
+ int lastbut;
+ Control **buttons;
+ int nbuttons;
+ char *eventstr;
+};
+
+enum{
+ EAdd,
+ EButton,
+ EFocus,
+ EFormat,
+ EHide,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAdd] = "add",
+ [EButton] = "button",
+ [EFocus] = "focus",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [EValue] = "value",
+ nil
+};
+
+static void radioshow(Radio*);
+static void radiofree(Radio*);
+
+static void
+radiomouse(Control *c, Mouse *m)
+{
+ Radio *r;
+ int i;
+
+ r = (Radio*)c;
+ for(i=0; i<r->nbuttons; i++)
+ if(ptinrect(m->xy, r->buttons[i]->rect) && r->buttons[i]->mouse){
+ (r->buttons[i]->mouse)(r->buttons[i], m);
+ break;
+ }
+}
+
+static void
+radiofree(Radio*)
+{
+}
+
+static void
+radioshow(Radio *r)
+{
+ int i;
+
+ if (r->hidden)
+ return;
+ for(i=0; i<r->nbuttons; i++){
+ _ctlprint(r->buttons[i], "value %d", (r->value==i));
+ _ctlprint(r->buttons[i], "show");
+ }
+}
+
+static void
+radioctl(Control *c, CParse *cp)
+{
+ int cmd, i;
+ Rectangle rect;
+ Radio *r;
+ char fmt[256];
+
+ r = (Radio*)c;
+
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", r->name, cp->str);
+ break;
+ case EAdd:
+ _ctlargcount(r, cp, 2);
+ c = controlcalled(cp->args[1]);
+ if(c == nil)
+ ctlerror("%q: can't add %s: %r", r->name, cp->args[1]);
+ snprint(fmt, sizeof fmt, "%%q: %q button %%d", r->name);
+ _ctlprint(c, "format %q", fmt);
+ controlwire(c, "event", c->controlset->ctl);
+ r->buttons = ctlrealloc(r->buttons, (r->nbuttons+1)*sizeof(Control*));
+ r->buttons[r->nbuttons] = c;
+ r->nbuttons++;
+ r->value = -1;
+ radioshow(r);
+ break;
+ case EButton:
+ if (cp->sender == nil)
+ ctlerror("%q: senderless buttonevent: %q", r->name, cp->str);
+ c = controlcalled(cp->sender);
+ for(i=0; i<r->nbuttons; i++)
+ if (c == r->buttons[i])
+ break;
+ if (i == r->nbuttons)
+ ctlerror("%q: not my event: %q", r->name, cp->str);
+ if(cp->iargs[1] == 0){
+ /* button was turned off; turn it back on */
+ _ctlprint(c, "value 1");
+ }else{
+ r->value = i;
+ chanprint(r->event, r->format, r->name, i);
+ radioshow(r);
+ }
+ break;
+ case EFormat:
+ _ctlargcount(r, cp, 2);
+ r->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(r, cp, 1);
+ r->hidden = 1;
+ break;
+ case EFocus:
+ /* ignore focus change */
+ break;
+ case ERect:
+ _ctlargcount(r, cp, 5);
+ rect.min.x = cp->iargs[1];
+ rect.min.y = cp->iargs[2];
+ rect.max.x = cp->iargs[3];
+ rect.max.y = cp->iargs[4];
+ r->rect = rect;
+ break;
+ case EReveal:
+ _ctlargcount(r, cp, 1);
+ r->hidden = 0;
+ radioshow(r);
+ break;
+ case EShow:
+ _ctlargcount(r, cp, 1);
+ radioshow(r);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ rect.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(r, cp, 5);
+ rect.max.x = cp->iargs[3];
+ rect.max.y = cp->iargs[4];
+ }
+ rect.min.x = cp->iargs[1];
+ rect.min.y = cp->iargs[2];
+ if(rect.min.x<=0 || rect.min.y<=0 || rect.max.x<=0 || rect.max.y<=0 || rect.max.x < rect.min.x || rect.max.y < rect.min.y)
+ ctlerror("%q: bad sizes: %s", r->name, cp->str);
+ r->size.min = rect.min;
+ r->size.max = rect.max;
+ break;
+ case EValue:
+ _ctlargcount(r, cp, 2);
+ r->value = cp->iargs[1];
+ radioshow(r);
+ break;
+ }
+}
+
+Control*
+createradiobutton(Controlset *cs, char *name)
+{
+ Radio *r;
+
+ r = (Radio*)_createctl(cs, "label", sizeof(Radio), name);
+ r->format = ctlstrdup("%q: value %d");
+ r->value = -1; /* nobody set at first */
+ r->mouse = radiomouse;
+ r->ctl = radioctl;
+ return (Control*)r;
+}
diff --git a/sys/src/libcontrol/scribble.c b/sys/src/libcontrol/scribble.c
new file mode 100755
index 000000000..4e10ab68f
--- /dev/null
+++ b/sys/src/libcontrol/scribble.c
@@ -0,0 +1,325 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+#include <scribble.h>
+
+typedef struct Scrib Scrib;
+
+struct Scrib
+{
+ Control;
+ int border;
+ CImage *image;
+ CImage *color;
+ CImage *bordercolor;
+ CFont *font;
+ int align;
+ int lastbut;
+ char lastchar[8];
+ Scribble *scrib;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFont,
+ EHide,
+ EImage,
+ ELinecolor,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] ="bordercolor",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ELinecolor] = "linecolor",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ nil
+};
+
+static void scribshow(Scrib*);
+static void scribchar(Scrib*, Rune);
+
+static void resetstroke(Scrib *w);
+static void displaystroke(Scrib *w);
+static void displaylast(Scrib *w);
+static void addpoint(Scrib *w, Point p);
+
+static void
+scribmouse(Control *c, Mouse *m)
+{
+ Scrib *b;
+ Rune r;
+
+ b = (Scrib*)c;
+ if (m->buttons & 0x1) {
+ if ((b->lastbut & 0x1) == 0) {
+ /* mouse went down */
+ resetstroke(b);
+ }
+ /* mouse is down */
+ if (ptinrect(m->xy,b->rect))
+ addpoint(b, m->xy);
+ } else if (b->lastbut & 0x1) {
+ /* mouse went up */
+ if (ptinrect(m->xy,b->rect)) {
+ r = recognize(b->scrib);
+ scribchar(b, r);
+ scribshow(b);
+ if (r) chanprint(b->event, b->format, b->name, r);
+ }
+ }
+ b->lastbut = m->buttons;
+}
+
+static void
+scribfree(Control *c)
+{
+ Scrib *b;
+
+ b = (Scrib*)c;
+ _putctlimage(b->image);
+ _putctlimage(b->color);
+ _putctlimage(b->bordercolor);
+ _putctlfont(b->font);
+// scribblefree(b->scrib);
+}
+
+static void
+scribchar(Scrib *b, Rune r)
+{
+ if(r == 0)
+ b->lastchar[0] = '\0';
+ else if(r == ' ')
+ strcpy(b->lastchar, "' '");
+ else if(r < ' ')
+ sprint(b->lastchar, "ctl-%c", r+'@');
+ else
+ sprint(b->lastchar, "%C", r);
+}
+
+
+static void
+scribshow(Scrib *b)
+{
+ Image *i;
+ Rectangle r;
+ char *mode;
+ Scribble *s = b->scrib;
+ char buf[32];
+
+ if (b->hidden)
+ return;
+ if(b->border > 0){
+ r = insetrect(b->rect, b->border);
+ border(b->screen, b->rect, b->border, b->bordercolor->image, ZP);
+ }else
+ r = b->rect;
+
+ i = b->image->image;
+ draw(b->screen, r, i, nil, i->r.min);
+
+ if (s->ctrlShift)
+ mode = " ^C";
+ else if (s->puncShift)
+ mode = " #&^";
+ else if (s->curCharSet == CS_DIGITS)
+ mode = " 123";
+ else if (s->capsLock)
+ mode = " ABC";
+ else if (s->tmpShift)
+ mode = " Abc";
+ else
+ mode = " abc";
+
+ snprint(buf, sizeof buf, "%s %s", mode, b->lastchar);
+
+ string(b->screen, r.min, b->color->image, ZP, b->font->font, buf);
+ flushimage(display, 1);
+}
+
+static void
+scribctl(Control *c, CParse *cp)
+{
+ int cmd;
+ Rectangle r;
+ Scrib *b;
+
+ b = (Scrib*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ abort();
+ ctlerror("%q: unrecognized message '%s'", b->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(b, cp, 2);
+ b->align = _ctlalignment(cp->args[1]);
+ break;
+ case EBorder:
+ _ctlargcount(b, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", b->name, cp->str);
+ b->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->bordercolor, cp->args[1]);
+ break;
+ case EFocus:
+ break;
+ case EImage:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->image, cp->args[1]);
+ break;
+ case ELinecolor:
+ _ctlargcount(b, cp, 2);
+ _setctlimage(b, &b->bordercolor, cp->args[1]);
+ break;
+ case ERect:
+ _ctlargcount(b, 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", b->name, cp->str);
+ b->rect = r;
+ break;
+ case EReveal:
+ _ctlargcount(b, cp, 1);
+ b->hidden = 0;
+ scribshow(b);
+ break;
+ case EShow:
+ _ctlargcount(b, cp, 1);
+ scribshow(b);
+ break;
+ case EFont:
+ _ctlargcount(b, cp, 2);
+ _setctlfont(b, &b->font, cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(b, cp, 1);
+ b->hidden = 1;
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(b, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", b->name, cp->str);
+ b->size.min = r.min;
+ b->size.max = r.max;
+ break;
+ }
+}
+
+static void
+resetstroke(Scrib *w)
+{
+ Scribble *s = w->scrib;
+
+ s->ps.npts = 0;
+ scribshow(w);
+}
+
+static void
+displaystroke(Scrib *b)
+{
+ Scribble *s = b->scrib;
+
+ poly(b->screen, s->pt, s->ps.npts, Endsquare, Endsquare, 0, b->color->image, ZP);
+ flushimage(display, 1);
+}
+
+static void
+displaylast(Scrib *w)
+{
+ int npt;
+ Scribble *s = w->scrib;
+
+ npt = s->ps.npts;
+ if (npt > 2)
+ npt = 2;
+ poly(w->screen, s->pt + (s->ps.npts - npt), npt, Endsquare, Endsquare,
+ 0, w->color->image, ZP);
+ flushimage(display, 1);
+}
+
+static void
+addpoint(Scrib *w, Point p)
+{
+ pen_point *ppa;
+ Point *pt;
+ int ppasize;
+ Scribble *s = w->scrib;
+
+ if (s->ps.npts == s->ppasize) {
+ ppasize = s->ppasize + 100;
+ ppa = malloc ((sizeof (pen_point) + sizeof (Point)) * ppasize);
+ if (!ppa)
+ return;
+ pt = (Point *) (ppa + ppasize);
+ memmove(ppa, s->ps.pts, s->ppasize * sizeof (pen_point));
+ memmove(pt, s->pt, s->ppasize * sizeof (Point));
+ free (s->ps.pts);
+ s->ps.pts = ppa;
+ s->pt = pt;
+ s->ppasize = ppasize;
+ }
+ ppa = &s->ps.pts[s->ps.npts];
+ ppa->Point = p;
+
+ pt = &s->pt[s->ps.npts];
+ *pt = p;
+
+ s->ps.npts++;
+
+ displaylast(w);
+}
+
+Control*
+createscribble(Controlset *cs, char *name)
+{
+ Scrib *b;
+
+ b = (Scrib*)_createctl(cs, "scribble", sizeof(Scrib), name);
+ b->image = _getctlimage("white");
+ b->color = _getctlimage("black");
+ b->bordercolor = _getctlimage("black");
+ b->align = Aupperleft;
+ b->format = ctlstrdup("%q: value 0x%x");
+ b->font = _getctlfont("font");
+ b->scrib = scribblealloc();
+ b->lastbut = 0;
+ b->bordercolor = _getctlimage("black");
+ b->border = 0;
+ b->ctl = scribctl;
+ b->mouse = scribmouse;
+ b->exit = scribfree;
+ return (Control*)b;
+}
diff --git a/sys/src/libcontrol/slider.c b/sys/src/libcontrol/slider.c
new file mode 100755
index 000000000..d7948cd2c
--- /dev/null
+++ b/sys/src/libcontrol/slider.c
@@ -0,0 +1,326 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Slider Slider;
+
+struct Slider
+{
+ Control;
+ int border;
+ CImage *image;
+ CImage *textcolor;
+ CImage *bordercolor;
+ CImage *indicatorcolor;
+ int absolute;
+ int max;
+ int vis;
+ int value;
+ int clamphigh;
+ int clamplow;
+ int horizontal;
+ int lastbut;
+};
+
+enum{
+ EAbsolute,
+ EBorder,
+ EBordercolor,
+ EClamp,
+ EFocus,
+ EFormat,
+ EHide,
+ EImage,
+ EIndicatorcolor,
+ EMax,
+ EOrient,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ EValue,
+ EVis,
+};
+
+static char *cmds[] = {
+ [EAbsolute] = "absolute",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EClamp] = "clamp",
+ [EFocus] = "focus",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [EIndicatorcolor] = "indicatorcolor",
+ [EMax] = "max",
+ [EOrient] = "orient",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [EValue] = "value",
+ [EVis] = "vis",
+};
+
+static void
+sliderfree(Control *c)
+{
+ Slider *s;
+
+ s = (Slider*)c;
+ _putctlimage(s->image);
+ _putctlimage(s->textcolor);
+ _putctlimage(s->bordercolor);
+ _putctlimage(s->indicatorcolor);
+}
+
+static void
+slidershow(Slider *s)
+{
+ Rectangle r, t;
+ int l, h, d;
+
+ if (s->hidden)
+ return;
+ r = s->rect;
+ draw(s->screen, r, s->image->image, nil, s->image->image->r.min);
+ if(s->border > 0){
+ border(s->screen, r, s->border, s->bordercolor->image, s->bordercolor->image->r.min);
+ r = insetrect(r, s->border);
+ }
+ if(s->max <= 0)
+ return;
+ if(s->horizontal)
+ d = Dx(r);
+ else
+ d = Dy(r);
+ l = muldiv(s->value, d, s->max);
+ h = muldiv(s->value+s->vis, d, s->max);
+ if(s->clamplow && s->clamphigh){
+ l = 0;
+ h = d;
+ }else if(s->clamplow){
+ h = l;
+ l = 0;
+ }else if(s->clamphigh)
+ h = d;
+ t = r;
+ if(s->horizontal){
+ r.max.x = r.min.x+h;
+ r.min.x += l;
+ }else{
+ r.max.y = r.min.y+h;
+ r.min.y += l;
+ }
+ if(rectclip(&r, t))
+ draw(s->screen, r, s->indicatorcolor->image, nil, s->indicatorcolor->image->r.min);
+ flushimage(display, 1);
+}
+
+static void
+sliderctl(Control *c, CParse *cp)
+{
+ int cmd, prev;
+ Rectangle r;
+ Slider *s;
+
+ s = (Slider*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", s->name, cp->str);
+ break;
+ case EAbsolute:
+ _ctlargcount(s, cp, 2);
+ s->absolute = cp->iargs[1];
+ break;
+ case EBorder:
+ _ctlargcount(s, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", s->name, cp->str);
+ s->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(s, cp, 2);
+ _setctlimage(s, &s->bordercolor, cp->args[1]);
+ break;
+ case EClamp:
+ _ctlargcount(s, cp, 3);
+ if(strcmp(cp->args[1], "high") == 0)
+ s->clamphigh = cp->iargs[2];
+ else if(strcmp(cp->args[1], "low") == 0)
+ s->clamplow = cp->iargs[2];
+ else
+ ctlerror("%q: unrecognized clamp: %s", s->name, cp->str);
+ break;
+ case EFocus:
+ /* ignore focus change */
+ break;
+ case EFormat:
+ _ctlargcount(s, cp, 2);
+ s->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(s, cp, 1);
+ s->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(s, cp, 2);
+ _setctlimage(s, &s->image, cp->args[1]);
+ break;
+ case EIndicatorcolor:
+ _ctlargcount(s, cp, 2);
+ _setctlimage(s, &s->indicatorcolor, cp->args[1]);
+ break;
+ case EMax:
+ _ctlargcount(s, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: negative max value: %s", s->name, cp->str);
+ if(s->max != cp->iargs[1]){
+ s->max = cp->iargs[1];
+ slidershow(s);
+ }
+ break;
+ case EOrient:
+ _ctlargcount(s, cp, 2);
+ prev = s->horizontal;
+ if(strncmp(cp->args[1], "hor", 3) == 0)
+ s->horizontal = 1;
+ else if(strncmp(cp->args[1], "ver", 3) == 0)
+ s->horizontal = 0;
+ else
+ ctlerror("%q: unrecognized orientation: %s", s->name, cp->str);
+ if(s->horizontal != prev)
+ slidershow(s);
+ break;
+ case ERect:
+ _ctlargcount(s, 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", s->name, cp->str);
+ s->rect = r;
+ break;
+ case EReveal:
+ _ctlargcount(s, cp, 1);
+ s->hidden = 0;
+ slidershow(s);
+ break;
+ case EShow:
+ _ctlargcount(s, cp, 1);
+ slidershow(s);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(s, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", s->name, cp->str);
+ s->size.min = r.min;
+ s->size.max = r.max;
+ break;
+ case EValue:
+ _ctlargcount(s, cp, 2);
+ if(s->value != cp->iargs[1]){
+ s->value = cp->iargs[1];
+ slidershow(s);
+ }
+ break;
+ case EVis:
+ _ctlargcount(s, cp, 2);
+ if(s->vis != cp->iargs[1]){
+ s->vis = cp->iargs[1];
+ slidershow(s);
+ }
+ break;
+ }
+}
+
+static void
+slidermouse(Control *c, Mouse *m)
+{
+ Rectangle r;
+ int v, l, d, b;
+ Slider *s;
+
+ s =(Slider*)c;
+ if(m->buttons == 0){
+ /* buttons now up */
+ s->lastbut = 0;
+ return;
+ }
+ if(!s->absolute && s->lastbut==m->buttons && s->lastbut!=2){
+ /* clicks only on buttons 1 & 3; continuous motion on 2 (or when absolute) */
+ return;
+ }
+ if(s->lastbut!=0 && m->buttons!=s->lastbut){
+ /* buttons down have changed; wait for button up */
+ return;
+ }
+ s->lastbut = m->buttons;
+
+ r = insetrect(s->rect, s->border);
+ if(s->horizontal){
+ v = m->xy.x - r.min.x;
+ d = Dx(r);
+ }else{
+ v = m->xy.y - r.min.y;
+ d = Dy(r);
+ }
+ if(s->absolute)
+ b = 2;
+ else
+ b = m->buttons;
+ switch(b){
+ default:
+ return;
+ case 1:
+ l = s->value - muldiv(v, s->vis, d);
+ break;
+ case 2:
+ l = muldiv(v, s->max, d);
+ break;
+ case 4:
+ l = s->value + muldiv(v, s->vis, d);
+ break;
+ }
+ if(l < 0)
+ l = 0;
+ if(l > s->max)
+ l = s->max;
+ if(l != s->value){
+ s->value = l;
+ chanprint(s->event, s->format, s->name, s->value);
+ slidershow(s);
+ }
+}
+
+Control*
+createslider(Controlset *cs, char *name)
+{
+ Slider *s;
+
+ s = (Slider*)_createctl(cs, "slider", sizeof(Slider), name);
+ s->image = _getctlimage("white");
+ s->textcolor = _getctlimage("black");
+ s->bordercolor = _getctlimage("black");
+ s->indicatorcolor = _getctlimage("black");
+ s->format = ctlstrdup("%q: value %d");
+ s->border = 0;
+ s->mouse = slidermouse;
+ s->ctl = sliderctl;
+ s->exit = sliderfree;
+ return (Control*)s;
+}
diff --git a/sys/src/libcontrol/tabs.c b/sys/src/libcontrol/tabs.c
new file mode 100755
index 000000000..41737c2f4
--- /dev/null
+++ b/sys/src/libcontrol/tabs.c
@@ -0,0 +1,256 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+#include "group.h"
+
+typedef struct Tab Tab;
+
+struct Tab {
+ Control;
+ int border;
+ int selected;
+ int separation;
+ char *format;
+ CImage *bordercolor;
+ CImage *image;
+ Control *tabrow;
+ Control *tabstack;
+ Control *tabcolumn;
+ int ntabs;
+ int nbuttons;
+ Control **buttons;
+};
+
+enum{
+ EAdd,
+ EBorder,
+ EBordercolor,
+ EButton,
+ EFocus,
+ EFormat,
+ EHide,
+ EImage,
+ ERect,
+ EReveal,
+ ESeparation,
+ ESeparatorcolor,
+ EShow,
+ ESize,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAdd] = "add",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EButton] = "button",
+ [EFocus] = "focus",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [ESeparation] = "separation",
+ [ESeparatorcolor] = "separatorcolor",
+ [EShow] = "show",
+ [ESize] = "size",
+ [EValue] = "value",
+};
+
+static void
+tabshow(Tab *t)
+{
+ int i;
+ Rectangle r;
+ Group *g;
+
+ if (t->hidden)
+ return;
+ for(i=0; i<t->nbuttons; i++){
+ _ctlprint(t->buttons[i], "value %d", (t->selected==i));
+ }
+ _ctlprint(t->tabstack, "reveal %d", t->selected);
+ _ctlprint(t->tabcolumn, "show");
+ if (t->selected < 0)
+ return;
+ g = (Group*)t->tabcolumn;
+ if (g->nseparators == 0){
+ return;
+ }
+ r = g->separators[0];
+ r.min.x = t->buttons[t->selected]->rect.min.x;
+ r.max.x = t->buttons[t->selected]->rect.max.x;
+ draw(t->screen, r, t->image->image, nil, t->image->image->r.min);
+ flushimage(display, 1);
+}
+
+static void
+tabsize(Control *c)
+{
+ Tab *t = (Tab*)c;
+ if (t->tabcolumn && t->tabcolumn->setsize)
+ t->tabcolumn->setsize((Control*)t->tabcolumn);
+}
+
+static void
+tabctl(Control *c, CParse *cp)
+{
+ int cmd, i;
+ Control *cbut, *cwin;
+ Tab *t;
+ Rectangle r;
+
+ t = (Tab*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ case EAdd:
+ if ((cp->nargs & 1) == 0)
+ ctlerror("%q: arg count: %s", t->name, cp->args[1]);
+ for (i = 1; i < cp->nargs; i += 2){
+ cbut = controlcalled(cp->args[i]);
+ if (cbut == nil)
+ ctlerror("%q: no such control: %s", t->name, cp->args[i]);
+ cwin = controlcalled(cp->args[i+1]);
+ if (cwin == nil)
+ ctlerror("%q: no such control: %s", t->name, cp->args[i+1]);
+ _ctladdgroup(t->tabrow, cbut);
+ _ctlprint(t->tabstack, "add %q", cp->args[i+1]);
+ _ctlprint(cbut, "format '%%s: %q button %%d'", t->name);
+ controlwire(cbut, "event", t->controlset->ctl);
+ t->buttons = ctlrealloc(t->buttons, (t->nbuttons+1)*sizeof(Control*));
+ t->buttons[t->nbuttons] = cbut;
+ t->nbuttons++;
+ t->selected = -1;
+ }
+ _ctlprint(t->tabcolumn, "size");
+ t->size = t->tabcolumn->size;
+ break;
+ case EBorder:
+ _ctlargcount(t, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", t->name, cp->str);
+ t->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->bordercolor, cp->args[1]);
+ break;
+ case EButton:
+ _ctlargcount(t, cp, 2);
+ if (cp->sender == nil)
+ ctlerror("%q: senderless buttonevent: %q", t->name, cp->str);
+ cbut = controlcalled(cp->sender);
+ for(i=0; i<t->nbuttons; i++)
+ if (cbut == t->buttons[i])
+ break;
+ if (i == t->nbuttons)
+ ctlerror("%q: not my event: %q", t->name, cp->str);
+ if(cp->iargs[1] == 0){
+ /* button was turned off; turn it back on */
+ _ctlprint(cbut, "value 1");
+ }else{
+ t->selected = i;
+ if (t->format)
+ chanprint(t->event, t->format, t->name, i);
+ tabshow(t);
+ }
+ break;
+ case EFocus:
+ /* ignore focus change */
+ break;
+ case EFormat:
+ _ctlargcount(t, cp, 2);
+ t->format = ctlstrdup(cp->args[1]);
+ break;
+ case EImage:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->image, cp->args[1]);
+ break;
+ case ESeparation:
+ t->tabrow->ctl(t->tabrow, cp);
+ t->tabcolumn->ctl(t->tabcolumn, cp);
+ break;
+ case ERect:
+ _ctlargcount(t, 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", t->name, cp->str);
+ t->rect = r;
+ r = insetrect(r, t->border);
+ _ctlprint(t->tabcolumn, "rect %R", r);
+ break;
+ case EReveal:
+ _ctlargcount(t, cp, 1);
+ case EHide:
+ case ESize:
+ t->tabcolumn->ctl(t->tabcolumn, cp);
+ break;
+ case EShow:
+ tabshow(t);
+ break;
+ case EValue:
+ _ctlargcount(t, cp, 2);
+ if (cp->iargs[1] < 0 || cp->iargs[1] >= t->nbuttons)
+ ctlerror("%q: illegal value '%s'", t->name, cp->str);
+ t->selected = cp->iargs[1];
+ tabshow(t);
+ break;
+ default:
+ ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
+ break;
+ }
+}
+
+static void
+tabfree(Control*c)
+{
+ Tab *t = (Tab*)c;
+ t->nbuttons = 0;
+ free(t->buttons);
+ t->buttons = 0;
+}
+
+static void
+activatetab(Control *c, int act)
+{
+ Tab *t;
+
+ t = (Tab*)c;
+ if (act)
+ activate(t->tabcolumn);
+ else
+ deactivate(t->tabcolumn);
+}
+
+Control *
+createtab(Controlset *cs, char *name)
+{
+ char s[128];
+
+ Tab *t;
+ t = (Tab*)_createctl(cs, "tab", sizeof(Tab), name);
+ t->selected = -1;
+ t->nbuttons = 0;
+ t->ctl = tabctl;
+ t->mouse = nil;
+ t->exit = tabfree;
+ snprint(s, sizeof s, "_%s-row", name);
+ t->tabrow = createrow(cs, s);
+ snprint(s, sizeof s, "_%s-stack", name);
+ t->tabstack = createstack(cs, s);
+ snprint(s, sizeof s, "_%s-column", name);
+ t->tabcolumn = createcolumn(cs, s);
+ ctlprint(t->tabcolumn, "add %q %q", t->tabrow->name, t->tabstack->name);
+ t->bordercolor = _getctlimage("black");
+ t->image = _getctlimage("white");
+ t->setsize = tabsize;
+ t->activate = activatetab;
+ return (Control*)t;
+}
diff --git a/sys/src/libcontrol/text.c b/sys/src/libcontrol/text.c
new file mode 100755
index 000000000..7c3d9f86a
--- /dev/null
+++ b/sys/src/libcontrol/text.c
@@ -0,0 +1,542 @@
+#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;
+
+typedef struct Text Text;
+
+struct Text
+{
+ Control;
+ int border;
+ int topline;
+ int scroll;
+ int nvis;
+ int lastbut;
+ CFont *font;
+ CImage *image;
+ CImage *textcolor;
+ CImage *bordercolor;
+ CImage *selectcolor;
+ CImage *selectingcolor;
+ Rune **line;
+ int selectmode; // Selsingle, Selmulti
+ int selectstyle; // Seldown, Selup (use Selup only with Selsingle)
+ uchar *selected;
+ int nline;
+ int warp;
+ int align;
+ int sel; // line nr of selection made by last button down
+ int but; // last button down (still being hold)
+ int offsel; // we are on selection
+};
+
+enum
+{
+ Selsingle,
+ Selmulti,
+ Seldown,
+ Selup,
+};
+
+enum{
+ EAccumulate,
+ EAdd,
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EClear,
+ EDelete,
+ EFocus,
+ EFont,
+ EHide,
+ EImage,
+ ERect,
+ EReplace,
+ EReveal,
+ EScroll,
+ ESelect,
+ ESelectcolor,
+ ESelectingcolor,
+ ESelectmode,
+ ESelectstyle,
+ EShow,
+ ESize,
+ ETextcolor,
+ ETopline,
+ EValue,
+ EWarp,
+};
+
+static char *cmds[] = {
+ [EAccumulate] = "accumulate",
+ [EAdd] = "add",
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EClear] = "clear",
+ [EDelete] = "delete",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ERect] = "rect",
+ [EReplace] = "replace",
+ [EReveal] = "reveal",
+ [EScroll] = "scroll",
+ [ESelect] = "select",
+ [ESelectcolor] = "selectcolor",
+ [ESelectingcolor] = "selectingcolor",
+ [ESelectmode] = "selectmode",
+ [ESelectstyle] = "selectstyle",
+ [EShow] = "show",
+ [ESize] = "size",
+ [ETextcolor] = "textcolor",
+ [ETopline] = "topline",
+ [EValue] = "value",
+ [EWarp] = "warp",
+ nil
+};
+
+static void textshow(Text*);
+static void texttogglei(Text*, int);
+static int textline(Text*, Point);
+static int texttoggle(Text*, Point);
+
+static void
+textmouse(Control *c, Mouse *m)
+{
+ Text *t;
+ int sel;
+
+ t = (Text*)c;
+ if (debug) fprint(2, "textmouse %s t->lastbut %d; m->buttons %d\n", t->name, t->lastbut, m->buttons);
+ if (t->warp >= 0)
+ return;
+ if ((t->selectstyle == Selup) && (m->buttons&7)) {
+ sel = textline(t, m->xy);
+ if (t->sel >= 0) {
+// if (debug) fprint(2, "textmouse Selup %q sel=%d t->sel=%d t->but=%d\n",
+// t->name, sel, t->sel, t->but);
+ t->offsel = (sel == t->sel) ? 0 : 1;
+ if ((sel == t->sel &&
+ ((t->selected[t->sel] && !t->but) ||
+ ((!t->selected[t->sel]) && t->but))) ||
+ (sel != t->sel &&
+ ((t->selected[t->sel] && t->but) ||
+ ((!t->selected[t->sel]) && (!t->but))))) {
+ texttogglei(t, t->sel);
+ }
+ }
+ }
+ if(t->lastbut != (m->buttons&7)){
+ if(m->buttons & 7){
+ sel = texttoggle(t, m->xy);
+ if(sel >= 0) {
+ if (t->selectstyle == Seldown) {
+ chanprint(t->event, "%q: select %d %d",
+ t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
+ if (debug) fprint(2, "textmouse Seldown event %q: select %d %d\n",
+ t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
+ } else {
+ if (debug) fprint(2, "textmouse Selup no event yet %q: select %d %d\n",
+ t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
+ t->sel = sel;
+ t->but = t->selected[sel] ? (m->buttons & 7) : 0;
+ }
+ }
+ } else if (t->selectstyle == Selup) {
+ sel = textline(t, m->xy);
+ t->offsel = 0;
+ if ((sel >= 0) && (sel == t->sel)) {
+ chanprint(t->event, "%q: select %d %d",
+ t->name, sel, t->but);
+ if (debug) fprint(2, "textmouse Selup event %q: select %d %d\n",
+ t->name, sel, t->but);
+ } else if (sel != t->sel) {
+ if ((t->selected[t->sel] && t->but) ||
+ ((!t->selected[t->sel]) && (!t->but))) {
+ texttogglei(t, t->sel);
+ } else {
+ textshow(t);
+ }
+ if (debug) fprint(2, "textmouse Selup cancel %q: select %d %d\n",
+ t->name, sel, t->but);
+ }
+ t->sel = -1;
+ t->but = 0;
+ }
+ t->lastbut = m->buttons & 7;
+ }
+}
+
+static void
+textfree(Control *c)
+{
+ int i;
+ Text *t;
+
+ t = (Text*)c;
+ _putctlfont(t->font);
+ _putctlimage(t->image);
+ _putctlimage(t->textcolor);
+ _putctlimage(t->bordercolor);
+ _putctlimage(t->selectcolor);
+ _putctlimage(t->selectingcolor);
+ for(i=0; i<t->nline; i++)
+ free(t->line[i]);
+ free(t->line);
+ free(t->selected);
+}
+
+static void
+textshow(Text *t)
+{
+ Rectangle r, tr;
+ Point p;
+ int i, ntext;
+ Font *f;
+ Rune *text;
+
+ if (t->hidden)
+ return;
+ r = t->rect;
+ f = t->font->font;
+ draw(t->screen, r, t->image->image, nil, t->image->image->r.min);
+ if(t->border > 0){
+ border(t->screen, r, t->border, t->bordercolor->image, t->bordercolor->image->r.min);
+ r = insetrect(r, t->border);
+ }
+ tr = r;
+ t->nvis = Dy(r)/f->height;
+ for(i=t->topline; i<t->nline && i<t->topline+t->nvis; i++){
+ text = t->line[i];
+ ntext = runestrlen(text);
+ r.max.y = r.min.y+f->height;
+ if(t->sel == i && t->offsel)
+ draw(t->screen, r, t->selectingcolor->image, nil, ZP);
+ else if(t->selected[i])
+ draw(t->screen, r, t->selectcolor->image, nil, ZP);
+ p = _ctlalignpoint(r,
+ runestringnwidth(f, text, ntext),
+ f->height, t->align);
+ if(t->warp == i) {
+ Point p2;
+ p2.x = p.x + 0.5*runestringnwidth(f, text, ntext);
+ p2.y = p.y + 0.5*f->height;
+ moveto(t->controlset->mousectl, p2);
+ t->warp = -1;
+ }
+ _string(t->screen, p, t->textcolor->image,
+ ZP, f, nil, text, ntext, tr,
+ nil, ZP, SoverD);
+ r.min.y += f->height;
+ }
+ flushimage(display, 1);
+}
+
+static void
+textctl(Control *c, CParse *cp)
+{
+ int cmd, i, n;
+ Rectangle r;
+ Text *t;
+ Rune *rp;
+
+ t = (Text*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(t, cp, 2);
+ t->align = _ctlalignment(cp->args[1]);
+ break;
+ case EBorder:
+ _ctlargcount(t, cp, 2);
+ if(cp->iargs[1] < 0)
+ ctlerror("%q: bad border: %c", t->name, cp->str);
+ t->border = cp->iargs[1];
+ break;
+ case EBordercolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->bordercolor, cp->args[1]);
+ break;
+ case EClear:
+ _ctlargcount(t, cp, 1);
+ for(i=0; i<t->nline; i++)
+ free(t->line[i]);
+ free(t->line);
+ free(t->selected);
+ t->line = ctlmalloc(sizeof(Rune*));
+ t->selected = ctlmalloc(1);
+ t->nline = 0;
+ textshow(t);
+ break;
+ case EDelete:
+ _ctlargcount(t, cp, 2);
+ i = cp->iargs[1];
+ if(i<0 || i>=t->nline)
+ ctlerror("%q: line number out of range: %s", t->name, cp->str);
+ free(t->line[i]);
+ memmove(t->line+i, t->line+i+1, (t->nline-(i+1))*sizeof(Rune*));
+ memmove(t->selected+i, t->selected+i+1, t->nline-(i+1));
+ t->nline--;
+ textshow(t);
+ break;
+ case EFocus:
+ break;
+ case EFont:
+ _ctlargcount(t, cp, 2);
+ _setctlfont(t, &t->font, cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(t, cp, 1);
+ t->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->image, cp->args[1]);
+ break;
+ case ERect:
+ _ctlargcount(t, 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", t->name, cp->str);
+ t->rect = r;
+ t->nvis = (Dy(r)-2*t->border)/t->font->font->height;
+ break;
+ case EReplace:
+ _ctlargcount(t, cp, 3);
+ i = cp->iargs[1];
+ if(i<0 || i>=t->nline)
+ ctlerror("%q: line number out of range: %s", t->name, cp->str);
+ free(t->line[i]);
+ t->line[i] = _ctlrunestr(cp->args[2]);
+ textshow(t);
+ break;
+ case EReveal:
+ _ctlargcount(t, cp, 1);
+ t->hidden = 0;
+ textshow(t);
+ break;
+ case EScroll:
+ _ctlargcount(t, cp, 2);
+ t->scroll = cp->iargs[1];
+ break;
+ case ESelect:
+ if(cp->nargs!=2 && cp->nargs!=3)
+ badselect:
+ ctlerror("%q: bad select message: %s", t->name, cp->str);
+ if(cp->nargs == 2){
+ if(strcmp(cp->args[1], "all") == 0){
+ memset(t->selected, 1, t->nline);
+ break;
+ }
+ if(strcmp(cp->args[1], "none") == 0){
+ memset(t->selected, 0, t->nline);
+ break;
+ }
+ if(cp->args[1][0]<'0' && '9'<cp->args[1][0])
+ goto badselect;
+ texttogglei(t, cp->iargs[1]);
+ break;
+ }
+ if(cp->iargs[1]<0 || cp->iargs[1]>=t->nline)
+ ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
+ if(t->selected[cp->iargs[1]] != (cp->iargs[2]!=0))
+ texttogglei(t, cp->iargs[1]);
+ break;
+ case ESelectcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->selectcolor, cp->args[1]);
+ break;
+ case ESelectmode:
+ _ctlargcount(t, cp, 2);
+ if(strcmp(cp->args[1], "single") == 0)
+ t->selectmode = Selsingle;
+ else if(strncmp(cp->args[1], "multi", 5) == 0)
+ t->selectmode = Selmulti;
+ break;
+ case ESelectstyle:
+ _ctlargcount(t, cp, 2);
+ if(strcmp(cp->args[1], "down") == 0)
+ t->selectstyle = Seldown;
+ else if(strcmp(cp->args[1], "up") == 0)
+ t->selectstyle = Selup;
+ break;
+ case EShow:
+ _ctlargcount(t, cp, 1);
+ textshow(t);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(10000, 10000);
+ else{
+ _ctlargcount(t, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", t->name, cp->str);
+ t->size.min = r.min;
+ t->size.max = r.max;
+ break;
+ case ETextcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->textcolor, cp->args[1]);
+ break;
+ case ETopline:
+ _ctlargcount(t, cp, 2);
+ i = cp->iargs[1];
+ if(i < 0)
+ i = 0;
+ if(i > t->nline)
+ i = t->nline;
+ if(t->topline != i){
+ t->topline = i;
+ textshow(t);
+ }
+ break;
+ case EValue:
+ /* set contents to single line */
+ /* free existing text and fall through to add */
+ for(i=0; i<t->nline; i++){
+ free(t->line[i]);
+ t->line[i] = nil;
+ }
+ t->nline = 0;
+ t->topline = 0;
+ /* fall through */
+ case EAccumulate:
+ case EAdd:
+ switch (cp->nargs) {
+ default:
+ ctlerror("%q: wrong argument count in '%s'", t->name, cp->str);
+ case 2:
+ n = t->nline;
+ break;
+ case 3:
+ n = cp->iargs[1];
+ if(n<0 || n>t->nline)
+ ctlerror("%q: line number out of range: %s", t->name, cp->str);
+ break;
+ }
+ rp = _ctlrunestr(cp->args[cp->nargs-1]);
+ t->line = ctlrealloc(t->line, (t->nline+1)*sizeof(Rune*));
+ memmove(t->line+n+1, t->line+n, (t->nline-n)*sizeof(Rune*));
+ t->line[n] = rp;
+ t->selected = ctlrealloc(t->selected, t->nline+1);
+ memmove(t->selected+n+1, t->selected+n, t->nline-n);
+ t->selected[n] = (t->selectmode==Selmulti && cmd!=EAccumulate);
+ t->nline++;
+ if(t->scroll) {
+ if(n > t->topline + (t->nvis - 1)){
+ t->topline = n - (t->nvis - 1);
+ if(t->topline < 0)
+ t->topline = 0;
+ }
+ if(n < t->topline)
+ t->topline = n;
+ }
+ if(cmd != EAccumulate)
+ if(t->scroll || t->nline<=t->topline+t->nvis)
+ textshow(t);
+ break;
+ case EWarp:
+ _ctlargcount(t, cp, 2);
+ i = cp->iargs[1];
+ if(i <0 || i>=t->nline)
+ ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
+ if(i < t->topline || i >= t->topline+t->nvis){
+ t->topline = i;
+ }
+ t->warp = cp->iargs[1];
+ textshow(t);
+ t->warp = -1;
+ break;
+ }
+}
+
+static void
+texttogglei(Text *t, int i)
+{
+ int prev;
+
+ if(t->selectmode == Selsingle){
+ /* clear the others */
+ prev = t->selected[i];
+ memset(t->selected, 0, t->nline);
+ t->selected[i] = prev;
+ }
+ t->selected[i] ^= 1;
+ textshow(t);
+}
+
+static int
+textline(Text *t, Point p)
+{
+ Rectangle r;
+ int i;
+
+ r = t->rect;
+ if(t->border > 0)
+ r = insetrect(r, t->border);
+ if(!ptinrect(p, r))
+ return -1;
+ i = (p.y-r.min.y)/t->font->font->height;
+ i += t->topline;
+ if(i >= t->nline)
+ return -1;
+ return i;
+}
+
+static int
+texttoggle(Text *t, Point p)
+{
+ int i;
+
+ i = textline(t, p);
+ if (i >= 0)
+ texttogglei(t, i);
+ return i;
+}
+
+Control*
+createtext(Controlset *cs, char *name)
+{
+ Text *t;
+
+ t = (Text*)_createctl(cs, "text", sizeof(Text), name);
+ t->line = ctlmalloc(sizeof(Rune*));
+ t->selected = ctlmalloc(1);
+ t->nline = 0;
+ t->image = _getctlimage("white");
+ t->textcolor = _getctlimage("black");
+ t->bordercolor = _getctlimage("black");
+ t->selectcolor = _getctlimage("yellow");
+ t->selectingcolor = _getctlimage("paleyellow");
+ t->font = _getctlfont("font");
+ t->selectmode = Selsingle;
+ t->selectstyle = Selup; // Seldown;
+ t->lastbut = 0;
+ t->mouse = textmouse;
+ t->ctl = textctl;
+ t->exit = textfree;
+ t->warp = -1;
+ t->sel = -1;
+ t->offsel = 0;
+ t->but = 0;
+ return (Control *)t;
+}
diff --git a/sys/src/libcontrol/textbutton.c b/sys/src/libcontrol/textbutton.c
new file mode 100755
index 000000000..9f1730762
--- /dev/null
+++ b/sys/src/libcontrol/textbutton.c
@@ -0,0 +1,344 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Textbutton Textbutton;
+
+struct Textbutton
+{
+ Control;
+ CFont *font;
+ CImage *image;
+ CImage *mask;
+ CImage *light;
+ CImage *bordercolor;
+ CImage *textcolor;
+ CImage *pressedtextcolor;
+ CImage *paletextcolor;
+ int pressed;
+ int lastbut;
+ int lastshow;
+ char **line;
+ int nline;
+ int align;
+ int border;
+ int off;
+ int showoff;
+ int prepress;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFont,
+ EFormat,
+ EHide,
+ EImage,
+ ELight,
+ EMask,
+ EPaletextcolor,
+ EPressedtextcolor,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ EText,
+ ETextcolor,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ELight] = "light",
+ [EMask] = "mask",
+ [EPaletextcolor] ="paletextcolor",
+ [EPressedtextcolor] ="pressedtextcolor",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [EText] = "text",
+ [ETextcolor] = "textcolor",
+ [EValue] = "value",
+ nil
+};
+
+static void textbuttonshow(Textbutton*);
+
+static void
+textbuttonmouse(Control *c, Mouse *m)
+{
+ Textbutton *t;
+
+ t = (Textbutton *)c;
+ if(m->buttons&7) {
+ if (ptinrect(m->xy,t->rect)) {
+ if (t->off) {
+ t->off = 0;
+ textbuttonshow(t);
+ }
+ } else {
+ if (!t->off) {
+ t->off = 1;
+ textbuttonshow(t);
+ }
+ }
+ }
+ if((m->buttons&7) != t->lastbut){
+ if(m->buttons & 7){
+ t->prepress = t->pressed;
+ if (t->pressed)
+ t->pressed = 0;
+ else
+ t->pressed = 1;
+ textbuttonshow(t);
+ }else{ /* generate event on button up */
+ if (ptinrect(m->xy,t->rect))
+ chanprint(t->event, t->format, t->name, t->pressed);
+ else {
+ t->off = 0;
+ t->pressed = t->prepress;
+ textbuttonshow(t);
+ }
+ }
+ }
+ t->lastbut = m->buttons & 7;
+}
+
+static void
+textbuttonfree(Control *c)
+{
+ int i;
+ Textbutton *t;
+
+ t = (Textbutton*)c;
+ _putctlfont(t->font);
+ _putctlimage(t->image);
+ _putctlimage(t->light);
+ _putctlimage(t->mask);
+ _putctlimage(t->textcolor);
+ _putctlimage(t->bordercolor);
+ _putctlimage(t->paletextcolor);
+ _putctlimage(t->pressedtextcolor);
+ for(i=0; i<t->nline; i++)
+ free(t->line[i]);
+ free(t->line);
+}
+
+static void
+textbuttonshow(Textbutton *t)
+{
+ Rectangle r, clipr;
+ int i, dx, dy, w;
+ Font *f;
+ Point p, q;
+ Image *im;
+
+ if(t->hidden || (t->lastshow == t->pressed && t->showoff == t->off))
+ return;
+ f = t->font->font;
+ draw(t->screen, t->rect, t->image->image, nil, t->image->image->r.min);
+ if(t->border > 0)
+ border(t->screen, t->rect, t->border, t->bordercolor->image, ZP);
+ /* text goes here */
+ dx = 0;
+ for(i=0; i<t->nline; i++){
+ w = stringwidth(f, t->line[i]);
+ if(dx < w)
+ dx = w;
+ }
+ dy = t->nline*f->height;
+ clipr = insetrect(t->rect, t->border);
+ p = _ctlalignpoint(clipr, dx, dy, t->align);
+ im = t->textcolor->image;
+ if(t->off)
+ im = t->paletextcolor->image;
+ else if(t->pressed)
+ im = t->pressedtextcolor->image;
+ for(i=0; i<t->nline; i++){
+ r.min = p;
+ r.max.x = p.x+dx;
+ r.max.y = p.y+f->height;
+ q = _ctlalignpoint(r, stringwidth(f, t->line[i]), f->height, t->align%3);
+ _string(t->screen, q, im,
+ ZP, f, t->line[i], nil, strlen(t->line[i]),
+ clipr, nil, ZP, SoverD);
+ p.y += f->height;
+ }
+ if(t->off || t->pressed)
+ draw(t->screen, t->rect, t->light->image, t->mask->image, t->mask->image->r.min);
+ t->lastshow = t->pressed;
+ t->showoff = t->off;
+ flushimage(display, 1);
+}
+
+static void
+textbuttonctl(Control *c, CParse *cp)
+{
+ int cmd, i;
+ Rectangle r;
+ Textbutton *t;
+
+ t = (Textbutton*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(t, cp, 2);
+ t->align = _ctlalignment(cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EBorder:
+ _ctlargcount(t, cp, 2);
+ t->border = cp->iargs[1];
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EBordercolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->bordercolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EFocus:
+ break;
+ case EFont:
+ _ctlargcount(t, cp, 2);
+ _setctlfont(t, &t->font, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EFormat:
+ _ctlargcount(t, cp, 2);
+ t->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(t, cp, 1);
+ t->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->image, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case ELight:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->light, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EMask:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->mask, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EPaletextcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->paletextcolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EPressedtextcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->pressedtextcolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case ERect:
+ _ctlargcount(t, 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", t->name, cp->str);
+ t->rect = r;
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EReveal:
+ _ctlargcount(t, cp, 1);
+ t->hidden = 0;
+ t->lastshow = -1; /* force redraw */
+ textbuttonshow(t);
+ break;
+ case EShow:
+ _ctlargcount(t, cp, 1);
+ t->lastshow = -1; /* force redraw */
+ textbuttonshow(t);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(t, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", t->name, cp->str);
+ t->size.min = r.min;
+ t->size.max = r.max;
+ break;
+ case EText:
+ /* free existing text */
+ for(i=0; i<t->nline; i++)
+ free(t->line[i]);
+ t->nline = cp->nargs-1;
+ t->line = ctlrealloc(t->line, t->nline*sizeof(char*));
+ for(i=0; i<t->nline; i++)
+ t->line[i] = ctlstrdup(cp->args[i+1]);
+ t->lastshow = -1; /* force redraw */
+ textbuttonshow(t);
+ break;
+ case ETextcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->textcolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EValue:
+ _ctlargcount(t, cp, 2);
+ if((cp->iargs[1]!=0) != t->pressed){
+ t->pressed ^= 1;
+ textbuttonshow(t);
+ }
+ break;
+ }
+}
+
+Control*
+createtextbutton(Controlset *cs, char *name)
+{
+ Textbutton *t;
+
+ t = (Textbutton *)_createctl(cs, "textbutton", sizeof(Textbutton), name);
+ t->line = ctlmalloc(sizeof(char*));
+ t->nline = 0;
+ t->image = _getctlimage("white");
+ t->light = _getctlimage("yellow");
+ t->mask = _getctlimage("opaque");
+ t->bordercolor = _getctlimage("black");
+ t->textcolor = _getctlimage("black");
+ t->pressedtextcolor = _getctlimage("black");
+ t->paletextcolor = _getctlimage("paleyellow");
+ t->font = _getctlfont("font");
+ t->format = ctlstrdup("%q: value %d");
+ t->lastshow = -1;
+ t->mouse = textbuttonmouse;
+ t->ctl = textbuttonctl;
+ t->exit = textbuttonfree;
+ t->prepress = 0;
+ t->off = 0;
+ t->showoff = -1;
+ return (Control *)t;
+}
diff --git a/sys/src/libcontrol/textbutton3.c b/sys/src/libcontrol/textbutton3.c
new file mode 100755
index 000000000..1a3cd1307
--- /dev/null
+++ b/sys/src/libcontrol/textbutton3.c
@@ -0,0 +1,393 @@
+/* use button 3 for a proper function to the application, that is not for plumber
+ * as default control(2) supposes.
+ * codes are mostly from /sys/src/libcontrol/textbutton.c
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+typedef struct Textbutton3 Textbutton3;
+
+struct Textbutton3
+{
+ Control;
+ CFont *font;
+ CImage *image;
+ CImage *mask;
+ CImage *light;
+ CImage *bordercolor;
+ CImage *textcolor;
+ CImage *pressedtextcolor;
+ int pressed;
+ int lastbut;
+ int lastshow;
+ char **line;
+ int nline;
+ int align;
+ int border;
+ int left;
+ int middle;
+ int right;
+ int toggle;
+ int gettextflg;
+};
+
+enum{
+ EAlign,
+ EBorder,
+ EBordercolor,
+ EFocus,
+ EFont,
+ EFormat,
+ EHide,
+ EImage,
+ ELight,
+ EMask,
+ EPressedtextcolor,
+ ERect,
+ EReveal,
+ EShow,
+ ESize,
+ EText,
+ ETextcolor,
+ EEnable,
+ EDisable,
+ EToggle,
+ EGettext,
+ EValue,
+};
+
+static char *cmds[] = {
+ [EAlign] = "align",
+ [EBorder] = "border",
+ [EBordercolor] = "bordercolor",
+ [EFocus] = "focus",
+ [EFont] = "font",
+ [EFormat] = "format",
+ [EHide] = "hide",
+ [EImage] = "image",
+ [ELight] = "light",
+ [EMask] = "mask",
+ [EPressedtextcolor] ="pressedtextcolor",
+ [ERect] = "rect",
+ [EReveal] = "reveal",
+ [EShow] = "show",
+ [ESize] = "size",
+ [EText] = "text",
+ [ETextcolor] = "textcolor",
+ [EEnable] = "enable",
+ [EDisable] = "disable",
+ [EToggle] = "toggle",
+ [EGettext] = "gettext",
+ [EValue] = "value",
+ nil
+};
+
+static void textbutton3show(Textbutton3 *);
+
+static void
+textbutton3mouse(Control *c, Mouse *m)
+{
+ Textbutton3 *t;
+
+ t = (Textbutton3 *)c;
+ if(t->left == 1) {
+ if((m->buttons&1) == 1 && (t->lastbut&1) == 0){
+ t->pressed ^= 1;
+ textbutton3show(t);
+ t->lastbut = m->buttons & 1;
+ }else if((m->buttons&1) == 0 && (t->lastbut&1) == 1){
+ if(t->gettextflg == 0)
+ chanprint(t->event, t->format, t->name, t->pressed, m->xy.x, m->xy.y);
+ else
+ chanprint(t->event, "%q: value %q", t->name, t->line[0]);
+ t->pressed ^= 1;
+ textbutton3show(t);
+ t->lastbut = m->buttons & 1;
+ }
+ }
+ if(t->middle == 1) {
+ if((m->buttons&2) == 2 && (t->lastbut&2) == 0){
+ t->pressed ^= 2;
+ textbutton3show(t);
+ t->lastbut = m->buttons & 2;
+ }else if((m->buttons&2) == 0 && (t->lastbut&2) == 2){
+ if(t->gettextflg == 0)
+ chanprint(t->event, t->format, t->name, t->pressed, m->xy.x, m->xy.y);
+ else
+ chanprint(t->event, "%q: value %q", t->name, t->line[0]);
+ t->pressed ^= 2;
+ textbutton3show(t);
+ t->lastbut = m->buttons & 2;
+ }
+ }
+ if(t->right == 1) {
+ if((m->buttons&4) == 4 && (t->lastbut&4) == 0){
+ t->pressed ^= 4;
+ textbutton3show(t);
+ t->lastbut = m->buttons & 4;
+ }else if((m->buttons&4) == 0 && (t->lastbut&4) == 4){
+ if(t->gettextflg == 0)
+ chanprint(t->event, t->format, t->name, t->pressed, m->xy.x, m->xy.y);
+ else
+ chanprint(t->event, "%q: value %q", t->name, t->line[0]);
+ t->pressed ^= 4;
+ textbutton3show(t);
+ t->lastbut = m->buttons & 4;
+ }
+ }
+}
+
+static void
+textbutton3free(Control *c)
+{
+ int i;
+ Textbutton3 *t;
+
+ t = (Textbutton3*)c;
+ _putctlfont(t->font);
+ _putctlimage(t->image);
+ _putctlimage(t->light);
+ _putctlimage(t->mask);
+ _putctlimage(t->textcolor);
+ _putctlimage(t->bordercolor);
+ _putctlimage(t->pressedtextcolor);
+ for(i=0; i<t->nline; i++)
+ free(t->line[i]);
+ free(t->line);
+}
+
+static void
+textbutton3show(Textbutton3 *t)
+{
+ Rectangle r, clipr;
+ int i, dx, dy, w;
+ Font *f;
+ Point p, q;
+ Image *im;
+
+ if(t->hidden || t->lastshow == t->pressed)
+ return;
+ f = t->font->font;
+ draw(t->screen, t->rect, t->image->image, nil, t->image->image->r.min);
+ if(t->pressed || t->toggle)
+ draw(t->screen, t->rect, t->light->image, t->mask->image, t->mask->image->r.min);
+ if(t->border > 0)
+ border(t->screen, t->rect, t->border, t->bordercolor->image, ZP);
+ /* text goes here */
+ dx = 0;
+ for(i=0; i<t->nline; i++){
+ w = stringwidth(f, t->line[i]); /*****/
+ if(dx < w)
+ dx = w;
+ }
+ dy = t->nline*f->height;
+ clipr = insetrect(t->rect, t->border);
+ p = _ctlalignpoint(clipr, dx, dy, t->align);
+ im = t->textcolor->image;
+ if(t->pressed)
+ im = t->pressedtextcolor->image;
+ for(i=0; i<t->nline; i++){
+ r.min = p;
+ r.max.x = p.x+dx;
+ r.max.y = p.y+f->height;
+ q = _ctlalignpoint(r, stringwidth(f, t->line[i]), f->height, t->align%3);
+ _string(t->screen, q, im,
+ ZP, f, t->line[i], nil, strlen(t->line[i]),
+ clipr, nil, ZP, SoverD);
+ p.y += f->height;
+ }
+ t->lastshow = t->pressed;
+ flushimage(display, 1);
+}
+
+static void
+textbutton3ctl(Control *c, CParse *cp)
+{
+ int cmd, i;
+ Rectangle r;
+ Textbutton3 *t;
+
+ t = (Textbutton3*)c;
+ cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
+ switch(cmd){
+ default:
+ ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
+ break;
+ case EAlign:
+ _ctlargcount(t, cp, 2);
+ t->align = _ctlalignment(cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EBorder:
+ _ctlargcount(t, cp, 2);
+ t->border = cp->iargs[1];
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EBordercolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->bordercolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EFocus:
+ break;
+ case EFont:
+ _ctlargcount(t, cp, 2);
+ _setctlfont(t, &t->font, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EFormat:
+ _ctlargcount(t, cp, 2);
+ t->format = ctlstrdup(cp->args[1]);
+ break;
+ case EHide:
+ _ctlargcount(t, cp, 1);
+ t->hidden = 1;
+ break;
+ case EImage:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->image, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case ELight:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->light, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EMask:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->mask, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EPressedtextcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->pressedtextcolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case ERect:
+ _ctlargcount(t, 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", t->name, cp->str);
+ t->rect = r;
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EReveal:
+ _ctlargcount(t, cp, 1);
+ t->hidden = 0;
+ t->lastshow = -1; /* force redraw */
+ textbutton3show(t);
+ break;
+ case EShow:
+ _ctlargcount(t, cp, 1);
+ t->lastshow = -1; /* force redraw */
+ textbutton3show(t);
+ break;
+ case ESize:
+ if (cp->nargs == 3)
+ r.max = Pt(0x7fffffff, 0x7fffffff);
+ else{
+ _ctlargcount(t, cp, 5);
+ r.max.x = cp->iargs[3];
+ r.max.y = cp->iargs[4];
+ }
+ 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", t->name, cp->str);
+ t->size.min = r.min;
+ t->size.max = r.max;
+ break;
+ case EText:
+ /* free existing text */
+ for(i=0; i<t->nline; i++)
+ free(t->line[i]);
+ t->nline = cp->nargs-1;
+ t->line = ctlrealloc(t->line, t->nline*sizeof(char*));
+ for(i=0; i<t->nline; i++)
+ t->line[i] = ctlstrdup(cp->args[i+1]);
+ t->lastshow = -1; /* force redraw */
+ textbutton3show(t);
+ break;
+ case ETextcolor:
+ _ctlargcount(t, cp, 2);
+ _setctlimage(t, &t->textcolor, cp->args[1]);
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EEnable:
+ _ctlargcount(t, cp, 2);
+ if(strcmp(cp->args[1], "left") == 0)
+ t->left = 1;
+ else if(strcmp(cp->args[1], "middle") == 0)
+ t->middle = 1;
+ else if(strcmp(cp->args[1], "right") == 0)
+ t->right = 1;
+ break;
+ case EDisable:
+ _ctlargcount(t, cp, 2);
+ if(strcmp(cp->args[1], "left") == 0)
+ t->left = 0;
+ else if(strcmp(cp->args[1], "middle") == 0)
+ t->middle = 0;
+ else if(strcmp(cp->args[1], "right") == 0)
+ t->right = 0;
+ break;
+ case EToggle:
+ _ctlargcount(t, cp, 2);
+ if(strcmp(cp->args[1], "on") == 0)
+ t->toggle = 1;
+ else if(strcmp(cp->args[1], "off") == 0)
+ t->toggle = 0;
+ t->lastshow = -1; /* force redraw */
+ break;
+ case EGettext:
+ _ctlargcount(t, cp, 2);
+ if(strcmp(cp->args[1], "on") == 0)
+ t->gettextflg = 1;
+ else if(strcmp(cp->args[1], "off") == 0)
+ t->gettextflg = 0;
+ break;
+ case EValue:
+ _ctlargcount(t, cp, 2);
+ if((cp->iargs[1]!=0) != t->pressed){
+ t->pressed ^= 1;
+ textbutton3show(t);
+ }
+ break;
+ }
+}
+
+Control*
+createtextbutton3(Controlset *cs, char *name)
+{
+ Textbutton3 *t;
+
+ t = (Textbutton3 *)_createctl(cs, "textbutton3", sizeof(Textbutton3), name);
+ t->line = ctlmalloc(sizeof(char*));
+ t->nline = 0;
+ t->image = _getctlimage("white");
+ t->light = _getctlimage("yellow");
+ t->mask = _getctlimage("opaque");
+ t->bordercolor = _getctlimage("black");
+ t->textcolor = _getctlimage("black");
+ t->pressedtextcolor = _getctlimage("black");
+ t->font = _getctlfont("font");
+ t->format = ctlstrdup("%q: value %d %d %d");
+ t->lastshow = -1;
+ t->mouse = textbutton3mouse;
+ t->ctl = textbutton3ctl;
+ t->exit = textbutton3free;
+ t->left = 1;
+ t->middle = 1;
+ t->right = 1;
+ t->toggle = 0;
+ t->gettextflg = 0;
+ return (Control *)t;
+}