#include "dat.h" #include #include #include #include "fns.h" static Handlers handlers; static int ltwidth; static void _menu_draw(bool); enum { ACCEPT = CARET_LAST, REJECT, HIST, KILL, CMPL_NEXT, CMPL_PREV, CMPL_FIRST, CMPL_LAST, CMPL_NEXT_PAGE, CMPL_PREV_PAGE, }; void menu_init(void) { WinAttr wa; wa.event_mask = ExposureMask | KeyPressMask; barwin = createwindow(&scr.root, Rect(-1, -1, 1, 1), scr.depth, InputOutput, &wa, CWEventMask); changeprop_long(barwin, Net("WM_WINDOW_TYPE"), "ATOM", (long[]){ TYPE("MENU") }, 1); changeprop_string(barwin, "_WMII_TAGS", "sel"); changeprop_textlist(barwin, "WM_CLASS", "STRING", (char*[3]){ "wimenu", "wimenu", nil }); sethandler(barwin, &handlers); mapwin(barwin); int i = 0; while(!grabkeyboard(barwin)) { if(i++ > 1000) fatal("can't grab keyboard"); usleep(1000); } } static void menu_unmap(long id, void *p) { USED(id, p); unmapwin(barwin); XFlush(display); } static void selectitem(Item *i) { if(i != matchidx) { caret_set(input.filter_start, input.pos - input.string); caret_insert(i->string, 0); matchidx = i; } } static void menu_cmd(int op, int motion) { int n; switch(op) { case HIST: n = input.pos - input.string; caret_insert(history_search(motion, input.string, n), true); input.pos = input.string + n; break; case KILL: caret_delete(BACKWARD, motion); break; default: goto next; } update_filter(true); next: switch(op) { case ACCEPT: srv.running = false; if(!matchidx && matchfirst->retstring && !motion) if(input.filter_start == 0 && input.pos == input.end) menu_cmd(CMPL_FIRST, 0); if(!motion && matchidx && !strcmp(input.string, matchidx->string)) lprint(1, "%s", matchidx->retstring); else lprint(1, "%s", input.string); break; case REJECT: srv.running = false; result = 1; break; case BACKWARD: case FORWARD: caret_move(op, motion); update_input(); break; case CMPL_NEXT: selectitem(matchidx ? matchidx->next : matchfirst); break; case CMPL_PREV: selectitem((matchidx ? matchidx : matchstart)->prev); break; case CMPL_FIRST: matchstart = matchfirst; matchend = nil; selectitem(matchstart); break; case CMPL_LAST: selectitem(matchfirst->prev); break; case CMPL_NEXT_PAGE: if(matchend) selectitem(matchend->next); break; case CMPL_PREV_PAGE: matchend = matchstart->prev; matchidx = nil; _menu_draw(false); selectitem(matchstart); break; } menu_draw(); } static void _menu_draw(bool draw) { Rectangle r, rd, rp, r2, extent; CTuple *c; Item *i; int inputw, itemoff, end, pad, n, offset; r = barwin->r; r = rectsetorigin(r, ZP); pad = (font->height & ~1) + font->pad.min.x + font->pad.max.x; rd = r; rp = ZR; // SET(rp) if (prompt) { if (!promptw) promptw = textwidth(font, prompt) + 2 * ltwidth + pad; rd.min.x += promptw; rp = r; rp.max.x = promptw; } inputw = min(Dx(rd) / 3, maxwidth); inputw = max(inputw, textwidth(font, input.string)) + pad; itemoff = inputw + 2 * ltwidth; end = Dx(rd) - ltwidth; fill(ibuf, r, cnorm.bg); if(matchend && matchidx == matchend->next) matchstart = matchidx; else if(matchidx == matchstart->prev) matchend = matchidx; if (matchend == nil) matchend = matchstart; if(matchend == matchstart->prev && matchstart != matchidx) { n = itemoff; matchstart = matchend; for(i=matchend; ; i=i->prev) { n += i->width + pad; if(n > end) break; matchstart = i; if(i == matchfirst) break; } } if(!draw) return; r2 = rd; for(i=matchstart; i->string; i=i->next) { r2.min.x = promptw + itemoff; itemoff = itemoff + i->width + pad; r2.max.x = promptw + min(itemoff, end); if(i != matchstart && itemoff > end) break; c = (i == matchidx) ? &csel : &cnorm; fill(ibuf, r2, c->bg); drawstring(ibuf, font, r2, Center, i->string, c->fg); matchend = i; if(i->next == matchfirst) break; } r2 = rd; r2.min.x = promptw + inputw; if(matchstart != matchfirst) drawstring(ibuf, font, r2, West, "<", cnorm.fg); if(matchend->next != matchfirst) drawstring(ibuf, font, r2, East, ">", cnorm.fg); r2 = rd; r2.max.x = promptw + inputw; drawstring(ibuf, font, r2, West, input.string, cnorm.fg); extent = textextents_l(font, input.string, input.pos - input.string, &offset); r2.min.x = promptw + offset + font->pad.min.x - extent.min.x + pad/2 - 1; r2.max.x = r2.min.x + 2; r2.min.y++; r2.max.y--; border(ibuf, r2, 1, cnorm.border); if (prompt) drawstring(ibuf, font, rp, West, prompt, cnorm.fg); border(ibuf, rd, 1, cnorm.border); copyimage(barwin, r, ibuf, ZP); } void menu_draw(void) { _menu_draw(true); } void menu_show(void) { Rectangle r; int height, pad; USED(menu_unmap); ltwidth = textwidth(font, "<"); pad = (font->height & ~1)/2; height = labelh(font); r = scr.rect; if(ontop) r.max.y = r.min.y + height; else r.min.y = r.max.y - height; reshapewin(barwin, r); freeimage(ibuf); ibuf = allocimage(Dx(r), Dy(r), scr.depth); mapwin(barwin); raisewin(barwin); menu_draw(); } static bool kdown_event(Window *w, void *aux, XKeyEvent *e) { char **action, **p; char *key; char buf[32]; int num; KeySym ksym; buf[0] = 0; num = XLookupString(e, buf, sizeof buf, &ksym, 0); key = XKeysymToString(ksym); if(IsKeypadKey(ksym)) if(ksym == XK_KP_Enter) ksym = XK_Return; else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) ksym = (ksym - XK_KP_0) + XK_0; if(IsFunctionKey(ksym) || IsMiscFunctionKey(ksym) || IsKeypadKey(ksym) || IsPrivateKeypadKey(ksym) || IsPFKey(ksym)) return false; action = find_key(key, e->state); if(action == nil || action[0] == nil) { if(num && !iscntrl(buf[0])) { caret_insert(buf, false); update_filter(true); menu_draw(); } } else { long mask = 0; # define have(val) !!(mask & (1 << val)) for(p=action+1; *p; p++) mask |= 1 << getsym(*p); int amount = ( have(LCHAR) ? CHAR : have(LWORD) ? WORD : have(LLINE) ? LINE : -1); switch(getsym(action[0])) { case LACCEPT: menu_cmd(ACCEPT, have(LLITERAL)); break; case LBACKWARD: menu_cmd(BACKWARD, amount); break; case LCOMPLETE: amount = ( have(LNEXT) ? CMPL_NEXT : have(LPREV) ? CMPL_PREV : have(LNEXTPAGE) ? CMPL_NEXT_PAGE : have(LPREVPAGE) ? CMPL_PREV_PAGE : have(LFIRST) ? CMPL_FIRST : have(LLAST) ? CMPL_LAST : CMPL_NEXT); menu_cmd(amount, 0); break; case LFORWARD: menu_cmd(FORWARD, amount); break; case LHISTORY: menu_cmd(HIST, have(LBACKWARD) ? BACKWARD : FORWARD); break; case LKILL: menu_cmd(KILL, amount); break; case LREJECT: menu_cmd(REJECT, 0); break; } } return false; } static bool expose_event(Window *w, void *aux, XExposeEvent *e) { USED(w); menu_draw(); return false; } static Handlers handlers = { .expose = expose_event, .kdown = kdown_event, };