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