From 37da2899f40661e3e9631e497da8dc59b971cbd0 Mon Sep 17 00:00:00 2001 From: "Charles.Forsyth" Date: Fri, 22 Dec 2006 17:07:39 +0000 Subject: 20060303a --- libtk/menus.c | 1840 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1840 insertions(+) create mode 100644 libtk/menus.c (limited to 'libtk/menus.c') diff --git a/libtk/menus.c b/libtk/menus.c new file mode 100644 index 00000000..ca8f424c --- /dev/null +++ b/libtk/menus.c @@ -0,0 +1,1840 @@ +#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 = 2; + 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 = atoi(arg); + 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 = 2; + + 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 = tklabelmargin(tk); + 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 +}; -- cgit v1.2.3