xref: /inferno-os/libtk/listb.c (revision 48c2bcd8842a77d6fca4a18505e622e9a9d3d38b)
1 #include "lib9.h"
2 #include "draw.h"
3 #include "keyboard.h"
4 #include "tk.h"
5 #include "listb.h"
6 
7 #define	O(t, e)		((long)(&((t*)0)->e))
8 
9 /* Layout constants */
10 enum {
11 	Listpadx	= 2,	/* X padding of text in listboxes */
12 };
13 
14 typedef struct TkLentry TkLentry;
15 typedef struct TkListbox TkListbox;
16 
17 struct TkLentry
18 {
19 	TkLentry*	link;
20 	int		flag;
21 	int		width;
22 	char		text[TKSTRUCTALIGN];
23 };
24 
25 struct TkListbox
26 {
27 	TkLentry*	head;
28 	TkLentry*	anchor;
29 	TkLentry*	active;
30 	int		yelem;		/* Y element at top of box */
31 	int		xdelta;		/* h-scroll position */
32 	int		nitem;
33 	int		nwidth;
34 	int		selmode;
35 	int		sborderwidth;
36 	char*		xscroll;
37 	char*		yscroll;
38 };
39 
40 TkStab tkselmode[] =
41 {
42 	"single",	TKsingle,
43 	"browse",	TKbrowse,
44 	"multiple",	TKmultiple,
45 	"extended",	TKextended,
46 	nil
47 };
48 
49 static
50 TkOption opts[] =
51 {
52 	"xscrollcommand",	OPTtext,	O(TkListbox, xscroll),	nil,
53 	"yscrollcommand",	OPTtext,	O(TkListbox, yscroll),	nil,
54 	"selectmode",		OPTstab,	O(TkListbox, selmode),	tkselmode,
55 	"selectborderwidth",	OPTnndist,	O(TkListbox, sborderwidth),	nil,
56 	nil
57 };
58 
59 static
60 TkEbind b[] =
61 {
62 	{TkButton1P,		"%W tkListbButton1P %y"},
63 	{TkButton1R,	"%W tkListbButton1R"},
64 	{TkButton1P|TkMotion,	"%W tkListbButton1MP %y"},
65 	{TkMotion,		""},
66 	{TkKey,	"%W tkListbKey 0x%K"},
67 };
68 
69 
70 static int
lineheight(Tk * tk)71 lineheight(Tk *tk)
72 {
73 	TkListbox *l = TKobj(TkListbox, tk);
74 	return tk->env->font->height+2*(l->sborderwidth+tk->highlightwidth);
75 }
76 
77 char*
tklistbox(TkTop * t,char * arg,char ** ret)78 tklistbox(TkTop *t, char *arg, char **ret)
79 {
80 	Tk *tk;
81 	char *e;
82 	TkName *names;
83 	TkListbox *tkl;
84 	TkOptab tko[3];
85 
86 	tk = tknewobj(t, TKlistbox, sizeof(Tk)+sizeof(TkListbox));
87 	if(tk == nil)
88 		return TkNomem;
89 
90 	tkl = TKobj(TkListbox, tk);
91 	tkl->sborderwidth = 1;
92 	tk->relief = TKsunken;
93 	tk->borderwidth = 1;
94 	tk->highlightwidth = 1;
95 	tk->flag |= Tktakefocus;
96 	tk->req.width = 170;
97 	tk->req.height = lineheight(tk)*10;
98 
99 	tko[0].ptr = tk;
100 	tko[0].optab = tkgeneric;
101 	tko[1].ptr = tkl;
102 	tko[1].optab = opts;
103 	tko[2].ptr = nil;
104 
105 	names = nil;
106 	e = tkparse(t, arg, tko, &names);
107 	if(e != nil) {
108 		tkfreeobj(tk);
109 		return e;
110 	}
111 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
112 
113 	e = tkbindings(t, tk, b, nelem(b));
114 	if(e != nil) {
115 		tkfreeobj(tk);
116 		return e;
117 	}
118 
119 	e = tkaddchild(t, tk, &names);
120 	tkfreename(names);
121 	if(e != nil) {
122 		tkfreeobj(tk);
123 		return e;
124 	}
125 	tk->name->link = nil;
126 
127 	return tkvalue(ret, "%s", tk->name->name);
128 }
129 
130 char*
tklistbcget(Tk * tk,char * arg,char ** val)131 tklistbcget(Tk *tk, char *arg, char **val)
132 {
133 	TkOptab tko[3];
134 	TkListbox *tkl = TKobj(TkListbox, tk);
135 
136 	tko[0].ptr = tk;
137 	tko[0].optab = tkgeneric;
138 	tko[1].ptr = tkl;
139 	tko[1].optab = opts;
140 	tko[2].ptr = nil;
141 
142 	return tkgencget(tko, arg, val, tk->env->top);
143 }
144 
145 void
tkfreelistb(Tk * tk)146 tkfreelistb(Tk *tk)
147 {
148 	TkLentry *e, *next;
149 	TkListbox *l = TKobj(TkListbox, tk);
150 
151 	for(e = l->head; e; e = next) {
152 		next = e->link;
153 		free(e);
154 	}
155 	if(l->xscroll != nil)
156 		free(l->xscroll);
157 	if(l->yscroll != nil)
158 		free(l->yscroll);
159 }
160 
161 char*
tkdrawlistb(Tk * tk,Point orig)162 tkdrawlistb(Tk *tk, Point orig)
163 {
164 	Point p;
165 	TkEnv *env;
166 	TkLentry *e;
167 	int lh, w, n, ly;
168 	Rectangle r, a;
169 	Image *i, *fg;
170 	TkListbox *l = TKobj(TkListbox, tk);
171 
172 	env = tk->env;
173 
174 	r.min = ZP;
175 	r.max.x = tk->act.width + 2*tk->borderwidth;
176 	r.max.y = tk->act.height + 2*tk->borderwidth;
177 	i = tkitmp(env, r.max, TkCbackgnd);
178 	if(i == nil)
179 		return nil;
180 
181 	w = tk->act.width;
182 	if (w < l->nwidth)
183 		w = l->nwidth;
184 	lh = lineheight(tk);
185 	ly = tk->borderwidth;
186 	p.x = tk->borderwidth+l->sborderwidth+tk->highlightwidth+Listpadx-l->xdelta;
187 	p.y = tk->borderwidth+l->sborderwidth+tk->highlightwidth;
188 	n = 0;
189 	for(e = l->head; e && ly < r.max.y; e = e->link) {
190 		if(n++ < l->yelem)
191 			continue;
192 
193 		a.min.x = tk->borderwidth;
194 		a.min.y = ly;
195 		a.max.x = a.min.x + tk->act.width;
196 		a.max.y = a.min.y + lh;
197 		if(e->flag & Tkactivated) {
198 			draw(i, a, tkgc(env, TkCselectbgnd), nil, ZP);
199 		}
200 
201 		if(e->flag & Tkactivated)
202 			fg = tkgc(env, TkCselectfgnd);
203 		else
204 			fg = tkgc(env, TkCforegnd);
205 		string(i, p, fg, p, env->font, e->text);
206 		if((e->flag & Tkactive) && tkhaskeyfocus(tk)) {
207 			a.min.x = tk->borderwidth-l->xdelta;
208 			a.max.x = a.min.x+w;
209 			a = insetrect(a, l->sborderwidth);
210 			tkbox(i, a, tk->highlightwidth, fg);
211 		}
212 		ly += lh;
213 		p.y += lh;
214 	}
215 
216 	tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief);
217 
218 	p.x = tk->act.x + orig.x;
219 	p.y = tk->act.y + orig.y;
220 	r = rectaddpt(r, p);
221 	draw(tkimageof(tk), r, i, nil, ZP);
222 
223 	return nil;
224 }
225 
226 int
tklindex(Tk * tk,char * buf)227 tklindex(Tk *tk, char *buf)
228 {
229 	int index;
230 	TkListbox *l;
231 	TkLentry *e, *s;
232 
233 	l = TKobj(TkListbox, tk);
234 
235 	if(*buf == '@') {
236 		while(*buf && *buf != ',')
237 			buf++;
238 		index = l->yelem + atoi(buf+1)/lineheight(tk);
239 		if (index < 0)
240 			return 0;
241 		if (index > l->nitem)
242 			return l->nitem;
243 		return index;
244 	}
245 	if(*buf >= '0' && *buf <= '9')
246 		return atoi(buf);
247 
248 	if(strcmp(buf, "end") == 0) {
249 		if(l->nitem == 0)
250 			return 0;
251 		return l->nitem-1;
252 	}
253 
254 	index = 0;
255 	if(strcmp(buf, "active") == 0)
256 		s = l->active;
257 	else
258 	if(strcmp(buf, "anchor") == 0)
259 		s = l->anchor;
260 	else
261 		return -1;
262 
263 	for(e = l->head; e; e = e->link) {
264 		if(e == s)
265 			return index;
266 		index++;
267 	}
268 	return -1;
269 }
270 
271 void
tklistsv(Tk * tk)272 tklistsv(Tk *tk)
273 {
274 	TkListbox *l;
275 	int nl, lh, top, bot;
276 	char val[Tkminitem], cmd[Tkmaxitem], *v, *e;
277 
278 	l = TKobj(TkListbox, tk);
279 	if(l->yscroll == nil)
280 		return;
281 
282 	top = 0;
283 	bot = TKI2F(1);
284 
285 	if(l->nitem != 0) {
286 		lh = lineheight(tk);
287 		nl = tk->act.height/lh;			/* Lines in the box */
288 		top = TKI2F(l->yelem)/l->nitem;
289 		bot = TKI2F(l->yelem+nl)/l->nitem;
290 	}
291 
292 	v = tkfprint(val, top);
293 	*v++ = ' ';
294 	tkfprint(v, bot);
295 	snprint(cmd, sizeof(cmd), "%s %s", l->yscroll, val);
296 	e = tkexec(tk->env->top, cmd, nil);
297 	if ((e != nil) && (tk->name != nil))
298 		print("tk: yscrollcommand \"%s\": %s\n", tk->name->name, e);
299 }
300 
301 void
tklistsh(Tk * tk)302 tklistsh(Tk *tk)
303 {
304 	int nl, top, bot;
305 	char val[Tkminitem], cmd[Tkmaxitem], *v, *e;
306 	TkListbox *l = TKobj(TkListbox, tk);
307 
308 	if(l->xscroll == nil)
309 		return;
310 
311 	top = 0;
312 	bot = TKI2F(1);
313 
314 	if(l->nwidth != 0) {
315 		nl = tk->act.width;
316 		top = TKI2F(l->xdelta)/l->nwidth;
317 		bot = TKI2F(l->xdelta+nl)/l->nwidth;
318 	}
319 
320 	v = tkfprint(val, top);
321 	*v++ = ' ';
322 	tkfprint(v, bot);
323 	snprint(cmd, sizeof(cmd), "%s %s", l->xscroll, val);
324 	e = tkexec(tk->env->top, cmd, nil);
325 	if ((e != nil) && (tk->name != nil))
326 		print("tk: xscrollcommand \"%s\": %s\n", tk->name->name, e);
327 }
328 
329 void
tklistbgeom(Tk * tk)330 tklistbgeom(Tk *tk)
331 {
332 	tklistsv(tk);
333 	tklistsh(tk);
334 }
335 
336 static void
listbresize(Tk * tk)337 listbresize(Tk *tk)
338 {
339 	TkLentry *e;
340 	TkListbox *l = TKobj(TkListbox, tk);
341 
342 	l->nwidth = 0;
343 	for (e = l->head; e != nil; e = e->link) {
344 		e->width = stringwidth(tk->env->font, e->text)+2*(Listpadx+l->sborderwidth+tk->highlightwidth);
345 		if(e->width > l->nwidth)
346 			l->nwidth = e->width;
347 	}
348 	tklistbgeom(tk);
349 }
350 
351 
352 /* Widget Commands (+ means implemented)
353 	+activate
354 	 bbox
355 	+cget
356 	+configure
357 	+curselection
358 	+delete
359 	+get
360 	+index
361 	+insert
362 	+nearest
363 	+see
364 	+selection
365 	+size
366 	+xview
367 	+yview
368 */
369 
370 char*
tklistbconf(Tk * tk,char * arg,char ** val)371 tklistbconf(Tk *tk, char *arg, char **val)
372 {
373 	char *e;
374 	TkGeom g;
375 	int bd, sbw, hlw;
376 	TkOptab tko[3];
377 	Font *f;
378 	TkListbox *tkl = TKobj(TkListbox, tk);
379 
380 	sbw = tkl->sborderwidth;
381 	hlw = tk->highlightwidth;
382 	f = tk->env->font;
383 	tko[0].ptr = tk;
384 	tko[0].optab = tkgeneric;
385 	tko[1].ptr = tkl;
386 	tko[1].optab = opts;
387 	tko[2].ptr = nil;
388 
389 	if(*arg == '\0')
390 		return tkconflist(tko, val);
391 
392 	g = tk->req;
393 	bd = tk->borderwidth;
394 	e = tkparse(tk->env->top, arg, tko, nil);
395 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
396 	tkgeomchg(tk, &g, bd);
397 
398 	if (sbw != tkl->sborderwidth || f != tk->env->font || hlw != tk->highlightwidth)
399 		listbresize(tk);
400 	tk->dirty = tkrect(tk, 1);
401 	return e;
402 }
403 
404 static void
entryactivate(Tk * tk,int index)405 entryactivate(Tk *tk, int index)
406 {
407 	TkListbox *l = TKobj(TkListbox, tk);
408 	TkLentry *e;
409 	int flag = Tkactive;
410 
411 	if (l->selmode == TKbrowse)
412 		flag |= Tkactivated;
413 	for(e = l->head; e; e = e->link) {
414 		if(index-- == 0) {
415 			e->flag |= flag;
416 			l->active = e;
417 		} else
418 			e->flag &= ~flag;
419 	}
420 	tk->dirty = tkrect(tk, 1);
421 }
422 
423 char*
tklistbactivate(Tk * tk,char * arg,char ** val)424 tklistbactivate(Tk *tk, char *arg, char **val)
425 {
426 	int index;
427 	char buf[Tkmaxitem];
428 
429 	USED(val);
430 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
431 	index = tklindex(tk, buf);
432 	if(index == -1)
433 		return TkBadix;
434 
435 	entryactivate(tk, index);
436 	return nil;
437 }
438 
439 char*
tklistbnearest(Tk * tk,char * arg,char ** val)440 tklistbnearest(Tk *tk, char *arg, char **val)
441 {
442 	int lh, y, index;
443 	TkListbox *l = TKobj(TkListbox, tk);
444 
445 	lh = lineheight(tk);	/* Line height */
446 	y = atoi(arg);
447 	index = l->yelem + y/lh;
448 	if(index > l->nitem)
449 		index = l->nitem;
450 	return tkvalue(val, "%d", index);
451 }
452 
453 char*
tklistbindex(Tk * tk,char * arg,char ** val)454 tklistbindex(Tk *tk, char *arg, char **val)
455 {
456 	int index;
457 	char buf[Tkmaxitem];
458 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
459 	index = tklindex(tk, buf);
460 	if(index == -1)
461 		return TkBadix;
462 	return tkvalue(val, "%d", index);
463 }
464 
465 char*
tklistbsize(Tk * tk,char * arg,char ** val)466 tklistbsize(Tk *tk, char *arg, char **val)
467 {
468 	TkListbox *l = TKobj(TkListbox, tk);
469 
470 	USED(arg);
471 	return tkvalue(val, "%d", l->nitem);
472 }
473 
474 char*
tklistbinsert(Tk * tk,char * arg,char ** val)475 tklistbinsert(Tk *tk, char *arg, char **val)
476 {
477 	int n, index;
478 	TkListbox *l;
479 	TkLentry *e, **el;
480 	char *tbuf, buf[Tkmaxitem];
481 
482 	USED(val);
483 	l = TKobj(TkListbox, tk);
484 
485 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
486 	if(strcmp(buf, "end") == 0) {
487 		el = &l->head;
488 		if(*el != nil) {
489 			for(e = *el; e->link; e = e->link)
490 				;
491 			el = &e->link;
492 		}
493 	}
494 	else {
495 		index = tklindex(tk, buf);
496 		if(index == -1)
497 			return TkBadix;
498 		el = &l->head;
499 		for(e = *el; e && index-- > 0; e = e->link)
500 			el = &e->link;
501 	}
502 
503 	n = strlen(arg);
504 	if(n > Tkmaxitem) {
505 		n = (n*3)/2;
506 		tbuf = malloc(n);
507 		if(tbuf == nil)
508 			return TkNomem;
509 	}
510 	else {
511 		tbuf = buf;
512 		n = sizeof(buf);
513 	}
514 
515 	while(*arg) {
516 		arg = tkword(tk->env->top, arg, tbuf, &tbuf[n], nil);
517 		e = malloc(sizeof(TkLentry)+strlen(tbuf)+1);
518 		if(e == nil)
519 			return TkNomem;
520 
521 		e->flag = 0;
522 		strcpy(e->text, tbuf);
523 		e->link = *el;
524 		*el = e;
525 		el = &e->link;
526 		e->width = stringwidth(tk->env->font, e->text)+2*(Listpadx+l->sborderwidth+tk->highlightwidth);
527 		if(e->width > l->nwidth)
528 			l->nwidth = e->width;
529 		l->nitem++;
530 	}
531 
532 	if(tbuf != buf)
533 		free(tbuf);
534 
535 	tklistbgeom(tk);
536 	tk->dirty = tkrect(tk, 1);
537 	return nil;
538 }
539 
540 int
tklistbrange(Tk * tk,char * arg,int * s,int * e)541 tklistbrange(Tk *tk, char *arg, int *s, int *e)
542 {
543 	char buf[Tkmaxitem];
544 
545 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
546 	*s = tklindex(tk, buf);
547 	if(*s == -1)
548 		return -1;
549 	*e = *s;
550 	if(*arg == '\0')
551 		return 0;
552 
553 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
554 	*e = tklindex(tk, buf);
555 	if(*e == -1)
556 		return -1;
557 	return 0;
558 }
559 
560 char*
tklistbselection(Tk * tk,char * arg,char ** val)561 tklistbselection(Tk *tk, char *arg, char **val)
562 {
563 	TkTop *t;
564 	TkLentry *f;
565 	TkListbox *l;
566 	int s, e, indx;
567 	char buf[Tkmaxitem];
568 
569 	l = TKobj(TkListbox, tk);
570 
571 	t = tk->env->top;
572 	arg = tkword(t, arg, buf, buf+sizeof(buf), nil);
573 	if(strcmp(buf, "includes") == 0) {
574 		tkword(t, arg, buf, buf+sizeof(buf), nil);
575 		indx = tklindex(tk, buf);
576 		if(indx == -1)
577 			return TkBadix;
578 		for(f = l->head; f && indx > 0; f = f->link)
579 			indx--;
580 		s = 0;
581 		if(f && (f->flag&Tkactivated))
582 			s = 1;
583 		return tkvalue(val, "%d", s);
584 	}
585 
586 	if(strcmp(buf, "anchor") == 0) {
587 		tkword(t, arg, buf, buf+sizeof(buf), nil);
588 		indx = tklindex(tk, buf);
589 		if(indx == -1)
590 			return TkBadix;
591 		for(f = l->head; f && indx > 0; f = f->link)
592 			indx--;
593 		if(f != nil)
594 			l->anchor = f;
595 		return nil;
596 	}
597 	indx = 0;
598 	if(strcmp(buf, "clear") == 0) {
599 		if(tklistbrange(tk, arg, &s, &e) != 0)
600 			return TkBadix;
601 		for(f = l->head; f; f = f->link) {
602 			if(indx <= e && indx++ >= s)
603 				f->flag &= ~Tkactivated;
604 		}
605 		tk->dirty = tkrect(tk, 1);
606 		return nil;
607 	}
608 	if(strcmp(buf, "set") == 0) {
609 		if(tklistbrange(tk, arg, &s, &e) != 0)
610 			return TkBadix;
611 		for(f = l->head; f; f = f->link) {
612 			if(indx <= e && indx++ >= s)
613 				f->flag |= Tkactivated;
614 		}
615 		tk->dirty = tkrect(tk, 1);
616 		return nil;
617 	}
618 	return TkBadcm;
619 }
620 
621 char*
tklistbdelete(Tk * tk,char * arg,char ** val)622 tklistbdelete(Tk *tk, char *arg, char **val)
623 {
624 	TkLentry *e, **el;
625 	int start, end, indx, bh;
626 	TkListbox *l = TKobj(TkListbox, tk);
627 
628 	USED(val);
629 	if(tklistbrange(tk, arg, &start, &end) != 0)
630 		return TkBadix;
631 
632 	indx = 0;
633 	el = &l->head;
634 	for(e = l->head; e && indx < start; e = e->link) {
635 		indx++;
636 		el = &e->link;
637 	}
638 	while(e != nil && indx <= end) {
639 		*el = e->link;
640 		if(e->width == l->nwidth)
641 			l->nwidth = 0;
642 		if (e == l->anchor)
643 			l->anchor = nil;
644 		if (e == l->active)
645 			l->active = nil;
646 		free(e);
647 		e = *el;
648 		indx++;
649 		l->nitem--;
650 	}
651 	if(l->nwidth == 0) {
652 		for(e = l->head; e; e = e->link)
653 			if(e->width > l->nwidth)
654 				l->nwidth = e->width;
655 	}
656 	bh = tk->act.height/lineheight(tk);	/* Box height */
657 	if(l->yelem + bh > l->nitem)
658 		l->yelem = l->nitem - bh;
659 	if(l->yelem < 0)
660 		l->yelem = 0;
661 
662 	tklistbgeom(tk);
663 	tk->dirty = tkrect(tk, 1);
664 	return nil;
665 }
666 
667 char*
tklistbget(Tk * tk,char * arg,char ** val)668 tklistbget(Tk *tk, char *arg, char **val)
669 {
670 	TkLentry *e;
671 	char *r, *fmt;
672 	int start, end, indx;
673 	TkListbox *l = TKobj(TkListbox, tk);
674 
675 	if(tklistbrange(tk, arg, &start, &end) != 0)
676 		return TkBadix;
677 
678 	indx = 0;
679 	for(e = l->head; e && indx < start; e = e->link)
680 		indx++;
681 	fmt = "%s";
682 	while(e != nil && indx <= end) {
683 		r = tkvalue(val, fmt, e->text);
684 		if(r != nil)
685 			return r;
686 		indx++;
687 		fmt = " %s";
688 		e = e->link;
689 	}
690 	return nil;
691 }
692 
693 char*
tklistbcursel(Tk * tk,char * arg,char ** val)694 tklistbcursel(Tk *tk, char *arg, char **val)
695 {
696 	int indx;
697 	TkLentry *e;
698 	char *r, *fmt;
699 	TkListbox *l = TKobj(TkListbox, tk);
700 
701 	USED(arg);
702 	indx = 0;
703 	fmt = "%d";
704 	for(e = l->head; e; e = e->link) {
705 		if(e->flag & Tkactivated) {
706 			r = tkvalue(val, fmt, indx);
707 			if(r != nil)
708 				return r;
709 			fmt = " %d";
710 		}
711 		indx++;
712 	}
713 	return nil;
714 }
715 
716 static char*
tklistbview(Tk * tk,char * arg,char ** val,int nl,int * posn,int max)717 tklistbview(Tk *tk, char *arg, char **val, int nl, int *posn, int max)
718 {
719 	int top, bot, amount;
720 	char buf[Tkmaxitem];
721 	char *v, *e;
722 
723 	top = 0;
724 	if(*arg == '\0') {
725 		if ( max <= nl || max == 0 ) {	/* Double test redundant at
726 						 * this time, but want to
727 						 * protect against future
728 						 * calls. -- DBK */
729 			top = 0;
730 			bot = TKI2F(1);
731 		}
732 		else {
733 			top = TKI2F(*posn)/max;
734 			bot = TKI2F(*posn+nl)/max;
735 		}
736 		v = tkfprint(buf, top);
737 		*v++ = ' ';
738 		tkfprint(v, bot);
739 		return tkvalue(val, "%s", buf);
740 	}
741 
742 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
743 	if(strcmp(buf, "moveto") == 0) {
744 		e = tkfracword(tk->env->top, &arg, &top, nil);
745 		if (e != nil)
746 			return e;
747 		*posn = TKF2I((top+1)*max);
748 	}
749 	else
750 	if(strcmp(buf, "scroll") == 0) {
751 		arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
752 		amount = atoi(buf);
753 		tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
754 		if(buf[0] == 'p')		/* Pages */
755 			amount *= nl;
756 		*posn += amount;
757 	}
758 	else {
759 		top = tklindex(tk, buf);
760 		if(top == -1)
761 			return TkBadix;
762 		*posn = top;
763 	}
764 
765 	bot = max - nl;
766 	if(*posn > bot)
767 		*posn = bot;
768 	if(*posn < 0)
769 		*posn = 0;
770 
771 	tk->dirty = tkrect(tk, 1);
772 	return nil;
773 }
774 
775 static int
entrysee(Tk * tk,int index)776 entrysee(Tk *tk, int index)
777 {
778 	TkListbox *l = TKobj(TkListbox, tk);
779 	int bh;
780 
781 	/* Box height in lines */
782 	bh = tk->act.height/lineheight(tk);
783 	if (bh > l->nitem)
784 		bh = l->nitem;
785 	if (index >= l->nitem)
786 		index = l->nitem -1;
787 	if (index < 0)
788 		index = 0;
789 
790 	/* If the item is already visible, do nothing */
791 	if (l->nitem == 0 || index >= l->yelem && index < l->yelem+bh)
792 		return index;
793 
794 	if (index < l->yelem)
795 		l->yelem = index;
796 	else
797 		l->yelem = index - (bh-1);
798 
799 	tklistsv(tk);
800 	tk->dirty = tkrect(tk, 1);
801 	return index;
802 }
803 
804 char*
tklistbsee(Tk * tk,char * arg,char ** val)805 tklistbsee(Tk *tk, char *arg, char **val)
806 {
807 	int index;
808 	char buf[Tkmaxitem];
809 
810 	USED(val);
811 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
812 	index = tklindex(tk, buf);
813 	if(index == -1)
814 		return TkBadix;
815 
816 	entrysee(tk, index);
817 	return nil;
818 }
819 
820 char*
tklistbyview(Tk * tk,char * arg,char ** val)821 tklistbyview(Tk *tk, char *arg, char **val)
822 {
823 	int bh;
824 	char *e;
825 	TkListbox *l = TKobj(TkListbox, tk);
826 
827 	bh = tk->act.height/lineheight(tk);	/* Box height */
828 	e = tklistbview(tk, arg, val, bh, &l->yelem, l->nitem);
829 	tklistsv(tk);
830 	return e;
831 }
832 
833 char*
tklistbxview(Tk * tk,char * arg,char ** val)834 tklistbxview(Tk *tk, char *arg, char **val)
835 {
836 	char *e;
837 	TkListbox *l = TKobj(TkListbox, tk);
838 
839 	e = tklistbview(tk, arg, val, tk->act.width, &l->xdelta, l->nwidth);
840 	tklistsh(tk);
841 	return e;
842 }
843 
844 static TkLentry*
entryset(TkListbox * l,int indx,int toggle)845 entryset(TkListbox *l, int indx, int toggle)
846 {
847 	TkLentry *e, *anchor;
848 
849 	anchor = nil;
850 	for(e = l->head; e; e = e->link) {
851 		if (indx-- == 0) {
852 			anchor = e;
853 			if (toggle) {
854 				e->flag ^= Tkactivated;
855 				break;
856 			} else
857 				e->flag |= Tkactivated;
858 			continue;
859 		}
860 		if (!toggle)
861 			e->flag &= ~Tkactivated;
862 	}
863 	return anchor;
864 }
865 
866 static void
selectto(TkListbox * l,int indx)867 selectto(TkListbox *l, int indx)
868 {
869 	TkLentry *e;
870 	int inrange;
871 
872 	if (l->anchor == nil)
873 		return;
874 	inrange = 0;
875 	for(e = l->head; e; e = e->link) {
876 		if(indx == 0)
877 			inrange = !inrange;
878 		if(e == l->anchor)
879 			inrange = !inrange;
880 		if(inrange || e == l->anchor || indx == 0)
881 			e->flag |= Tkactivated;
882 		else
883 			e->flag &= ~Tkactivated;
884 		indx--;
885 	}
886 }
887 
888 static char*
dragto(Tk * tk,int y)889 dragto(Tk *tk, int y)
890 {
891 	int indx;
892 	TkLentry *e;
893 	TkListbox *l = TKobj(TkListbox, tk);
894 
895 	indx = y/lineheight(tk);
896 	if (y < 0)
897 		indx--;	/* int division rounds towards 0 */
898 	if (y < tk->act.height && indx >= l->nitem)
899 		return nil;
900 	indx = entrysee(tk, l->yelem+indx);
901 	entryactivate(tk, indx);
902 
903 	if(l->selmode == TKsingle || l->selmode == TKmultiple)
904 		return nil;
905 
906 	if(l->selmode == TKbrowse) {
907 		for(e = l->head; e; e = e->link) {
908 			if(indx-- == 0) {
909 				if (e == l->anchor)
910 					return nil;
911 				l->anchor = e;
912 				e->flag |= Tkactivated;
913 			} else
914 				e->flag &= ~Tkactivated;
915 		}
916 		return nil;
917 	}
918 	/* extended selection mode */
919 	selectto(l, indx);
920 	tk->dirty = tkrect(tk, 1);
921 	return nil;
922 }
923 
924 static void
autoselect(Tk * tk,void * v,int cancelled)925 autoselect(Tk *tk, void *v, int cancelled)
926 {
927 	Point pt;
928 	int y, eh, ne;
929 
930 	USED(v);
931 	if (cancelled)
932 		return;
933 
934 	pt = tkposn(tk);
935 	pt.y += tk->borderwidth;
936 	y = tk->env->top->ctxt->mstate.y;
937 	y -= pt.y;
938 	eh = lineheight(tk);
939 	ne = tk->act.height/eh;
940 	if (y >= 0 && y < eh*ne)
941 		return;
942 	dragto(tk, y);
943 	tkdirty(tk);
944 	tkupdate(tk->env->top);
945 }
946 
947 static char*
tklistbbutton1p(Tk * tk,char * arg,char ** val)948 tklistbbutton1p(Tk *tk, char *arg, char **val)
949 {
950 	TkListbox *l = TKobj(TkListbox, tk);
951 	int y, indx;
952 
953 	USED(val);
954 
955 	y = atoi(arg);
956 	indx = y/lineheight(tk);
957 	indx += l->yelem;
958 	if (indx < l->nitem) {
959 		l->anchor = entryset(l, indx, l->selmode == TKmultiple);
960 		entryactivate(tk, indx);
961 		entrysee(tk, indx);
962 	}
963 	tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval);
964 	return nil;
965 }
966 
967 char *
tklistbbutton1r(Tk * tk,char * arg,char ** val)968 tklistbbutton1r(Tk *tk, char *arg, char **val)
969 {
970 	USED(arg);
971 	USED(val);
972 	tkcancelrepeat(tk);
973 	return nil;
974 }
975 
976 char*
tklistbbutton1m(Tk * tk,char * arg,char ** val)977 tklistbbutton1m(Tk *tk, char *arg, char **val)
978 {
979 	int y, eh, ne;
980 	USED(val);
981 
982 	eh = lineheight(tk);
983 	ne = tk->act.height/eh;
984 	y = atoi(arg);
985 	/* If outside the box, let autoselect handle it */
986 	if (y < 0 || y >= ne * eh)
987 		return nil;
988 	return dragto(tk, y);
989 }
990 
991 char*
tklistbkey(Tk * tk,char * arg,char ** val)992 tklistbkey(Tk *tk, char *arg, char **val)
993 {
994 	TkListbox *l = TKobj(TkListbox, tk);
995 	TkLentry *e;
996 	int key, active;
997 	USED(val);
998 
999 	if(tk->flag & Tkdisabled)
1000 		return nil;
1001 
1002 	key = strtol(arg, nil, 0);
1003 	active = 0;
1004 	for (e = l->head; e != nil; e = e->link) {
1005 		if (e->flag & Tkactive)
1006 			break;
1007 		active++;
1008 	}
1009 
1010 	if (key == '\n' || key == ' ') {
1011 		l->anchor = entryset(l, active, l->selmode == TKmultiple);
1012 		tk->dirty = tkrect(tk, 0);
1013 		return nil;
1014 	}
1015 	if (key == Up)
1016 		active--;
1017 	else if (key == Down)
1018 		active++;
1019 	else
1020 		return nil;
1021 
1022 	if (active < 0)
1023 		active = 0;
1024 	if (active >= l->nitem)
1025 		active = l->nitem-1;
1026 	entryactivate(tk, active);
1027 	if (l->selmode == TKextended) {
1028 		selectto(l, active);
1029 		tk->dirty = tkrect(tk, 0);
1030 	}
1031 	entrysee(tk, active);
1032 	return nil;
1033 }
1034 
1035 static
1036 TkCmdtab tklistcmd[] =
1037 {
1038 	"activate",		tklistbactivate,
1039 	"cget",			tklistbcget,
1040 	"configure",		tklistbconf,
1041 	"curselection",		tklistbcursel,
1042 	"delete",		tklistbdelete,
1043 	"get",			tklistbget,
1044 	"index",		tklistbindex,
1045 	"insert",		tklistbinsert,
1046 	"nearest",		tklistbnearest,
1047 	"selection",		tklistbselection,
1048 	"see",			tklistbsee,
1049 	"size",			tklistbsize,
1050 	"xview",		tklistbxview,
1051 	"yview",		tklistbyview,
1052 	"tkListbButton1P",	tklistbbutton1p,
1053 	"tkListbButton1R",	tklistbbutton1r,
1054 	"tkListbButton1MP",	tklistbbutton1m,
1055 	"tkListbKey",	tklistbkey,
1056 	nil
1057 };
1058 
1059 TkMethod listboxmethod = {
1060 	"listbox",
1061 	tklistcmd,
1062 	tkfreelistb,
1063 	tkdrawlistb,
1064 	tklistbgeom
1065 };
1066