#include "lib9.h" #include "draw.h" #include "keyboard.h" #include "tk.h" #include "listb.h" #define O(t, e) ((long)(&((t*)0)->e)) /* Layout constants */ enum { Listpadx = 2, /* X padding of text in listboxes */ }; typedef struct TkLentry TkLentry; typedef struct TkListbox TkListbox; struct TkLentry { TkLentry* link; int flag; int width; char text[TKSTRUCTALIGN]; }; struct TkListbox { TkLentry* head; TkLentry* anchor; TkLentry* active; int yelem; /* Y element at top of box */ int xdelta; /* h-scroll position */ int nitem; int nwidth; int selmode; int sborderwidth; char* xscroll; char* yscroll; }; TkStab tkselmode[] = { "single", TKsingle, "browse", TKbrowse, "multiple", TKmultiple, "extended", TKextended, nil }; static TkOption opts[] = { "xscrollcommand", OPTtext, O(TkListbox, xscroll), nil, "yscrollcommand", OPTtext, O(TkListbox, yscroll), nil, "selectmode", OPTstab, O(TkListbox, selmode), tkselmode, "selectborderwidth", OPTnndist, O(TkListbox, sborderwidth), nil, nil }; static TkEbind b[] = { {TkButton1P, "%W tkListbButton1P %y"}, {TkButton1R, "%W tkListbButton1R"}, {TkButton1P|TkMotion, "%W tkListbButton1MP %y"}, {TkMotion, ""}, {TkKey, "%W tkListbKey 0x%K"}, }; static int lineheight(Tk *tk) { TkListbox *l = TKobj(TkListbox, tk); return tk->env->font->height+2*(l->sborderwidth+tk->highlightwidth); } char* tklistbox(TkTop *t, char *arg, char **ret) { Tk *tk; char *e; TkName *names; TkListbox *tkl; TkOptab tko[3]; tk = tknewobj(t, TKlistbox, sizeof(Tk)+sizeof(TkListbox)); if(tk == nil) return TkNomem; tkl = TKobj(TkListbox, tk); tkl->sborderwidth = 1; tk->relief = TKsunken; tk->borderwidth = 1; tk->highlightwidth = 1; tk->flag |= Tktakefocus; tk->req.width = 170; tk->req.height = lineheight(tk)*10; tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkl; tko[1].optab = opts; tko[2].ptr = nil; names = nil; e = tkparse(t, arg, tko, &names); if(e != nil) { tkfreeobj(tk); return e; } tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); e = tkbindings(t, tk, b, nelem(b)); if(e != nil) { tkfreeobj(tk); return e; } e = tkaddchild(t, tk, &names); tkfreename(names); if(e != nil) { tkfreeobj(tk); return e; } tk->name->link = nil; return tkvalue(ret, "%s", tk->name->name); } char* tklistbcget(Tk *tk, char *arg, char **val) { TkOptab tko[3]; TkListbox *tkl = TKobj(TkListbox, tk); tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkl; tko[1].optab = opts; tko[2].ptr = nil; return tkgencget(tko, arg, val, tk->env->top); } void tkfreelistb(Tk *tk) { TkLentry *e, *next; TkListbox *l = TKobj(TkListbox, tk); for(e = l->head; e; e = next) { next = e->link; free(e); } if(l->xscroll != nil) free(l->xscroll); if(l->yscroll != nil) free(l->yscroll); } char* tkdrawlistb(Tk *tk, Point orig) { Point p; TkEnv *env; TkLentry *e; int lh, w, n, ly; Rectangle r, a; Image *i, *fg; TkListbox *l = TKobj(TkListbox, tk); env = tk->env; r.min = ZP; r.max.x = tk->act.width + 2*tk->borderwidth; r.max.y = tk->act.height + 2*tk->borderwidth; i = tkitmp(env, r.max, TkCbackgnd); if(i == nil) return nil; w = tk->act.width; if (w < l->nwidth) w = l->nwidth; lh = lineheight(tk); ly = tk->borderwidth; p.x = tk->borderwidth+l->sborderwidth+tk->highlightwidth+Listpadx-l->xdelta; p.y = tk->borderwidth+l->sborderwidth+tk->highlightwidth; n = 0; for(e = l->head; e && ly < r.max.y; e = e->link) { if(n++ < l->yelem) continue; a.min.x = tk->borderwidth; a.min.y = ly; a.max.x = a.min.x + tk->act.width; a.max.y = a.min.y + lh; if(e->flag & Tkactivated) { draw(i, a, tkgc(env, TkCselectbgnd), nil, ZP); } if(e->flag & Tkactivated) fg = tkgc(env, TkCselectfgnd); else fg = tkgc(env, TkCforegnd); string(i, p, fg, p, env->font, e->text); if((e->flag & Tkactive) && tkhaskeyfocus(tk)) { a.min.x = tk->borderwidth-l->xdelta; a.max.x = a.min.x+w; a = insetrect(a, l->sborderwidth); tkbox(i, a, tk->highlightwidth, fg); } ly += lh; p.y += lh; } tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief); p.x = tk->act.x + orig.x; p.y = tk->act.y + orig.y; r = rectaddpt(r, p); draw(tkimageof(tk), r, i, nil, ZP); return nil; } int tklindex(Tk *tk, char *buf) { int index; TkListbox *l; TkLentry *e, *s; l = TKobj(TkListbox, tk); if(*buf == '@') { while(*buf && *buf != ',') buf++; index = l->yelem + atoi(buf+1)/lineheight(tk); if (index < 0) return 0; if (index > l->nitem) return l->nitem; return index; } if(*buf >= '0' && *buf <= '9') return atoi(buf); if(strcmp(buf, "end") == 0) { if(l->nitem == 0) return 0; return l->nitem-1; } index = 0; if(strcmp(buf, "active") == 0) s = l->active; else if(strcmp(buf, "anchor") == 0) s = l->anchor; else return -1; for(e = l->head; e; e = e->link) { if(e == s) return index; index++; } return -1; } void tklistsv(Tk *tk) { TkListbox *l; int nl, lh, top, bot; char val[Tkminitem], cmd[Tkmaxitem], *v, *e; l = TKobj(TkListbox, tk); if(l->yscroll == nil) return; top = 0; bot = TKI2F(1); if(l->nitem != 0) { lh = lineheight(tk); nl = tk->act.height/lh; /* Lines in the box */ top = TKI2F(l->yelem)/l->nitem; bot = TKI2F(l->yelem+nl)/l->nitem; } v = tkfprint(val, top); *v++ = ' '; tkfprint(v, bot); snprint(cmd, sizeof(cmd), "%s %s", l->yscroll, val); e = tkexec(tk->env->top, cmd, nil); if ((e != nil) && (tk->name != nil)) print("tk: yscrollcommand \"%s\": %s\n", tk->name->name, e); } void tklistsh(Tk *tk) { int nl, top, bot; char val[Tkminitem], cmd[Tkmaxitem], *v, *e; TkListbox *l = TKobj(TkListbox, tk); if(l->xscroll == nil) return; top = 0; bot = TKI2F(1); if(l->nwidth != 0) { nl = tk->act.width; top = TKI2F(l->xdelta)/l->nwidth; bot = TKI2F(l->xdelta+nl)/l->nwidth; } v = tkfprint(val, top); *v++ = ' '; tkfprint(v, bot); snprint(cmd, sizeof(cmd), "%s %s", l->xscroll, val); e = tkexec(tk->env->top, cmd, nil); if ((e != nil) && (tk->name != nil)) print("tk: xscrollcommand \"%s\": %s\n", tk->name->name, e); } void tklistbgeom(Tk *tk) { tklistsv(tk); tklistsh(tk); } static void listbresize(Tk *tk) { TkLentry *e; TkListbox *l = TKobj(TkListbox, tk); l->nwidth = 0; for (e = l->head; e != nil; e = e->link) { e->width = stringwidth(tk->env->font, e->text)+2*(Listpadx+l->sborderwidth+tk->highlightwidth); if(e->width > l->nwidth) l->nwidth = e->width; } tklistbgeom(tk); } /* Widget Commands (+ means implemented) +activate bbox +cget +configure +curselection +delete +get +index +insert +nearest +see +selection +size +xview +yview */ char* tklistbconf(Tk *tk, char *arg, char **val) { char *e; TkGeom g; int bd, sbw, hlw; TkOptab tko[3]; Font *f; TkListbox *tkl = TKobj(TkListbox, tk); sbw = tkl->sborderwidth; hlw = tk->highlightwidth; f = tk->env->font; tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkl; tko[1].optab = opts; tko[2].ptr = nil; if(*arg == '\0') return tkconflist(tko, val); g = tk->req; bd = tk->borderwidth; e = tkparse(tk->env->top, arg, tko, nil); tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); tkgeomchg(tk, &g, bd); if (sbw != tkl->sborderwidth || f != tk->env->font || hlw != tk->highlightwidth) listbresize(tk); tk->dirty = tkrect(tk, 1); return e; } static void entryactivate(Tk *tk, int index) { TkListbox *l = TKobj(TkListbox, tk); TkLentry *e; int flag = Tkactive; if (l->selmode == TKbrowse) flag |= Tkactivated; for(e = l->head; e; e = e->link) { if(index-- == 0) { e->flag |= flag; l->active = e; } else e->flag &= ~flag; } tk->dirty = tkrect(tk, 1); } char* tklistbactivate(Tk *tk, char *arg, char **val) { int index; char buf[Tkmaxitem]; USED(val); tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); index = tklindex(tk, buf); if(index == -1) return TkBadix; entryactivate(tk, index); return nil; } char* tklistbnearest(Tk *tk, char *arg, char **val) { int lh, y, index; TkListbox *l = TKobj(TkListbox, tk); lh = lineheight(tk); /* Line height */ y = atoi(arg); index = l->yelem + y/lh; if(index > l->nitem) index = l->nitem; return tkvalue(val, "%d", index); } char* tklistbindex(Tk *tk, char *arg, char **val) { int index; char buf[Tkmaxitem]; tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); index = tklindex(tk, buf); if(index == -1) return TkBadix; return tkvalue(val, "%d", index); } char* tklistbsize(Tk *tk, char *arg, char **val) { TkListbox *l = TKobj(TkListbox, tk); USED(arg); return tkvalue(val, "%d", l->nitem); } char* tklistbinsert(Tk *tk, char *arg, char **val) { int n, index; TkListbox *l; TkLentry *e, **el; char *tbuf, buf[Tkmaxitem]; USED(val); l = TKobj(TkListbox, tk); arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); if(strcmp(buf, "end") == 0) { el = &l->head; if(*el != nil) { for(e = *el; e->link; e = e->link) ; el = &e->link; } } else { index = tklindex(tk, buf); if(index == -1) return TkBadix; el = &l->head; for(e = *el; e && index-- > 0; e = e->link) el = &e->link; } n = strlen(arg); if(n > Tkmaxitem) { n = (n*3)/2; tbuf = malloc(n); if(tbuf == nil) return TkNomem; } else { tbuf = buf; n = sizeof(buf); } while(*arg) { arg = tkword(tk->env->top, arg, tbuf, &tbuf[n], nil); e = malloc(sizeof(TkLentry)+strlen(tbuf)+1); if(e == nil) return TkNomem; e->flag = 0; strcpy(e->text, tbuf); e->link = *el; *el = e; el = &e->link; e->width = stringwidth(tk->env->font, e->text)+2*(Listpadx+l->sborderwidth+tk->highlightwidth); if(e->width > l->nwidth) l->nwidth = e->width; l->nitem++; } if(tbuf != buf) free(tbuf); tklistbgeom(tk); tk->dirty = tkrect(tk, 1); return nil; } int tklistbrange(Tk *tk, char *arg, int *s, int *e) { char buf[Tkmaxitem]; arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); *s = tklindex(tk, buf); if(*s == -1) return -1; *e = *s; if(*arg == '\0') return 0; tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); *e = tklindex(tk, buf); if(*e == -1) return -1; return 0; } char* tklistbselection(Tk *tk, char *arg, char **val) { TkTop *t; TkLentry *f; TkListbox *l; int s, e, indx; char buf[Tkmaxitem]; l = TKobj(TkListbox, tk); t = tk->env->top; arg = tkword(t, arg, buf, buf+sizeof(buf), nil); if(strcmp(buf, "includes") == 0) { tkword(t, arg, buf, buf+sizeof(buf), nil); indx = tklindex(tk, buf); if(indx == -1) return TkBadix; for(f = l->head; f && indx > 0; f = f->link) indx--; s = 0; if(f && (f->flag&Tkactivated)) s = 1; return tkvalue(val, "%d", s); } if(strcmp(buf, "anchor") == 0) { tkword(t, arg, buf, buf+sizeof(buf), nil); indx = tklindex(tk, buf); if(indx == -1) return TkBadix; for(f = l->head; f && indx > 0; f = f->link) indx--; if(f != nil) l->anchor = f; return nil; } indx = 0; if(strcmp(buf, "clear") == 0) { if(tklistbrange(tk, arg, &s, &e) != 0) return TkBadix; for(f = l->head; f; f = f->link) { if(indx <= e && indx++ >= s) f->flag &= ~Tkactivated; } tk->dirty = tkrect(tk, 1); return nil; } if(strcmp(buf, "set") == 0) { if(tklistbrange(tk, arg, &s, &e) != 0) return TkBadix; for(f = l->head; f; f = f->link) { if(indx <= e && indx++ >= s) f->flag |= Tkactivated; } tk->dirty = tkrect(tk, 1); return nil; } return TkBadcm; } char* tklistbdelete(Tk *tk, char *arg, char **val) { TkLentry *e, **el; int start, end, indx, bh; TkListbox *l = TKobj(TkListbox, tk); USED(val); if(tklistbrange(tk, arg, &start, &end) != 0) return TkBadix; indx = 0; el = &l->head; for(e = l->head; e && indx < start; e = e->link) { indx++; el = &e->link; } while(e != nil && indx <= end) { *el = e->link; if(e->width == l->nwidth) l->nwidth = 0; if (e == l->anchor) l->anchor = nil; if (e == l->active) l->active = nil; free(e); e = *el; indx++; l->nitem--; } if(l->nwidth == 0) { for(e = l->head; e; e = e->link) if(e->width > l->nwidth) l->nwidth = e->width; } bh = tk->act.height/lineheight(tk); /* Box height */ if(l->yelem + bh > l->nitem) l->yelem = l->nitem - bh; if(l->yelem < 0) l->yelem = 0; tklistbgeom(tk); tk->dirty = tkrect(tk, 1); return nil; } char* tklistbget(Tk *tk, char *arg, char **val) { TkLentry *e; char *r, *fmt; int start, end, indx; TkListbox *l = TKobj(TkListbox, tk); if(tklistbrange(tk, arg, &start, &end) != 0) return TkBadix; indx = 0; for(e = l->head; e && indx < start; e = e->link) indx++; fmt = "%s"; while(e != nil && indx <= end) { r = tkvalue(val, fmt, e->text); if(r != nil) return r; indx++; fmt = " %s"; e = e->link; } return nil; } char* tklistbcursel(Tk *tk, char *arg, char **val) { int indx; TkLentry *e; char *r, *fmt; TkListbox *l = TKobj(TkListbox, tk); USED(arg); indx = 0; fmt = "%d"; for(e = l->head; e; e = e->link) { if(e->flag & Tkactivated) { r = tkvalue(val, fmt, indx); if(r != nil) return r; fmt = " %d"; } indx++; } return nil; } static char* tklistbview(Tk *tk, char *arg, char **val, int nl, int *posn, int max) { s32 top, bot, amount; char buf[Tkmaxitem]; char *v, *e; top = 0; if(*arg == '\0') { if ( max <= nl || max == 0 ) { /* Double test redundant at * this time, but want to * protect against future * calls. -- DBK */ top = 0; bot = TKI2F(1); } else { top = TKI2F(*posn)/max; bot = TKI2F(*posn+nl)/max; } v = tkfprint(buf, top); *v++ = ' '; tkfprint(v, bot); return tkvalue(val, "%s", buf); } arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); if(strcmp(buf, "moveto") == 0) { e = tkfracword(tk->env->top, &arg, &top, nil); if (e != nil) return e; *posn = TKF2I((top+1)*max); } else if(strcmp(buf, "scroll") == 0) { arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); amount = atoi(buf); tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); if(buf[0] == 'p') /* Pages */ amount *= nl; *posn += amount; } else { top = tklindex(tk, buf); if(top == -1) return TkBadix; *posn = top; } bot = max - nl; if(*posn > bot) *posn = bot; if(*posn < 0) *posn = 0; tk->dirty = tkrect(tk, 1); return nil; } static int entrysee(Tk *tk, int index) { TkListbox *l = TKobj(TkListbox, tk); int bh; /* Box height in lines */ bh = tk->act.height/lineheight(tk); if (bh > l->nitem) bh = l->nitem; if (index >= l->nitem) index = l->nitem -1; if (index < 0) index = 0; /* If the item is already visible, do nothing */ if (l->nitem == 0 || index >= l->yelem && index < l->yelem+bh) return index; if (index < l->yelem) l->yelem = index; else l->yelem = index - (bh-1); tklistsv(tk); tk->dirty = tkrect(tk, 1); return index; } char* tklistbsee(Tk *tk, char *arg, char **val) { int index; char buf[Tkmaxitem]; USED(val); tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); index = tklindex(tk, buf); if(index == -1) return TkBadix; entrysee(tk, index); return nil; } char* tklistbyview(Tk *tk, char *arg, char **val) { int bh; char *e; TkListbox *l = TKobj(TkListbox, tk); bh = tk->act.height/lineheight(tk); /* Box height */ e = tklistbview(tk, arg, val, bh, &l->yelem, l->nitem); tklistsv(tk); return e; } char* tklistbxview(Tk *tk, char *arg, char **val) { char *e; TkListbox *l = TKobj(TkListbox, tk); e = tklistbview(tk, arg, val, tk->act.width, &l->xdelta, l->nwidth); tklistsh(tk); return e; } static TkLentry* entryset(TkListbox *l, int indx, int toggle) { TkLentry *e, *anchor; anchor = nil; for(e = l->head; e; e = e->link) { if (indx-- == 0) { anchor = e; if (toggle) { e->flag ^= Tkactivated; break; } else e->flag |= Tkactivated; continue; } if (!toggle) e->flag &= ~Tkactivated; } return anchor; } static void selectto(TkListbox *l, int indx) { TkLentry *e; int inrange; if (l->anchor == nil) return; inrange = 0; for(e = l->head; e; e = e->link) { if(indx == 0) inrange = !inrange; if(e == l->anchor) inrange = !inrange; if(inrange || e == l->anchor || indx == 0) e->flag |= Tkactivated; else e->flag &= ~Tkactivated; indx--; } } static char* dragto(Tk *tk, int y) { int indx; TkLentry *e; TkListbox *l = TKobj(TkListbox, tk); indx = y/lineheight(tk); if (y < 0) indx--; /* int division rounds towards 0 */ if (y < tk->act.height && indx >= l->nitem) return nil; indx = entrysee(tk, l->yelem+indx); entryactivate(tk, indx); if(l->selmode == TKsingle || l->selmode == TKmultiple) return nil; if(l->selmode == TKbrowse) { for(e = l->head; e; e = e->link) { if(indx-- == 0) { if (e == l->anchor) return nil; l->anchor = e; e->flag |= Tkactivated; } else e->flag &= ~Tkactivated; } return nil; } /* extended selection mode */ selectto(l, indx); tk->dirty = tkrect(tk, 1); return nil; } static void autoselect(Tk *tk, void *v, int cancelled) { Point pt; int y, eh, ne; USED(v); if (cancelled) return; pt = tkposn(tk); pt.y += tk->borderwidth; y = tk->env->top->ctxt->mstate.y; y -= pt.y; eh = lineheight(tk); ne = tk->act.height/eh; if (y >= 0 && y < eh*ne) return; dragto(tk, y); tkdirty(tk); tkupdate(tk->env->top); } static char* tklistbbutton1p(Tk *tk, char *arg, char **val) { TkListbox *l = TKobj(TkListbox, tk); int y, indx; USED(val); y = atoi(arg); indx = y/lineheight(tk); indx += l->yelem; if (indx < l->nitem) { l->anchor = entryset(l, indx, l->selmode == TKmultiple); entryactivate(tk, indx); entrysee(tk, indx); } tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval); return nil; } char * tklistbbutton1r(Tk *tk, char *arg, char **val) { USED(arg); USED(val); tkcancelrepeat(tk); return nil; } char* tklistbbutton1m(Tk *tk, char *arg, char **val) { int y, eh, ne; USED(val); eh = lineheight(tk); ne = tk->act.height/eh; y = atoi(arg); /* If outside the box, let autoselect handle it */ if (y < 0 || y >= ne * eh) return nil; return dragto(tk, y); } char* tklistbkey(Tk *tk, char *arg, char **val) { TkListbox *l = TKobj(TkListbox, tk); TkLentry *e; int key, active; USED(val); if(tk->flag & Tkdisabled) return nil; key = strtol(arg, nil, 0); active = 0; for (e = l->head; e != nil; e = e->link) { if (e->flag & Tkactive) break; active++; } if (key == '\n' || key == ' ') { l->anchor = entryset(l, active, l->selmode == TKmultiple); tk->dirty = tkrect(tk, 0); return nil; } if (key == Up) active--; else if (key == Down) active++; else return nil; if (active < 0) active = 0; if (active >= l->nitem) active = l->nitem-1; entryactivate(tk, active); if (l->selmode == TKextended) { selectto(l, active); tk->dirty = tkrect(tk, 0); } entrysee(tk, active); return nil; } static TkCmdtab tklistcmd[] = { "activate", tklistbactivate, "cget", tklistbcget, "configure", tklistbconf, "curselection", tklistbcursel, "delete", tklistbdelete, "get", tklistbget, "index", tklistbindex, "insert", tklistbinsert, "nearest", tklistbnearest, "selection", tklistbselection, "see", tklistbsee, "size", tklistbsize, "xview", tklistbxview, "yview", tklistbyview, "tkListbButton1P", tklistbbutton1p, "tkListbButton1R", tklistbbutton1r, "tkListbButton1MP", tklistbbutton1m, "tkListbKey", tklistbkey, nil }; TkMethod listboxmethod = { "listbox", tklistcmd, tkfreelistb, tkdrawlistb, tklistbgeom };