for (item = prevmenu->list; item->next != NULL; item = item->next)
;
+ /* a separator is no valid root for a submenu */
+ if (!item->label)
+ errx(1, "a separator is no valid root for a submenu");
+
prevmenu = menu;
menu->caller = item;
item->submenu = menu;
/* set window title (used if wflag is on) */
if (menu->parent == NULL) {
title = classh->res_name;
- } else {
+ } else if (menu->caller->output) {
title = menu->caller->output;
+ } else {
+ title = "\0";
}
XStringListToTextProperty(&title, 1, &wintitle);
return item;
}
+/* check if button is used to scroll */
+static int
+isscrollbutton(unsigned int button)
+{
+ if (button == Button4 || button == Button5)
+ return 1;
+ return 0;
+}
+
/* check if button is used to open a item on click */
static int
isclickbutton(unsigned int button)
{
- if (button == Button1)
+ if (button == Button1 || button == Button2)
return 1;
if (!rflag && button == Button3)
return 1;
return 0;
}
+/* warp pointer to center of selected item */
+static void
+warppointer(struct Menu *menu, struct Item *item)
+{
+ if (menu == NULL || item == NULL)
+ return;
+ if (menu->selected) {
+ XWarpPointer(dpy, None, menu->win, 0, 0, 0, 0, menu->w / 2, item->y + item->h / 2);
+ }
+}
+
+/* append buf into text */
+static int
+append(char *text, char *buf, size_t textsize, size_t buflen)
+{
+ size_t textlen;
+
+ textlen = strlen(text);
+ if (iscntrl(*buf))
+ return 0;
+ if (textlen + buflen > textsize - 1)
+ return 0;
+ if (buflen < 1)
+ return 0;
+ memcpy(text + textlen, buf, buflen);
+ text[textlen + buflen] = '\0';
+ return 1;
+}
+
+/* get item in menu matching text from given direction (or from beginning, if dir = 0) */
+static struct Item *
+matchitem(struct Menu *menu, char *text, int dir)
+{
+ struct Item *item, *lastitem;
+ char *s;
+ size_t textlen;
+
+ for (lastitem = menu->list; lastitem && lastitem->next; lastitem = lastitem->next)
+ ;
+ textlen = strlen(text);
+ if (dir < 0) {
+ if (menu->selected && menu->selected->prev)
+ item = menu->selected->prev;
+ else
+ item = lastitem;
+ } else if (dir > 0) {
+ if (menu->selected && menu->selected->next)
+ item = menu->selected->next;
+ else
+ item = menu->list;
+ } else {
+ item = menu->list;
+ }
+ /* find next item from selected item */
+ for ( ; item; item = (dir < 0) ? item->prev : item->next)
+ for (s = item->label; s && *s; s++)
+ if (strncasecmp(s, text, textlen) == 0)
+ return item;
+ /* if not found, try to find from the beginning/end of list */
+ if (dir > 0) {
+ for (item = menu->list ; item; item = item->next) {
+ for (s = item->label; s && *s; s++) {
+ if (strncasecmp(s, text, textlen) == 0) {
+ return item;
+ }
+ }
+ }
+ } else {
+ for (item = lastitem ; item; item = item->prev) {
+ for (s = item->label; s && *s; s++) {
+ if (strncasecmp(s, text, textlen) == 0) {
+ return item;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+/* check keysyms defined on config.h */
+static KeySym
+normalizeksym(KeySym ksym)
+{
+ if (ksym == KSYMFIRST)
+ return XK_Home;
+ if (ksym == KSYMLAST)
+ return XK_End;
+ if (ksym == KSYMUP)
+ return XK_Up;
+ if (ksym == KSYMDOWN)
+ return XK_Down;
+ if (ksym == KSYMLEFT)
+ return XK_Left;
+ if (ksym == KSYMRIGHT)
+ return XK_Right;
+ return ksym;
+}
+
/* run event loop */
static void
run(struct Menu *currmenu)
{
+ char text[BUFSIZ];
+ char buf[32];
struct Menu *menu;
struct Item *item;
struct Item *previtem = NULL;
- struct Item *lastitem;
+ struct Item *lastitem, *select;
KeySym ksym;
+ Status status;
XEvent ev;
+ int warped = 0;
+ int action;
+ int len;
+ int i;
+ text[0] = '\0';
mapmenu(currmenu);
while (!XNextEvent(dpy, &ev)) {
+ if (XFilterEvent(&ev, None))
+ continue;
+ action = ACTION_NOP;
switch(ev.type) {
case Expose:
if (ev.xexpose.count == 0)
- drawmenus(currmenu);
+ action = ACTION_DRAW;
break;
case MotionNotify:
- menu = getmenu(currmenu, ev.xbutton.window);
- item = getitem(menu, ev.xbutton.y);
- if (menu == NULL || item == NULL || previtem == item)
- break;
- previtem = item;
- menu->selected = item;
- if (item->submenu != NULL) {
- currmenu = item->submenu;
- currmenu->selected = NULL;
- } else {
- currmenu = menu;
+ if (!warped) {
+ menu = getmenu(currmenu, ev.xbutton.window);
+ item = getitem(menu, ev.xbutton.y);
+ if (menu == NULL || item == NULL || previtem == item)
+ break;
+ previtem = item;
+ select = menu->selected = item;
+ if (item->submenu != NULL) {
+ currmenu = item->submenu;
+ select = NULL;
+ } else {
+ currmenu = menu;
+ }
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
}
- mapmenu(currmenu);
- drawmenus(currmenu);
+ warped = 0;
break;
case ButtonRelease:
- if (!isclickbutton(ev.xbutton.button))
- break;
- menu = getmenu(currmenu, ev.xbutton.window);
- item = getitem(menu, ev.xbutton.y);
- if (menu == NULL || item == NULL)
- break;
-selectitem:
- if (item->label == NULL)
- break; /* ignore separators */
- if (item->submenu != NULL) {
- currmenu = item->submenu;
- } else {
- printf("%s\n", item->output);
- return;
+ if (isscrollbutton(ev.xbutton.button)) {
+ if (ev.xbutton.button == Button4)
+ select = itemcycle(currmenu, ITEMPREV);
+ else
+ select = itemcycle(currmenu, ITEMNEXT);
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW | ACTION_WARP;
+ } else if (isclickbutton(ev.xbutton.button)) {
+ menu = getmenu(currmenu, ev.xbutton.window);
+ item = getitem(menu, ev.xbutton.y);
+ if (menu == NULL || item == NULL)
+ break;
+enteritem:
+ if (item->label == NULL)
+ break; /* ignore separators */
+ if (item->submenu != NULL) {
+ currmenu = item->submenu;
+ } else {
+ printf("%s\n", item->output);
+ return;
+ }
+ select = currmenu->list;
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
+ if (ev.xbutton.button == Button2) {
+ action |= ACTION_WARP;
+ }
}
- mapmenu(currmenu);
- currmenu->selected = currmenu->list;
- drawmenus(currmenu);
break;
case ButtonPress:
menu = getmenu(currmenu, ev.xbutton.window);
return;
break;
case KeyPress:
- ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0);
+ len = XmbLookupString(currmenu->xic, &ev.xkey, buf, sizeof buf, &ksym, &status);
+ switch(status) {
+ default: /* XLookupNone, XBufferOverflow */
+ continue;
+ case XLookupChars:
+ goto append;
+ case XLookupKeySym: /* FALLTHROUGH */
+ case XLookupBoth:
+ break;
+ }
/* esc closes xmenu when current menu is the root menu */
if (ksym == XK_Escape && currmenu->parent == NULL)
ksym = XK_ISO_Left_Tab;
/* cycle through menu */
- item = NULL;
- if (ksym == XK_Home || ksym == KSYMFIRST) {
- item = itemcycle(currmenu, ITEMFIRST);
- } else if (ksym == XK_End || ksym == KSYMLAST) {
- item = itemcycle(currmenu, ITEMLAST);
- } else if (ksym == XK_ISO_Left_Tab || ksym == XK_Up || ksym == KSYMUP) {
- item = itemcycle(currmenu, ITEMPREV);
- } else if (ksym == XK_Tab || ksym == XK_Down || ksym == KSYMDOWN) {
- item = itemcycle(currmenu, ITEMNEXT);
- } else if (ksym >= XK_1 && ksym <= XK_9){
+ select = NULL;
+ ksym = normalizeksym(ksym);
+ switch (ksym) {
+ case XK_Home:
+ select = itemcycle(currmenu, ITEMFIRST);
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
+ break;
+ case XK_End:
+ select = itemcycle(currmenu, ITEMLAST);
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
+ break;
+ case XK_ISO_Left_Tab:
+ if (*text) {
+ select = matchitem(currmenu, text, -1);
+ action = ACTION_SELECT | ACTION_DRAW;
+ break;
+ }
+ /* FALLTHROUGH */
+ case XK_Up:
+ select = itemcycle(currmenu, ITEMPREV);
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
+ break;
+ case XK_Tab:
+ if (*text) {
+ select = matchitem(currmenu, text, 1);
+ action = ACTION_SELECT | ACTION_DRAW;
+ break;
+ }
+ /* FALLTHROUGH */
+ case XK_Down:
+ select = itemcycle(currmenu, ITEMNEXT);
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
+ break;
+ case XK_1: case XK_2: case XK_3: case XK_4: case XK_5: case XK_6: case XK_7: case XK_8: case XK_9:
item = itemcycle(currmenu, ITEMFIRST);
lastitem = itemcycle(currmenu, ITEMLAST);
for (int i = ksym - XK_1; i > 0 && item != lastitem; i--) {
currmenu->selected = item;
item = itemcycle(currmenu, ITEMNEXT);
}
- } else if ((ksym == XK_Return || ksym == XK_Right || ksym == KSYMRIGHT) &&
- currmenu->selected != NULL) {
- item = currmenu->selected;
- goto selectitem;
- } else if ((ksym == XK_Escape || ksym == XK_Left || ksym == KSYMLEFT) &&
- currmenu->parent != NULL) {
- item = currmenu->parent->selected;
- currmenu = currmenu->parent;
- mapmenu(currmenu);
- } else
+ select = item;
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
break;
- currmenu->selected = item;
- drawmenus(currmenu);
+ case XK_Return: case XK_Right:
+ if (currmenu->selected) {
+ item = currmenu->selected;
+ goto enteritem;
+ }
+ break;
+ case XK_Escape: case XK_Left:
+ if (currmenu->parent) {
+ select = currmenu->parent->selected;
+ currmenu = currmenu->parent;
+ action = ACTION_CLEAR | ACTION_MAP | ACTION_SELECT | ACTION_DRAW;
+ }
+ break;
+ case XK_BackSpace: case XK_Clear: case XK_Delete:
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
+ break;
+ default:
+append:
+ if (*buf == '\0' || iscntrl(*buf))
+ break;
+ for (i = 0; i < 2; i++) {
+ append(text, buf, sizeof text, len);
+ if ((select = matchitem(currmenu, text, 0)))
+ break;
+ text[0] = '\0';
+ }
+ action = ACTION_SELECT | ACTION_DRAW;
+ break;
+ }
break;
case LeaveNotify:
previtem = NULL;
- currmenu->selected = NULL;
- drawmenus(currmenu);
+ select = NULL;
+ action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
break;
case ConfigureNotify:
menu = getmenu(currmenu, ev.xconfigure.window);
if (menu->parent == NULL)
return; /* closing the root menu closes the program */
currmenu = menu->parent;
- mapmenu(currmenu);
+ action = ACTION_MAP;
break;
}
+ if (action & ACTION_CLEAR)
+ text[0] = '\0';
+ if (action & ACTION_SELECT)
+ currmenu->selected = select;
+ if (action & ACTION_MAP)
+ mapmenu(currmenu);
+ if (action & ACTION_DRAW)
+ drawmenus(currmenu);
+ if (action & ACTION_WARP) {
+ warppointer(currmenu, select);
+ warped = 1;
+ }
}
}
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
errx(1, "XOpenIM: could not open input device");
- /* get configuration */
+ /* process configuration and window class */
getresources();
classh.res_class = PROGNAME;
classh.res_name = getoptions(argc, argv);