xref: /inferno-os/libtk/entry.c (revision adad243147f6102c6e975f48951c05745d56b92d)
1 #include <lib9.h>
2 #include <kernel.h>
3 #include "draw.h"
4 #include "keyboard.h"
5 #include "tk.h"
6 
7 /* Widget Commands (+ means implemented)
8 	+bbox
9 	+cget
10 	+configure
11 	+delete
12 	+get
13 	+icursor
14 	+index
15 	 scan
16 	+selection
17 	+xview
18 	+see
19 */
20 
21 #define	O(t, e)		((long)(&((t*)0)->e))
22 
23 #define CNTL(c) ((c)&0x1f)
24 #define DEL 0x7f
25 
26 /* Layout constants */
27 enum {
28 	Entrypady	= 0,
29 	Entrypadx	= 0,
30 	Inswidth = 2,
31 
32 	Ecursoron = 1<<0,
33 	Ecenter = 1<<1,
34 	Eright = 1<<2,
35 	Eleft = 1<<3,
36 	Ewordsel = 1<<4,
37 
38 	Ejustify = Ecenter|Eleft|Eright
39 };
40 
41 static TkStab tkjust[] =
42 {
43 	"left",	Eleft,
44 	"right",	Eright,
45 	"center",	Ecenter,
46 	nil
47 };
48 
49 static
50 TkEbind b[] =
51 {
52 	{TkKey,			"%W delete sel.first sel.last; %W insert insert {%A};%W see insert"},
53 	{TkKey|CNTL('a'),	"%W icursor 0;%W see insert;%W selection clear"},
54 	{TkKey|Home,		"%W icursor 0;%W see insert;%W selection clear"},
55 	{TkKey|CNTL('d'),	"%W delete insert; %W see insert"},
56 	{TkKey|CNTL('e'),    "%W icursor end; %W see insert;%W selection clear"},
57 	{TkKey|End,	     "%W icursor end; %W see insert;%W selection clear"},
58 	{TkKey|CNTL('h'),	"%W tkEntryBS;%W see insert"},
59 	{TkKey|CNTL('k'),	"%W delete insert end;%W see insert"},
60 	{TkKey|CNTL('u'),	"%W delete 0 end;%W see insert"},
61 	{TkKey|CNTL('w'),	"%W delete sel.first sel.last; %W tkEntryBW;%W see insert"},
62 	{TkKey|DEL,		"%W tkEntryBS 1;%W see insert"},
63 	{TkKey|CNTL('\\'),	"%W selection clear"},
64 	{TkKey|CNTL('/'),	"%W selection range 0 end"},
65 	{TkKey|Left,	"%W icursor insert-1;%W selection clear;%W selection from insert;%W see insert"},
66 	{TkKey|Right,	"%W icursor insert+1;%W selection clear;%W selection from insert;%W see insert"},
67 	{TkButton1P,		"focus %W; %W tkEntryB1P %X"},
68 	{TkButton1P|TkMotion, 	"%W tkEntryB1M %X"},
69 	{TkButton1R,		"%W tkEntryB1R"},
70 	{TkButton1P|TkDouble,	"%W tkEntryB1P %X;%W selection word @%x"},
71 	{TkButton2P,			"%W tkEntryB2P %x"},
72 	{TkButton2P|TkMotion,	"%W xview scroll %x scr"},
73 	{TkFocusin,		"%W tkEntryFocus in"},
74 	{TkFocusout,		"%W tkEntryFocus out"},
75 	{TkKey|APP|'\t',	""},
76 	{TkKey|BackTab,		""},
77 };
78 
79 typedef struct TkEntry TkEntry;
80 struct TkEntry
81 {
82 	Rune*	text;
83 	int		textlen;
84 
85 	char*	xscroll;
86 	char*	show;
87 	int		flag;
88 	int		oldx;
89 
90 	int		icursor;		/* index of insertion cursor */
91 	int		anchor;		/* selection anchor point */
92 	int		sel0;			/* index of start of selection */
93 	int		sel1;			/* index of end of selection */
94 
95 	int		x0;			/* x-offset of visible area */
96 
97 	/* derived values */
98 	int		v0;			/* index of first visible character */
99 	int		v1;			/* index of last visible character + 1 */
100 	int		xlen;			/* length of text in pixels*/
101 	int		xv0;			/* position of first visible character */
102 	int		xsel0;		/* position of start of selection */
103 	int		xsel1;		/* position of end of selection */
104 	int		xicursor;		/* position of insertion cursor */
105 };
106 
107 static void blinkreset(Tk*);
108 
109 static
110 TkOption opts[] =
111 {
112 	"xscrollcommand",	OPTtext,	O(TkEntry, xscroll),	nil,
113 	"justify",		OPTstab,	O(TkEntry, flag),	tkjust,
114 	"show",			OPTtext,	O(TkEntry, show),	nil,
115 	nil
116 };
117 
118 static int
119 xinset(Tk *tk)
120 {
121 	return Entrypadx + tk->highlightwidth;
122 }
123 
124 static int
125 yinset(Tk *tk)
126 {
127 	return Entrypady + tk->highlightwidth;
128 }
129 
130 static void
131 tksizeentry(Tk *tk)
132 {
133 	if((tk->flag & Tksetwidth) == 0)
134 		tk->req.width = tk->env->wzero*25 + 2*xinset(tk) + Inswidth;
135 	if((tk->flag & Tksetheight) == 0)
136 		tk->req.height = tk->env->font->height+ 2*yinset(tk);
137 }
138 
139 int
140 entrytextwidth(Tk *tk, int n)
141 {
142 	TkEntry *tke = TKobj(TkEntry, tk);
143 	Rune c;
144 	Font *f;
145 
146 	f = tk->env->font;
147 	if (tke->show != nil) {
148 		chartorune(&c, tke->show);
149 		return n * runestringnwidth(f, &c, 1);
150 	}
151 	return runestringnwidth(f, tke->text, n);
152 }
153 
154 static int
155 x2index(Tk *tk,  int x, int *xc)
156 {
157 	TkEntry *tke = TKobj(TkEntry, tk);
158 	int t0, t1, r, q;
159 
160 	t0 = 0;
161 	t1 = tke->textlen;
162 	while (t0 <= t1) {
163 		r = (t0 + t1) / 2;
164 		q = entrytextwidth(tk, r);
165 		if (q == x) {
166 			if (xc != nil)
167 				*xc = q;
168 			return r;
169 		}
170 		if (q < x)
171 			t0 = r + 1;
172 		else
173 			t1 = r - 1;
174 	}
175 	if (xc != nil)
176 		*xc = t1 > 0 ? entrytextwidth(tk, t1) : 0;
177 	if (t1 < 0)
178 		t1 = 0;
179 	return t1;
180 }
181 
182 static int
183 x2index0(Tk *tk, int x, int *xc)
184 {
185 	int xx, z;
186 	z = x2index(tk, x, &xx);
187 	print("x2index(%d)-> (%d, %d)\n", x, z, xx);
188 	if (xc)
189 		*xc = xx;
190 	return z;
191 }
192 
193 /*
194  * recalculate derived values
195  */
196 static void
197 recalcentry(Tk *tk)
198 {
199 	TkEntry *tke = TKobj(TkEntry, tk);
200 	int x, avail, locked;
201 
202 	locked = lockdisplay(tk->env->top->display);
203 
204 	tke->xlen = entrytextwidth(tk, tke->textlen) + Inswidth;
205 
206 	avail = tk->act.width - 2*xinset(tk);
207 	if (tke->xlen < avail) {
208 		switch(tke->flag & Ejustify) {
209 		default:
210 			tke->x0 = 0;
211 			break;
212 		case Eright:
213 			tke->x0 = -(avail - tke->xlen);
214 			break;
215 		case Ecenter:
216 			tke->x0 = -(avail - tke->xlen) / 2;
217 			break;
218 		}
219 	}
220 
221 	tke->v0 = x2index(tk, tke->x0, &tke->xv0);
222 	tke->v1 = x2index(tk, tk->act.width + tke->x0, &x);
223 	/* perhaps include partial last character */
224 	if (tke->v1 < tke->textlen && x < avail + tke->x0)
225 		tke->v1++;
226 	tke->xsel0 = entrytextwidth(tk, tke->sel0);
227 	tke->xsel1 = entrytextwidth(tk, tke->sel1);
228 	tke->xicursor = entrytextwidth(tk, tke->icursor);
229 
230 	if (locked)
231 		unlockdisplay(tk->env->top->display);
232 }
233 
234 char*
235 tkentry(TkTop *t, char *arg, char **ret)
236 {
237 	Tk *tk;
238 	char *e;
239 	TkName *names;
240 	TkEntry *tke;
241 	TkOptab tko[3];
242 
243 	tk = tknewobj(t, TKentry, sizeof(Tk)+sizeof(TkEntry));
244 	if(tk == nil)
245 		return TkNomem;
246 
247 	tk->relief = TKsunken;
248 	tk->borderwidth = 1;
249 	tk->flag |= Tktakefocus;
250 	tk->highlightwidth = 1;
251 
252 	tke = TKobj(TkEntry, tk);
253 
254 	tko[0].ptr = tk;
255 	tko[0].optab = tkgeneric;
256 	tko[1].ptr = tke;
257 	tko[1].optab = opts;
258 	tko[2].ptr = nil;
259 
260 	names = nil;
261 	e = tkparse(t, arg, tko, &names);
262 	if(e != nil) {
263 		tkfreeobj(tk);
264 		return e;
265 	}
266 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
267 	tksizeentry(tk);
268 	e = tkbindings(t, tk, b, nelem(b));
269 
270 	if(e != nil) {
271 		tkfreeobj(tk);
272 		return e;
273 	}
274 
275 	e = tkaddchild(t, tk, &names);
276 	tkfreename(names);
277 	if(e != nil) {
278 		tkfreeobj(tk);
279 		return e;
280 	}
281 	tk->name->link = nil;
282 	recalcentry(tk);
283 
284 	return tkvalue(ret, "%s", tk->name->name);
285 }
286 
287 static char*
288 tkentrycget(Tk *tk, char *arg, char **val)
289 {
290 	TkOptab tko[3];
291 	TkEntry *tke = TKobj(TkEntry, tk);
292 
293 	tko[0].ptr = tk;
294 	tko[0].optab = tkgeneric;
295 	tko[1].ptr = tke;
296 	tko[1].optab = opts;
297 	tko[2].ptr = nil;
298 
299 	return tkgencget(tko, arg, val, tk->env->top);
300 }
301 
302 void
303 tkfreeentry(Tk *tk)
304 {
305 	TkEntry *tke = TKobj(TkEntry, tk);
306 
307 	free(tke->xscroll);
308 	free(tke->text);
309 	free(tke->show);
310 }
311 
312 static void
313 tkentrytext(Image *i, Rectangle s, Tk *tk, TkEnv *env)
314 {
315 	TkEntry *tke = TKobj(TkEntry, tk);
316 	Point dp;
317 	int s0, s1, xs0, xs1, j;
318 	Rectangle r;
319 	Rune showr, *text;
320 
321 	dp = Pt(s.min.x - (tke->x0 - tke->xv0), s.min.y);
322 	if (tke->show) {
323 		chartorune(&showr, tke->show);
324 		text = mallocz(sizeof(Rune) * (tke->textlen+1), 0);
325 		if (text == nil)
326 			return;
327 		for (j = 0; j < tke->textlen; j++)
328 			text[j] = showr;
329 	} else
330 		text = tke->text;
331 
332 	runestringn(i, dp, tkgc(env, TkCforegnd), dp, env->font,
333 				text+tke->v0, tke->v1-tke->v0);
334 
335 	if (tke->sel0 < tke->v1 && tke->sel1 > tke->v0) {
336 		if (tke->sel0 < tke->v0) {
337 			s0 = tke->v0;
338 			xs0 = tke->xv0 - tke->x0;
339 		} else {
340 			s0 = tke->sel0;
341 			xs0 = tke->xsel0 - tke->x0;
342 		}
343 
344 		if (tke->sel1 > tke->v1) {
345 			s1 = tke->v1;
346 			xs1 = s.max.x;
347 		} else {
348 			s1 = tke->sel1;
349 			xs1 = tke->xsel1 - tke->x0;
350 		}
351 
352 		r = rectaddpt(Rect(xs0, 0, xs1, env->font->height), s.min);
353 		tktextsdraw(i, r, env, 1);
354 		runestringn(i, r.min, tkgc(env, TkCselectfgnd), r.min, env->font,
355 				text+s0, s1-s0);
356 	}
357 
358 	if((tke->flag&Ecursoron) && tke->icursor >= tke->v0 && tke->icursor <= tke->v1) {
359 		r = Rect(
360 			tke->xicursor - tke->x0, 0,
361 			tke->xicursor - tke->x0 + Inswidth, env->font->height
362 		);
363 		draw(i, rectaddpt(r, s.min), tkgc(env, TkCforegnd), nil, ZP);
364 	}
365 	if (tke->show)
366 		free(text);
367 }
368 
369 char*
370 tkdrawentry(Tk *tk, Point orig)
371 {
372 	Point p;
373 	TkEnv *env;
374 	Rectangle r, s;
375 	Image *i;
376 	int xp, yp;
377 
378 	env = tk->env;
379 
380 	r.min = ZP;
381 	r.max.x = tk->act.width + 2*tk->borderwidth;
382 	r.max.y = tk->act.height + 2*tk->borderwidth;
383 	i = tkitmp(env, r.max, TkCbackgnd);
384 	if(i == nil)
385 		return nil;
386 
387 	xp = tk->borderwidth + xinset(tk);
388 	yp = tk->borderwidth + yinset(tk);
389 	s = r;
390 	s.min.x += xp;
391 	s.max.x -= xp;
392 	s.min.y += yp;
393 	s.max.y -= yp;
394 	tkentrytext(i, s, tk, env);
395 
396 	tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief);
397 
398 	if (tkhaskeyfocus(tk))
399 		tkbox(i, insetrect(r, tk->borderwidth), tk->highlightwidth, tkgc(tk->env, TkChighlightfgnd));
400 
401 	p.x = tk->act.x + orig.x;
402 	p.y = tk->act.y + orig.y;
403 	r = rectaddpt(r, p);
404 	draw(tkimageof(tk), r, i, nil, ZP);
405 
406 	return nil;
407 }
408 
409 char*
410 tkentrysh(Tk *tk)
411 {
412 	TkEntry *tke = TKobj(TkEntry, tk);
413 	int dx, top, bot;
414 	char *val, *cmd, *v, *e;
415 
416 	if(tke->xscroll == nil)
417 		return nil;
418 
419 	bot = 0;
420 	top = Tkfpscalar;
421 
422 	if(tke->text != 0 && tke->textlen != 0) {
423 		dx = tk->act.width - 2*xinset(tk);
424 
425 		if (tke->xlen > dx) {
426 			bot = TKI2F(tke->x0) / tke->xlen;
427 			top = TKI2F(tke->x0 + dx) / tke->xlen;
428 		}
429 	}
430 
431 	val = mallocz(Tkminitem, 0);
432 	if(val == nil)
433 		return TkNomem;
434 	v = tkfprint(val, bot);
435 	*v++ = ' ';
436 	tkfprint(v, top);
437 	cmd = mallocz(Tkminitem, 0);
438 	if(cmd == nil) {
439 		free(val);
440 		return TkNomem;
441 	}
442 	sprint(cmd, "%s %s", tke->xscroll, val);
443 	e = tkexec(tk->env->top, cmd, nil);
444 	free(cmd);
445 	free(val);
446 	return e;
447 }
448 
449 void
450 tkentrygeom(Tk *tk)
451 {
452 	char *e;
453 	e = tkentrysh(tk);
454 	if ((e != nil) &&	/* XXX - Tad: should propagate not print */
455              (tk->name != nil))
456 		print("tk: xscrollcommand \"%s\": %s\n", tk->name->name, e);
457 	recalcentry(tk);
458 }
459 
460 static char*
461 tkentryconf(Tk *tk, char *arg, char **val)
462 {
463 	char *e;
464 	TkGeom g;
465 	int bd;
466 	TkOptab tko[3];
467 	TkEntry *tke = TKobj(TkEntry, tk);
468 
469 	tko[0].ptr = tk;
470 	tko[0].optab = tkgeneric;
471 	tko[1].ptr = tke;
472 	tko[1].optab = opts;
473 	tko[2].ptr = nil;
474 
475 	if(*arg == '\0')
476 		return tkconflist(tko, val);
477 
478 	bd = tk->borderwidth;
479 	g = tk->req;
480 	e = tkparse(tk->env->top, arg, tko, nil);
481 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
482 	tksizeentry(tk);
483 	tkgeomchg(tk, &g, bd);
484 	recalcentry(tk);
485 	tk->dirty = tkrect(tk, 1);
486 	return e;
487 }
488 
489 static char*
490 tkentryparseindex(Tk *tk, char *buf, int *index)
491 {
492 	TkEntry *tke = TKobj(TkEntry, tk);
493 	TkEnv *env;
494 	char *mod;
495 	int i, x, locked, modstart;
496 
497 	modstart = 0;
498 	for(mod = buf; *mod != '\0'; mod++)
499 		if(*mod == '-' || *mod == '+') {
500 			modstart = *mod;
501 			*mod = '\0';
502 			break;
503 		}
504 	if(strcmp(buf, "end") == 0)
505 		i = tke->textlen;
506 	else
507 	if(strcmp(buf, "anchor") == 0)
508 		i = tke->anchor;
509 	else
510 	if(strcmp(buf, "insert") == 0)
511 		i = tke->icursor;
512 	else
513 	if(strcmp(buf, "sel.first") == 0)
514 		i = tke->sel0;
515 	else
516 	if(strcmp(buf, "sel.last") == 0)
517 		i = tke->sel1;
518 	else
519 	if(buf[0] >= '0' && buf[0] <= '9')
520 		i = atoi(buf);
521 	else
522 	if(buf[0] == '@') {
523 		x = atoi(buf+1) - xinset(tk);
524 		if(tke->textlen == 0) {
525 			*index = 0;
526 			return nil;
527 		}
528 		env = tk->env;
529 		locked = lockdisplay(env->top->display);
530 		i = x2index(tk, x + tke->x0, nil);	/* XXX could possibly select nearest character? */
531 		if(locked)
532 			unlockdisplay(env->top->display);
533 	}
534 	else
535 		return TkBadix;
536 
537 	if(i < 0 || i > tke->textlen)
538 		return TkBadix;
539 	if(modstart) {
540 		*mod = modstart;
541 		i += atoi(mod);
542 		if(i < 0)
543 			i = 0;
544 		if(i > tke->textlen)
545 			i = tke->textlen;
546 	}
547 	*index = i;
548 	return nil;
549 }
550 
551 /*
552  * return bounding box of character at index, in coords relative to
553  * the top left position of the text.
554  */
555 static Rectangle
556 tkentrybbox(Tk *tk, int index)
557 {
558 	TkEntry *tke;
559 	TkEnv *env;
560 	Display *d;
561 	int x, cw, locked;
562 	Rectangle r;
563 
564 	tke = TKobj(TkEntry, tk);
565 	env = tk->env;
566 
567 	d = env->top->display;
568 
569 	locked = lockdisplay(d);
570 	x = entrytextwidth(tk, index);
571 	if (index < tke->textlen)
572 		cw = entrytextwidth(tk, index+1) - x;
573 	else
574 		cw = Inswidth;
575 	if(locked)
576 		unlockdisplay(d);
577 
578 	r.min.x = x;
579 	r.min.y = 0;
580 	r.max.x = x + cw;
581 	r.max.y = env->font->height;
582 	return r;
583 }
584 
585 static void
586 tkentrysee(Tk *tk, int index, int jump)
587 {
588 	TkEntry *tke = TKobj(TkEntry, tk);
589 	int dx, margin;
590 	Rectangle r;
591 
592 	r = tkentrybbox(tk, index);
593 	dx = tk->act.width - 2*xinset(tk);
594 	if (jump)
595 		margin = dx / 4;
596 	else
597 		margin = 0;
598 	if (r.min.x <= tke->x0 || r.max.x > tke->x0 + dx) {
599 		if (r.min.x <= tke->x0) {
600 			tke->x0 = r.min.x - margin;
601 			if (tke->x0 < 0)
602 				tke->x0 = 0;
603 		} else if (r.max.x >= tke->x0 + dx) {
604 			tke->x0 = r.max.x - dx + margin;
605 			if (tke->x0 > tke->xlen - dx)
606 				tke->x0 = tke->xlen - dx;
607 		}
608 		tk->dirty = tkrect(tk, 0);
609 	}
610 	r = rectaddpt(r, Pt(xinset(tk) - tke->x0, yinset(tk)));
611 	tksee(tk, r, r.min);
612 }
613 
614 static char*
615 tkentryseecmd(Tk *tk, char *arg, char **val)
616 {
617 	int index;
618 	char *e, *buf;
619 
620 	USED(val);
621 
622 	buf = mallocz(Tkmaxitem, 0);
623 	if(buf == nil)
624 		return TkNomem;
625 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
626 	e = tkentryparseindex(tk, buf, &index);
627 	free(buf);
628 	if(e != nil)
629 		return e;
630 
631 	tkentrysee(tk, index, 1);
632 	recalcentry(tk);
633 
634 	return nil;
635 }
636 
637 static char*
638 tkentrybboxcmd(Tk *tk, char *arg, char **val)
639 {
640 	TkEntry *tke = TKobj(TkEntry, tk);
641 	char *r, *buf;
642 	int index;
643 	Rectangle bbox;
644 
645 	buf = mallocz(Tkmaxitem, 0);
646 	if(buf == nil)
647 		return TkNomem;
648 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
649 	r = tkentryparseindex(tk, buf, &index);
650 	free(buf);
651 	if(r != nil)
652 		return r;
653 	bbox = rectaddpt(tkentrybbox(tk, index), Pt(xinset(tk) - tke->x0, yinset(tk)));
654 	return tkvalue(val, "%d %d %d %d", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y);
655 }
656 
657 static char*
658 tkentryindex(Tk *tk, char *arg, char **val)
659 {
660 	int index;
661 	char *r, *buf;
662 
663 	buf = mallocz(Tkmaxitem, 0);
664 	if(buf == nil)
665 		return TkNomem;
666 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
667 	r = tkentryparseindex(tk, buf, &index);
668 	free(buf);
669 	if(r != nil)
670 		return r;
671 	return tkvalue(val, "%d", index);
672 }
673 
674 static char*
675 tkentryicursor(Tk *tk, char *arg, char **val)
676 {
677 	TkEntry *tke = TKobj(TkEntry, tk);
678 	int index, locked;
679 	char *r, *buf;
680 
681 	USED(val);
682 	buf = mallocz(Tkmaxitem, 0);
683 	if(buf == nil)
684 		return TkNomem;
685 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
686 	r = tkentryparseindex(tk, buf, &index);
687 	free(buf);
688 	if(r != nil)
689 		return r;
690 	tke->icursor = index;
691 	locked = lockdisplay(tk->env->top->display);
692 	tke->xicursor = entrytextwidth(tk, tke->icursor);
693 	if (locked)
694 		unlockdisplay(tk->env->top->display);
695 
696 	blinkreset(tk);
697 	tk->dirty = tkrect(tk, 1);
698 	return nil;
699 }
700 
701 static int
702 adjustforins(int i, int n, int q)
703 {
704 	if (i <= q)
705 		q += n;
706 	return q;
707 }
708 
709 static int
710 adjustfordel(int d0, int d1, int q)
711 {
712 	if (d1 <= q)
713 		q -= d1 - d0;
714 	else if (d0 <= q && q <= d1)
715 		q = d0;
716 	return q;
717 }
718 
719 static char*
720 tkentryget(Tk *tk, char *arg, char **val)
721 {
722 	TkTop *top;
723 	TkEntry *tke;
724 	int first, last;
725 	char *e, *buf;
726 
727 	tke = TKobj(TkEntry, tk);
728 	if(tke->text == nil)
729 		return nil;
730 
731 	arg = tkskip(arg, " \t");
732 	if(*arg == '\0')
733 		return tkvalue(val, "%.*S", tke->textlen, tke->text);
734 
735 	top = tk->env->top;
736 	buf = mallocz(Tkmaxitem, 0);
737 	if(buf == nil)
738 		return TkNomem;
739 	arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
740 	e = tkentryparseindex(tk, buf, &first);
741 	if(e != nil) {
742 		free(buf);
743 		return e;
744 	}
745 	last = first+1;
746 	tkword(top, arg, buf, buf+Tkmaxitem, nil);
747 	if(buf[0] != '\0') {
748 		e = tkentryparseindex(tk, buf, &last);
749 		if(e != nil) {
750 			free(buf);
751 			return e;
752 		}
753 	}
754 	free(buf);
755 	if(last <= first || tke->textlen == 0 || first == tke->textlen)
756 		return tkvalue(val, "%S", L"");
757 	return tkvalue(val, "%.*S", last-first, tke->text+first);
758 }
759 
760 static char*
761 tkentryinsert(Tk *tk, char *arg, char **val)
762 {
763 	TkTop *top;
764 	TkEntry *tke;
765 	int ins, i, n, locked;
766 	char *e, *t, *text, *buf;
767 	Rune *etext;
768 
769 	USED(val);
770 	tke = TKobj(TkEntry, tk);
771 
772 	top = tk->env->top;
773 	buf = mallocz(Tkmaxitem, 0);
774 	if(buf == nil)
775 		return TkNomem;
776 	arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
777 	e = tkentryparseindex(tk, buf, &ins);
778 	free(buf);
779 	if(e != nil)
780 		return e;
781 
782 	if(*arg == '\0')
783 		return nil;
784 
785 	n = strlen(arg) + 1;
786 	if(n < Tkmaxitem)
787 		n = Tkmaxitem;
788 	text = malloc(n);
789 	if(text == nil)
790 		return TkNomem;
791 
792 	tkword(top, arg, text, text+n, nil);
793 	n = utflen(text);
794 	etext = realloc(tke->text, (tke->textlen+n+1)*sizeof(Rune));
795 	if(etext == nil) {
796 		free(text);
797 		return TkNomem;
798 	}
799 	tke->text = etext;
800 
801 	memmove(tke->text+ins+n, tke->text+ins, (tke->textlen-ins)*sizeof(Rune));
802 	t = text;
803 	for(i=0; i<n; i++)
804 		t += chartorune(tke->text+ins+i, t);
805 	free(text);
806 
807 	tke->textlen += n;
808 
809 	tke->sel0 = adjustforins(ins, n, tke->sel0);
810 	tke->sel1 = adjustforins(ins, n, tke->sel1);
811 	tke->icursor = adjustforins(ins, n, tke->icursor);
812 	tke->anchor = adjustforins(ins, n, tke->anchor);
813 
814 	locked = lockdisplay(tk->env->top->display);
815 	if (ins < tke->v0)
816 		tke->x0 += entrytextwidth(tk, tke->v0 + n) + (tke->x0 - tke->xv0);
817 	if (locked)
818 		unlockdisplay(tk->env->top->display);
819 	recalcentry(tk);
820 
821 	e = tkentrysh(tk);
822 	blinkreset(tk);
823 	tk->dirty = tkrect(tk, 1);
824 
825 	return e;
826 }
827 
828 static char*
829 tkentrydelete(Tk *tk, char *arg, char **val)
830 {
831 	TkTop *top;
832 	TkEntry *tke;
833 	int d0, d1, locked;
834 	char *e, *buf;
835 	Rune *text;
836 
837 	USED(val);
838 
839 	tke = TKobj(TkEntry, tk);
840 
841 	top = tk->env->top;
842 	buf = mallocz(Tkmaxitem, 0);
843 	if(buf == nil)
844 		return TkNomem;
845 	arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
846 	e = tkentryparseindex(tk, buf, &d0);
847 	if(e != nil) {
848 		free(buf);
849 		return e;
850 	}
851 
852 	d1 = d0+1;
853 	tkword(top, arg, buf, buf+Tkmaxitem, nil);
854 	if(buf[0] != '\0') {
855 		e = tkentryparseindex(tk, buf, &d1);
856 		if(e != nil) {
857 			free(buf);
858 			return e;
859 		}
860 	}
861 	free(buf);
862 	if(d1 <= d0 || tke->textlen == 0 || d0 >= tke->textlen)
863 		return nil;
864 
865 	memmove(tke->text+d0, tke->text+d1, (tke->textlen-d1)*sizeof(Rune));
866 	tke->textlen -= d1 - d0;
867 
868 	text = realloc(tke->text, (tke->textlen+1) * sizeof(Rune));
869 	if (text != nil)
870 		tke->text = text;
871 	tke->sel0 = adjustfordel(d0, d1, tke->sel0);
872 	tke->sel1 = adjustfordel(d0, d1, tke->sel1);
873 	tke->icursor = adjustfordel(d0, d1, tke->icursor);
874 	tke->anchor = adjustfordel(d0, d1, tke->anchor);
875 
876 	locked = lockdisplay(tk->env->top->display);
877 	if (d1 < tke->v0)
878 		tke->x0 = entrytextwidth(tk, tke->v0 - (d1 - d0)) + (tke->x0 - tke->xv0);
879 	else if (d0 < tke->v0)
880 		tke->x0 = entrytextwidth(tk, d0);
881 	if (locked)
882 		unlockdisplay(tk->env->top->display);
883 	recalcentry(tk);
884 
885 	e = tkentrysh(tk);
886 	blinkreset(tk);
887 	tk->dirty = tkrect(tk, 1);
888 
889 	return e;
890 }
891 
892 /*	Used for both backspace and DEL.  If a selection exists, delete it.
893  *	Otherwise delete the character to the left(right) of the insertion
894  *	cursor, if any.
895  */
896 static char*
897 tkentrybs(Tk *tk, char *arg, char **val)
898 {
899 	TkEntry *tke = TKobj(TkEntry, tk);
900 	char *buf, *e;
901 	int ix;
902 
903 	USED(val);
904 	USED(arg);
905 
906 	if(tke->textlen == 0)
907 		return nil;
908 
909 	if(tke->sel0 < tke->sel1)
910 		return tkentrydelete(tk, "sel.first sel.last", nil);
911 
912 	buf = mallocz(Tkmaxitem, 0);
913 	if(buf == nil)
914 		return TkNomem;
915 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
916 	ix = -1;
917 	if(buf[0] != '\0') {
918 		e = tkentryparseindex(tk, buf, &ix);
919 		if(e != nil) {
920 			free(buf);
921 			return e;
922 		}
923 	}
924 	if(ix > -1) {			/* DEL */
925 		if(tke->icursor >= tke->textlen) {
926 			free(buf);
927 			return nil;
928 		}
929 	}
930 	else {				/* backspace */
931 		if(tke->icursor == 0) {
932 			free(buf);
933 			return nil;
934 		}
935 		tke->icursor--;
936 	}
937 	snprint(buf, Tkmaxitem, "%d", tke->icursor);
938 	e = tkentrydelete(tk, buf, nil);
939 	free(buf);
940 	return e;
941 }
942 
943 static char*
944 tkentrybw(Tk *tk, char *arg, char **val)
945 {
946 	int start;
947 	Rune *text;
948 	TkEntry *tke;
949 	char buf[32];
950 
951 	USED(val);
952 	USED(arg);
953 
954 	tke = TKobj(TkEntry, tk);
955 	if(tke->textlen == 0 || tke->icursor == 0)
956 		return nil;
957 
958 	text = tke->text;
959 	start = tke->icursor-1;
960 	while(start > 0 && !tkiswordchar(text[start]))
961 		--start;
962 	while(start > 0 && tkiswordchar(text[start-1]))
963 		--start;
964 
965 	snprint(buf, sizeof(buf), "%d %d", start, tke->icursor);
966 	return tkentrydelete(tk, buf, nil);
967 }
968 
969 char*
970 tkentryselect(Tk *tk, char *arg, char **val)
971 {
972 	TkTop *top;
973 	int start, from, to, locked;
974 	TkEntry *tke;
975 	char *e, *buf;
976 
977 	buf = mallocz(Tkmaxitem, 0);
978 	if(buf == nil)
979 		return TkNomem;
980 
981 	tke = TKobj(TkEntry, tk);
982 
983 	top = tk->env->top;
984 	arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
985 	if(strcmp(buf, "clear") == 0) {
986 		tke->sel0 = 0;
987 		tke->sel1 = 0;
988 	}
989 	else
990 	if(strcmp(buf, "from") == 0) {
991 		tkword(top, arg, buf, buf+Tkmaxitem, nil);
992 		e = tkentryparseindex(tk, buf, &tke->anchor);
993 		tke->flag &= ~Ewordsel;
994 		free(buf);
995 		return e;
996 	}
997 	else
998 	if(strcmp(buf, "to") == 0) {
999 		tkword(top, arg, buf, buf+Tkmaxitem, nil);
1000 		e = tkentryparseindex(tk, buf, &to);
1001 		if(e != nil) {
1002 			free(buf);
1003 			return e;
1004 		}
1005 
1006 		if(to < tke->anchor) {
1007 			if(tke->flag & Ewordsel)
1008 				while(to > 0 && tkiswordchar(tke->text[to-1]))
1009 					--to;
1010 			tke->sel0 = to;
1011 			tke->sel1 = tke->anchor;
1012 		}
1013 		else
1014 		if(to >= tke->anchor) {
1015 			if(tke->flag & Ewordsel)
1016 				while(to < tke->textlen &&
1017 						tkiswordchar(tke->text[to]))
1018 					to++;
1019 			tke->sel0 = tke->anchor;
1020 			tke->sel1 = to;
1021 		}
1022 		tkentrysee(tk, to, 0);
1023 		recalcentry(tk);
1024 	}
1025 	else
1026 	if(strcmp(buf, "word") == 0) {	/* inferno invention */
1027 		tkword(top, arg, buf, buf+Tkmaxitem, nil);
1028 		e = tkentryparseindex(tk, buf, &start);
1029 		if(e != nil) {
1030 			free(buf);
1031 			return e;
1032 		}
1033 		from = start;
1034 		while(from > 0 && tkiswordchar(tke->text[from-1]))
1035 			--from;
1036 		to = start;
1037 		while(to < tke->textlen && tkiswordchar(tke->text[to]))
1038 			to++;
1039 		tke->sel0 = from;
1040 		tke->sel1 = to;
1041 		tke->anchor = from;
1042 		tke->icursor = from;
1043 		tke->flag |= Ewordsel;
1044 		locked = lockdisplay(tk->env->top->display);
1045 		tke->xicursor = entrytextwidth(tk, tke->icursor);
1046 		if (locked)
1047 			unlockdisplay(tk->env->top->display);
1048 	}
1049 	else
1050 	if(strcmp(buf, "present") == 0) {
1051 		e = tkvalue(val, "%d", tke->sel1 > tke->sel0);
1052 		free(buf);
1053 		return e;
1054 	}
1055 	else
1056 	if(strcmp(buf, "range") == 0) {
1057 		arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
1058 		e = tkentryparseindex(tk, buf, &from);
1059 		if(e != nil) {
1060 			free(buf);
1061 			return e;
1062 		}
1063 		tkword(top, arg, buf, buf+Tkmaxitem, nil);
1064 		e = tkentryparseindex(tk, buf, &to);
1065 		if(e != nil) {
1066 			free(buf);
1067 			return e;
1068 		}
1069 		tke->sel0 = from;
1070 		tke->sel1 = to;
1071 		if(to <= from) {
1072 			tke->sel0 = 0;
1073 			tke->sel1 = 0;
1074 		}
1075 	}
1076 	else
1077 	if(strcmp(buf, "adjust") == 0) {
1078 		tkword(top, arg, buf, buf+Tkmaxitem, nil);
1079 		e = tkentryparseindex(tk, buf, &to);
1080 		if(e != nil) {
1081 			free(buf);
1082 			return e;
1083 		}
1084 		if(tke->sel0 == 0 && tke->sel1 == 0) {
1085 			tke->sel0 = tke->anchor;
1086 			tke->sel1 = to;
1087 		}
1088 		else {
1089 			if(abs(tke->sel0-to) < abs(tke->sel1-to)) {
1090 				tke->sel0 = to;
1091 				tke->anchor = tke->sel1;
1092 			}
1093 			else {
1094 				tke->sel1 = to;
1095 				tke->anchor = tke->sel0;
1096 			}
1097 		}
1098 		if(tke->sel0 > tke->sel1) {
1099 			to = tke->sel0;
1100 			tke->sel0 = tke->sel1;
1101 			tke->sel1 = to;
1102 		}
1103 	}
1104 	else {
1105 		free(buf);
1106 		return TkBadcm;
1107 	}
1108 	locked = lockdisplay(tk->env->top->display);
1109 	tke->xsel0 = entrytextwidth(tk, tke->sel0);
1110 	tke->xsel1 = entrytextwidth(tk, tke->sel1);
1111 	if (locked)
1112 		unlockdisplay(tk->env->top->display);
1113 	tk->dirty = tkrect(tk, 1);
1114 	free(buf);
1115 	return nil;
1116 }
1117 
1118 
1119 static char*
1120 tkentryb2p(Tk *tk, char *arg, char **val)
1121 {
1122 	TkEntry *tke;
1123 	char *buf;
1124 
1125 	USED(val);
1126 
1127 	tke = TKobj(TkEntry, tk);
1128 	buf = malloc(Tkmaxitem);
1129 	if (buf == nil)
1130 		return TkNomem;
1131 
1132 	tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
1133 	tke->oldx = atoi(buf);
1134 	return nil;
1135 }
1136 
1137 static char*
1138 tkentryxview(Tk *tk, char *arg, char **val)
1139 {
1140 	int locked;
1141 	TkEnv *env;
1142 	TkEntry *tke;
1143 	char *buf, *v;
1144 	int dx, top, bot, amount, ix, x;
1145 	char *e;
1146 
1147 	tke = TKobj(TkEntry, tk);
1148 	env = tk->env;
1149 	dx = tk->act.width - 2*xinset(tk);
1150 
1151 	buf = mallocz(Tkmaxitem, 0);
1152 	if(buf == nil)
1153 		return TkNomem;
1154 
1155 	if(*arg == '\0') {
1156 		if (tke->textlen == 0 || tke->xlen < dx) {
1157 			bot = TKI2F(0);
1158 			top = TKI2F(1);
1159 		} else {
1160 			bot = TKI2F(tke->x0) / tke->xlen;
1161 			top = TKI2F(tke->x0 + dx) / tke->xlen;
1162 		}
1163 		v = tkfprint(buf, bot);
1164 		*v++ = ' ';
1165 		tkfprint(v, top);
1166 		e = tkvalue(val, "%s", buf);
1167 		free(buf);
1168 		return e;
1169 	}
1170 
1171 	arg = tkitem(buf, arg);
1172 	if(strcmp(buf, "moveto") == 0) {
1173 		e = tkfracword(env->top, &arg, &top, nil);
1174 		if (e != nil) {
1175 			free(buf);
1176 			return e;
1177 		}
1178 		tke->x0 = TKF2I(top*tke->xlen);
1179 	}
1180 	else
1181 	if(strcmp(buf, "scroll") == 0) {
1182 		arg = tkitem(buf, arg);
1183 		amount = atoi(buf);
1184 		if(*arg == 'p')		/* Pages */
1185 			amount *= (9*tke->xlen)/10;
1186 		else
1187 		if(*arg == 's') {		/* Inferno-ism, "scr", must be used in the context of button2p */
1188 			x = amount;
1189 			amount = x < tke->oldx ? env->wzero : (x > tke->oldx ? -env->wzero : 0);
1190 			tke->oldx = x;
1191 		}
1192 		tke->x0 += amount;
1193 	}
1194 	else {
1195 		e = tkentryparseindex(tk, buf, &ix);
1196 		if(e != nil) {
1197 			free(buf);
1198 			return e;
1199 		}
1200 		locked = lockdisplay(env->top->display);
1201 		tke->x0 = entrytextwidth(tk, ix);
1202 		if (locked)
1203 			unlockdisplay(env->top->display);
1204 	}
1205 	free(buf);
1206 
1207 	if (tke->x0 > tke->xlen - dx)
1208 		tke->x0 = tke->xlen - dx;
1209 	if (tke->x0 < 0)
1210 		tke->x0 = 0;
1211 	recalcentry(tk);
1212 	e = tkentrysh(tk);
1213 	blinkreset(tk);
1214 	tk->dirty = tkrect(tk, 1);
1215 	return e;
1216 }
1217 
1218 static void
1219 autoselect(Tk *tk, void *v, int cancelled)
1220 {
1221 	TkEntry *tke = TKobj(TkEntry, tk);
1222 	Rectangle hitr;
1223 	char buf[32];
1224 	Point p;
1225 
1226 	USED(v);
1227 
1228 	if (cancelled)
1229 		return;
1230 
1231 	p = tkscrn2local(tk, Pt(tke->oldx, 0));
1232 	p.y = 0;
1233 	if (tkvisiblerect(tk, &hitr) && ptinrect(p, hitr))
1234 		return;
1235 
1236 	snprint(buf, sizeof(buf), "to @%d", p.x);
1237 	tkentryselect(tk, buf, nil);
1238 	tkdirty(tk);
1239 	tkupdate(tk->env->top);
1240 }
1241 
1242 static char*
1243 tkentryb1p(Tk *tk, char* arg, char **ret)
1244 {
1245 	TkEntry *tke = TKobj(TkEntry, tk);
1246 	Point p;
1247 	int i, locked, x;
1248 	char buf[32], *e;
1249 	USED(ret);
1250 
1251 	x = atoi(arg);
1252 	p = tkscrn2local(tk, Pt(x, 0));
1253 	sprint(buf, "@%d", p.x);
1254 	e = tkentryparseindex(tk, buf, &i);
1255 	if (e != nil)
1256 		return e;
1257 	tke->sel0 = 0;
1258 	tke->sel1 = 0;
1259 	tke->icursor = i;
1260 	tke->anchor = i;
1261 	tke->flag &= ~Ewordsel;
1262 
1263 	locked = lockdisplay(tk->env->top->display);
1264 	tke->xsel0 = 0;
1265 	tke->xsel1 = 0;
1266 	tke->xicursor = entrytextwidth(tk, tke->icursor);
1267 	if (locked)
1268 		unlockdisplay(tk->env->top->display);
1269 
1270 	tke->oldx = x;
1271 	blinkreset(tk);
1272 	tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval);
1273 	tk->dirty = tkrect(tk, 0);
1274 	return nil;
1275 }
1276 
1277 static char*
1278 tkentryb1m(Tk *tk, char* arg, char **ret)
1279 {
1280 	TkEntry *tke = TKobj(TkEntry, tk);
1281 	Point p;
1282 	Rectangle hitr;
1283 	char buf[32];
1284 	USED(ret);
1285 
1286 	p.x = atoi(arg);
1287 	tke->oldx = p.x;
1288 	p = tkscrn2local(tk, p);
1289 	p.y = 0;
1290 	if (!tkvisiblerect(tk, &hitr) || !ptinrect(p, hitr))
1291 		return nil;
1292 	snprint(buf, sizeof(buf), "to @%d", p.x);
1293 	tkentryselect(tk, buf, nil);
1294 	return nil;
1295 }
1296 
1297 static char*
1298 tkentryb1r(Tk *tk, char* arg, char **ret)
1299 {
1300 	USED(tk);
1301 	USED(arg);
1302 	USED(ret);
1303 	tkcancelrepeat(tk);
1304 	return nil;
1305 }
1306 
1307 static void
1308 blinkreset(Tk *tk)
1309 {
1310 	TkEntry *e = TKobj(TkEntry, tk);
1311 	if (!tkhaskeyfocus(tk) || tk->flag&Tkdisabled)
1312 		return;
1313 	e->flag |= Ecursoron;
1314 	tkblinkreset(tk);
1315 }
1316 
1317 static void
1318 showcaret(Tk *tk, int on)
1319 {
1320 	TkEntry *e = TKobj(TkEntry, tk);
1321 
1322 	if (on)
1323 		e->flag |= Ecursoron;
1324 	else
1325 		e->flag &= ~Ecursoron;
1326 	tk->dirty = tkrect(tk, 0);
1327 }
1328 
1329 char*
1330 tkentryfocus(Tk *tk, char* arg, char **ret)
1331 {
1332 	int on = 0;
1333 	USED(ret);
1334 
1335 	if (tk->flag&Tkdisabled)
1336 		return nil;
1337 
1338 	if(strcmp(arg, " in") == 0) {
1339 		tkblink(tk, showcaret);
1340 		on = 1;
1341 	}
1342 	else
1343 		tkblink(nil, nil);
1344 
1345 	showcaret(tk, on);
1346 	return nil;
1347 }
1348 
1349 static
1350 TkCmdtab tkentrycmd[] =
1351 {
1352 	"cget",			tkentrycget,
1353 	"configure",		tkentryconf,
1354 	"delete",		tkentrydelete,
1355 	"get",			tkentryget,
1356 	"icursor",		tkentryicursor,
1357 	"index",		tkentryindex,
1358 	"insert",		tkentryinsert,
1359 	"selection",		tkentryselect,
1360 	"xview",		tkentryxview,
1361 	"tkEntryBS",		tkentrybs,
1362 	"tkEntryBW",		tkentrybw,
1363 	"tkEntryB1P",		tkentryb1p,
1364 	"tkEntryB1M",		tkentryb1m,
1365 	"tkEntryB1R",		tkentryb1r,
1366 	"tkEntryB2P",		tkentryb2p,
1367 	"tkEntryFocus",		tkentryfocus,
1368 	"bbox",			tkentrybboxcmd,
1369 	"see",		tkentryseecmd,
1370 	nil
1371 };
1372 
1373 TkMethod entrymethod = {
1374 	"entry",
1375 	tkentrycmd,
1376 	tkfreeentry,
1377 	tkdrawentry,
1378 	tkentrygeom
1379 };
1380