mirror of
https://github.com/0intro/wmii
synced 2024-11-24 22:59:45 +03:00
359 lines
8.6 KiB
C
359 lines
8.6 KiB
C
#include "dat.h"
|
|
#include <ctype.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
#include "fns.h"
|
|
|
|
static Handlers handlers;
|
|
static int promptw;
|
|
|
|
void
|
|
menu_init(void) {
|
|
WinAttr wa;
|
|
|
|
wa.event_mask = ExposureMask | KeyPressMask;
|
|
menu.win = createwindow(&scr.root, Rect(-1, -1, 1, 1), scr.depth, InputOutput,
|
|
&wa, CWEventMask);
|
|
if(scr.xim)
|
|
menu.win->xic = XCreateIC(scr.xim,
|
|
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
|
|
XNClientWindow, menu.win->xid,
|
|
XNFocusWindow, menu.win->xid,
|
|
nil);
|
|
|
|
changeprop_long(menu.win, Net("WM_WINDOW_TYPE"), "ATOM", (long[]){ TYPE("MENU") }, 1);
|
|
changeprop_string(menu.win, "_WMII_TAGS", "sel");
|
|
changeprop_textlist(menu.win, "WM_CLASS", "STRING", (char*[3]){ "wimenu", "wimenu" });
|
|
|
|
sethandler(menu.win, &handlers);
|
|
mapwin(menu.win);
|
|
|
|
int i = 0;
|
|
while(!grabkeyboard(menu.win)) {
|
|
if(i++ > 1000)
|
|
fatal("can't grab keyboard");
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
void
|
|
menu_show(void) {
|
|
Rectangle r;
|
|
|
|
if(menu.prompt)
|
|
promptw = textwidth(font, menu.prompt) + itempad;
|
|
|
|
r = textextents_l(font, "<", 1, nil);
|
|
menu.arrow = Pt(Dy(r) + itempad/2, Dx(r) + itempad/2);
|
|
|
|
menu.height = labelh(font);
|
|
|
|
freeimage(menu.buf);
|
|
menu.buf = allocimage(Dx(scr.rect),
|
|
!!menu.rows * 2 * menu.arrow.y + (menu.rows + 1) * menu.height,
|
|
menu.win->depth);
|
|
|
|
mapwin(menu.win);
|
|
raisewin(menu.win);
|
|
menu_draw();
|
|
}
|
|
|
|
/* I'd prefer to use ⌃ and ⌄, but few fonts support them. */
|
|
static void
|
|
drawarrow(Image *img, Rectangle r, int up, Color *col) {
|
|
Point p[3], pt;
|
|
|
|
pt = Pt(menu.arrow.x - itempad/2, menu.arrow.y - itempad/2 & ~1);
|
|
|
|
p[1] = Pt(r.min.x + Dx(r)/2, up ? r.min.y + itempad/4 : r.max.y - itempad/4);
|
|
p[0] = Pt(p[1].x - pt.x/2, up ? p[1].y + pt.y : p[1].y - pt.y);
|
|
p[2] = Pt(p[1].x + pt.x/2, p[0].y);
|
|
drawpoly(img, p, nelem(p), CapProjecting, 1, col);
|
|
}
|
|
|
|
static Rectangle
|
|
slice(Rectangle *rp, int x, int y) {
|
|
Rectangle r;
|
|
|
|
r = *rp;
|
|
if(x) rp->min.x += x, r.max.x = min(rp->min.x, rp->max.x);
|
|
if(y) rp->min.y += y, r.max.y = min(rp->min.y, rp->max.y);
|
|
return r;
|
|
}
|
|
|
|
static bool
|
|
nextrect(Item *i, Rectangle *rp, Rectangle *src) {
|
|
Rectangle r;
|
|
|
|
if(menu.rows)
|
|
r = slice(src, 0, menu.height);
|
|
else
|
|
r = slice(src, i->width, 0);
|
|
return (Dx(*src) >= 0 && Dy(*src) >= 0) && (*rp = r, 1);
|
|
}
|
|
|
|
void
|
|
menu_draw(void) {
|
|
Rectangle barr, extent, itemr, inputr, r, r2;
|
|
Item *item;
|
|
int inputw, offset;
|
|
|
|
barr = r2 = Rect(0, 0, Dx(menu.win->r), menu.height);
|
|
|
|
inputw = max(match.maxwidth + textwidth_l(font, input.string, min(input.filter_start, strlen(input.string))),
|
|
max(itempad + textwidth(font, input.string),
|
|
Dx(barr) / 3));
|
|
|
|
/* Calculate items box, w/ and w/o arrows */
|
|
if(menu.rows) {
|
|
menu.itemr = barr;
|
|
menu.itemr.max.y += Dy(barr) * (menu.rows - 1);
|
|
if(menu.ontop)
|
|
menu.itemr = rectaddpt(menu.itemr, Pt(0, Dy(barr)));
|
|
itemr = menu.itemr;
|
|
if(match.start != match.first)
|
|
menu.itemr = rectaddpt(menu.itemr, Pt(0, menu.arrow.y));
|
|
}
|
|
else {
|
|
itemr = r2;
|
|
slice(&itemr, inputw + promptw, 0);
|
|
menu.itemr = Rect(itemr.min.x + menu.arrow.x, itemr.min.y,
|
|
itemr.max.x - menu.arrow.x, itemr.max.y);
|
|
}
|
|
|
|
fill(menu.buf, menu.buf->r, &cnorm.bg);
|
|
|
|
/* Draw items */
|
|
item = match.start, r2 = menu.itemr;
|
|
nextrect(item, &r, &r2);
|
|
do {
|
|
match.end = item;
|
|
if(item->string)
|
|
fillstring(menu.buf, font, r, West, item->string,
|
|
(item == match.sel ? &csel : &cnorm), 0);
|
|
item = item->next;
|
|
} while(item != match.first && nextrect(item, &r, &r2));
|
|
|
|
/* Adjust dimensions for arrows/number of items */
|
|
if(menu.rows)
|
|
itemr.max.y = r.max.y + (match.end->next != match.first ? menu.arrow.y : 0);
|
|
else
|
|
itemr.max.x = r.max.x + menu.arrow.x;
|
|
if(menu.rows && !menu.ontop)
|
|
barr = rectaddpt(barr, Pt(0, itemr.max.y));
|
|
|
|
/* Draw indicators */
|
|
if(!menu.rows && match.start != match.first)
|
|
drawstring(menu.buf, font, itemr, West, "<", &cnorm.fg);
|
|
if(!menu.rows && match.end->next != match.first)
|
|
drawstring(menu.buf, font, itemr, East, ">", &cnorm.fg);
|
|
|
|
if(menu.rows && match.start != match.first)
|
|
drawarrow(menu.buf, itemr, 1, &cnorm.fg);
|
|
if(menu.rows && match.end->next != match.first)
|
|
drawarrow(menu.buf, itemr, 0, &cnorm.fg);
|
|
|
|
/* Draw prompt */
|
|
r2 = barr;
|
|
if(menu.prompt)
|
|
drawstring(menu.buf, font, slice(&r2, promptw, 0),
|
|
West, menu.prompt, &cnorm.fg);
|
|
|
|
/* Border input/horizontal items */
|
|
border(menu.buf, r2, 1, &cnorm.border);
|
|
|
|
/* Draw input */
|
|
inputr = slice(&r2, inputw, 0);
|
|
drawstring(menu.buf, font, inputr, West, input.string, &cnorm.fg);
|
|
|
|
/* Draw cursor */
|
|
extent = textextents_l(font, input.string, input.pos - input.string, &offset);
|
|
r2 = insetrect(inputr, 2);
|
|
r2.min.x = inputr.min.x - extent.min.x + offset + font->pad.min.x + itempad/2 - 1;
|
|
r2.max.x = r2.min.x + 1;
|
|
fill(menu.buf, r2, &cnorm.border);
|
|
|
|
/* Reshape window */
|
|
r = scr.rect;
|
|
if(menu.ontop)
|
|
r.max.y = r.min.y + itemr.max.y;
|
|
else
|
|
r.min.y = r.max.y - barr.max.y;
|
|
reshapewin(menu.win, r);
|
|
|
|
/* Border window */
|
|
r = rectsubpt(r, r.min);
|
|
border(menu.buf, r, 1, &cnorm.border);
|
|
copyimage(menu.win, r, menu.buf, ZP);
|
|
}
|
|
|
|
static Item*
|
|
pagestart(Item *i) {
|
|
Rectangle r, r2;
|
|
|
|
r = menu.itemr;
|
|
nextrect(i, &r2, &r);
|
|
while(i->prev != match.first->prev && nextrect(i->prev, &r2, &r))
|
|
i = i->prev;
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
selectitem(Item *i) {
|
|
if(i != match.sel) {
|
|
caret_set(input.filter_start, input.pos - input.string);
|
|
caret_insert(i->string, 0);
|
|
match.sel = i;
|
|
if(i == match.start->prev)
|
|
match.start = pagestart(i);
|
|
if(i == match.end->next)
|
|
match.start = i;
|
|
}
|
|
}
|
|
|
|
static void
|
|
paste(void *aux, char *str) {
|
|
if(str)
|
|
caret_insert(str, false);
|
|
menu_draw();
|
|
}
|
|
|
|
static bool
|
|
kdown_event(Window *w, void *aux, XKeyEvent *e) {
|
|
char **action, **p;
|
|
char *key;
|
|
char buf[128];
|
|
int num, status;
|
|
KeySym ksym;
|
|
|
|
if(XFilterEvent((XEvent*)e, w->xid))
|
|
return false;
|
|
|
|
status = XLookupBoth;
|
|
if(w->xic)
|
|
num = Xutf8LookupString(w->xic, e, buf, sizeof buf - 1, &ksym, &status);
|
|
else
|
|
num = XLookupString(e, buf, sizeof buf - 1, &ksym, nil);
|
|
|
|
if(status != XLookupChars && status != XLookupKeySym && status != XLookupBoth)
|
|
return false;
|
|
|
|
if(status == XLookupKeySym || status == XLookupBoth) {
|
|
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(status == XLookupChars || action == nil || action[0] == nil) {
|
|
if(num && !iscntrl(buf[0])) {
|
|
buf[num] = '\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])) {
|
|
default:
|
|
return false;
|
|
case LHISTORY:
|
|
num = input.pos - input.string;
|
|
amount = have(LBACKWARD) ? BACKWARD : FORWARD;
|
|
caret_insert(history_search(amount, input.string, num), true);
|
|
input.pos = input.string + num;
|
|
update_filter(true);
|
|
break;
|
|
case LKILL:
|
|
caret_delete(BACKWARD, amount);
|
|
update_filter(true);
|
|
break;
|
|
case LDELETE:
|
|
caret_delete(FORWARD, amount);
|
|
update_filter(true);
|
|
break;
|
|
|
|
case LACCEPT:
|
|
srv.running = false;
|
|
if(!have(LLITERAL) && !match.sel && match.start->retstring)
|
|
if(input.filter_start == 0 && input.pos == input.end)
|
|
selectitem(match.start);
|
|
|
|
if(!have(LLITERAL) && match.sel && !strcmp(input.string, match.sel->string))
|
|
lprint(1, "%s", match.sel->retstring);
|
|
else
|
|
lprint(1, "%s", input.string);
|
|
break;
|
|
case LBACKWARD:
|
|
caret_move(BACKWARD, amount);
|
|
update_input();
|
|
break;
|
|
case LCOMPLETE:
|
|
if(have(LNEXT))
|
|
selectitem(match.sel ? match.sel->next : match.first);
|
|
else if(have(LPREV))
|
|
selectitem((match.sel ? match.sel : match.start)->prev);
|
|
else if(have(LFIRST)) {
|
|
match.start = match.first;
|
|
selectitem(match.start);
|
|
}
|
|
else if(have(LLAST))
|
|
selectitem(match.first->prev);
|
|
else if(have(LNEXTPAGE))
|
|
selectitem(match.end->next);
|
|
else if(have(LPREVPAGE)) {
|
|
match.start = pagestart(match.start->prev);
|
|
selectitem(match.start);
|
|
}
|
|
break;
|
|
case LFORWARD:
|
|
caret_move(FORWARD, amount);
|
|
update_input();
|
|
break;
|
|
case LPASTE:
|
|
getselection(action[1] ? action[1] : "PRIMARY", paste, nil);
|
|
break;
|
|
case LREJECT:
|
|
srv.running = false;
|
|
result = 1;
|
|
break;
|
|
}
|
|
menu_draw();
|
|
}
|
|
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,
|
|
};
|
|
|