Merge pull request #30 from paniash/master
[xmenu] / xmenu.c
diff --git a/xmenu.c b/xmenu.c
index acdabc2..9b3a250 100644 (file)
--- a/xmenu.c
+++ b/xmenu.c
@@ -141,7 +141,7 @@ getoptions(int argc, char *argv[])
 {
        int ch;
 
 {
        int ch;
 
-       while ((ch = getopt(argc, argv, "ip:rtw")) != -1) {
+       while ((ch = getopt(argc, argv, "ip:rw")) != -1) {
                switch (ch) {
                case 'i':
                        iflag = 1;
                switch (ch) {
                case 'i':
                        iflag = 1;
@@ -153,9 +153,6 @@ getoptions(int argc, char *argv[])
                case 'r':
                        rflag = 1;
                        break;
                case 'r':
                        rflag = 1;
                        break;
-               case 't':
-                       config.typetoselect = !config.typetoselect;
-                       break;
                case 'w':
                        wflag = 1;
                        break;
                case 'w':
                        wflag = 1;
                        break;
@@ -435,6 +432,10 @@ buildmenutree(unsigned level, const char *label, const char *output, char *file)
                for (item = prevmenu->list; item->next != NULL; item = item->next)
                        ;
 
                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;
                prevmenu = menu;
                menu->caller = item;
                item->submenu = menu;
@@ -720,8 +721,10 @@ setupmenu(struct Menu *menu, XClassHint *classh)
        /* set window title (used if wflag is on) */
        if (menu->parent == NULL) {
                title = classh->res_name;
        /* 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;
                title = menu->caller->output;
+       } else {
+               title = "\0";
        }
        XStringListToTextProperty(&title, 1, &wintitle);
 
        }
        XStringListToTextProperty(&title, 1, &wintitle);
 
@@ -1132,17 +1135,37 @@ itemcycle(struct Menu *currmenu, int direction)
        return item;
 }
 
        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)
 {
 /* 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;
 }
 
                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)
 /* append buf into text */
 static int
 append(char *text, char *buf, size_t textsize, size_t buflen)
@@ -1243,6 +1266,7 @@ run(struct Menu *currmenu)
        KeySym ksym;
        Status status;
        XEvent ev;
        KeySym ksym;
        Status status;
        XEvent ev;
+       int warped = 0;
        int action;
        int len;
        int i;
        int action;
        int len;
        int i;
@@ -1259,38 +1283,50 @@ run(struct Menu *currmenu)
                                action = ACTION_DRAW;
                        break;
                case MotionNotify:
                                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;
-                       select = menu->selected = item;
-                       if (item->submenu != NULL) {
-                               currmenu = item->submenu;
-                               select = 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;
                        }
                        }
-                       action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
+                       warped = 0;
                        break;
                case ButtonRelease:
                        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;
+                       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:
 enteritem:
-                       if (item->label == NULL)
-                               break;  /* ignore separators */
-                       if (item->submenu != NULL) {
-                               currmenu = item->submenu;
-                       } else {
-                               printf("%s\n", item->output);
-                               return;
+                               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;
+                               }
                        }
                        }
-                       select = currmenu->list;
-                       action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
                        break;
                case ButtonPress:
                        menu = getmenu(currmenu, ev.xbutton.window);
                        break;
                case ButtonPress:
                        menu = getmenu(currmenu, ev.xbutton.window);
@@ -1318,36 +1354,38 @@ enteritem:
                                ksym = XK_ISO_Left_Tab;
 
                        /* cycle through menu */
                                ksym = XK_ISO_Left_Tab;
 
                        /* cycle through menu */
-                       item = NULL;
+                       select = NULL;
                        ksym = normalizeksym(ksym);
                        switch (ksym) {
                        case XK_Home:
                        ksym = normalizeksym(ksym);
                        switch (ksym) {
                        case XK_Home:
-                               item = itemcycle(currmenu, ITEMFIRST);
-                               action = ACTION_CLEAR;
+                               select = itemcycle(currmenu, ITEMFIRST);
+                               action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
                                break;
                        case XK_End:
                                break;
                        case XK_End:
-                               item = itemcycle(currmenu, ITEMLAST);
-                               action = ACTION_CLEAR;
+                               select = itemcycle(currmenu, ITEMLAST);
+                               action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
                                break;
                        case XK_ISO_Left_Tab:
                                if (*text) {
                                break;
                        case XK_ISO_Left_Tab:
                                if (*text) {
-                                       item = matchitem(currmenu, text, -1);
+                                       select = matchitem(currmenu, text, -1);
+                                       action = ACTION_SELECT | ACTION_DRAW;
                                        break;
                                }
                                /* FALLTHROUGH */
                        case XK_Up:
                                        break;
                                }
                                /* FALLTHROUGH */
                        case XK_Up:
-                               item = itemcycle(currmenu, ITEMPREV);
-                               action = ACTION_CLEAR;
+                               select = itemcycle(currmenu, ITEMPREV);
+                               action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
                                break;
                        case XK_Tab:
                                if (*text) {
                                break;
                        case XK_Tab:
                                if (*text) {
-                                       item = matchitem(currmenu, text, 1);
+                                       select = matchitem(currmenu, text, 1);
+                                       action = ACTION_SELECT | ACTION_DRAW;
                                        break;
                                }
                                /* FALLTHROUGH */
                        case XK_Down:
                                        break;
                                }
                                /* FALLTHROUGH */
                        case XK_Down:
-                               item = itemcycle(currmenu, ITEMNEXT);
-                               action = ACTION_CLEAR;
+                               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);
                                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);
@@ -1356,7 +1394,8 @@ enteritem:
                                        currmenu->selected = item;
                                        item = itemcycle(currmenu, ITEMNEXT);
                                }
                                        currmenu->selected = item;
                                        item = itemcycle(currmenu, ITEMNEXT);
                                }
-                               action = ACTION_CLEAR;
+                               select = item;
+                               action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
                                break;
                        case XK_Return: case XK_Right:
                                if (currmenu->selected) {
                                break;
                        case XK_Return: case XK_Right:
                                if (currmenu->selected) {
@@ -1366,28 +1405,27 @@ enteritem:
                                break;
                        case XK_Escape: case XK_Left:
                                if (currmenu->parent) {
                                break;
                        case XK_Escape: case XK_Left:
                                if (currmenu->parent) {
-                                       item = currmenu->parent->selected;
+                                       select = currmenu->parent->selected;
                                        currmenu = currmenu->parent;
                                        currmenu = currmenu->parent;
-                                       action = ACTION_CLEAR | ACTION_MAP;
+                                       action = ACTION_CLEAR | ACTION_MAP | ACTION_SELECT | ACTION_DRAW;
                                }
                                break;
                        case XK_BackSpace: case XK_Clear: case XK_Delete:
                                }
                                break;
                        case XK_BackSpace: case XK_Clear: case XK_Delete:
-                               action = ACTION_CLEAR;
+                               action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
                                break;
                        default:
 append:
                                break;
                        default:
 append:
-                               if (!config.typetoselect)
+                               if (*buf == '\0' || iscntrl(*buf))
                                        break;
                                for (i = 0; i < 2; i++) {
                                        append(text, buf, sizeof text, len);
                                        break;
                                for (i = 0; i < 2; i++) {
                                        append(text, buf, sizeof text, len);
-                                       if ((item = matchitem(currmenu, text, 0)))
+                                       if ((select = matchitem(currmenu, text, 0)))
                                                break;
                                        text[0] = '\0';
                                }
                                                break;
                                        text[0] = '\0';
                                }
+                               action = ACTION_SELECT | ACTION_DRAW;
                                break;
                        }
                                break;
                        }
-                       select = item;
-                       action |= ACTION_SELECT | ACTION_DRAW;
                        break;
                case LeaveNotify:
                        previtem = NULL;
                        break;
                case LeaveNotify:
                        previtem = NULL;
@@ -1420,6 +1458,10 @@ append:
                        mapmenu(currmenu);
                if (action & ACTION_DRAW)
                        drawmenus(currmenu);
                        mapmenu(currmenu);
                if (action & ACTION_DRAW)
                        drawmenus(currmenu);
+               if (action & ACTION_WARP) {
+                       warppointer(currmenu, select);
+                       warped = 1;
+               }
        }
 }
 
        }
 }
 
@@ -1490,7 +1532,7 @@ main(int argc, char *argv[])
        if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
                errx(1, "XOpenIM: could not open input device");
 
        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);
        getresources();
        classh.res_class = PROGNAME;
        classh.res_name = getoptions(argc, argv);