xref: /inferno-os/libtk/menus.c (revision d3641b487cf5cdc46e9b537d30eb37736e5c7b1a)
1 #include "lib9.h"
2 #include "draw.h"
3 #include "keyboard.h"
4 #include "tk.h"
5 #include "frame.h"
6 #include "label.h"
7 
8 /*
9 arrow annotation for choicebutton: how do we make sure
10 the menu items come up the same size?
11 	- set menu items to same req.width & height as button itself.
12 
13 autorepeat:
14 when we get mouse event at the edge of the screen
15 and the menu overlaps that edge,
16 start autorepeat timer to slide the menu the opposite direction.
17 
18 variable setting + command invocation:
19 is the value of the variable the text or the index?
20 same for the value appended to the command, text or index?
21 
22 if it's reimplemented as a custom widget, how does the custom widget
23 get notified of variable changes?
24 */
25 
26 /* Widget Commands (+ means implemented)
27 	+activate
28 	+add
29 	+cget
30 	+configure
31 	+delete
32 	+entrycget
33 	+entryconfigure
34 	+index
35 	+insert
36 	+invoke
37 	+post
38 	+postcascade
39 	+type
40 	+unpost
41 	+yposition
42 */
43 
44 #define	O(t, e)		((long)(&((t*)0)->e))
45 
46 /* Layout constants */
47 enum {
48 	Sepheight	= 6,	/* Height of menu separator */
49 };
50 
51 #define NOCHOICE "-----"
52 
53 enum {
54 	Startspeed = TKI2F(1),
55 };
56 
57 static
58 TkOption mbopts[] =
59 {
60 	"text",		OPTtext,	O(TkLabel, text),		nil,
61 	"anchor",	OPTflag,	O(TkLabel, anchor),	tkanchor,
62 	"underline",	OPTdist,	O(TkLabel, ul),		nil,
63 	"justify",	OPTstab,	O(TkLabel, justify),	tkjustify,
64 	"menu",		OPTtext,	O(TkLabel, menu),		nil,
65 	"bitmap",	OPTbmap,	O(TkLabel, bitmap),		nil,
66 	"image",	OPTimag,	O(TkLabel, img),		nil,
67 	nil
68 };
69 
70 static
71 TkOption choiceopts[] =
72 {
73 	"variable",	OPTtext,	O(TkLabel, variable),	nil,
74 	"values",	OPTlist,	O(TkLabel, values), nil,
75 	"command", OPTtext, O(TkLabel, command), nil,
76 	nil
77 };
78 
79 static
80 TkEbind mbbindings[] =
81 {
82 	{TkEnter,		"%W tkMBenter %s"},
83 	{TkLeave,		"%W tkMBleave"},
84 	{TkButton1P,		"%W tkMBpress 1"},
85 	{TkKey,		"%W tkMBkey 0x%K"},
86 	{TkButton1P|TkMotion,	"%W tkMBpress 0"},
87 };
88 
89 extern Rectangle bbnil;
90 static char* tkmpost(Tk*, int, int, int, int, int);
91 static void menuclr(Tk*);
92 static void freemenu(Tk*);
93 static void appenditem(Tk*, Tk*, int);
94 static void layout(Tk*);
95 static Tk* tkmenuindex2ptr(Tk*, char**);
96 static void activateitem(Tk*);
97 
98 /*
99  * unmap menu cascade upto (but not including) tk
100  */
101 static void
102 tkunmapmenus(TkTop *top, Tk *tk)
103 {
104 	TkTop *t;
105 	Tk *menu;
106 	TkWin *tkw;
107 
108 	menu = top->ctxt->tkmenu;
109 	if (menu == nil)
110 		return;
111 	t = menu->env->top;
112 
113 	/* if something went wrong, clear down all menus */
114 	if (tk != nil && tk->env->top != t)
115 		tk = nil;
116 
117 	while (menu != nil && menu != tk) {
118 		menuclr(menu);
119 		tkunmap(menu);
120 		tkcancelrepeat(menu);
121 		tkw = TKobj(TkWin, menu);
122 		if (tkw->cascade != nil) {
123 			menu = tklook(t, tkw->cascade, 0);
124 			free(tkw->cascade);
125 			tkw->cascade = nil;
126 		} else
127 			menu = nil;
128 	}
129 	top->ctxt->tkmenu = menu;
130 	tksetmgrab(top, menu);
131 }
132 
133 static void
134 tkunmapmenu(Tk *tk)
135 {
136 	TkTop *t;
137 	TkWin *tkw;
138 	Tk *parent;
139 
140 	parent = nil;
141 	tkw = TKobj(TkWin, tk);
142 	t = tk->env->top;
143 	if (tkw->cascade != nil)
144 		parent = tklook(t, tkw->cascade, 0);
145 	tkunmapmenus(t, parent);
146 	if (tkw->freeonunmap)
147 		freemenu(tk);
148 }
149 
150 static void
151 tksizemenubutton(Tk *tk)
152 {
153 	int w, h;
154 	char **v, *cur;
155 	TkLabel *tkl = TKobj(TkLabel, tk);
156 
157 	tksizelabel(tk);
158 	if(tk->type != TKchoicebutton)
159 		return;
160 	w = tk->req.width;
161 	h = tk->req.height;
162 	v = tkl->values;
163 	if (v == nil || *v == nil)
164 		return;
165 	cur = tkl->text;
166 	for (; *v; v++) {
167 		tkl->text = *v;
168 		tksizelabel(tk);
169 		if (tk->req.width > w)
170 			w = tk->req.width;
171 		if (tk->req.height > h)
172 			h = tk->req.height;
173 	}
174 	tkl->text = cur;
175 	tksizelabel(tk);
176 	tk->req.width = w;
177 	tk->req.height = h;
178 }
179 
180 static char*
181 tkmkmenubutton(TkTop *t, char *arg, char **ret, int type, TkOption *opts)
182 {
183 	Tk *tk;
184 	char *e, **v;
185 	TkName *names;
186 	TkLabel *tkl;
187 	TkOptab tko[3];
188 
189 /* need to get the label from elsewhere */
190 	tk = tknewobj(t, type, sizeof(Tk)+sizeof(TkLabel));
191 	if(tk == nil)
192 		return TkNomem;
193 	tk->borderwidth = 2;
194 	tk->flag |= Tknograb;
195 
196 	tkl = TKobj(TkLabel, tk);
197 	tkl->ul = -1;
198 	if(type == TKchoicebutton)
199 		tkl->anchor = Tknorth|Tkwest;
200 
201 	tko[0].ptr = tk;
202 	tko[0].optab = tkgeneric;
203 	tko[1].ptr = tkl;
204 	tko[1].optab = opts;
205 	tko[2].ptr = nil;
206 
207 	names = nil;
208 	e = tkparse(t, arg, tko, &names);
209 	if(e != nil) {
210 		tkfreeobj(tk);
211 		return e;
212 	}
213 	tkl->nvalues = 0;
214 	if (tkl->values != nil) {
215 		for (v = tkl->values; *v; v++)
216 			;
217 		tkl->nvalues = v - tkl->values;
218 	}
219 	if(type == TKchoicebutton){
220 		if(tkl->nvalues > 0)
221 			tkl->text = strdup(tkl->values[0]);
222 		else
223 			tkl->text = strdup(NOCHOICE);
224 	}
225 	tksettransparent(tk,
226 		tkhasalpha(tk->env, TkCbackgnd) ||
227 		tkhasalpha(tk->env, TkCselectbgnd) ||
228 		tkhasalpha(tk->env, TkCactivebgnd));
229 
230 	e = tkbindings(t, tk, mbbindings, nelem(mbbindings));
231 
232 	if(e != nil) {
233 		tkfreeobj(tk);
234 		return e;
235 	}
236 	tksizemenubutton(tk);
237 
238 	e = tkaddchild(t, tk, &names);
239 	tkfreename(names);
240 	if(e != nil) {
241 		tkfreeobj(tk);
242 		return e;
243 	}
244 	tk->name->link = nil;
245 
246 	return tkvalue(ret, "%s", tk->name->name);
247 }
248 
249 char*
250 tkchoicebutton(TkTop *t, char *arg, char **ret)
251 {
252 	return tkmkmenubutton(t, arg, ret, TKchoicebutton, choiceopts);
253 }
254 
255 char*
256 tkmenubutton(TkTop *t, char *arg, char **ret)
257 {
258 	return tkmkmenubutton(t, arg, ret, TKmenubutton, mbopts);
259 }
260 
261 static char*
262 tkmenubutcget(Tk *tk, char *arg, char **val)
263 {
264 	TkOptab tko[3];
265 	TkLabel *tkl = TKobj(TkLabel, tk);
266 
267 	tko[0].ptr = tk;
268 	tko[0].optab = tkgeneric;
269 	tko[1].ptr = tkl;
270 	tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts);
271 	tko[2].ptr = nil;
272 
273 	return tkgencget(tko, arg, val, tk->env->top);
274 }
275 
276 static char*
277 tkmenubutconf(Tk *tk, char *arg, char **val)
278 {
279 	char *e, **v;
280 	TkGeom g;
281 	int bd;
282 	TkOptab tko[3];
283 	TkLabel *tkl = TKobj(TkLabel, tk);
284 
285 	tko[0].ptr = tk;
286 	tko[0].optab = tkgeneric;
287 	tko[1].ptr = tkl;
288 	tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts);
289 	tko[2].ptr = nil;
290 
291 	if(*arg == '\0')
292 		return tkconflist(tko, val);
293 
294 	g = tk->req;
295 	bd = tk->borderwidth;
296 	e = tkparse(tk->env->top, arg, tko, nil);
297 
298 	if (tk->type == TKchoicebutton) {
299 		tkl->nvalues = 0;
300 		if (tkl->values != nil) {
301 			for (v = tkl->values; *v; v++)
302 				;
303 			tkl->nvalues = v - tkl->values;
304 		}
305 		if (tkl->check >= tkl->nvalues || strcmp(tkl->text, tkl->values[tkl->check])) {
306 			/*
307 			 * try to keep selected value the same if possible
308 			 */
309 			for (v = tkl->values; v && *v; v++)
310 				if (!strcmp(*v, tkl->text))
311 					break;
312 			free(tkl->text);
313 			if (v == nil || *v == nil) {
314 				tkl->text = strdup(tkl->nvalues > 0 ? tkl->values[0] : NOCHOICE);
315 				tkl->check = 0;
316 			} else {
317 				tkl->check = v - tkl->values;
318 				tkl->text = strdup(*v);
319 			}
320 		}
321 	}
322 	tksettransparent(tk,
323 		tkhasalpha(tk->env, TkCbackgnd) ||
324 		tkhasalpha(tk->env, TkCselectbgnd) ||
325 		tkhasalpha(tk->env, TkCactivebgnd));
326 	tksizemenubutton(tk);
327 	tkgeomchg(tk, &g, bd);
328 
329 	tk->dirty = tkrect(tk, 1);
330 	return e;
331 }
332 
333 static char*
334 tkMBleave(Tk *tk, char *arg, char **val)
335 {
336 	USED(arg);
337 	USED(val);
338 
339 	tk->flag &= ~Tkactive;
340 	tk->dirty = tkrect(tk, 1);
341 	return nil;
342 }
343 
344 static Tk*
345 mkchoicemenu(Tk *tkb)
346 {
347 	Tk *menu, *tkc;
348 	int i;
349 	TkLabel *tkl, *tkcl;
350 	TkWin *tkw;
351 	TkTop *t;
352 
353 	tkl = TKobj(TkLabel, tkb);
354 	t = tkb->env->top;
355 
356 	menu = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin));
357 	if(menu == nil)
358 		return nil;
359 
360 	menu->relief = TKraised;
361 	menu->flag |= Tknograb;
362 	menu->borderwidth = 1;
363 	tkputenv(menu->env);
364 	menu->env = tkb->env;
365 	menu->env->ref++;
366 
367 	menu->flag |= Tkwindow;
368 	menu->geom = tkmoveresize;
369 	tkw = TKobj(TkWin, menu);
370 	tkw->cbname = strdup(tkb->name->name);
371 	tkw->di = (void*)-1;			// XXX
372 
373 	for(i = tkl->nvalues - 1; i >= 0; i--){
374 		tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel));
375 		/* XXX recover from malloc failure */
376 		tkc->flag = Tkwest|Tkfillx|Tktop;
377 		tkc->highlightwidth = 0;
378 		tkc->borderwidth = 1;
379 		tkc->relief = TKflat;
380 		tkputenv(tkc->env);
381 		tkc->env = tkb->env;
382 		tkc->env->ref++;
383 		tkcl = TKobj(TkLabel, tkc);
384 		tkcl->anchor = Tkwest;
385 		tkcl->ul = -1;
386 		tkcl->justify = Tkleft;
387 		tkcl->text = strdup(tkl->values[i]);
388 		tkcl->command = smprint("%s invoke %d", tkb->name->name, i);
389 		/* XXX recover from malloc failure */
390 		tksizelabel(tkc);
391 		tkc->req.height = tkb->req.height;
392 		appenditem(menu, tkc, 0);
393 	}
394 	layout(menu);
395 
396 	tkw->next = t->windows;
397 	tkw->freeonunmap = 1;
398 	t->windows = menu;
399 	return menu;
400 }
401 
402 static char*
403 tkMBpress(Tk *tk, char *arg, char **val)
404 {
405 	Tk *menu, *item;
406 	TkLabel *tkl = TKobj(TkLabel, tk);
407 	Point g;
408 	char buf[12], *bufp, *e;
409 
410 	USED(arg);
411 	USED(val);
412 
413 	g = tkposn(tk);
414 	if (tk->type == TKchoicebutton) {
415 		menu = mkchoicemenu(tk);
416 		if (menu == nil)
417 			return TkNomem;
418 		sprint(buf, "%d", tkl->check);
419 		bufp = buf;
420 		item = tkmenuindex2ptr(menu, &bufp);
421 		if(item == nil)
422 			return nil;
423 		g.y -= item->act.y;
424 		e = tkmpost(menu, g.x, g.y, 0, 0, 0);
425 		activateitem(item);
426 		return e;
427 	} else {
428 		if (tkl->menu == nil)
429 			return nil;
430 		menu = tklook(tk->env->top, tkl->menu, 0);
431 		if(menu == nil || menu->type != TKmenu)
432 			return TkBadwp;
433 
434 		if(menu->flag & Tkmapped) {
435 			if(atoi(arg))
436 				tkunmapmenu(menu);
437 			return nil;
438 		}
439 		return tkmpost(menu, g.x, g.y, 0, tk->act.height + 2*tk->borderwidth, 1);
440 	}
441 }
442 
443 static char*
444 tkMBkey(Tk *tk, char *arg, char **val)
445 {
446 	int key;
447 	USED(val);
448 
449 	if(tk->flag & Tkdisabled)
450 		return nil;
451 
452 	key = strtol(arg, nil, 0);
453 	if (key == '\n' || key == ' ')
454 		return tkMBpress(tk, "1", nil);
455 	return nil;
456 }
457 
458 static char*
459 tkMBenter(Tk *tk, char *arg, char **val)
460 {
461 	USED(arg);
462 	USED(val);
463 
464 	tk->flag |= Tkactive;
465 	tk->dirty = tkrect(tk, 1);
466 	return nil;
467 }
468 
469 static char*
470 tkchoicebutset(Tk *tk, char *arg, char **val)
471 {
472 	char buf[12], *e;
473 	int v;
474 	TkLabel *tkl = TKobj(TkLabel, tk);
475 
476 	USED(val);
477 
478 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
479 	if (*buf == '\0')
480 		return TkBadvl;
481 	v = atoi(buf);
482 	if (v < 0 || v >= tkl->nvalues)
483 		return TkBadvl;
484 	if (v == tkl->check)
485 		return nil;
486 	free(tkl->text);
487 	tkl->text = strdup(tkl->values[v]);
488 	/* XXX recover from malloc error */
489 	tkl->check = v;
490 
491 	sprint(buf, "%d", v);
492 	e = tksetvar(tk->env->top, tkl->variable, buf);
493 	if(e != nil)
494 		return e;
495 
496 	tk->dirty = tkrect(tk, 1);
497 	return nil;
498 }
499 
500 static char*
501 tkchoicebutinvoke(Tk *tk, char *arg, char **val)
502 {
503 	TkLabel *tkl = TKobj(TkLabel, tk);
504 	char *e;
505 
506 	e = tkchoicebutset(tk, arg, val);
507 	if(e != nil)
508 		return e;
509 	if(tkl->command)
510 		return tkexec(tk->env->top, tkl->command, val);
511 	return nil;
512 }
513 
514 static char*
515 tkchoicebutgetvalue(Tk *tk, char *arg, char **val)
516 {
517 	char buf[12];
518 	int gotarg, v;
519 	TkLabel *tkl = TKobj(TkLabel, tk);
520 	if (tkl->nvalues == 0)
521 		return nil;
522 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), &gotarg);
523 	if (!gotarg)
524 		return tkvalue(val, "%s", tkl->values[tkl->check]);
525 	v = atoi(buf);
526 	if (buf[0] < '0' || buf[0] > '9' || v >= tkl->nvalues)
527 		return TkBadvl;
528 	return tkvalue(val, "%s", tkl->values[tkl->check]);
529 }
530 
531 static char*
532 tkchoicebutsetvalue(Tk *tk, char *arg, char **val)
533 {
534 	char *buf;
535 	char **v;
536 	int gotarg;
537 	TkLabel *tkl = TKobj(TkLabel, tk);
538 
539 	USED(val);
540 	if (tkl->nvalues == 0)
541 		return TkBadvl;
542 	buf = mallocz(Tkmaxitem, 0);
543 	if (buf == nil)
544 		return TkNomem;
545 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg);
546 	if (!gotarg) {
547 		free(buf);
548 		return TkBadvl;
549 	}
550 	for (v = tkl->values; *v; v++)
551 		if (strcmp(*v, buf) == 0)
552 			break;
553 	free(buf);
554 	if (*v == nil)
555 		return TkBadvl;
556 	free(tkl->text);
557 	tkl->text = strdup(*v);
558 	/* XXX recover from malloc error */
559 	tkl->check = v - tkl->values;
560 
561 	tk->dirty = tkrect(tk, 1);
562 	return nil;
563 }
564 
565 static char*
566 tkchoicebutget(Tk *tk, char *arg, char **val)
567 {
568 	TkLabel *tkl = TKobj(TkLabel, tk);
569 	char *buf, **v;
570 	int gotarg;
571 
572 	if (tkl->nvalues == 0)
573 		return nil;
574 	buf = mallocz(Tkmaxitem, 0);
575 	if (buf == nil)
576 		return TkNomem;
577 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg);
578 	if (!gotarg) {
579 		free(buf);
580 		return tkvalue(val, "%d", tkl->check);
581 	}
582 
583 	for (v = tkl->values; *v; v++)
584 		if (strcmp(*v, buf) == 0)
585 			break;
586 	free(buf);
587 	if (*v)
588 		return tkvalue(val, "%d", v - tkl->values);
589 	return nil;
590 }
591 
592 static char*
593 tkchoicebutvaluecount(Tk *tk, char *arg, char **val)
594 {
595 	TkLabel *tkl = TKobj(TkLabel, tk);
596 	USED(arg);
597 	return tkvalue(val, "%d", tkl->nvalues);
598 }
599 
600 
601 static void
602 tkchoicevarchanged(Tk *tk, char *var, char *value)
603 {
604 	TkLabel *tkl = TKobj(TkLabel, tk);
605 	int v;
606 
607 	if(tkl->variable != nil && strcmp(tkl->variable, var) == 0){
608 		if(value[0] < '0' || value[0] > '9')
609 			return;
610 		v = atoi(value);
611 		if(v < 0 || v >= tkl->nvalues)
612 			return;		/* what else can we do? */
613 		free(tkl->text);
614 		tkl->text = strdup(tkl->values[v]);
615 		/* XXX recover from malloc error */
616 		tkl->check = v;
617 		tk->dirty = tkrect(tk, 0);
618 		tkdirty(tk);
619 	}
620 }
621 
622 Tk *
623 tkfindchoicemenu(Tk *tkb)
624 {
625 	Tk *tk, *next;
626 	TkTop *top;
627 	TkWin *tkw;
628 
629 	top = tkb->env->top;
630 	for (tk = top->windows; tk != nil; tk = next){
631 		tkw = TKobj(TkWin, tk);
632 		if(tk->name == nil){
633 			assert(strcmp(tkw->cbname, tkb->name->name) == 0);
634 			return tk;
635 		}
636 		next = tkw->next;
637 	}
638 	return nil;
639 }
640 
641 static
642 TkOption menuopt[] =
643 {
644 	"postcommand",	OPTtext,	O(TkWin, postcmd),		nil,
645 	nil,
646 };
647 
648 char*
649 tkmenu(TkTop *t, char *arg, char **ret)
650 {
651 	Tk *tk;
652 	char *e;
653 	TkWin *tkw;
654 	TkName *names;
655 	TkOptab tko[3];
656 
657 	tk = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin));
658 	if(tk == nil)
659 		return TkNomem;
660 
661 	tkw = TKobj(TkWin, tk);
662 	tkw->di = (void*)-1;		// XXX
663 	tk->relief = TKraised;
664 	tk->flag |= Tknograb;
665 	tk->borderwidth = 1;
666 
667 	tko[0].ptr = tk;
668 	tko[0].optab = tkgeneric;
669 	tko[1].ptr = tkw;
670 	tko[1].optab = menuopt;
671 	tko[2].ptr = nil;
672 
673 	names = nil;
674 	e = tkparse(t, arg, tko, &names);
675 	if(e != nil) {
676 		tkfreeobj(tk);
677 		return e;
678 	}
679 
680 	e = tkaddchild(t, tk, &names);
681 	tkfreename(names);
682 	if(e != nil) {
683 		tkfreeobj(tk);
684 		return e;
685 	}
686 	tk->name->link = nil;
687 
688 	tk->flag |= Tkwindow;
689 	tk->geom = tkmoveresize;
690 
691 	tkw->next = t->windows;
692 	t->windows = tk;
693 
694 	return tkvalue(ret, "%s", tk->name->name);
695 }
696 
697 static void
698 freemenu(Tk *top)
699 {
700 	Tk *tk, *f, *nexttk, *nextf;
701 	TkWin *tkw;
702 
703 	tkunmapmenu(top);
704 	tkw = TKobj(TkWin, top);
705 	for(tk = tkw->slave; tk; tk = nexttk) {
706 		nexttk = tk->next;
707 		for(f = tk->slave; f; f = nextf) {
708 			nextf = f->next;
709 			tkfreeobj(f);
710 		}
711 		tkfreeobj(tk);
712 	}
713 	top->slave = nil;
714 	tkfreeframe(top);
715 }
716 
717 static
718 TkOption mopt[] =
719 {
720 	"menu",		OPTtext,	O(TkLabel, menu),		nil,
721 	nil,
722 };
723 
724 static void
725 tkbuildmopt(TkOptab *tko, int n, Tk *tk)
726 {
727 	memset(tko, 0, n*sizeof(TkOptab));
728 
729 	n = 0;
730 	tko[n].ptr = tk;
731 	tko[n++].optab = tkgeneric;
732 
733 	switch(tk->type) {
734 	case TKcascade:
735 		tko[n].ptr = TKobj(TkLabel, tk);
736 		tko[n++].optab = mopt;
737 		goto norm;
738 	case TKradiobutton:
739 		tko[n].ptr = TKobj(TkLabel, tk);
740 		tko[n++].optab = tkradopts;
741 		goto norm;
742 	case TKcheckbutton:
743 		tko[n].ptr = TKobj(TkLabel, tk);
744 		tko[n++].optab = tkcbopts;
745 		/* fall through */
746 	case TKlabel:
747 	norm:
748 		tko[n].ptr = TKobj(TkLabel, tk);
749 		tko[n].optab = tkbutopts;
750 		break;
751 	}
752 }
753 
754 static char*
755 tkmenuentryconf(Tk *menu, Tk *tk, char *arg)
756 {
757 	char *e;
758 	TkOptab tko[4];
759 
760 	USED(menu);
761 
762 	tkbuildmopt(tko, nelem(tko), tk);
763 	e = tkparse(tk->env->top, arg, tko, nil);
764 	switch (tk->type) {
765 	case TKlabel:
766 	case TKcascade:
767 		tksizelabel(tk);
768 		break;
769 	case TKradiobutton:
770 	case TKcheckbutton:
771 		tksizebutton(tk);
772 	}
773 
774 	return e;
775 }
776 
777 static void
778 layout(Tk *menu)
779 {
780 	TkWin *tkw;
781 	Tk *tk;
782 	int m, w, y, maxmargin, maxw;
783 
784 	y = 0;
785 	maxmargin = 0;
786 	maxw = 0;
787 
788 	tkw = TKobj(TkWin, menu);
789 
790 	/* determine padding for item text alignment */
791 	for (tk = tkw->slave; tk != nil; tk = tk->next) {
792 		m = tkbuttonmargin(tk);	/* TO DO: relies on buttonmargin defaulting to labelmargin */
793 		tk->act.x = m;		/* temp store */
794 		if (m > maxmargin)
795 			maxmargin = m;
796 	}
797 	/* set x pos and determine max width */
798 	for (tk = tkw->slave; tk != nil; tk = tk->next) {
799 		tk->act.x = tk->borderwidth + maxmargin - tk->act.x;
800 		tk->act.y = y + tk->borderwidth;
801 		tk->act.height = tk->req.height;
802 		tk->act.width = tk->req.width;
803 		y += tk->act.height+2*tk->borderwidth;
804 		w = tk->act.x + tk->req.width + 2* tk->borderwidth;
805 		if (w > maxw)
806 			maxw = w;
807 	}
808 	/* expand separators and cascades and mark all as dirty */
809 	for (tk = tkw->slave; tk != nil; tk = tk->next) {
810 		switch (tk->type) {
811 		case TKseparator:
812 			tk->act.x = tk->borderwidth;
813 			/*FALLTHRU*/
814 		case TKcascade:
815 			tk->act.width = (maxw - tk->act.x) - tk->borderwidth;
816 		}
817 		tk->dirty = tkrect(tk, 1);
818 	}
819 	menu->dirty = tkrect(menu, 1);
820 	tkmoveresize(menu, 0, 0, maxw, y);
821 }
822 
823 static void
824 menuitemgeom(Tk *sub, int x, int y, int w, int h)
825 {
826 	if (sub->parent == nil)
827 		return;
828 	if(w < 0)
829 		w = 0;
830 	if(h < 0)
831 		h = 0;
832 	sub->req.x = x;
833 	sub->req.y = y;
834 	sub->req.width = w;
835 	sub->req.height = h;
836 	layout(sub->parent);
837 }
838 
839 static void
840 appenditem(Tk *menu, Tk *item, int where)
841 {
842 	TkWin *tkw;
843 	Tk *f, **l;
844 
845 	tkw = TKobj(TkWin, menu);
846 	l = &tkw->slave;
847 	for (f = *l; f != nil; f = f->next) {
848 		if (where-- == 0)
849 			break;
850 		l = &f->next;
851 	}
852 	*l = item;
853 	item->next = f;
854 	item->parent = menu;
855 	item->geom = menuitemgeom;
856 }
857 
858 static char*
859 menuadd(Tk *menu, char *arg, int where)
860 {
861 	Tk *tkc;
862 	int configure;
863 	char *e;
864 	TkTop *t;
865 	TkLabel *tkl;
866 	char buf[Tkmaxitem];
867 
868 	t = menu->env->top;
869 	arg = tkword(t, arg, buf, buf+sizeof(buf), nil);
870 	configure = 1;
871 	e = nil;
872 
873 	if(strcmp(buf, "checkbutton") == 0)
874 		tkc = tkmkbutton(t, TKcheckbutton);
875 	else if(strcmp(buf, "radiobutton") == 0)
876 		tkc = tkmkbutton(t, TKradiobutton);
877 	else if(strcmp(buf, "command") == 0)
878 		tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel));
879 	else if(strcmp(buf, "cascade") == 0)
880 		tkc = tknewobj(t, TKcascade, sizeof(Tk)+sizeof(TkLabel));
881 	else if(strcmp(buf, "separator") == 0) {
882 		tkc = tknewobj(t, TKseparator, sizeof(Tk));	/* it's really a frame */
883 		if (tkc != nil) {
884 			tkc->flag = Tkfillx|Tktop;
885 			tkc->req.height = Sepheight;
886 			configure = 0;
887 		}
888 	}
889 	else
890 		return TkBadvl;
891 
892 	if (tkc == nil)
893 		e = TkNomem;
894 
895 	if (e == nil) {
896 		if(tkc->env == t->env && menu->env != t->env) {
897 			tkputenv(tkc->env);
898 			tkc->env = menu->env;
899 			tkc->env->ref++;
900 		}
901 		if (configure) {
902 			tkc->flag = Tkwest|Tkfillx|Tktop;
903 			tkc->highlightwidth = 0;
904 			tkc->borderwidth = 1;
905 			tkc->relief = TKflat;
906 			tkl = TKobj(TkLabel, tkc);
907 			tkl->anchor = Tkwest;
908 			tkl->ul = -1;
909 			tkl->justify = Tkleft;
910 			e = tkmenuentryconf(menu, tkc, arg);
911 		}
912 	}
913 
914 	if(e != nil) {
915 		if (tkc != nil)
916 			tkfreeobj(tkc);
917 		return e;
918 	}
919 
920 	appenditem(menu, tkc, where);
921 	layout(menu);
922 	return nil;
923 }
924 
925 static int
926 tkmindex(Tk *tk, char *p)
927 {
928 	TkWin *tkw;
929 	int y, n;
930 
931 	if(*p >= '0' && *p <= '9')
932 		return atoi(p);
933 
934 	tkw = TKobj(TkWin, tk);
935 	n = 0;
936 	if(*p == '@') {
937 		y = atoi(p+1);
938 		for(tk = tkw->slave; tk; tk = tk->next) {
939 			if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth )
940 				return n;
941 			n++;
942 		}
943 	}
944 	if(strcmp(p, "end") == 0 || strcmp(p, "last") == 0) {
945 		for(tk = tkw->slave; tk && tk->next; tk = tk->next)
946 			n++;
947 		return n;
948 	}
949 	if(strcmp(p, "active") == 0) {
950 		for(tk = tkw->slave; tk; tk = tk->next) {
951 			if(tk->flag & Tkactive)
952 				return n;
953 			n++;
954 		}
955 		return -2;
956 	}
957 	if(strcmp(p, "none") == 0)
958 		return -2;
959 
960 	return -1;
961 }
962 
963 static int
964 tkmenudel(Tk *tk, int y)
965 {
966 	TkWin *tkw;
967 	Tk *f, **l, *next;
968 
969 	tkw = TKobj(TkWin, tk);
970 	l = &tkw->slave;
971 	for(tk = *l; tk; tk = tk->next) {
972 		if(y-- == 0) {
973 			*l = tk->next;
974 			for(f = tk->slave; f; f = next) {
975 				next = f->next;
976 				tkfreeobj(f);
977 			}
978 			tkfreeobj(tk);
979 			return 1;
980 		}
981 		l = &tk->next;
982 	}
983 	return 0;
984 }
985 
986 static char*
987 tkmpost(Tk *tk, int x, int y, int cascade, int bh, int adjust)
988 {
989 	char *e;
990 	TkWin *w;
991 	TkTop *t;
992 	Rectangle *dr;
993 
994 	t = tk->env->top;
995 	if(adjust){
996 		dr = &t->screenr;
997 		if(x+tk->act.width > dr->max.x)
998 			x = dr->max.x - tk->act.width;
999 		if(x < 0)
1000 			x = 0;
1001 		if(y+bh+tk->act.height > dr->max.y)
1002 			y -= tk->act.height + 2* tk->borderwidth;
1003 		else
1004 			y += bh;
1005 		if(y < 0)
1006 			y = 0;
1007 	}
1008 	menuclr(tk);
1009 	tkmovewin(tk, Pt(x, y));
1010 
1011 	/* stop possible postcommand recursion */
1012 	if (tk->flag & Tkmapped)
1013 		return nil;
1014 
1015 	w = TKobj(TkWin, tk);
1016 	if(w->postcmd != nil) {
1017 		e = tkexec(tk->env->top, w->postcmd, nil);
1018 		if(e != nil) {
1019 			print("%s: postcommand: %s: %s\n", tkname(tk), w->postcmd, e);
1020 			return e;
1021 		}
1022 	}
1023 	if (!cascade)
1024 		tkunmapmenus(t, nil);
1025 
1026 	e = tkmap(tk);
1027 	if(e != nil)
1028 		return e;
1029 
1030 	if (t->ctxt->tkmenu != nil)
1031 		w->cascade = strdup(t->ctxt->tkmenu->name->name);
1032 	t->ctxt->tkmenu = tk;
1033 	tksetmgrab(t, tk);
1034 
1035 	/* Make sure slaves are redrawn */
1036 	return tkupdate(tk->env->top);
1037 }
1038 
1039 static Tk*
1040 tkmenuindex2ptr(Tk *tk, char **arg)
1041 {
1042 	TkWin *tkw;
1043 	int index;
1044 	char *buf;
1045 
1046 	buf = mallocz(Tkmaxitem, 0);
1047 	if(buf == nil)
1048 		return nil;
1049 	*arg = tkword(tk->env->top, *arg, buf, buf+Tkmaxitem, nil);
1050 	index = tkmindex(tk, buf);
1051 	free(buf);
1052 	if(index < 0)
1053 		return nil;
1054 
1055 	tkw = TKobj(TkWin, tk);
1056 	for(tk = tkw->slave; tk && index; tk = tk->next)
1057 		index--;
1058 
1059 	if(tk == nil)
1060 		return nil;
1061 
1062 	return tk;
1063 }
1064 
1065 static char*
1066 tkmenuentrycget(Tk *tk, char *arg, char **val)
1067 {
1068 	Tk *etk;
1069 	TkOptab tko[4];
1070 
1071 	etk = tkmenuindex2ptr(tk, &arg);
1072 	if(etk == nil)
1073 		return TkBadix;
1074 
1075 	tkbuildmopt(tko, nelem(tko), etk);
1076 	return tkgencget(tko, arg, val, tk->env->top);
1077 }
1078 
1079 static char*
1080 tkmenucget(Tk *tk, char *arg, char **val)
1081 {
1082 	TkWin *tkw;
1083 	TkOptab tko[4];
1084 
1085 	tkw = TKobj(TkWin, tk);
1086 	tko[0].ptr = tk;
1087 	tko[0].optab = tkgeneric;
1088 	tko[1].ptr = tkw;
1089 	tko[1].optab = tktop;
1090 	tko[2].ptr = tkw;
1091 	tko[2].optab = menuopt;
1092 	tko[3].ptr = nil;
1093 
1094 	return tkgencget(tko, arg, val, tk->env->top);
1095 }
1096 
1097 static char*
1098 tkmenuconf(Tk *tk, char *arg, char **val)
1099 {
1100 	char *e;
1101 	TkGeom g;
1102 	int bd;
1103 	TkWin *tkw;
1104 	TkOptab tko[3];
1105 
1106 	tkw = TKobj(TkWin, tk);
1107 	tko[0].ptr = tk;
1108 	tko[0].optab = tkgeneric;
1109 	tko[1].ptr = tkw;
1110 	tko[1].optab = menuopt;
1111 	tko[2].ptr = nil;
1112 
1113 	if(*arg == '\0')
1114 		return tkconflist(tko, val);
1115 
1116 	g = tk->req;
1117 	bd = tk->borderwidth;
1118 	e = tkparse(tk->env->top, arg, tko, nil);
1119 	tkgeomchg(tk, &g, bd);
1120 	tk->dirty = tkrect(tk, 1);
1121 	return e;
1122 }
1123 
1124 static char*
1125 tkmenuadd(Tk *tk, char *arg, char **val)
1126 {
1127 	USED(val);
1128 	return menuadd(tk, arg, -1);
1129 }
1130 
1131 static char*
1132 tkmenuinsert(Tk *tk, char *arg, char **val)
1133 {
1134 	int index;
1135 	char *buf;
1136 
1137 	USED(val);
1138 	buf = mallocz(Tkmaxitem, 0);
1139 	if(buf == nil)
1140 		return TkNomem;
1141 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
1142 	index = tkmindex(tk, buf);
1143 	free(buf);
1144 	if (index < 0)
1145 		return TkBadix;
1146 	return menuadd(tk, arg, index);
1147 }
1148 
1149 static void
1150 menuitemdirty(Tk *item)
1151 {
1152 	Tk *menu;
1153 	Rectangle r;
1154 
1155 	menu = item->parent;
1156 	if (menu == nil)
1157 		return;
1158 	item->dirty = tkrect(item, 1);
1159 	r = rectaddpt(item->dirty, Pt(item->act.x, item->act.y));
1160 	combinerect(&menu->dirty, r);
1161 }
1162 
1163 static void
1164 menuclr(Tk *tk)
1165 {
1166 	TkWin *tkw;
1167 	Tk *f;
1168 	tkw = TKobj(TkWin, tk);
1169 	for(f = tkw->slave; f; f = f->next) {
1170 		if(f->flag & Tkactive) {
1171 			f->flag &= ~Tkactive;
1172 			menuitemdirty(f);
1173 		}
1174 	}
1175 }
1176 
1177 static char*
1178 tkpostcascade(Tk *parent, Tk *tk, int toggle)
1179 {
1180 	Tk *tkm;
1181 	TkWin *tkw;
1182 	Point g;
1183 	TkTop *t;
1184 	TkLabel *tkl;
1185 	char *e;
1186 
1187 	if(tk->flag & Tkdisabled)
1188 		return nil;
1189 
1190 	tkl = TKobj(TkLabel, tk);
1191 	t = tk->env->top;
1192 	tkm = tklook(t, tkl->menu, 0);
1193 	if(tkm == nil || tkm->type != TKmenu)
1194 		return TkBadwp;
1195 
1196 	if((tkm->flag & Tkmapped)) {
1197 		if (toggle) {
1198 			tkunmapmenus(t, parent);
1199 			return nil;
1200 		} else {
1201 			/* check that it is immediate cascade */
1202 			tkw = TKobj(TkWin, t->ctxt->tkmenu);
1203 			if (strcmp(tkw->cascade, parent->name->name) == 0)
1204 				return nil;
1205 		}
1206 	}
1207 
1208 	tkunmapmenus(t, parent);
1209 
1210 	tkl = TKobj(TkLabel, tk);
1211 	if(tkl->command != nil) {
1212 		e = tkexec(t, tkl->command, nil);
1213 		if (e != nil)
1214 			return e;
1215 	}
1216 
1217 	g = tkposn(tk);
1218 	g.x += tk->act.width;
1219 	g.y -= tkm->borderwidth;
1220 	e = tkmpost(tkm, g.x, g.y, 1, 0, 1);
1221 	return e;
1222 }
1223 
1224 static void
1225 activateitem(Tk *item)
1226 {
1227 	Tk *menu;
1228 	if (item == nil || (menu = item->parent) == nil)
1229 		return;
1230 	menuclr(menu);
1231 	if (!(item->flag & Tkdisabled)) {
1232 		item->flag |= Tkactive;
1233 		menuitemdirty(item);
1234 	}
1235 }
1236 
1237 static char*
1238 tkmenuactivate(Tk *tk, char *arg, char **val)
1239 {
1240 	Tk *f;
1241 	TkWin *tkw;
1242 	int index;
1243 	char *buf;
1244 
1245 	USED(val);
1246 	buf = mallocz(Tkmaxitem, 0);
1247 	if(buf == nil)
1248 		return TkNomem;
1249 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
1250 	index = tkmindex(tk, buf);
1251 	free(buf);
1252 	if (index == -1)
1253 		return TkBadix;
1254 	if (index == -2) {
1255 		menuclr(tk);
1256 		return nil;
1257 	}
1258 
1259 	tkw = TKobj(TkWin, tk);
1260 	for(f = tkw->slave; f; f = f->next)
1261 		if(index-- == 0)
1262 			break;
1263 
1264 	if(f == nil || f->flag & Tkdisabled) {
1265 		menuclr(tk);
1266 		return nil;
1267 	}
1268 	if(f->flag & Tkactive)
1269 		return nil;
1270 
1271 	activateitem(f);
1272 	return nil;
1273 }
1274 
1275 static int
1276 iteminvoke(Tk *tk, Tk *tki, char *arg)
1277 {
1278 	int unmap = 0;
1279 	menuitemdirty(tki);
1280 	switch(tki->type) {
1281 	case TKlabel:
1282 		unmap = 1;
1283 	case TKcheckbutton:
1284 	case TKradiobutton:
1285 		tkbuttoninvoke(tki, arg, nil);
1286 		break;
1287 	case TKcascade:
1288 		tkpostcascade(tk, tki, 0);
1289 		break;
1290 	}
1291 	return unmap;
1292 }
1293 
1294 static char*
1295 tkmenuinvoke(Tk *tk, char *arg, char **val)
1296 {
1297 	Tk *tki;
1298 	USED(val);
1299 	tki = tkmenuindex2ptr(tk, &arg);
1300 	if(tki == nil)
1301 		return nil;
1302 	iteminvoke(tk, tki, arg);
1303 	return nil;
1304 }
1305 
1306 static char*
1307 tkmenudelete(Tk *tk, char *arg, char **val)
1308 {
1309 	int index1, index2;
1310 	char *buf;
1311 
1312 	USED(val);
1313 	buf = mallocz(Tkmaxitem, 0);
1314 	if(buf == nil)
1315 		return TkNomem;
1316 	arg = tkitem(buf, arg);
1317 	index1 = tkmindex(tk, buf);
1318 	if(index1 < 0) {
1319 		free(buf);
1320 		return TkBadix;
1321 	}
1322 	index2 = index1;
1323 	if(*arg != '\0') {
1324 		tkitem(buf, arg);
1325 		index2 = tkmindex(tk, buf);
1326 	}
1327 	free(buf);
1328 	if(index2 < 0)
1329 		return TkBadix;
1330 	while(index2 >= index1 && tkmenudel(tk, index2))
1331 		index2--;
1332 
1333 	layout(tk);
1334 	return nil;
1335 }
1336 
1337 static char*
1338 tkmenupost(Tk *tk, char *arg, char **val)
1339 {
1340 	int x, y;
1341 	TkTop *t;
1342 	char *buf;
1343 
1344 	USED(val);
1345 
1346 	buf = mallocz(Tkmaxitem, 0);
1347 	if(buf == nil)
1348 		return TkNomem;
1349 	t = tk->env->top;
1350 	arg = tkword(t, arg, buf, buf+Tkmaxitem, nil);
1351 	if(buf[0] == '\0') {
1352 		free(buf);
1353 		return TkBadvl;
1354 	}
1355 	x = atoi(buf);
1356 	tkword(t, arg, buf, buf+Tkmaxitem, nil);
1357 	if(buf[0] == '\0') {
1358 		free(buf);
1359 		return TkBadvl;
1360 	}
1361 	y = atoi(buf);
1362 	free(buf);
1363 
1364 	return tkmpost(tk, x, y, 0, 0, 1);
1365 }
1366 
1367 static char*
1368 tkmenuunpost(Tk *tk, char *arg, char **val)
1369 {
1370 	USED(arg);
1371 	USED(val);
1372 	tkunmapmenu(tk);
1373 	return nil;
1374 }
1375 
1376 static char*
1377 tkmenuindex(Tk *tk, char *arg, char **val)
1378 {
1379 	char *buf;
1380 	int index;
1381 
1382 	buf = mallocz(Tkmaxitem, 0);
1383 	if(buf == nil)
1384 		return TkNomem;
1385 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
1386 	index = tkmindex(tk, buf);
1387 	free(buf);
1388 	if (index == -1)
1389 		return TkBadix;
1390 	if (index == -2)
1391 		return "none";
1392 	return tkvalue(val, "%d", index);
1393 }
1394 
1395 static char*
1396 tkmenuyposn(Tk *tk, char *arg, char **val)
1397 {
1398 	tk = tkmenuindex2ptr(tk, &arg);
1399 	if(tk == nil)
1400 		return TkBadix;
1401 	return tkvalue(val, "%d", tk->act.y);
1402 }
1403 
1404 static char*
1405 tkmenupostcascade(Tk *tk, char *arg, char **val)
1406 {
1407 	Tk *tki;
1408 	USED(val);
1409 	tki = tkmenuindex2ptr(tk, &arg);
1410 	if(tki == nil || tki->type != TKcascade)
1411 		return nil;
1412 
1413 	return tkpostcascade(tk, tki, 0);
1414 }
1415 
1416 static char*
1417 tkmenutype(Tk *tk, char *arg, char **val)
1418 {
1419 	tk = tkmenuindex2ptr(tk, &arg);
1420 	if(tk == nil)
1421 		return TkBadix;
1422 
1423 	return tkvalue(val, tk->type == TKlabel ? "command" : tkmethod[tk->type]->name);
1424 }
1425 
1426 static char*
1427 tkmenususpend(Tk *tk, char *arg, char **val)
1428 {
1429 	USED(arg);
1430 	USED(val);
1431 	if(tk->type == TKchoicebutton){
1432 		tk = tkfindchoicemenu(tk);
1433 		if(tk == nil)
1434 			return TkNotwm;
1435 	}
1436 	tk->flag |= Tksuspended;
1437 	return nil;
1438 }
1439 
1440 static char*
1441 tkmenuentryconfig(Tk *tk, char *arg, char **val)
1442 {
1443 	Tk *etk;
1444 	char *e;
1445 
1446 	USED(val);
1447 	etk = tkmenuindex2ptr(tk, &arg);
1448 	if(etk == nil)
1449 		return TkBadix;
1450 
1451 	e = tkmenuentryconf(tk, etk, arg);
1452 	layout(tk);
1453 	return e;
1454 }
1455 
1456 static Tk*
1457 xymenuitem(Tk *tk, int x, int y)
1458 {
1459 	TkWin *tkw = TKobj(TkWin, tk);
1460 	x -= tkw->act.x;
1461 	y -= tkw->act.y;
1462 
1463 	x -= tk->borderwidth;
1464 	y -= tk->act.y + tk->borderwidth;
1465 	if (x < tk->act.x || x > tk->act.x+tk->act.width)
1466 		return nil;
1467 	for(tk = tkw->slave; tk; tk = tk->next) {
1468 		if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth)
1469 			return tk;
1470 	}
1471 	return nil;
1472 }
1473 
1474 static char *
1475 menukey(Tk *tk, int key)
1476 {
1477 	Tk *scan, *active, *first, *last, *prev, *next;
1478 	TkWin *tkw;
1479 	TkTop *top;
1480 
1481 	top = tk->env->top;
1482 
1483 	active = first = last = prev = next = nil;
1484 	tkw = TKobj(TkWin, tk);
1485 	for(scan = tkw->slave; scan != nil; scan = scan->next) {
1486 		if(scan->type == TKseparator)
1487 			continue;
1488 		if(first == nil)
1489 			first = scan;
1490 		if (active != nil && next == nil)
1491 			next = scan;
1492 		if(active == nil && scan->flag & Tkactive)
1493 			active = scan;
1494 		if (active == nil)
1495 			prev = scan;
1496 		last = scan;
1497 	}
1498 	if (next == nil)
1499 		next = first;
1500 	if (prev == nil)
1501 		prev = last;
1502 
1503 	switch (key) {
1504 	case Esc:
1505 		tkunmapmenus(top, nil);
1506 		break;
1507 	case Left:
1508 		if (tkw->cascade != nil)
1509 			tkunmapmenu(tk);
1510 		break;
1511 	case Right:
1512 		if (active == nil || active->type != TKcascade)
1513 			break;
1514 	case ' ':
1515 	case '\n':
1516 		if (active != nil) {
1517 			if (iteminvoke(tk, active, nil))
1518 				tkunmapmenus(top, nil);
1519 		}
1520 		break;
1521 	case Up:
1522 		next = prev;
1523 	case Down:
1524 		if (next != nil)
1525 			activateitem(next);
1526 	}
1527 	return nil;
1528 }
1529 
1530 static char*
1531 drawmenu(Tk *tk, Point orig)
1532 {
1533 	Image *dst;
1534 	TkWin *tkw;
1535 	Tk *sub;
1536 	Point p, bd;
1537 	int bg;
1538 	Rectangle mainr, clientr, subr;
1539 
1540 	tkw = TKobj(TkWin, tk);
1541 	dst = tkimageof(tk);
1542 
1543 	bd = Pt(tk->borderwidth, tk->borderwidth);
1544 	mainr.min = addpt(orig, Pt(tk->act.x, tk->act.y));
1545 	clientr.min = addpt(mainr.min, bd);
1546 	clientr.max = addpt(clientr.min, Pt(tk->act.width, tk->act.height));
1547 	mainr.max = addpt(clientr.max, bd);
1548 
1549 	/*
1550 	 * note that we draw item background to get full menu width
1551 	 * active indicator, this means we must dirty the entire
1552 	 * item rectangle to ensure it is fully redrawn
1553 	 */
1554 	p = clientr.min;
1555 	subr = clientr;
1556 	for (sub = tkw->slave; sub != nil; sub = sub->next) {
1557 		if (Dx(sub->dirty) == 0)
1558 			continue;
1559 		subr.min.y = p.y + sub->act.y - sub->borderwidth;
1560 		subr.max.y = p.y + sub->act.y + sub->act.height + sub->borderwidth;
1561 		bg = TkCbackgnd;
1562 		if (sub->flag & Tkactive)
1563 			bg = TkCactivebgnd;
1564 		draw(dst, subr, tkgc(sub->env, bg), nil, ZP);
1565 		sub->dirty = tkrect(sub, 1);
1566 		sub->flag |= Tkrefresh;
1567 		tkmethod[sub->type]->draw(sub, p);
1568 		sub->dirty = bbnil;
1569 		sub->flag &= ~Tkrefresh;
1570 	}
1571 	/* todo: dirty check */
1572 	tkdrawrelief(dst, tk, mainr.min, TkCbackgnd, tk->relief);
1573 	return nil;
1574 }
1575 
1576 static void
1577 menudirty(Tk *sub)
1578 {
1579 	menuitemdirty(sub);
1580 }
1581 
1582 static Point
1583 menurelpos(Tk *sub)
1584 {
1585 	return Pt(sub->act.x-sub->borderwidth, sub->act.y-sub->borderwidth);
1586 }
1587 
1588 static void
1589 autoscroll(Tk *tk, void *v, int cancelled)
1590 {
1591 	TkWin *tkw;
1592 	Rectangle r, dr;
1593 	Point delta, od;
1594 	TkMouse *m;
1595 	Tk *item;
1596 	USED(v);
1597 
1598 	tkw = TKobj(TkWin, tk);
1599 	if (cancelled) {
1600 		tkw->speed = 0;
1601 		return;
1602 	}
1603 	if(!eqpt(tkw->act, tkw->req)){
1604 print("not autoscrolling, act: %P, req: %P\n", tkw->act, tkw->req);
1605 		return;
1606 }
1607 	dr = tk->env->top->screenr;
1608 	delta.x = TKF2I(tkw->delta.x * tkw->speed);
1609 	delta.y = TKF2I(tkw->delta.y * tkw->speed);
1610 	r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y));
1611 
1612 	od = delta;
1613 	/* make sure we don't go too far */
1614 	if (delta.x > 0 && r.min.x + delta.x > dr.min.x)
1615 		delta.x = dr.min.x - r.min.x;
1616 	else if (delta.x < 0 && r.max.x + delta.x < dr.max.x)
1617 		delta.x = dr.max.x - r.max.x;
1618 	if (delta.y > 0 && r.min.y + delta.y > dr.min.y)
1619 		delta.y = dr.min.y - r.min.y;
1620 	else if (delta.y < 0 && r.max.y + delta.y < dr.max.y)
1621 		delta.y = dr.max.y - r.max.y;
1622 
1623 	m = &tk->env->top->ctxt->mstate;
1624 	item = xymenuitem(tk, m->x - delta.x, m->y - delta.y);
1625 	if (item == nil)
1626 		menuclr(tk);
1627 	else
1628 		activateitem(item);
1629 	tkmovewin(tk, Pt(tkw->req.x + delta.x, tkw->req.y + delta.y));
1630 	tkupdate(tk->env->top);
1631 	/* tkenterleave won't do this for us, so we have to do it ourselves */
1632 
1633 	tkw->speed += tkw->speed / 3;
1634 
1635 	r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y));
1636 	if((delta.y > 0 && r.min.x >= dr.min.x) || (delta.x < 0 && r.max.x <= dr.max.x))
1637 		tkw->delta.x = 0;
1638 	if((delta.y > 0 && r.min.y >= dr.min.y) || (delta.y < 0 && r.max.y <= dr.max.y))
1639 		tkw->delta.y = 0;
1640 	if (eqpt(tkw->delta, ZP)) {
1641 		tkcancelrepeat(tk);
1642 		tkw->speed = 0;
1643 	}
1644 }
1645 
1646 static void
1647 startautoscroll(Tk *tk, TkMouse *m)
1648 {
1649 	Rectangle dr, r;
1650 	Point d;
1651 	TkWin *tkw;
1652 	tkw = TKobj(TkWin, tk);
1653 	dr = tk->env->top->screenr;
1654 	r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y));
1655 	d = Pt(0, 0);
1656 	if(m->x <= 0 && r.min.x < dr.min.x)
1657 		d.x = 1;
1658 	else if (m->x >= dr.max.x - 1 && r.max.x >= dr.max.x)
1659 		d.x = -1;
1660 	if(m->y <= 0 && r.min.y < dr.min.y)
1661 		d.y = 1;
1662 	else if (m->y >= dr.max.y - 1 && r.max.y >= dr.max.y)
1663 		d.y = -1;
1664 //print("startautoscroll, delta %P\n", d);
1665 	if (d.x == 0 && d.y == 0){
1666 		if (tkw->speed > 0){
1667 			tkcancelrepeat(tk);
1668 			tkw->speed = 0;
1669 		}
1670 		return;
1671 	}
1672 	if (tkw->speed == 0) {
1673 		tkw->speed = TKI2F(Dy(r)) / 100;
1674 		tkrepeat(tk, autoscroll, nil, 0, TkRptinterval/2);
1675 	}
1676 	tkw->delta = d;
1677 }
1678 
1679 static void
1680 menuevent1(Tk *tk, int event, void *a)
1681 {
1682 	TkMouse *m;
1683 	Tk *item;
1684 
1685 	if (event & TkKey) {
1686 		menukey(tk, event & 0xffff);
1687 		return;
1688 	}
1689 
1690 	if (event & TkLeave) {
1691 		menuclr(tk);
1692 		return;
1693 	}
1694 
1695 	if ((!(event & TkEmouse) || (event & TkTakefocus)) && !(event & TkEnter))
1696 		return;
1697 
1698 	m = (TkMouse*)a;
1699 
1700 	startautoscroll(tk, m);
1701 
1702 	item = xymenuitem(tk, m->x, m->y);
1703 	if (item == nil)
1704 		menuclr(tk);
1705 	else
1706 		activateitem(item);
1707 	if ((event & (TkMotion|TkEnter)) && item == nil)
1708 		return;
1709 	if (event & TkEpress) {
1710 		if (item == nil) {
1711 			tkunmapmenus(tk->env->top, nil);
1712 			return;
1713 		}
1714 		if (item->type == TKcascade)
1715 			tkpostcascade(tk, item, !(event & TkMotion));
1716 		else
1717 			tkunmapmenus(tk->env->top, tk);
1718 		return;
1719 	}
1720 	if ((event & TkErelease) && m->b == 0) {
1721 		if (item != nil) {
1722 			if (item->type == TKcascade)
1723 				return;
1724 			if (!iteminvoke(tk, item, nil))
1725 				return;
1726 		}
1727 		tkunmapmenus(tk->env->top, nil);
1728 	}
1729 }
1730 
1731 static Tk*
1732 menuevent(Tk *tk, int event, void *a)
1733 {
1734 	menuevent1(tk, event, a);
1735 	tksubdeliver(tk, tk->binds, event, a, 0);
1736 	return nil;
1737 }
1738 
1739 static
1740 TkCmdtab menucmd[] =
1741 {
1742 	"activate",		tkmenuactivate,
1743 	"add",			tkmenuadd,
1744 	"cget",			tkmenucget,
1745 	"configure",		tkmenuconf,
1746 	"delete",		tkmenudelete,
1747 	"entryconfigure",	tkmenuentryconfig,
1748 	"entrycget",		tkmenuentrycget,
1749 	"index",		tkmenuindex,
1750 	"insert",		tkmenuinsert,
1751 	"invoke",		tkmenuinvoke,
1752 	"post",			tkmenupost,
1753 	"postcascade",		tkmenupostcascade,
1754 	"type",			tkmenutype,
1755 	"unpost",		tkmenuunpost,
1756 	"yposition",		tkmenuyposn,
1757 	"suspend",		tkmenususpend,
1758 	nil
1759 };
1760 
1761 static
1762 TkCmdtab menubutcmd[] =
1763 {
1764 	"cget",			tkmenubutcget,
1765 	"configure",		tkmenubutconf,
1766 	"tkMBenter",		tkMBenter,
1767 	"tkMBleave",		tkMBleave,
1768 	"tkMBpress",		tkMBpress,
1769 	"tkMBkey",		tkMBkey,
1770 	nil
1771 };
1772 
1773 static
1774 TkCmdtab choicebutcmd[] =
1775 {
1776 	"cget",			tkmenubutcget,
1777 	"configure",		tkmenubutconf,
1778 	"set",			tkchoicebutset,
1779 	"get",			tkchoicebutget,
1780 	"setvalue",		tkchoicebutsetvalue,
1781 	"getvalue",		tkchoicebutgetvalue,
1782 	"invoke",			tkchoicebutinvoke,
1783 	"valuecount",		tkchoicebutvaluecount,
1784 	"tkMBenter",		tkMBenter,
1785 	"tkMBleave",		tkMBleave,
1786 	"tkMBpress",		tkMBpress,
1787 	"tkMBkey",		tkMBkey,
1788 	"suspend",		tkmenususpend,
1789 	nil
1790 };
1791 
1792 TkMethod menumethod = {
1793 	"menu",
1794 	menucmd,
1795 	freemenu,
1796 	drawmenu,
1797 	nil,
1798 	nil,
1799 	nil,
1800 	menudirty,
1801 	menurelpos,
1802 	menuevent
1803 };
1804 
1805 TkMethod menubuttonmethod = {
1806 	"menubutton",
1807 	menubutcmd,
1808 	tkfreelabel,
1809 	tkdrawlabel
1810 };
1811 
1812 TkMethod choicebuttonmethod = {
1813 	"choicebutton",
1814 	choicebutcmd,
1815 	tkfreelabel,
1816 	tkdrawlabel,
1817 	nil,
1818 	nil,
1819 	nil,
1820 	nil,
1821 	nil,
1822 	nil,
1823 	nil,
1824 	nil,
1825 	tkchoicevarchanged
1826 };
1827 
1828 TkMethod separatormethod = {
1829 	"separator",
1830 	nil,
1831 	tkfreeframe,
1832 	tkdrawframe
1833 };
1834 
1835 TkMethod cascademethod = {
1836 	"cascade",
1837 	nil,
1838 	tkfreelabel,
1839 	tkdrawlabel
1840 };
1841