shithub: purgatorio

ref: f5cc6fbe3a7bcf8bdb002c646ddd519014afafd2
dir: /libtk/utils.c/

View raw version
#include "lib9.h"
#include "draw.h"
#include "tk.h"

struct TkCol
{
	ulong	rgba1;
	ulong	rgba3;	/* if mixed, otherwise DNotacolor */
	Image*	i;
	TkCol*	forw;
};

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

static int
tkcachecol(TkCtxt *c, Image *i, ulong one, ulong three)
{
	TkCol *cc;

	cc = malloc(sizeof(*cc));
	if(cc == nil)
		return 0;
	cc->rgba1 = one;
	cc->rgba3 = three;
	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 1;
}

static Image*
tkfindcol(TkCtxt *c, ulong one, ulong three)
{
	TkCol *cc, **l;

	for(l = &c->chead; (cc = *l) != nil; l = &cc->forw)
		if(cc->rgba1 == one && cc->rgba3 == three){
			/* 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;
		}
	return nil;
}

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*
tkcolormix(TkCtxt *c, ulong one, ulong three)
{
	Image *i;
	Display *d;

	i = tkfindcol(c, one, three);
	if(i != nil)
		return i;
	d = c->display;
	i = allocimagemix(d, one, three);
	if(i == nil)
		return d->black;
	if(!tkcachecol(c, i, one, three)){
		freeimage(i);
		return d->black;
	}
	return i;
}

Image*
tkcolor(TkCtxt *c, ulong pix)
{
	Image *i;
	Display *d;
	Rectangle r;

	d = c->display;
	if(pix == DWhite)
		return d->white;
	if(pix == DBlack)
		return d->black;
	i = tkfindcol(c, pix, DNotacolor);
	if(i != nil)
		return i;
	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;
	if(!tkcachecol(c, i, pix, DNotacolor)) {
		freeimage(i);
		return d->black;
	}
	return i;
}

Image*
tkgradient(TkCtxt *c, Rectangle r, int dir, ulong pix0, ulong pix1)
{
	Display *d;
	Image *i;
	uchar *b, *p, *e;
	int c0[3], c1[3], delta[3], a, j, x, y, n, locked;
	Rectangle s;

	d = c->display;
	y = Dy(r);
	x = Dx(r);
	if(x <= 0 || y <= 0) {
		r = Rect(0, 0, 1, 1);
		x = y = 1;
	}
	/* TO DO: diagonal */
	s = r;
	if(dir == Tkhorizontal){
		n = x;
		r.max.y = r.min.y+1;
	}else{
		n = y;
		r.max.x = r.min.x+1;
	}
	b = mallocz(3*n, 0);
	if(b == nil)
		return nil;
	locked = lockdisplay(d);
	i = allocimage(d, r, RGB24, 1, DNofill);
	if(i == nil)
		goto Ret;
	tkrgbavals(pix0, &c0[2], &c0[1], &c0[0], &a);
	tkrgbavals(pix1, &c1[2], &c1[1], &c1[0], &a);
	for(j = 0; j < 3; j++){
		c0[j] <<= 12;
		c1[j] <<= 12;
		delta[j] = ((c1[j]-c0[j])+(1<<11))/n;
	}
	e = b+3*n;
	for(p = b; p < e; p += 3) {
		p[0] = c0[0]>>12;
		p[1] = c0[1]>>12;
		p[2] = c0[2]>>12;
		c0[0] += delta[0];
		c0[1] += delta[1];
		c0[2] += delta[2];
	}
	loadimage(i, r, b, 3*n);
	replclipr(i, 1, s);
Ret:
	if(locked)
		unlockdisplay(d);
	free(b);
	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);
	h = s = v = 0;
	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 void
tkulall(Image *i, Point o, Image *col, Font *f, char *text)
{
	Rectangle r;

	r.max = stringsize(f, text);
	r.max = addpt(r.max, o);
	r.min.x = o.x;
	r.min.y = r.max.y - 1;
	draw(i, r, col, nil, ZP);	
}

static void
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++;
	draw(i, r, col, nil, ZP);	
}

void
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) {
				tkul(i, o, col, ul, e->font, text);
				ul = -1;
			} else if(ul == n) {
				tkulall(i, o, col, e->font, text);
				ul = -1;
			} else
				ul -= n;
		}
		o.y += e->font->height;
		if(q == nil)
			break;
		text = q+1;
		*q = '\n';
	}
}

/* for debugging */
char*
tkname(Tk *tk)
{
	if(tk == nil)
		return "(nil)";
	if(tk->name == nil)
		return "(noname)";
	return tk->name->name;
}

Tk*
tkdeliver(Tk *tk, int event, void *data)
{
	Tk *dest;

	if(tk != nil && ((ulong)tk->type >= TKwidgets || (ulong)tk->name < 4096 && tk->name != nil)){
		print("invalid Tk: type %d name %p\n", tk->type, tk->name);
		abort();
	}
//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;
}

/*
 * if tk is a subwindow or a descendent, return the subwindow;
 * return nil otherwise
 */
Tk*
tkfindsub(Tk *tk)
{
	for(; tk != nil; tk = tk->master){
		if(tk->parent != nil)
			return tk;	/* tk->parent is canvas or text */
	}
	return nil;
}

/*
 * 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 != nil) {
		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);
}

/*
 * debugging
 */
void
tkdump(Tk *tk)
{
	Tk *sl;

	if(tk == nil)
		return;
	if((uint)tk->type < TKwidgets)
		print("%s", tkmethod[tk->type]->name);
	else
		print("TYPE#%#ux", tk->type);
	if(tk->name == nil || (ulong)tk->name < 512)
		print(" NAME %p", tk->name);
	else
		print(" %s", tkname(tk));
	print(" # tk %#p flag %#ux grid %#p", tk, tk->flag, tk->grid);
	if(tk->parent != nil)
		print(" parent [%#p %q]", tk->parent, tkname(tk->parent));
	if(tk->master != nil)
		print(" master [%#p %q]", tk->master, tkname(tk->master));
	if(tk->slave != nil){
		print(" slaves");
		for(sl = tk->slave; sl != nil; sl = sl->next)
			print(" [%#p %q]", sl, tkname(sl));
	}
	print("\n");
	if(tk->type != TKcanvas)
		return;
	tkcvsdump(tk);
}

void
tktopdump(Tk *tk)
{
	TkTop *top;
	Tk *sub;

	if(tk == nil || tk->env == nil){
		print("# %#p no top\n", tk);
		return;
	}
	top = tk->env->top;
	print("# env %#p top %#p\n", tk->env, top);
	if(top != nil){
		for(sub = top->root; sub != nil; sub = sub->siblings)
			tkdump(sub);
	}
}