ref: d0ab3a0dd8f6355b3603d0fb04043a9ae867639b
dir: /libtk/menus.c/
#include "lib9.h" #include "draw.h" #include "keyboard.h" #include "tk.h" #include "frame.h" #include "label.h" /* arrow annotation for choicebutton: how do we make sure the menu items come up the same size? - set menu items to same req.width & height as button itself. autorepeat: when we get mouse event at the edge of the screen and the menu overlaps that edge, start autorepeat timer to slide the menu the opposite direction. variable setting + command invocation: is the value of the variable the text or the index? same for the value appended to the command, text or index? if it's reimplemented as a custom widget, how does the custom widget get notified of variable changes? */ /* Widget Commands (+ means implemented) +activate +add +cget +configure +delete +entrycget +entryconfigure +index +insert +invoke +post +postcascade +type +unpost +yposition */ #define O(t, e) ((long)(&((t*)0)->e)) /* Layout constants */ enum { Sepheight = 6, /* Height of menu separator */ }; #define NOCHOICE "-----" enum { Startspeed = TKI2F(1), }; static TkOption mbopts[] = { "text", OPTtext, O(TkLabel, text), nil, "anchor", OPTflag, O(TkLabel, anchor), tkanchor, "underline", OPTdist, O(TkLabel, ul), nil, "justify", OPTstab, O(TkLabel, justify), tkjustify, "menu", OPTtext, O(TkLabel, menu), nil, "bitmap", OPTbmap, O(TkLabel, bitmap), nil, "image", OPTimag, O(TkLabel, img), nil, nil }; static TkOption choiceopts[] = { "variable", OPTtext, O(TkLabel, variable), nil, "values", OPTlist, O(TkLabel, values), nil, "command", OPTtext, O(TkLabel, command), nil, nil }; static TkEbind mbbindings[] = { {TkEnter, "%W tkMBenter %s"}, {TkLeave, "%W tkMBleave"}, {TkButton1P, "%W tkMBpress 1"}, {TkKey, "%W tkMBkey 0x%K"}, {TkButton1P|TkMotion, "%W tkMBpress 0"}, }; extern Rectangle bbnil; static char* tkmpost(Tk*, int, int, int, int, int); static void menuclr(Tk*); static void freemenu(Tk*); static void appenditem(Tk*, Tk*, int); static void layout(Tk*); static Tk* tkmenuindex2ptr(Tk*, char**); static void activateitem(Tk*); /* * unmap menu cascade upto (but not including) tk */ static void tkunmapmenus(TkTop *top, Tk *tk) { TkTop *t; Tk *menu; TkWin *tkw; menu = top->ctxt->tkmenu; if (menu == nil) return; t = menu->env->top; /* if something went wrong, clear down all menus */ if (tk != nil && tk->env->top != t) tk = nil; while (menu != nil && menu != tk) { menuclr(menu); tkunmap(menu); tkcancelrepeat(menu); tkw = TKobj(TkWin, menu); if (tkw->cascade != nil) { menu = tklook(t, tkw->cascade, 0); free(tkw->cascade); tkw->cascade = nil; } else menu = nil; } top->ctxt->tkmenu = menu; tksetmgrab(top, menu); } static void tkunmapmenu(Tk *tk) { TkTop *t; TkWin *tkw; Tk *parent; parent = nil; tkw = TKobj(TkWin, tk); t = tk->env->top; if (tkw->cascade != nil) parent = tklook(t, tkw->cascade, 0); tkunmapmenus(t, parent); if (tkw->freeonunmap) freemenu(tk); } static void tksizemenubutton(Tk *tk) { int w, h; char **v, *cur; TkLabel *tkl = TKobj(TkLabel, tk); tksizelabel(tk); if(tk->type != TKchoicebutton) return; w = tk->req.width; h = tk->req.height; v = tkl->values; if (v == nil || *v == nil) return; cur = tkl->text; for (; *v; v++) { tkl->text = *v; tksizelabel(tk); if (tk->req.width > w) w = tk->req.width; if (tk->req.height > h) h = tk->req.height; } tkl->text = cur; tksizelabel(tk); tk->req.width = w; tk->req.height = h; } static char* tkmkmenubutton(TkTop *t, char *arg, char **ret, int type, TkOption *opts) { Tk *tk; char *e, **v; TkName *names; TkLabel *tkl; TkOptab tko[3]; /* need to get the label from elsewhere */ tk = tknewobj(t, type, sizeof(Tk)+sizeof(TkLabel)); if(tk == nil) return TkNomem; tk->borderwidth = 2; tk->flag |= Tknograb; tkl = TKobj(TkLabel, tk); tkl->ul = -1; if(type == TKchoicebutton) tkl->anchor = Tknorth|Tkwest; 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; } tkl->nvalues = 0; if (tkl->values != nil) { for (v = tkl->values; *v; v++) ; tkl->nvalues = v - tkl->values; } if(type == TKchoicebutton){ if(tkl->nvalues > 0) tkl->text = strdup(tkl->values[0]); else tkl->text = strdup(NOCHOICE); } tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd) || tkhasalpha(tk->env, TkCselectbgnd) || tkhasalpha(tk->env, TkCactivebgnd)); e = tkbindings(t, tk, mbbindings, nelem(mbbindings)); if(e != nil) { tkfreeobj(tk); return e; } tksizemenubutton(tk); 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* tkchoicebutton(TkTop *t, char *arg, char **ret) { return tkmkmenubutton(t, arg, ret, TKchoicebutton, choiceopts); } char* tkmenubutton(TkTop *t, char *arg, char **ret) { return tkmkmenubutton(t, arg, ret, TKmenubutton, mbopts); } static char* tkmenubutcget(Tk *tk, char *arg, char **val) { TkOptab tko[3]; TkLabel *tkl = TKobj(TkLabel, tk); tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkl; tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts); tko[2].ptr = nil; return tkgencget(tko, arg, val, tk->env->top); } static char* tkmenubutconf(Tk *tk, char *arg, char **val) { char *e, **v; TkGeom g; int bd; TkOptab tko[3]; TkLabel *tkl = TKobj(TkLabel, tk); tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkl; tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts); 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); if (tk->type == TKchoicebutton) { tkl->nvalues = 0; if (tkl->values != nil) { for (v = tkl->values; *v; v++) ; tkl->nvalues = v - tkl->values; } if (tkl->check >= tkl->nvalues || strcmp(tkl->text, tkl->values[tkl->check])) { /* * try to keep selected value the same if possible */ for (v = tkl->values; v && *v; v++) if (!strcmp(*v, tkl->text)) break; free(tkl->text); if (v == nil || *v == nil) { tkl->text = strdup(tkl->nvalues > 0 ? tkl->values[0] : NOCHOICE); tkl->check = 0; } else { tkl->check = v - tkl->values; tkl->text = strdup(*v); } } } tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd) || tkhasalpha(tk->env, TkCselectbgnd) || tkhasalpha(tk->env, TkCactivebgnd)); tksizemenubutton(tk); tkgeomchg(tk, &g, bd); tk->dirty = tkrect(tk, 1); return e; } static char* tkMBleave(Tk *tk, char *arg, char **val) { USED(arg); USED(val); tk->flag &= ~Tkactive; tk->dirty = tkrect(tk, 1); return nil; } static Tk* mkchoicemenu(Tk *tkb) { Tk *menu, *tkc; int i; TkLabel *tkl, *tkcl; TkWin *tkw; TkTop *t; tkl = TKobj(TkLabel, tkb); t = tkb->env->top; menu = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin)); if(menu == nil) return nil; menu->relief = TKraised; menu->flag |= Tknograb; menu->borderwidth = 1; tkputenv(menu->env); menu->env = tkb->env; menu->env->ref++; menu->flag |= Tkwindow; menu->geom = tkmoveresize; tkw = TKobj(TkWin, menu); tkw->cbname = strdup(tkb->name->name); tkw->di = (void*)-1; // XXX for(i = tkl->nvalues - 1; i >= 0; i--){ tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); /* XXX recover from malloc failure */ tkc->flag = Tkwest|Tkfillx|Tktop; tkc->highlightwidth = 0; tkc->borderwidth = 1; tkc->relief = TKflat; tkputenv(tkc->env); tkc->env = tkb->env; tkc->env->ref++; tkcl = TKobj(TkLabel, tkc); tkcl->anchor = Tkwest; tkcl->ul = -1; tkcl->justify = Tkleft; tkcl->text = strdup(tkl->values[i]); tkcl->command = smprint("%s invoke %d", tkb->name->name, i); /* XXX recover from malloc failure */ tksizelabel(tkc); tkc->req.height = tkb->req.height; appenditem(menu, tkc, 0); } layout(menu); tkw->next = t->windows; tkw->freeonunmap = 1; t->windows = menu; return menu; } static char* tkMBpress(Tk *tk, char *arg, char **val) { Tk *menu, *item; TkLabel *tkl = TKobj(TkLabel, tk); Point g; char buf[12], *bufp, *e; USED(arg); USED(val); g = tkposn(tk); if (tk->type == TKchoicebutton) { menu = mkchoicemenu(tk); if (menu == nil) return TkNomem; sprint(buf, "%d", tkl->check); bufp = buf; item = tkmenuindex2ptr(menu, &bufp); if(item == nil) return nil; g.y -= item->act.y; e = tkmpost(menu, g.x, g.y, 0, 0, 0); activateitem(item); return e; } else { if (tkl->menu == nil) return nil; menu = tklook(tk->env->top, tkl->menu, 0); if(menu == nil || menu->type != TKmenu) return TkBadwp; if(menu->flag & Tkmapped) { if(atoi(arg)) tkunmapmenu(menu); return nil; } return tkmpost(menu, g.x, g.y, 0, tk->act.height + 2*tk->borderwidth, 1); } } static char* tkMBkey(Tk *tk, char *arg, char **val) { int key; USED(val); if(tk->flag & Tkdisabled) return nil; key = strtol(arg, nil, 0); if (key == '\n' || key == ' ') return tkMBpress(tk, "1", nil); return nil; } static char* tkMBenter(Tk *tk, char *arg, char **val) { USED(arg); USED(val); tk->flag |= Tkactive; tk->dirty = tkrect(tk, 1); return nil; } static char* tkchoicebutset(Tk *tk, char *arg, char **val) { char buf[12], *e; int v; TkLabel *tkl = TKobj(TkLabel, tk); USED(val); tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); if (*buf == '\0') return TkBadvl; v = atoi(buf); if (v < 0 || v >= tkl->nvalues) return TkBadvl; if (v == tkl->check) return nil; free(tkl->text); tkl->text = strdup(tkl->values[v]); /* XXX recover from malloc error */ tkl->check = v; sprint(buf, "%d", v); e = tksetvar(tk->env->top, tkl->variable, buf); if(e != nil) return e; tk->dirty = tkrect(tk, 1); return nil; } static char* tkchoicebutinvoke(Tk *tk, char *arg, char **val) { TkLabel *tkl = TKobj(TkLabel, tk); char *e; e = tkchoicebutset(tk, arg, val); if(e != nil) return e; if(tkl->command) return tkexec(tk->env->top, tkl->command, val); return nil; } static char* tkchoicebutgetvalue(Tk *tk, char *arg, char **val) { char buf[12]; int gotarg, v; TkLabel *tkl = TKobj(TkLabel, tk); if (tkl->nvalues == 0) return nil; tkword(tk->env->top, arg, buf, buf+sizeof(buf), &gotarg); if (!gotarg) return tkvalue(val, "%s", tkl->values[tkl->check]); v = atoi(buf); if (buf[0] < '0' || buf[0] > '9' || v >= tkl->nvalues) return TkBadvl; return tkvalue(val, "%s", tkl->values[tkl->check]); } static char* tkchoicebutsetvalue(Tk *tk, char *arg, char **val) { char *buf; char **v; int gotarg; TkLabel *tkl = TKobj(TkLabel, tk); USED(val); if (tkl->nvalues == 0) return TkBadvl; buf = mallocz(Tkmaxitem, 0); if (buf == nil) return TkNomem; tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg); if (!gotarg) { free(buf); return TkBadvl; } for (v = tkl->values; *v; v++) if (strcmp(*v, buf) == 0) break; free(buf); if (*v == nil) return TkBadvl; free(tkl->text); tkl->text = strdup(*v); /* XXX recover from malloc error */ tkl->check = v - tkl->values; tk->dirty = tkrect(tk, 1); return nil; } static char* tkchoicebutget(Tk *tk, char *arg, char **val) { TkLabel *tkl = TKobj(TkLabel, tk); char *buf, **v; int gotarg; if (tkl->nvalues == 0) return nil; buf = mallocz(Tkmaxitem, 0); if (buf == nil) return TkNomem; tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg); if (!gotarg) { free(buf); return tkvalue(val, "%d", tkl->check); } for (v = tkl->values; *v; v++) if (strcmp(*v, buf) == 0) break; free(buf); if (*v) return tkvalue(val, "%d", v - tkl->values); return nil; } static char* tkchoicebutvaluecount(Tk *tk, char *arg, char **val) { TkLabel *tkl = TKobj(TkLabel, tk); USED(arg); return tkvalue(val, "%d", tkl->nvalues); } static void tkchoicevarchanged(Tk *tk, char *var, char *value) { TkLabel *tkl = TKobj(TkLabel, tk); int v; if(tkl->variable != nil && strcmp(tkl->variable, var) == 0){ if(value[0] < '0' || value[0] > '9') return; v = atoi(value); if(v < 0 || v >= tkl->nvalues) return; /* what else can we do? */ free(tkl->text); tkl->text = strdup(tkl->values[v]); /* XXX recover from malloc error */ tkl->check = v; tk->dirty = tkrect(tk, 0); tkdirty(tk); } } Tk * tkfindchoicemenu(Tk *tkb) { Tk *tk, *next; TkTop *top; TkWin *tkw; top = tkb->env->top; for (tk = top->windows; tk != nil; tk = next){ tkw = TKobj(TkWin, tk); if(tk->name == nil){ assert(strcmp(tkw->cbname, tkb->name->name) == 0); return tk; } next = tkw->next; } return nil; } static TkOption menuopt[] = { "postcommand", OPTtext, O(TkWin, postcmd), nil, nil, }; char* tkmenu(TkTop *t, char *arg, char **ret) { Tk *tk; char *e; TkWin *tkw; TkName *names; TkOptab tko[3]; tk = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin)); if(tk == nil) return TkNomem; tkw = TKobj(TkWin, tk); tkw->di = (void*)-1; // XXX tk->relief = TKraised; tk->flag |= Tknograb; tk->borderwidth = 1; tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkw; tko[1].optab = menuopt; tko[2].ptr = nil; names = nil; e = tkparse(t, arg, tko, &names); 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; tk->flag |= Tkwindow; tk->geom = tkmoveresize; tkw->next = t->windows; t->windows = tk; return tkvalue(ret, "%s", tk->name->name); } static void freemenu(Tk *top) { Tk *tk, *f, *nexttk, *nextf; TkWin *tkw; tkunmapmenu(top); tkw = TKobj(TkWin, top); for(tk = tkw->slave; tk; tk = nexttk) { nexttk = tk->next; for(f = tk->slave; f; f = nextf) { nextf = f->next; tkfreeobj(f); } tkfreeobj(tk); } top->slave = nil; tkfreeframe(top); } static TkOption mopt[] = { "menu", OPTtext, O(TkLabel, menu), nil, nil, }; static void tkbuildmopt(TkOptab *tko, int n, Tk *tk) { memset(tko, 0, n*sizeof(TkOptab)); n = 0; tko[n].ptr = tk; tko[n++].optab = tkgeneric; switch(tk->type) { case TKcascade: tko[n].ptr = TKobj(TkLabel, tk); tko[n++].optab = mopt; goto norm; case TKradiobutton: tko[n].ptr = TKobj(TkLabel, tk); tko[n++].optab = tkradopts; goto norm; case TKcheckbutton: tko[n].ptr = TKobj(TkLabel, tk); tko[n++].optab = tkcbopts; /* fall through */ case TKlabel: norm: tko[n].ptr = TKobj(TkLabel, tk); tko[n].optab = tkbutopts; break; } } static char* tkmenuentryconf(Tk *menu, Tk *tk, char *arg) { char *e; TkOptab tko[4]; USED(menu); tkbuildmopt(tko, nelem(tko), tk); e = tkparse(tk->env->top, arg, tko, nil); switch (tk->type) { case TKlabel: case TKcascade: tksizelabel(tk); break; case TKradiobutton: case TKcheckbutton: tksizebutton(tk); } return e; } static void layout(Tk *menu) { TkWin *tkw; Tk *tk; int m, w, y, maxmargin, maxw; y = 0; maxmargin = 0; maxw = 0; tkw = TKobj(TkWin, menu); /* determine padding for item text alignment */ for (tk = tkw->slave; tk != nil; tk = tk->next) { m = tkbuttonmargin(tk); /* TO DO: relies on buttonmargin defaulting to labelmargin */ tk->act.x = m; /* temp store */ if (m > maxmargin) maxmargin = m; } /* set x pos and determine max width */ for (tk = tkw->slave; tk != nil; tk = tk->next) { tk->act.x = tk->borderwidth + maxmargin - tk->act.x; tk->act.y = y + tk->borderwidth; tk->act.height = tk->req.height; tk->act.width = tk->req.width; y += tk->act.height+2*tk->borderwidth; w = tk->act.x + tk->req.width + 2* tk->borderwidth; if (w > maxw) maxw = w; } /* expand separators and cascades and mark all as dirty */ for (tk = tkw->slave; tk != nil; tk = tk->next) { switch (tk->type) { case TKseparator: tk->act.x = tk->borderwidth; /*FALLTHRU*/ case TKcascade: tk->act.width = (maxw - tk->act.x) - tk->borderwidth; } tk->dirty = tkrect(tk, 1); } menu->dirty = tkrect(menu, 1); tkmoveresize(menu, 0, 0, maxw, y); } static void menuitemgeom(Tk *sub, int x, int y, int w, int h) { if (sub->parent == nil) return; if(w < 0) w = 0; if(h < 0) h = 0; sub->req.x = x; sub->req.y = y; sub->req.width = w; sub->req.height = h; layout(sub->parent); } static void appenditem(Tk *menu, Tk *item, int where) { TkWin *tkw; Tk *f, **l; tkw = TKobj(TkWin, menu); l = &tkw->slave; for (f = *l; f != nil; f = f->next) { if (where-- == 0) break; l = &f->next; } *l = item; item->next = f; item->parent = menu; item->geom = menuitemgeom; } static char* menuadd(Tk *menu, char *arg, int where) { Tk *tkc; int configure; char *e; TkTop *t; TkLabel *tkl; char buf[Tkmaxitem]; t = menu->env->top; arg = tkword(t, arg, buf, buf+sizeof(buf), nil); configure = 1; e = nil; if(strcmp(buf, "checkbutton") == 0) tkc = tkmkbutton(t, TKcheckbutton); else if(strcmp(buf, "radiobutton") == 0) tkc = tkmkbutton(t, TKradiobutton); else if(strcmp(buf, "command") == 0) tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); else if(strcmp(buf, "cascade") == 0) tkc = tknewobj(t, TKcascade, sizeof(Tk)+sizeof(TkLabel)); else if(strcmp(buf, "separator") == 0) { tkc = tknewobj(t, TKseparator, sizeof(Tk)); /* it's really a frame */ if (tkc != nil) { tkc->flag = Tkfillx|Tktop; tkc->req.height = Sepheight; configure = 0; } } else return TkBadvl; if (tkc == nil) e = TkNomem; if (e == nil) { if(tkc->env == t->env && menu->env != t->env) { tkputenv(tkc->env); tkc->env = menu->env; tkc->env->ref++; } if (configure) { tkc->flag = Tkwest|Tkfillx|Tktop; tkc->highlightwidth = 0; tkc->borderwidth = 1; tkc->relief = TKflat; tkl = TKobj(TkLabel, tkc); tkl->anchor = Tkwest; tkl->ul = -1; tkl->justify = Tkleft; e = tkmenuentryconf(menu, tkc, arg); } } if(e != nil) { if (tkc != nil) tkfreeobj(tkc); return e; } appenditem(menu, tkc, where); layout(menu); return nil; } static int tkmindex(Tk *tk, char *p) { TkWin *tkw; int y, n; if(*p >= '0' && *p <= '9') return atoi(p); tkw = TKobj(TkWin, tk); n = 0; if(*p == '@') { y = atoi(p+1); for(tk = tkw->slave; tk; tk = tk->next) { if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth ) return n; n++; } } if(strcmp(p, "end") == 0 || strcmp(p, "last") == 0) { for(tk = tkw->slave; tk && tk->next; tk = tk->next) n++; return n; } if(strcmp(p, "active") == 0) { for(tk = tkw->slave; tk; tk = tk->next) { if(tk->flag & Tkactive) return n; n++; } return -2; } if(strcmp(p, "none") == 0) return -2; return -1; } static int tkmenudel(Tk *tk, int y) { TkWin *tkw; Tk *f, **l, *next; tkw = TKobj(TkWin, tk); l = &tkw->slave; for(tk = *l; tk; tk = tk->next) { if(y-- == 0) { *l = tk->next; for(f = tk->slave; f; f = next) { next = f->next; tkfreeobj(f); } tkfreeobj(tk); return 1; } l = &tk->next; } return 0; } static char* tkmpost(Tk *tk, int x, int y, int cascade, int bh, int adjust) { char *e; TkWin *w; TkTop *t; Rectangle *dr; t = tk->env->top; if(adjust){ dr = &t->screenr; if(x+tk->act.width > dr->max.x) x = dr->max.x - tk->act.width; if(x < 0) x = 0; if(y+bh+tk->act.height > dr->max.y) y -= tk->act.height + 2* tk->borderwidth; else y += bh; if(y < 0) y = 0; } menuclr(tk); tkmovewin(tk, Pt(x, y)); /* stop possible postcommand recursion */ if (tk->flag & Tkmapped) return nil; w = TKobj(TkWin, tk); if(w->postcmd != nil) { e = tkexec(tk->env->top, w->postcmd, nil); if(e != nil) { print("%s: postcommand: %s: %s\n", tkname(tk), w->postcmd, e); return e; } } if (!cascade) tkunmapmenus(t, nil); e = tkmap(tk); if(e != nil) return e; if (t->ctxt->tkmenu != nil) w->cascade = strdup(t->ctxt->tkmenu->name->name); t->ctxt->tkmenu = tk; tksetmgrab(t, tk); /* Make sure slaves are redrawn */ return tkupdate(tk->env->top); } static Tk* tkmenuindex2ptr(Tk *tk, char **arg) { TkWin *tkw; int index; char *buf; buf = mallocz(Tkmaxitem, 0); if(buf == nil) return nil; *arg = tkword(tk->env->top, *arg, buf, buf+Tkmaxitem, nil); index = tkmindex(tk, buf); free(buf); if(index < 0) return nil; tkw = TKobj(TkWin, tk); for(tk = tkw->slave; tk && index; tk = tk->next) index--; if(tk == nil) return nil; return tk; } static char* tkmenuentrycget(Tk *tk, char *arg, char **val) { Tk *etk; TkOptab tko[4]; etk = tkmenuindex2ptr(tk, &arg); if(etk == nil) return TkBadix; tkbuildmopt(tko, nelem(tko), etk); return tkgencget(tko, arg, val, tk->env->top); } static char* tkmenucget(Tk *tk, char *arg, char **val) { TkWin *tkw; TkOptab tko[4]; tkw = TKobj(TkWin, tk); tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkw; tko[1].optab = tktop; tko[2].ptr = tkw; tko[2].optab = menuopt; tko[3].ptr = nil; return tkgencget(tko, arg, val, tk->env->top); } static char* tkmenuconf(Tk *tk, char *arg, char **val) { char *e; TkGeom g; int bd; TkWin *tkw; TkOptab tko[3]; tkw = TKobj(TkWin, tk); tko[0].ptr = tk; tko[0].optab = tkgeneric; tko[1].ptr = tkw; tko[1].optab = menuopt; 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); tkgeomchg(tk, &g, bd); tk->dirty = tkrect(tk, 1); return e; } static char* tkmenuadd(Tk *tk, char *arg, char **val) { USED(val); return menuadd(tk, arg, -1); } static char* tkmenuinsert(Tk *tk, char *arg, char **val) { int index; char *buf; USED(val); buf = mallocz(Tkmaxitem, 0); if(buf == nil) return TkNomem; arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); index = tkmindex(tk, buf); free(buf); if (index < 0) return TkBadix; return menuadd(tk, arg, index); } static void menuitemdirty(Tk *item) { Tk *menu; Rectangle r; menu = item->parent; if (menu == nil) return; item->dirty = tkrect(item, 1); r = rectaddpt(item->dirty, Pt(item->act.x, item->act.y)); combinerect(&menu->dirty, r); } static void menuclr(Tk *tk) { TkWin *tkw; Tk *f; tkw = TKobj(TkWin, tk); for(f = tkw->slave; f; f = f->next) { if(f->flag & Tkactive) { f->flag &= ~Tkactive; menuitemdirty(f); } } } static char* tkpostcascade(Tk *parent, Tk *tk, int toggle) { Tk *tkm; TkWin *tkw; Point g; TkTop *t; TkLabel *tkl; char *e; if(tk->flag & Tkdisabled) return nil; tkl = TKobj(TkLabel, tk); t = tk->env->top; tkm = tklook(t, tkl->menu, 0); if(tkm == nil || tkm->type != TKmenu) return TkBadwp; if((tkm->flag & Tkmapped)) { if (toggle) { tkunmapmenus(t, parent); return nil; } else { /* check that it is immediate cascade */ tkw = TKobj(TkWin, t->ctxt->tkmenu); if (strcmp(tkw->cascade, parent->name->name) == 0) return nil; } } tkunmapmenus(t, parent); tkl = TKobj(TkLabel, tk); if(tkl->command != nil) { e = tkexec(t, tkl->command, nil); if (e != nil) return e; } g = tkposn(tk); g.x += tk->act.width; g.y -= tkm->borderwidth; e = tkmpost(tkm, g.x, g.y, 1, 0, 1); return e; } static void activateitem(Tk *item) { Tk *menu; if (item == nil || (menu = item->parent) == nil) return; menuclr(menu); if (!(item->flag & Tkdisabled)) { item->flag |= Tkactive; menuitemdirty(item); } } static char* tkmenuactivate(Tk *tk, char *arg, char **val) { Tk *f; TkWin *tkw; int index; char *buf; USED(val); buf = mallocz(Tkmaxitem, 0); if(buf == nil) return TkNomem; tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); index = tkmindex(tk, buf); free(buf); if (index == -1) return TkBadix; if (index == -2) { menuclr(tk); return nil; } tkw = TKobj(TkWin, tk); for(f = tkw->slave; f; f = f->next) if(index-- == 0) break; if(f == nil || f->flag & Tkdisabled) { menuclr(tk); return nil; } if(f->flag & Tkactive) return nil; activateitem(f); return nil; } static int iteminvoke(Tk *tk, Tk *tki, char *arg) { int unmap = 0; menuitemdirty(tki); switch(tki->type) { case TKlabel: unmap = 1; case TKcheckbutton: case TKradiobutton: tkbuttoninvoke(tki, arg, nil); break; case TKcascade: tkpostcascade(tk, tki, 0); break; } return unmap; } static char* tkmenuinvoke(Tk *tk, char *arg, char **val) { Tk *tki; USED(val); tki = tkmenuindex2ptr(tk, &arg); if(tki == nil) return nil; iteminvoke(tk, tki, arg); return nil; } static char* tkmenudelete(Tk *tk, char *arg, char **val) { int index1, index2; char *buf; USED(val); buf = mallocz(Tkmaxitem, 0); if(buf == nil) return TkNomem; arg = tkitem(buf, arg); index1 = tkmindex(tk, buf); if(index1 < 0) { free(buf); return TkBadix; } index2 = index1; if(*arg != '\0') { tkitem(buf, arg); index2 = tkmindex(tk, buf); } free(buf); if(index2 < 0) return TkBadix; while(index2 >= index1 && tkmenudel(tk, index2)) index2--; layout(tk); return nil; } static char* tkmenupost(Tk *tk, char *arg, char **val) { int x, y; TkTop *t; char *buf; USED(val); buf = mallocz(Tkmaxitem, 0); if(buf == nil) return TkNomem; t = tk->env->top; arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); if(buf[0] == '\0') { free(buf); return TkBadvl; } x = atoi(buf); tkword(t, arg, buf, buf+Tkmaxitem, nil); if(buf[0] == '\0') { free(buf); return TkBadvl; } y = atoi(buf); free(buf); return tkmpost(tk, x, y, 0, 0, 1); } static char* tkmenuunpost(Tk *tk, char *arg, char **val) { USED(arg); USED(val); tkunmapmenu(tk); return nil; } static char* tkmenuindex(Tk *tk, char *arg, char **val) { char *buf; int index; buf = mallocz(Tkmaxitem, 0); if(buf == nil) return TkNomem; tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); index = tkmindex(tk, buf); free(buf); if (index == -1) return TkBadix; if (index == -2) return "none"; return tkvalue(val, "%d", index); } static char* tkmenuyposn(Tk *tk, char *arg, char **val) { tk = tkmenuindex2ptr(tk, &arg); if(tk == nil) return TkBadix; return tkvalue(val, "%d", tk->act.y); } static char* tkmenupostcascade(Tk *tk, char *arg, char **val) { Tk *tki; USED(val); tki = tkmenuindex2ptr(tk, &arg); if(tki == nil || tki->type != TKcascade) return nil; return tkpostcascade(tk, tki, 0); } static char* tkmenutype(Tk *tk, char *arg, char **val) { tk = tkmenuindex2ptr(tk, &arg); if(tk == nil) return TkBadix; return tkvalue(val, tk->type == TKlabel ? "command" : tkmethod[tk->type]->name); } static char* tkmenususpend(Tk *tk, char *arg, char **val) { USED(arg); USED(val); if(tk->type == TKchoicebutton){ tk = tkfindchoicemenu(tk); if(tk == nil) return TkNotwm; } tk->flag |= Tksuspended; return nil; } static char* tkmenuentryconfig(Tk *tk, char *arg, char **val) { Tk *etk; char *e; USED(val); etk = tkmenuindex2ptr(tk, &arg); if(etk == nil) return TkBadix; e = tkmenuentryconf(tk, etk, arg); layout(tk); return e; } static Tk* xymenuitem(Tk *tk, int x, int y) { TkWin *tkw = TKobj(TkWin, tk); x -= tkw->act.x; y -= tkw->act.y; x -= tk->borderwidth; y -= tk->act.y + tk->borderwidth; if (x < tk->act.x || x > tk->act.x+tk->act.width) return nil; for(tk = tkw->slave; tk; tk = tk->next) { if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth) return tk; } return nil; } static char * menukey(Tk *tk, int key) { Tk *scan, *active, *first, *last, *prev, *next; TkWin *tkw; TkTop *top; top = tk->env->top; active = first = last = prev = next = nil; tkw = TKobj(TkWin, tk); for(scan = tkw->slave; scan != nil; scan = scan->next) { if(scan->type == TKseparator) continue; if(first == nil) first = scan; if (active != nil && next == nil) next = scan; if(active == nil && scan->flag & Tkactive) active = scan; if (active == nil) prev = scan; last = scan; } if (next == nil) next = first; if (prev == nil) prev = last; switch (key) { case Esc: tkunmapmenus(top, nil); break; case Left: if (tkw->cascade != nil) tkunmapmenu(tk); break; case Right: if (active == nil || active->type != TKcascade) break; case ' ': case '\n': if (active != nil) { if (iteminvoke(tk, active, nil)) tkunmapmenus(top, nil); } break; case Up: next = prev; case Down: if (next != nil) activateitem(next); } return nil; } static char* drawmenu(Tk *tk, Point orig) { Image *dst; TkWin *tkw; Tk *sub; Point p, bd; int bg; Rectangle mainr, clientr, subr; tkw = TKobj(TkWin, tk); dst = tkimageof(tk); bd = Pt(tk->borderwidth, tk->borderwidth); mainr.min = addpt(orig, Pt(tk->act.x, tk->act.y)); clientr.min = addpt(mainr.min, bd); clientr.max = addpt(clientr.min, Pt(tk->act.width, tk->act.height)); mainr.max = addpt(clientr.max, bd); /* * note that we draw item background to get full menu width * active indicator, this means we must dirty the entire * item rectangle to ensure it is fully redrawn */ p = clientr.min; subr = clientr; for (sub = tkw->slave; sub != nil; sub = sub->next) { if (Dx(sub->dirty) == 0) continue; subr.min.y = p.y + sub->act.y - sub->borderwidth; subr.max.y = p.y + sub->act.y + sub->act.height + sub->borderwidth; bg = TkCbackgnd; if (sub->flag & Tkactive) bg = TkCactivebgnd; draw(dst, subr, tkgc(sub->env, bg), nil, ZP); sub->dirty = tkrect(sub, 1); sub->flag |= Tkrefresh; tkmethod[sub->type]->draw(sub, p); sub->dirty = bbnil; sub->flag &= ~Tkrefresh; } /* todo: dirty check */ tkdrawrelief(dst, tk, mainr.min, TkCbackgnd, tk->relief); return nil; } static void menudirty(Tk *sub) { menuitemdirty(sub); } static Point menurelpos(Tk *sub) { return Pt(sub->act.x-sub->borderwidth, sub->act.y-sub->borderwidth); } static void autoscroll(Tk *tk, void *v, int cancelled) { TkWin *tkw; Rectangle r, dr; Point delta, od; TkMouse *m; Tk *item; USED(v); tkw = TKobj(TkWin, tk); if (cancelled) { tkw->speed = 0; return; } if(!eqpt(tkw->act, tkw->req)){ print("not autoscrolling, act: %P, req: %P\n", tkw->act, tkw->req); return; } dr = tk->env->top->screenr; delta.x = TKF2I(tkw->delta.x * tkw->speed); delta.y = TKF2I(tkw->delta.y * tkw->speed); r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); od = delta; /* make sure we don't go too far */ if (delta.x > 0 && r.min.x + delta.x > dr.min.x) delta.x = dr.min.x - r.min.x; else if (delta.x < 0 && r.max.x + delta.x < dr.max.x) delta.x = dr.max.x - r.max.x; if (delta.y > 0 && r.min.y + delta.y > dr.min.y) delta.y = dr.min.y - r.min.y; else if (delta.y < 0 && r.max.y + delta.y < dr.max.y) delta.y = dr.max.y - r.max.y; m = &tk->env->top->ctxt->mstate; item = xymenuitem(tk, m->x - delta.x, m->y - delta.y); if (item == nil) menuclr(tk); else activateitem(item); tkmovewin(tk, Pt(tkw->req.x + delta.x, tkw->req.y + delta.y)); tkupdate(tk->env->top); /* tkenterleave won't do this for us, so we have to do it ourselves */ tkw->speed += tkw->speed / 3; r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); if((delta.y > 0 && r.min.x >= dr.min.x) || (delta.x < 0 && r.max.x <= dr.max.x)) tkw->delta.x = 0; if((delta.y > 0 && r.min.y >= dr.min.y) || (delta.y < 0 && r.max.y <= dr.max.y)) tkw->delta.y = 0; if (eqpt(tkw->delta, ZP)) { tkcancelrepeat(tk); tkw->speed = 0; } } static void startautoscroll(Tk *tk, TkMouse *m) { Rectangle dr, r; Point d; TkWin *tkw; tkw = TKobj(TkWin, tk); dr = tk->env->top->screenr; r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); d = Pt(0, 0); if(m->x <= 0 && r.min.x < dr.min.x) d.x = 1; else if (m->x >= dr.max.x - 1 && r.max.x >= dr.max.x) d.x = -1; if(m->y <= 0 && r.min.y < dr.min.y) d.y = 1; else if (m->y >= dr.max.y - 1 && r.max.y >= dr.max.y) d.y = -1; //print("startautoscroll, delta %P\n", d); if (d.x == 0 && d.y == 0){ if (tkw->speed > 0){ tkcancelrepeat(tk); tkw->speed = 0; } return; } if (tkw->speed == 0) { tkw->speed = TKI2F(Dy(r)) / 100; tkrepeat(tk, autoscroll, nil, 0, TkRptinterval/2); } tkw->delta = d; } static void menuevent1(Tk *tk, int event, void *a) { TkMouse *m; Tk *item; if (event & TkKey) { menukey(tk, event & 0xffff); return; } if (event & TkLeave) { menuclr(tk); return; } if ((!(event & TkEmouse) || (event & TkTakefocus)) && !(event & TkEnter)) return; m = (TkMouse*)a; startautoscroll(tk, m); item = xymenuitem(tk, m->x, m->y); if (item == nil) menuclr(tk); else activateitem(item); if ((event & (TkMotion|TkEnter)) && item == nil) return; if (event & TkEpress) { if (item == nil) { tkunmapmenus(tk->env->top, nil); return; } if (item->type == TKcascade) tkpostcascade(tk, item, !(event & TkMotion)); else tkunmapmenus(tk->env->top, tk); return; } if ((event & TkErelease) && m->b == 0) { if (item != nil) { if (item->type == TKcascade) return; if (!iteminvoke(tk, item, nil)) return; } tkunmapmenus(tk->env->top, nil); } } static Tk* menuevent(Tk *tk, int event, void *a) { menuevent1(tk, event, a); tksubdeliver(tk, tk->binds, event, a, 0); return nil; } static TkCmdtab menucmd[] = { "activate", tkmenuactivate, "add", tkmenuadd, "cget", tkmenucget, "configure", tkmenuconf, "delete", tkmenudelete, "entryconfigure", tkmenuentryconfig, "entrycget", tkmenuentrycget, "index", tkmenuindex, "insert", tkmenuinsert, "invoke", tkmenuinvoke, "post", tkmenupost, "postcascade", tkmenupostcascade, "type", tkmenutype, "unpost", tkmenuunpost, "yposition", tkmenuyposn, "suspend", tkmenususpend, nil }; static TkCmdtab menubutcmd[] = { "cget", tkmenubutcget, "configure", tkmenubutconf, "tkMBenter", tkMBenter, "tkMBleave", tkMBleave, "tkMBpress", tkMBpress, "tkMBkey", tkMBkey, nil }; static TkCmdtab choicebutcmd[] = { "cget", tkmenubutcget, "configure", tkmenubutconf, "set", tkchoicebutset, "get", tkchoicebutget, "setvalue", tkchoicebutsetvalue, "getvalue", tkchoicebutgetvalue, "invoke", tkchoicebutinvoke, "valuecount", tkchoicebutvaluecount, "tkMBenter", tkMBenter, "tkMBleave", tkMBleave, "tkMBpress", tkMBpress, "tkMBkey", tkMBkey, "suspend", tkmenususpend, nil }; TkMethod menumethod = { "menu", menucmd, freemenu, drawmenu, nil, nil, nil, menudirty, menurelpos, menuevent }; TkMethod menubuttonmethod = { "menubutton", menubutcmd, tkfreelabel, tkdrawlabel }; TkMethod choicebuttonmethod = { "choicebutton", choicebutcmd, tkfreelabel, tkdrawlabel, nil, nil, nil, nil, nil, nil, nil, nil, tkchoicevarchanged }; TkMethod separatormethod = { "separator", nil, tkfreeframe, tkdrawframe }; TkMethod cascademethod = { "cascade", nil, tkfreelabel, tkdrawlabel };