summaryrefslogtreecommitdiff
path: root/libtk/utils.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /libtk/utils.c
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'libtk/utils.c')
-rw-r--r--libtk/utils.c1951
1 files changed, 1951 insertions, 0 deletions
diff --git a/libtk/utils.c b/libtk/utils.c
new file mode 100644
index 00000000..4e86e40c
--- /dev/null
+++ b/libtk/utils.c
@@ -0,0 +1,1951 @@
+#include "lib9.h"
+#include "draw.h"
+#include "tk.h"
+
+extern void rptwakeup(void*, void*);
+extern void* rptproc(char*, int, void*, int (*)(void*), int (*)(void*,int), void (*)(void*));
+
+typedef struct Cmd Cmd;
+struct Cmd
+{
+ char* name;
+ char* (*fn)(TkTop*, char*, char**);
+};
+static struct Cmd cmdmain[] =
+{
+ "bind", tkbind,
+ "button", tkbutton,
+ "canvas", tkcanvas,
+ "checkbutton", tkcheckbutton,
+ "choicebutton", tkchoicebutton,
+ "cursor", tkcursorcmd,
+ "destroy", tkdestroy,
+ "entry", tkentry,
+ "focus", tkfocus,
+ "frame", tkframe,
+ "grab", tkgrab,
+ "grid", tkgrid,
+ "image", tkimage,
+ "label", tklabel,
+ "listbox", tklistbox,
+ "lower", tklower,
+ "menu", tkmenu,
+ "menubutton", tkmenubutton,
+ "pack", tkpack,
+ "panel", tkpanel,
+ "puts", tkputs,
+ "radiobutton", tkradiobutton,
+ "raise", tkraise,
+ "scale", tkscale,
+ "scrollbar", tkscrollbar,
+ "see", tkseecmd,
+ "send", tksend,
+ "text", tktext,
+ "update", tkupdatecmd,
+ "variable", tkvariable,
+ "winfo", tkwinfo,
+};
+
+char* tkfont;
+
+/* auto-repeating support
+ * should perhaps be one rptproc per TkCtxt
+ * This is not done for the moment as there isn't
+ * a mechanism for terminating the rptproc
+ */
+static void *autorpt;
+static int rptid;
+static Tk *rptw;
+static void *rptnote;
+static void (*rptcb)(Tk*, void*, int);
+static long rptto;
+static int rptint;
+
+/* blinking carets - should be per TkCtxt */
+static void *blinkrpt;
+static Tk *blinkw;
+static void (*blinkcb)(Tk*, int);
+static int blinkignore;
+static int blinkon;
+
+
+ulong
+tkrgba(int r, int g, int b, int a)
+{
+ ulong p;
+
+ if(r < 0)
+ r = 0;
+ else if(r > 255)
+ r = 255;
+ if(g < 0)
+ g = 0;
+ else if(g > 255)
+ g = 255;
+ if(b < 0)
+ b = 0;
+ else if(b > 255)
+ b = 255;
+ p = (r<<24)|(g<<16)|(b<<8)|0xFF;
+ if(a == 255)
+ return p;
+ return setalpha(p, a);
+}
+
+/* to be replaced */
+static int
+revalpha(int c, int a)
+{
+ if (a == 0)
+ return 0;
+ return (c & 0xff) * 255 / a;
+}
+
+void
+tkrgbavals(ulong rgba, int *R, int *G, int *B, int *A)
+{
+ int a;
+
+ a = rgba & 0xff;
+ *A = a;
+ if (a != 0xff) {
+ *R = revalpha(rgba>>24, a);
+ *G = revalpha((rgba>>16) & 0xFF, a);
+ *B = revalpha((rgba >> 8) & 0xFF, a);
+ } else {
+ *R = (rgba>>24);
+ *G = ((rgba>>16) & 0xFF);
+ *B = ((rgba >> 8) & 0xFF);
+ }
+}
+
+void
+tkfreecolcache(TkCtxt *c)
+{
+ TkCol *cc;
+
+ if(c == nil)
+ return;
+ while((cc = c->chead) != nil){
+ c->chead = cc->forw;
+ freeimage(cc->i);
+ free(cc);
+ }
+ c->ctail = nil;
+ c->ncol = 0;
+}
+
+Image*
+tkcolor(TkCtxt *c, ulong pix)
+{
+ Image *i;
+ TkCol *cc, **l;
+ Display *d;
+ Rectangle r;
+
+ for(l = &c->chead; (cc = *l) != nil; l = &cc->forw)
+ if(cc->rgba == pix){
+ /* move it up in the list */
+ *l = cc->forw;
+ cc->forw = c->chead;
+ c->chead = cc;
+ /* we assume it will be used right away and not stored */
+ return cc->i;
+ }
+ d = c->display;
+ if(pix == DWhite)
+ return d->white;
+ if(pix == DBlack)
+ return d->black;
+ r.min = ZP;
+ r.max.x = 1;
+ r.max.y = 1;
+ if ((pix & 0xff) == 0xff)
+ i = allocimage(d, r, RGB24, 1, pix);
+ else
+ i = allocimage(d, r, RGBA32, 1, pix);
+ if(i == nil)
+ return d->black;
+ cc = malloc(sizeof(*cc));
+ if(cc == nil){
+ freeimage(i);
+ return d->black;
+ }
+ cc->rgba = pix;
+ cc->i = i;
+ cc->forw = c->chead;
+ c->chead = cc;
+ c->ncol++;
+ /* we'll do LRU management at some point */
+ if(c->ncol > TkColcachesize){
+ static int warn;
+ if(warn == 0){
+ warn = 1;
+ print("tk: %d colours cached\n", TkColcachesize);
+ }
+ }
+ return i;
+}
+
+/*
+ * XXX should be in libdraw?
+ */
+int
+tkchanhastype(ulong c, int t)
+{
+ for(; c; c>>=8)
+ if(TYPE(c) == t)
+ return 1;
+ return 0;
+}
+
+void
+tksettransparent(Tk *tk, int transparent)
+{
+ if (transparent)
+ tk->flag |= Tktransparent;
+ else
+ tk->flag &= ~Tktransparent;
+}
+
+int
+tkhasalpha(TkEnv *e, int col)
+{
+ return (e->colors[col] & 0xff) != 0xff;
+}
+
+Image*
+tkgc(TkEnv *e, int col)
+{
+ return tkcolor(e->top->ctxt, e->colors[col]);
+}
+
+
+/*
+ * Todo: improve the fixed-point code
+ * the 255 scale factor is used because RGB ranges 0-255
+ */
+static void
+rgb2hsv(int r, int g, int b, int *h, int *s, int *v)
+{
+ int min, max, delta;
+
+ max = r;
+ if(g > max)
+ max = g;
+ if(b > max)
+ max = b;
+ min = r;
+ if(g < min)
+ min = g;
+ if(b < min)
+ min = b;
+ *v = max;
+ if (max != 0)
+ *s = ((max - min)*255) / max;
+ else
+ *s = 0;
+
+ if (*s == 0) {
+ *h = 0; /* undefined */
+ } else {
+ delta = max - min;
+ if (r == max)
+ *h = (g - b)*255 / delta;
+ else if (g == max)
+ *h = (2*255) + ((b - r)*255) / delta;
+ else if (b == max)
+ *h = (4*255) + ((r - g)*255)/ delta;
+ *h *= 60;
+ if (*h < 0)
+ *h += 360*255;
+ *h /= 255;
+ }
+}
+
+static void
+hsv2rgb(int h, int s, int v, int *r, int *g, int *b)
+{
+ int i;
+ int f,p,q,t;
+
+ if (s == 0 && h == 0) {
+ *r = *g = *b = v; /* achromatic case */
+ } else {
+ if (h >= 360)
+ h = 0;
+ i = h / 60;
+ h *= 255;
+ h /= 60;
+
+ f = h % 255;
+ p = v * (255 - s);
+ q = v * (255 - ((s * f)/255));
+ t = v * (255- ((s * (255 - f))/255));
+ p /= 255;
+ q /= 255;
+ t /= 255;
+ switch (i) {
+ case 0: *r = v; *g = t; *b = p; break;
+ case 1: *r = q; *g = v; *b = p; break;
+ case 2: *r = p; *g = v; *b = t; break;
+ case 3: *r = p; *g = q; *b = v; break;
+ case 4: *r = t; *g = p; *b = v; break;
+ case 5: *r = v; *g = p; *b = q; break;
+ }
+ }
+}
+
+enum {
+ MINDELTA = 0x10,
+ DELTA = 0x30,
+};
+
+ulong
+tkrgbashade(ulong rgba, int shade)
+{
+ int R, G, B, A, h, s, v, vl, vd;
+
+ if (shade == TkSameshade)
+ return rgba;
+
+ tkrgbavals(rgba, &R, &G, &B, &A);
+ rgb2hsv(R, G, B, &h, &s, &v);
+
+ if (v < MINDELTA) {
+ vd = v+DELTA;
+ vl = vd+DELTA;
+ } else if (v > 255-MINDELTA) {
+ vl = v-DELTA;
+ vd = vl-DELTA;
+ } else {
+ vl = v+DELTA;
+ vd = v-DELTA;
+ }
+
+ v = (shade == TkLightshade)?vl:vd;
+ if (v < 0)
+ v = 0;
+ if (v > 255)
+ v = 255;
+ hsv2rgb(h, s, v, &R, &G, &B);
+
+ return tkrgba(R, G, B, A);
+}
+
+Image*
+tkgshade(TkEnv *e, int col, int shade)
+{
+ ulong rgba;
+
+ if (col == TkCbackgnd || col == TkCselectbgnd || col == TkCactivebgnd)
+ return tkgc(e, col+shade);
+ rgba = tkrgbashade(e->colors[col], shade);
+ return tkcolor(e->top->ctxt, rgba);
+}
+
+TkEnv*
+tknewenv(TkTop *t)
+{
+ TkEnv *e;
+
+ e = malloc(sizeof(TkEnv));
+ if(e == nil)
+ return nil;
+
+ e->ref = 1;
+ e->top = t;
+ return e;
+}
+
+TkEnv*
+tkdefaultenv(TkTop *t)
+{
+ int locked;
+ TkEnv *env;
+ Display *d;
+
+ if(t->env != nil) {
+ t->env->ref++;
+ return t->env;
+ }
+ t->env = malloc(sizeof(TkEnv));
+ if(t->env == nil)
+ return nil;
+
+ env = t->env;
+ env->ref = 1;
+ env->top = t;
+
+ if(tkfont == nil)
+ tkfont = "/fonts/pelm/unicode.8.font";
+
+ d = t->display;
+ env->font = font_open(d, tkfont);
+ if(env->font == nil) {
+ static int warn;
+ if(warn == 0) {
+ warn = 1;
+ print("tk: font not found: %s\n", tkfont);
+ }
+ env->font = font_open(d, "*default*");
+ if(env->font == nil) {
+ free(t->env);
+ t->env = nil;
+ return nil;
+ }
+ }
+
+ locked = lockdisplay(d);
+ env->wzero = stringwidth(env->font, "0");
+ if ( env->wzero <= 0 )
+ env->wzero = env->font->height / 2;
+ if(locked)
+ unlockdisplay(d);
+
+ tksetenvcolours(env);
+ return env;
+}
+
+void
+tkputenv(TkEnv *env)
+{
+ Display *d;
+ int locked;
+
+ if(env == nil)
+ return;
+
+ env->ref--;
+ if(env->ref != 0)
+ return;
+
+ d = env->top->display;
+ locked = lockdisplay(d);
+
+ if(env->font != nil)
+ font_close(env->font);
+
+ if(locked)
+ unlockdisplay(d);
+
+ free(env);
+}
+
+TkEnv*
+tkdupenv(TkEnv **env)
+{
+ Display *d;
+ TkEnv *e, *ne;
+
+ e = *env;
+ if(e->ref == 1)
+ return e;
+
+ ne = malloc(sizeof(TkEnv));
+ if(ne == nil)
+ return nil;
+
+ ne->ref = 1;
+ ne->top = e->top;
+
+ d = e->top->display;
+ memmove(ne->colors, e->colors, sizeof(e->colors));
+ ne->set = e->set;
+ ne->font = font_open(d, e->font->name);
+ ne->wzero = e->wzero;
+
+ e->ref--;
+ *env = ne;
+ return ne;
+}
+
+Tk*
+tknewobj(TkTop *t, int type, int n)
+{
+ Tk *tk;
+
+ tk = malloc(n);
+ if(tk == 0)
+ return 0;
+
+ tk->type = type; /* Defaults */
+ tk->flag = Tktop;
+ tk->relief = TKflat;
+ tk->env = tkdefaultenv(t);
+ if(tk->env == nil) {
+ free(tk);
+ return nil;
+ }
+
+ return tk;
+}
+
+void
+tkfreebind(TkAction *a)
+{
+ TkAction *next;
+
+ while(a != nil) {
+ next = a->link;
+ if((a->type & 0xff) == TkDynamic)
+ free(a->arg);
+ free(a);
+ a = next;
+ }
+}
+
+void
+tkfreename(TkName *f)
+{
+ TkName *n;
+
+ while(f != nil) {
+ n = f->link;
+ free(f);
+ f = n;
+ }
+}
+
+void
+tkfreeobj(Tk *tk)
+{
+ TkCtxt *c;
+
+ c = tk->env->top->ctxt;
+ if(c != nil) {
+ if(c->tkkeygrab == tk)
+ c->tkkeygrab = nil;
+ if(c->mgrab == tk)
+ tksetmgrab(tk->env->top, nil);
+ if(c->mfocus == tk)
+ c->mfocus = nil;
+ if(c->entered == tk)
+ c->entered = nil;
+ }
+
+ if (tk == rptw) {
+ /* cancel the autorepeat without notifying the widget */
+ rptid++;
+ rptw = nil;
+ }
+ if (tk == blinkw)
+ blinkw = nil;
+ tkextnfreeobj(tk);
+ tkmethod[tk->type]->free(tk);
+ tkputenv(tk->env);
+ tkfreebind(tk->binds);
+ if(tk->name != nil)
+ free(tk->name);
+ free(tk);
+}
+
+char*
+tkaddchild(TkTop *t, Tk *tk, TkName **names)
+{
+ TkName *n;
+ Tk *f, **l;
+ int found, len;
+ char *s, *ep;
+
+ n = *names;
+ if(n == nil || n->name[0] != '.'){
+ if(n != nil)
+ tkerr(t, n->name);
+ return TkBadwp;
+ }
+
+ if (n->name[1] == '\0')
+ return TkDupli;
+
+ /*
+ * check that the name is well-formed.
+ * ep will point to end of parent component of the name.
+ */
+ ep = nil;
+ for (s = n->name + 1; *s; s++) {
+ if (*s == '.'){
+ tkerr(t, n->name);
+ return TkBadwp;
+ }
+ for (; *s && *s != '.'; s++)
+ ;
+ if (*s == '\0')
+ break;
+ ep = s;
+ }
+ if (ep == s - 1){
+ tkerr(t, n->name);
+ return TkBadwp;
+ }
+ if (ep == nil)
+ ep = n->name + 1;
+ len = ep - n->name;
+
+ found = 0;
+ l = &t->root;
+ for(f = *l; f; f = f->siblings) {
+ if (f->name != nil) {
+ if (strcmp(n->name, f->name->name) == 0)
+ return TkDupli;
+ if (!found &&
+ strncmp(n->name, f->name->name, len) == 0 &&
+ f->name->name[len] == '\0')
+ found = 1;
+ }
+ l = &f->siblings;
+ }
+ if (0) { /* don't enable this until a reasonably major release... if ever */
+ /*
+ * parent widget must already exist
+ */
+ if (!found){
+ tkerr(t, n->name);
+ return TkBadwp;
+ }
+ }
+ *l = tk;
+ tk->name = n;
+ *names = n->link;
+
+ return nil;
+}
+
+Tk*
+tklook(TkTop *t, char *wp, int parent)
+{
+ Tk *f;
+ char *p, *q;
+
+ if(wp == nil)
+ return nil;
+
+ if(parent) {
+ p = strdup(wp);
+ if(p == nil)
+ return nil;
+ q = strrchr(p, '.');
+ if(q == nil)
+ abort();
+ if(q == p) {
+ free(p);
+ return t->root;
+ }
+ *q = '\0';
+ } else
+ p = wp;
+
+ for(f = t->root; f; f = f->siblings)
+ if ((f->name != nil) && (strcmp(f->name->name, p) == 0))
+ break;
+
+ if(f != nil && (f->flag & Tkdestroy))
+ f = nil;
+
+ if (parent)
+ free(p);
+ return f;
+}
+
+void
+tktextsdraw(Image *img, Rectangle r, TkEnv *e, int sbw)
+{
+ Image *l, *d;
+ Rectangle s;
+
+ draw(img, r, tkgc(e, TkCselectbgnd), nil, ZP);
+ s.min = r.min;
+ s.min.x -= sbw;
+ s.min.y -= sbw;
+ s.max.x = r.max.x;
+ s.max.y = r.min.y;
+ l = tkgc(e, TkCselectbgndlght);
+ draw(img, s, l, nil, ZP);
+ s.max.x = s.min.x + sbw;
+ s.max.y = r.max.y + sbw;
+ draw(img, s, l, nil, ZP);
+ s.max = r.max;
+ s.max.x += sbw;
+ s.max.y += sbw;
+ s.min.x = r.min.x;
+ s.min.y = r.max.y;
+ d = tkgc(e, TkCselectbgnddark);
+ draw(img, s, d, nil, ZP);
+ s.min.x = r.max.x;
+ s.min.y = r.min.y - sbw;
+ draw(img, s, d, nil, ZP);
+}
+
+void
+tkbox(Image *i, Rectangle r, int bd, Image *fill)
+{
+ if (bd > 0) {
+ draw(i, Rect(r.min.x, r.min.y, r.max.x, r.min.y+bd), fill, nil, ZP);
+ draw(i, Rect(r.min.x, r.min.y+bd, r.min.x+bd, r.max.y-bd), fill, nil, ZP);
+ draw(i, Rect(r.min.x, r.max.y-bd, r.max.x, r.max.y), fill, nil, ZP);
+ draw(i, Rect(r.max.x-bd, r.min.y+bd, r.max.x, r.max.y), fill, nil, ZP);
+ }
+}
+
+void
+tkbevel(Image *i, Point o, int w, int h, int bw, Image *top, Image *bottom)
+{
+ Rectangle r;
+ int x, border;
+
+ border = 2 * bw;
+
+ r.min = o;
+ r.max.x = r.min.x + w + border;
+ r.max.y = r.min.y + bw;
+ draw(i, r, top, nil, ZP);
+
+ r.max.x = r.min.x + bw;
+ r.max.y = r.min.y + h + border;
+ draw(i, r, top, nil, ZP);
+
+ r.max.x = o.x + w + border;
+ r.max.y = o.y + h + border;
+ r.min.x = o.x + bw;
+ r.min.y = r.max.y - bw;
+ for(x = 0; x < bw; x++) {
+ draw(i, r, bottom, nil, ZP);
+ r.min.x--;
+ r.min.y++;
+ }
+ r.min.x = o.x + bw + w;
+ r.min.y = o.y + bw;
+ for(x = bw; x >= 0; x--) {
+ draw(i, r, bottom, nil, ZP);
+ r.min.x++;
+ r.min.y--;
+ }
+}
+
+/*
+ * draw a relief border.
+ * color is an index into tk->env->colors and assumes
+ * light and dark versions following immediately after
+ * that index
+ */
+void
+tkdrawrelief(Image *i, Tk *tk, Point o, int color, int rlf)
+{
+ TkEnv *e;
+ Image *l, *d, *t;
+ int h, w, bd, bd1, bd2;
+
+ if(tk->borderwidth == 0)
+ return;
+
+ h = tk->act.height;
+ w = tk->act.width;
+
+ e = tk->env;
+ if (color == TkCbackgnd || color == TkCselectbgnd || color == TkCactivebgnd) {
+ l = tkgc(e, color+TkLightshade);
+ d = tkgc(e, color+TkDarkshade);
+ } else {
+ l = tkgshade(e, color, TkLightshade);
+ d = tkgshade(e, color, TkDarkshade);
+ }
+ bd = tk->borderwidth;
+ if(rlf < 0)
+ rlf = TKraised;
+ switch(rlf) {
+ case TKflat:
+ break;
+ case TKsunken:
+ tkbevel(i, o, w, h, bd, d, l);
+ break;
+ case TKraised:
+ tkbevel(i, o, w, h, bd, l, d);
+ break;
+ case TKgroove:
+ t = d;
+ d = l;
+ l = t;
+ /* fall through */
+ case TKridge:
+ bd1 = bd/2;
+ bd2 = bd - bd1;
+ if(bd1 > 0)
+ tkbevel(i, o, w + 2*bd2, h + 2*bd2, bd1, l, d);
+ o.x += bd1;
+ o.y += bd1;
+ tkbevel(i, o, w, h, bd2, d, l);
+ break;
+ }
+}
+
+Point
+tkstringsize(Tk *tk, char *text)
+{
+ char *q;
+ int locked;
+ Display *d;
+ Point p, t;
+
+ if(text == nil) {
+ p.x = 0;
+ p.y = tk->env->font->height;
+ return p;
+ }
+
+ d = tk->env->top->display;
+ locked = lockdisplay(d);
+
+ p = ZP;
+ while(*text) {
+ q = strchr(text, '\n');
+ if(q != nil)
+ *q = '\0';
+ t = stringsize(tk->env->font, text);
+ p.y += t.y;
+ if(p.x < t.x)
+ p.x = t.x;
+ if(q == nil)
+ break;
+ text = q+1;
+ *q = '\n';
+ }
+ if(locked)
+ unlockdisplay(d);
+
+ return p;
+}
+
+static char*
+tkul(Image *i, Point o, Image *col, int ul, Font *f, char *text)
+{
+ char c, *v;
+ Rectangle r;
+
+ v = text+ul+1;
+ c = *v;
+ *v = '\0';
+ r.max = stringsize(f, text);
+ r.max = addpt(r.max, o);
+ r.min = stringsize(f, v-1);
+ *v = c;
+ r.min.x = r.max.x - r.min.x;
+ r.min.y = r.max.y - 1;
+ r.max.y += 2;
+ draw(i, r, col, nil, ZP);
+
+ return nil;
+}
+
+char*
+tkdrawstring(Tk *tk, Image *i, Point o, char *text, int ul, Image *col, int j)
+{
+ int n, l, maxl, sox;
+ char *q, *txt;
+ Point p;
+ TkEnv *e;
+
+ e = tk->env;
+ sox = maxl = 0;
+ if(j != Tkleft){
+ maxl = 0;
+ txt = text;
+ while(*txt){
+ q = strchr(txt, '\n');
+ if(q != nil)
+ *q = '\0';
+ l = stringwidth(e->font, txt);
+ if(l > maxl)
+ maxl = l;
+ if(q == nil)
+ break;
+ txt = q+1;
+ *q = '\n';
+ }
+ sox = o.x;
+ }
+ while(*text) {
+ q = strchr(text, '\n');
+ if(q != nil)
+ *q = '\0';
+ if(j != Tkleft){
+ o.x = sox;
+ l = stringwidth(e->font, text);
+ if(j == Tkcenter)
+ o.x += (maxl-l)/2;
+ else
+ o.x += maxl-l;
+ }
+ p = string(i, o, col, o, e->font, text);
+ if(ul >= 0) {
+ n = strlen(text);
+ if(ul < n) {
+ char *r;
+
+ r = tkul(i, o, col, ul, e->font, text);
+ if(r != nil)
+ return r;
+ ul = -1;
+ }
+ ul -= n;
+ }
+ o.y += e->font->height;
+ if(q == nil)
+ break;
+ text = q+1;
+ *q = '\n';
+ }
+ return nil;
+}
+
+/* for debugging */
+char*
+tkname(Tk *tk)
+{
+ return tk ? (tk->name ? tk->name->name : "(noname)") : "(nil)";
+}
+
+Tk*
+tkdeliver(Tk *tk, int event, void *data)
+{
+ Tk *dest;
+//print("tkdeliver %v to %s\n", event, tkname(tk));
+ if(tk == nil || ((tk->flag&Tkdestroy) && event != TkDestroy))
+ return tk;
+
+ if(event&(TkFocusin|TkFocusout) && (tk->flag&Tktakefocus))
+ tk->dirty = tkrect(tk, 1);
+
+ if (tkmethod[tk->type]->deliver != nil) {
+ dest = tkmethod[tk->type]->deliver(tk, event, data);
+ if (dest == nil)
+ return tk;
+ tkdirty(tk);
+ return dest;
+ }
+
+ if((tk->flag & Tkdisabled) == 0)
+ tksubdeliver(tk, tk->binds, event, data, 0);
+ tkdirty(tk);
+ return tk;
+}
+
+static int
+nullop(char *fmt, ...)
+{
+ USED(fmt);
+ return 0;
+}
+
+int
+tksubdeliver(Tk *tk, TkAction *binds, int event, void *data, int extn)
+{
+
+ TkAction *a;
+ int delivered, genkey, delivered2, iskey;
+//int (*debug)(char *fmt, ...);
+ if (!extn)
+ return tkextndeliver(tk, binds, event, data);
+
+//debug = (tk->name && !strcmp(tk->name->name, ".cd")) ? print : nullop;
+//debug("subdeliver %v\n", event);
+
+ if (event & TkTakefocus) {
+ if (tk->flag & Tktakefocus)
+ tksetkeyfocus(tk->env->top, tk, 0);
+ return TkDdelivered;
+ }
+
+ delivered = TkDnone;
+ genkey = 0;
+ for(a = binds; a != nil; a = a->link) {
+ if(event == a->event) {
+//debug(" exact match on %v\n", a->event);
+ tkcmdbind(tk, event, a->arg, data);
+ delivered = TkDdelivered;
+ } else if (a->event == TkKey && (a->type>>8)==TkAadd)
+ genkey = 1;
+ }
+ if(delivered != TkDnone && !((event & TkKey) && genkey))
+ return delivered;
+
+ delivered2 = delivered;
+ for(a = binds; a != nil; a = a->link) {
+ /*
+ * only bind to non-specific key events; if a specific
+ * key event has already been delivered, only deliver event if
+ * the non-specific binding was added. (TkAadd)
+ */
+ if (a->event & TkExtns)
+ continue;
+ iskey = (a->event & TkKey);
+ if (iskey ^ (event & TkKey))
+ continue;
+ if(iskey && (TKKEY(a->event) != 0
+ || ((a->type>>8) != TkAadd && delivered != TkDnone)))
+ continue;
+ if(!iskey && (a->event & TkMotion) && (a->event&TkEpress) != 0)
+ continue;
+ if(!(event & TkDouble) && (a->event & TkDouble))
+ continue;
+ if((event & ~TkDouble) & a->event) {
+//debug(" partial match on %v\n", a->event);
+ tkcmdbind(tk, event, a->arg, data);
+ delivered2 = TkDdelivered;
+ }
+ }
+ return delivered2;
+}
+
+void
+tkcancel(TkAction **l, int event)
+{
+ TkAction *a;
+
+ for(a = *l; a; a = *l) {
+ if(a->event == event) {
+ *l = a->link;
+ a->link = nil;
+ tkfreebind(a);
+ continue;
+ }
+ l = &a->link;
+ }
+}
+
+static void
+tkcancela(TkAction **l, int event, int type, char *arg)
+{
+ TkAction *a;
+
+ for(a = *l; a; a = *l) {
+ if(a->event == event && strcmp(a->arg, arg) == 0 && (a->type&0xff) == type){
+ *l = a->link;
+ a->link = nil;
+ tkfreebind(a);
+ continue;
+ }
+ l = &a->link;
+ }
+}
+
+char*
+tkaction(TkAction **l, int event, int type, char *arg, int how)
+{
+ TkAction *a;
+
+ if(arg == nil)
+ return nil;
+ if(how == TkArepl)
+ tkcancel(l, event);
+ else if(how == TkAadd){
+ for(a = *l; a; a = a->link)
+ if(a->event == event && strcmp(a->arg, arg) == 0 && (a->type&0xff) == type){
+ a->type = type + (how << 8);
+ return nil;
+ }
+ }
+ else if(how == TkAsub){
+ tkcancela(l, event, type, arg);
+ if(type == TkDynamic) /* should always be the case */
+ free(arg);
+ return nil;
+ }
+
+ a = malloc(sizeof(TkAction));
+ if(a == nil) {
+ if(type == TkDynamic)
+ free(arg);
+ return TkNomem;
+ }
+
+ a->event = event;
+ a->arg = arg;
+ a->type = type + (how << 8);
+
+ a->link = *l;
+ *l = a;
+
+ return nil;
+}
+
+char*
+tkitem(char *buf, char *a)
+{
+ char *e;
+
+ while(*a && (*a == ' ' || *a == '\t'))
+ a++;
+
+ e = buf + Tkmaxitem - 1;
+ while(*a && *a != ' ' && *a != '\t' && buf < e)
+ *buf++ = *a++;
+
+ *buf = '\0';
+ while(*a && (*a == ' ' || *a == '\t'))
+ a++;
+ return a;
+}
+
+int
+tkismapped(Tk *tk)
+{
+ while(tk->master)
+ tk = tk->master;
+
+ /* We need subwindows of text & canvas to appear mapped always
+ * so that the geom function update are seen by the parent
+ * widget
+ */
+ if((tk->flag & Tkwindow) == 0)
+ return 1;
+
+ return tk->flag & Tkmapped;
+}
+
+/*
+ * Return absolute screen position of tk (just outside its top-left border).
+ * When a widget is embedded in a text or canvas widget, we need to
+ * use the text or canvas's relpos() function instead of act{x,y}, and we
+ * need to folow up the parent pointer rather than the master one.
+ */
+Point
+tkposn(Tk *tk)
+{
+ Tk *f, *last;
+ Point g;
+
+ last = tk;
+ if(tk->parent != nil) {
+ g = tkmethod[tk->parent->type]->relpos(tk);
+ f = tk->parent;
+ }
+ else {
+ g.x = tk->act.x;
+ g.y = tk->act.y;
+ f = tk->master;
+ }
+ while(f) {
+ g.x += f->borderwidth;
+ g.y += f->borderwidth;
+ last = f;
+ if(f->parent != nil) {
+ g = addpt(g, tkmethod[f->parent->type]->relpos(f));
+ f = f->parent;
+ }
+ else {
+ g.x += f->act.x;
+ g.y += f->act.y;
+ f = f->master;
+ }
+ }
+ if (last->flag & Tkwindow)
+ g = addpt(g, TKobj(TkWin, last)->req);
+ return g;
+}
+
+/*
+ * convert screen coords to local widget coords
+ */
+Point
+tkscrn2local(Tk *tk, Point p)
+{
+ p = subpt(p, tkposn(tk));
+ p.x -= tk->borderwidth;
+ p.y -= tk->borderwidth;
+ return p;
+}
+
+int
+tkvisiblerect(Tk *tk, Rectangle *rr)
+{
+ Rectangle r;
+ Point g;
+ Tk *f, *last;
+ g = Pt(tk->borderwidth, tk->borderwidth);
+ last = tk;
+ if(tk->parent != nil) {
+ g = addpt(g, tkmethod[tk->parent->type]->relpos(tk));
+ f = tk->parent;
+ } else {
+ g.x += tk->act.x;
+ g.y += tk->act.y;
+ f = tk->master;
+ }
+ if (f == nil) {
+ *rr = tkrect(tk, 1);
+ return 1;
+ }
+ r = rectaddpt(tkrect(tk, 1), g);
+ while (f) {
+ if (!rectclip(&r, tkrect(f, 0)))
+ return 0;
+ g.x = f->borderwidth;
+ g.y = f->borderwidth;
+ last = f;
+ if (f->parent != nil) {
+ g = addpt(g, tkmethod[f->parent->type]->relpos(f));
+ f = f->parent;
+ } else {
+ g.x += f->act.x;
+ g.y += f->act.y;
+ f = f->master;
+ }
+ r = rectaddpt(r, g);
+ }
+ if (last->flag & Tkwindow)
+ r = rectaddpt(r, TKobj(TkWin, last)->act);
+ /*
+ * now we have the visible rectangle in screen coords;
+ * subtract actx+borderwidth and we've got it back in
+ * widget-local coords again
+ */
+ r = rectsubpt(r, tkposn(tk));
+ *rr = rectsubpt(r, Pt(tk->borderwidth, tk->borderwidth));
+ return 1;
+}
+
+Point
+tkanchorpoint(Rectangle r, Point size, int anchor)
+{
+ int dx, dy;
+ Point p;
+
+ p = r.min;
+ dx = Dx(r) - size.x;
+ dy = Dy(r) - size.y;
+ if((anchor & (Tknorth|Tksouth)) == 0)
+ p.y += dy/2;
+ else
+ if(anchor & Tksouth)
+ p.y += dy;
+
+ if((anchor & (Tkeast|Tkwest)) == 0)
+ p.x += dx/2;
+ else
+ if(anchor & Tkeast)
+ p.x += dx;
+ return p;
+}
+
+static char*
+tkunits(char c, int *d, TkEnv *e)
+{
+ switch(c) {
+ default:
+ if(c >= '0' || c <= '9' || c == '.')
+ break;
+ return TkBadvl;
+ case '\0':
+ break;
+ case 'c': /* Centimeters */
+ *d *= (Tkdpi*100)/254;
+ break;
+ case 'm': /* Millimeters */
+ *d *= (Tkdpi*10)/254;
+ break;
+ case 'i': /* Inches */
+ *d *= Tkdpi;
+ break;
+ case 'p': /* Points */
+ *d = (*d*Tkdpi)/72;
+ break;
+ case 'w': /* Character width */
+ if(e == nil)
+ return TkBadvl;
+ *d = *d * e->wzero;
+ break;
+ case 'h': /* Character height */
+ if(e == nil)
+ return TkBadvl;
+ *d = *d * e->font->height;
+ break;
+ }
+ return nil;
+}
+
+int
+TKF2I(int f)
+{
+ if (f >= 0)
+ return (f + Tkfpscalar/2) / Tkfpscalar;
+ return (f - Tkfpscalar/2) / Tkfpscalar;
+}
+
+/*
+ * Parse a floating point number into a decimal fixed point representation
+ */
+char*
+tkfrac(char **arg, int *f, TkEnv *env)
+{
+ int c, minus, i, fscale, seendigit;
+ char *p, *e;
+
+ seendigit = 0;
+
+ p = *arg;
+ p = tkskip(p, " \t");
+
+ minus = 0;
+ if(*p == '-') {
+ minus = 1;
+ p++;
+ }
+ i = 0;
+ while(*p) {
+ c = *p;
+ if(c == '.')
+ break;
+ if(c < '0' || c > '9')
+ break;
+ i = i*10 + (c - '0');
+ seendigit = 1;
+ p++;
+ }
+ i *= Tkfpscalar;
+ if(*p == '.')
+ p++;
+ fscale = Tkfpscalar;
+ while(*p && *p >= '0' && *p <= '9') {
+ fscale /= 10;
+ i += fscale * (*p++ - '0');
+ seendigit = 1;
+ }
+
+ if(minus)
+ i = -i;
+
+ if(!seendigit)
+ return TkBadvl;
+ e = tkunits(*p, &i, env);
+ if (e != nil)
+ return e;
+ while (*p && *p != ' ' && *p != '\t')
+ p++;
+ *arg = p;
+ *f = i;
+ return nil;
+}
+
+char*
+tkfracword(TkTop *t, char **arg, int *f, TkEnv *env)
+{
+ char *p;
+ char buf[Tkminitem];
+
+ *arg = tkword(t, *arg, buf, buf+sizeof(buf), nil);
+ p = buf;
+ return tkfrac(&p, f, env);
+}
+
+char*
+tkfprint(char *v, int frac)
+{
+ int fscale;
+
+ if(frac < 0) {
+ *v++ = '-';
+ frac = -frac;
+ }
+ v += sprint(v, "%d", frac/Tkfpscalar);
+ frac = frac%Tkfpscalar;
+ if(frac != 0)
+ *v++ = '.';
+ fscale = Tkfpscalar/10;
+ while(frac) {
+ *v++ = '0' + frac/fscale;
+ frac %= fscale;
+ fscale /= 10;
+ }
+ *v = '\0';
+ return v;
+}
+
+char*
+tkvalue(char **val, char *fmt, ...)
+{
+ va_list arg;
+ Fmt fmtx;
+
+ if(val == nil)
+ return nil;
+
+ fmtstrinit(&fmtx);
+ if(*val != nil)
+ if(fmtprint(&fmtx, "%s", *val) < 0)
+ return TkNomem;
+ va_start(arg, fmt);
+ fmtvprint(&fmtx, fmt, arg);
+ va_end(arg);
+ free(*val);
+ *val = fmtstrflush(&fmtx);
+ if(*val == nil)
+ return TkNomem;
+ return nil;
+}
+
+static char*
+tkwidgetcmd(TkTop *t, Tk *tk, char *arg, char **val)
+{
+ TkMethod *cm;
+ TkCmdtab *ct;
+ int bot, top, new, r;
+ char *e, *buf;
+
+ buf = mallocz(Tkmaxitem, 0);
+ if(buf == nil)
+ return TkNomem;
+
+ arg = tkword(t, arg, buf, buf+Tkmaxitem, nil);
+ if(val != nil)
+ *val = nil;
+
+ cm = tkmethod[tk->type];
+
+ e = TkBadcm;
+ bot = 0;
+ top = cm->ncmd - 1;
+
+ while(bot <= top) {
+ new = (bot + top)/2;
+ ct = &cm->cmd[new];
+ r = strcmp(ct->name, buf);
+ if(r == 0) {
+ e = ct->fn(tk, arg, val);
+ break;
+ }
+ if(r < 0)
+ bot = new + 1;
+ else
+ top = new - 1;
+ }
+ free(buf);
+ tkdirty(tk);
+ return e;
+}
+
+Rectangle
+tkrect(Tk *tk, int withborder)
+{
+ Rectangle r;
+ int bd;
+ bd = withborder ? tk->borderwidth : 0;
+ r.min.x = -bd;
+ r.min.y = -bd;
+ r.max.x = tk->act.width + bd;
+ r.max.y = tk->act.height + bd;
+ return r;
+}
+
+void
+tkdirty(Tk *tk)
+{
+ Tk *sub;
+ Point rel;
+ Rectangle dirty;
+ int isdirty, transparent;
+
+ /*
+ * mark as dirty all views underneath a dirty transparent widget
+ * down to the first opaque widget.
+ * inform parents about any dirtiness.
+
+ * XXX as Tksubsub never gets reset, testing against Tksubsub doesn't *exactly* test
+ * whether we're in a canvas/text widget, but merely
+ * whether it has ever been. Tksubsub should probably be reset on unpack.
+ */
+ isdirty = Dx(tk->dirty) > 0;
+ transparent = tk->flag & Tktransparent;
+ sub = tk;
+ while (isdirty && ((tk->flag&Tksubsub) || transparent)) {
+ if (tk->master != nil) {
+ if (transparent) {
+ rel.x = tk->act.x + tk->borderwidth;
+ rel.y = tk->act.y + tk->borderwidth;
+ dirty = rectaddpt(sub->dirty, rel);
+ sub = tk->master;
+ combinerect(&sub->dirty, dirty);
+ transparent = sub->flag & Tktransparent;
+ }
+ tk = tk->master;
+ } else if (tk->parent != nil) {
+ tkmethod[tk->parent->type]->dirtychild(sub);
+ tk = sub = tk->parent;
+ isdirty = Dx(sub->dirty) > 0;
+ transparent = sub->flag & Tktransparent;
+ } else
+ break;
+ }
+}
+
+static int
+qcmdcmp(const void *a, const void *b)
+{
+ return strcmp(((TkCmdtab*)a)->name, ((TkCmdtab*)b)->name);
+}
+
+void
+tksorttable(void)
+{
+ int i;
+ TkMethod *c;
+ TkCmdtab *cmd;
+
+ for(i = 0; i < TKwidgets; i++) {
+ c = tkmethod[i];
+ if(c->cmd == nil)
+ continue;
+
+ for(cmd = c->cmd; cmd->name != nil; cmd++)
+ ;
+ c->ncmd = cmd - c->cmd;
+
+ qsort(c->cmd, c->ncmd, sizeof(TkCmdtab), qcmdcmp);
+ }
+}
+
+static char*
+tksinglecmd(TkTop *t, char *arg, char **val)
+{
+ Tk *tk;
+ int bot, top, new;
+ char *e, *buf;
+
+ if(t->debug)
+ print("tk: '%s'\n", arg);
+
+ buf = mallocz(Tkmaxitem, 0);
+ if(buf == nil)
+ return TkNomem;
+
+ arg = tkword(t, arg, buf, buf+Tkmaxitem, nil);
+ switch(buf[0]) {
+ case '\0':
+ free(buf);
+ return nil;
+ case '.':
+ tk = tklook(t, buf, 0);
+ if(tk == nil){
+ tkerr(t, buf);
+ free(buf);
+ return TkBadwp;
+ }
+ e = tkwidgetcmd(t, tk, arg, val);
+ free(buf);
+ return e;
+ }
+
+ bot = 0;
+ top = nelem(cmdmain) - 1;
+ e = TkBadcm;
+ while(bot <= top) {
+ int rc;
+ new = (bot + top)/2;
+ rc = strcmp(cmdmain[new].name, buf);
+ if(!rc) {
+ e = cmdmain[new].fn(t, arg, val);
+ break;
+ }
+
+ if(rc < 0)
+ bot = new + 1;
+ else
+ top = new - 1;
+ }
+ free(buf);
+ return e;
+}
+
+static char*
+tkmatch(int inc, int dec, char *p)
+{
+ int depth, esc, c;
+
+ esc = 0;
+ depth = 1;
+ while(*p) {
+ c = *p;
+ if(esc == 0) {
+ if(c == inc)
+ depth++;
+ if(c == dec)
+ depth--;
+ if(depth == 0)
+ return p;
+ }
+ if(c == '\\' && esc == 0)
+ esc = 1;
+ else
+ esc = 0;
+ p++;
+ }
+ return nil;
+}
+
+char*
+tkexec(TkTop *t, char *arg, char **val)
+{
+ int cmdsz, n;
+ char *p, *cmd, *e, *c;
+
+ if(t->execdepth >= 0 && ++t->execdepth > 128)
+ return TkDepth;
+
+ cmd = nil;
+ cmdsz = 0;
+
+ p = arg;
+ for(;;) {
+ switch(*p++) {
+ case '[':
+ p = tkmatch('[', ']', p);
+ if(p == nil){
+ free(cmd);
+ return TkSyntx;
+ }
+ break;
+ case '{':
+ p = tkmatch('{', '}', p);
+ if(p == nil){
+ free(cmd);
+ return TkSyntx;
+ }
+ break;
+ case ';':
+ n = p - arg - 1;
+ if(cmdsz < n)
+ cmdsz = n;
+ c = realloc(cmd, cmdsz+1);
+ if(c == nil){
+ free(cmd);
+ return TkNomem;
+ }
+ cmd = c;
+ memmove(cmd, arg, n);
+ cmd[n] = '\0';
+ e = tksinglecmd(t, cmd, nil);
+ if(e != nil) {
+ t->err = e;
+ strncpy(t->errcmd, cmd, sizeof(t->errcmd));
+ t->errcmd[sizeof(t->errcmd)-1] = '\0';
+ free(cmd);
+ return e;
+ }
+ arg = p;
+ break;
+ case '\0':
+ case '\'':
+ free(cmd);
+ e = tksinglecmd(t, arg, val);
+ if(e != nil) {
+ t->err = e;
+ strncpy(t->errcmd, arg, sizeof(t->errcmd));
+ t->errcmd[sizeof(t->errcmd)-1] = '\0';
+ }
+ return e;
+ }
+ }
+}
+
+static struct {
+ char *name;
+ int mask;
+} events[] = {
+ "Button1P", TkButton1P,
+ "Button1R", TkButton1R,
+ "Button2P", TkButton2P,
+ "Button2R", TkButton2R,
+ "Button3P", TkButton3P,
+ "Button3R", TkButton3R,
+ "Button4P", TkButton4P,
+ "Button4R", TkButton4R,
+ "Button5P", TkButton5P,
+ "Button5R", TkButton5R,
+ "Button6P", TkButton6P,
+ "Button6R", TkButton6R,
+ "Extn1", TkExtn1,
+ "Extn2", TkExtn2,
+ "Takefocus", TkTakefocus,
+ "Destroy", TkDestroy,
+ "Enter", TkEnter,
+ "Leave", TkLeave,
+ "Motion", TkMotion,
+ "Map", TkMap,
+ "Unmap", TkUnmap,
+ "Key", TkKey,
+ "Focusin", TkFocusin,
+ "Focusout", TkFocusout,
+ "Configure", TkConfigure,
+ "Double", TkDouble,
+ 0
+};
+
+int
+tkeventfmt(Fmt *f)
+{
+ int k, i, d;
+ int e;
+
+ e = va_arg(f->args, int);
+
+ if ((f->flags & FmtSharp) && e == TkMotion)
+ return 0;
+ fmtprint(f, "<");
+ k = -1;
+ if (e & TkKey) {
+ k = e & 0xffff;
+ e &= ~0xffff;
+ }
+ d = 0;
+ for (i = 0; events[i].name; i++) {
+ if (e & events[i].mask) {
+ if (d++)
+ fmtprint(f, "|");
+ fmtprint(f, "%s", events[i].name);
+ }
+ }
+ if (k != -1) {
+ fmtprint(f, "[%c]", k);
+ } else if (e == 0)
+ fmtprint(f, "Noevent");
+ fmtprint(f, ">");
+ return 0;
+}
+
+void
+tkerr(TkTop *t, char *e)
+{
+ if(t != nil && e != nil){
+ strncpy(t->errx, e, sizeof(t->errx));
+ t->errx[sizeof(t->errx)-1] = '\0';
+ }
+}
+
+char*
+tkerrstr(TkTop *t, char *e)
+{
+ char *s = malloc(strlen(e)+1+strlen(t->errx)+1);
+
+ if(s == nil)
+ return nil;
+ strcpy(s, e);
+ if(*e == '!'){
+ strcat(s, " ");
+ strcat(s, t->errx);
+ }
+ t->errx[0] = '\0';
+ return s;
+}
+
+char*
+tksetmgrab(TkTop *t, Tk *tk)
+{
+ Tk *omgrab;
+ TkCtxt *c;
+ c = t->ctxt;
+ if (tk == nil) {
+ omgrab = c->mgrab;
+ c->mgrab = nil;
+ /*
+ * don't enterleave if grab reset would cause no leave event
+ */
+ if (!(omgrab != nil && (omgrab->flag & Tknograb) &&
+ c->entered != nil && (c->entered->flag & Tknograb)))
+ tkenterleave(t);
+ } else {
+ if (c->focused && c->mfocus != nil && c->mfocus->env->top != tk->env->top)
+ return "!grab already taken on another toplevel";
+ c->mgrab = tk;
+ if (tk->flag & Tknograb) {
+ if (c->focused) {
+ c->focused = 0;
+ c->mfocus = nil;
+ }
+ } else if (c->focused || c->mstate.b != 0) {
+ c->focused = 1;
+ c->mfocus = tk;
+ }
+//print("setmgrab(%s) focus now %s\n", tkname(tk), tkname(c->mfocus));
+ tkenterleave(t);
+ }
+ return nil;
+}
+
+int
+tkinsidepoly(Point *poly, int np, int winding, Point p)
+{
+ Point pi, pj;
+ int i, j, hit;
+
+ hit = 0;
+ j = np - 1;
+ for (i = 0; i < np; j = i++) {
+ pi = poly[i];
+ pj = poly[j];
+ if ((pi.y <= p.y && p.y < pj.y || pj.y <= p.y && p.y < pi.y) &&
+ p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x) {
+ if (winding == 1 || pi.y > p.y)
+ hit++;
+ else
+ hit--;
+ }
+ }
+ return (hit & winding) != 0;
+}
+
+int
+tklinehit(Point *a, int np, int w, Point p)
+{
+ Point *b;
+ int z, nx, ny, nrm;
+ while(np-- > 1) {
+ b = a+1;
+ nx = a->y - b->y;
+ ny = b->x - a->x;
+ nrm = (nx < 0? -nx : nx) + (ny < 0? -ny : ny);
+ if(nrm)
+ z = (p.x-b->x)*nx/nrm + (p.y-b->y)*ny/nrm;
+ else
+ z = (p.x-b->x) + (p.y-b->y);
+ if(z < 0)
+ z = -z;
+ if(z < w)
+ return 1;
+ a++;
+ }
+ return 0;
+}
+
+int
+tkiswordchar(int c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c >= 0xA0;
+}
+
+int
+tkhaskeyfocus(Tk *tk)
+{
+ if (tk == nil || tk->env->top->focused == 0)
+ return 0;
+ return tk == tk->env->top->ctxt->tkkeygrab;
+}
+
+static int
+rptactive(void *v)
+{
+ int id = (int)v;
+ if (id == rptid)
+ return 1;
+ return 0;
+}
+
+static int
+ckrpt(void *v, int interval)
+{
+ int id = (int)v;
+ if (id != rptid)
+ return -1;
+ if (interval < rptto)
+ return 0;
+ return 1;
+}
+
+static void
+dorpt(void *v)
+{
+ int id = (int)v;
+
+ if (id == rptid) {
+ rptto = rptint;
+ (*rptcb)(rptw, rptnote, 0);
+ if (rptint <= 0) {
+ rptid++;
+ rptw = nil;
+ }
+ }
+}
+
+void
+tkcancelrepeat(Tk *tk)
+{
+ if (tk == rptw) {
+ rptid++;
+ rptw = nil;
+ }
+}
+
+void
+tkrepeat(Tk *tk, void (*callback)(Tk*, void*, int), void *note, int pause, int interval)
+{
+ rptid++;
+ if (tk != rptw && rptw != nil)
+ /* existing callback being replaced- report to owner */
+ (*rptcb)(rptw, rptnote, 1);
+ rptw = tk;
+ if (tk == nil || callback == nil)
+ return;
+ rptnote = note;
+ rptcb = callback;
+ rptto = pause;
+ rptint = interval;
+ if (!autorpt)
+ autorpt = rptproc("autorepeat", TkRptclick, (void*)rptid, rptactive, ckrpt, dorpt);
+ else
+ rptwakeup((void*)rptid, autorpt);
+}
+
+static int
+blinkactive(void *v)
+{
+ USED(v);
+ return blinkw != nil;
+}
+
+static int
+ckblink(void *v, int interval)
+{
+ USED(v);
+ USED(interval);
+
+ if (blinkw == nil)
+ return -1;
+ if (blinkignore) {
+ blinkignore = 0;
+ return 0;
+ }
+ return 1;
+}
+
+static void
+doblink(void *v)
+{
+ USED(v);
+
+ if (blinkw == nil)
+ return;
+ blinkcb(blinkw, blinkon++ & 1);
+ tkupdate(blinkw->env->top);
+}
+
+void
+tkblinkreset(Tk *tk)
+{
+ if (blinkw == tk) {
+ blinkignore = 1;
+ blinkon = 0;
+ }
+}
+
+void
+tkblink(Tk *tk, void (*callback)(Tk*, int))
+{
+ if (tk == nil || callback == nil) {
+ blinkw = nil;
+ return;
+ }
+ blinkw = tk;
+ blinkcb = callback;
+ if (!blinkrpt)
+ blinkrpt = rptproc("blinker", TkBlinkinterval, nil, blinkactive, ckblink, doblink);
+ else
+ rptwakeup(nil, blinkrpt);
+}