From 4a48e94d50bad36ed872725db8d8cddbac5a9f47 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Tue, 8 Oct 2013 17:28:37 +0300 Subject: [PATCH] Horizontal keys in TTY menus work. --- lisp/menu-bar.el | 53 +++++++++++++------ src/menu.c | 29 ++++++++--- src/menu.h | 3 +- src/term.c | 132 +++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 185 insertions(+), 32 deletions(-) diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el index 400c65e505..71967e1474 100644 --- a/lisp/menu-bar.el +++ b/lisp/menu-bar.el @@ -2197,8 +2197,14 @@ FROM-MENU-BAR, if non-nil, means we are dropping one of menu-bar's menus." (filter (when (symbolp map) (plist-get (get map 'menu-prop) :filter)))) (if filter (funcall filter (symbol-function map)) map))))) - event cmd - (position (popup-menu-normalize-position position))) + (frame (selected-frame)) + event cmd) + (if from-menu-bar + (let* ((xy (posn-x-y position)) + (menu-symbol (menu-bar-menu-at-x-y (car xy) (cdr xy)))) + (setq position (list menu-symbol (list frame '(menu-bar) + xy 0)))) + (setq position (popup-menu-normalize-position position))) ;; The looping behavior was taken from lmenu's popup-menu-popup (while (and map (setq event ;; map could be a prefix key, in which case @@ -2209,19 +2215,36 @@ FROM-MENU-BAR, if non-nil, means we are dropping one of menu-bar's menus." ;; mouse-major-mode-menu was using a weird: ;; (key-binding (apply 'vector (append '(menu-bar) menu-prefix events))) (setq cmd - (if (and (not (keymapp map)) (listp map)) - ;; We were given a list of keymaps. Search them all - ;; in sequence until a first binding is found. - (let ((mouse-click (apply 'vector event)) - binding) - (while (and map (null binding)) - (setq binding (lookup-key (car map) mouse-click)) - (if (numberp binding) ; `too long' - (setq binding nil)) - (setq map (cdr map))) - binding) + (cond + ((and from-menu-bar + (consp event) + (numberp (car event)) + (numberp (cdr event))) + (let ((x (car event)) + (y (cdr event)) + menu-symbol) + (setq menu-symbol (menu-bar-menu-at-x-y x y)) + (setq position (list menu-symbol (list frame '(menu-bar) + event 0))) + (setq map + (or + (lookup-key global-map (vector 'menu-bar menu-symbol)) + (lookup-key (current-local-map) (vector 'menu-bar + menu-symbol)))))) + ((and (not (keymapp map)) (listp map)) + ;; We were given a list of keymaps. Search them all + ;; in sequence until a first binding is found. + (let ((mouse-click (apply 'vector event)) + binding) + (while (and map (null binding)) + (setq binding (lookup-key (car map) mouse-click)) + (if (numberp binding) ; `too long' + (setq binding nil)) + (setq map (cdr map))) + binding)) + (t ;; We were given a single keymap. - (lookup-key map (apply 'vector event)))) + (lookup-key map (apply 'vector event))))) ;; Clear out echoing, which perhaps shows a prefix arg. (message "") ;; Maybe try again but with the submap. @@ -2379,7 +2402,7 @@ If FRAME is nil or not given, use the selected frame." (popup-menu (or (lookup-key global-map (vector 'menu-bar menu)) (lookup-key (current-local-map) (vector 'menu-bar menu))) - (posn-at-x-y x 0 nil t) t))) + (posn-at-x-y x 0 nil t) nil t))) (t (with-selected-frame (or frame (selected-frame)) (tmm-menubar)))))) diff --git a/src/menu.c b/src/menu.c index 2c787e00b6..f741d686cd 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1036,8 +1036,8 @@ find_and_return_menu_selection (struct frame *f, bool keymaps, void *client_data } #endif /* HAVE_NS */ -static int -item_width (const char *str) +int +menu_item_width (const char *str) { int len; const char *p; @@ -1104,7 +1104,7 @@ into menu items. */) if (XINT (pos) <= col /* We use <= so the blank between 2 items on a TTY is considered part of the previous item. */ - && col <= XINT (pos) + item_width (SSDATA (str))) + && col <= XINT (pos) + menu_item_width (SSDATA (str))) { item = AREF (items, i); return item; @@ -1160,7 +1160,7 @@ event (indicating that the user invoked the menu with the mouse) then no quit occurs and `x-popup-menu' returns nil. */) (Lisp_Object position, Lisp_Object menu) { - Lisp_Object keymap, tem; + Lisp_Object keymap, tem, tem2; int xpos = 0, ypos = 0; Lisp_Object title; const char *error_name = NULL; @@ -1169,6 +1169,7 @@ no quit occurs and `x-popup-menu' returns nil. */) Lisp_Object x, y, window; bool keymaps = 0; bool for_click = 0; + bool kbd_menu_navigation = 0; ptrdiff_t specpdl_count = SPECPDL_INDEX (); struct gcpro gcpro1; @@ -1202,6 +1203,22 @@ no quit occurs and `x-popup-menu' returns nil. */) for_click = 1; tem = Fcar (Fcdr (position)); /* EVENT_START (position) */ window = Fcar (tem); /* POSN_WINDOW (tem) */ + tem2 = Fcar (Fcdr (tem)); /* POSN_POSN (tem) */ + /* The kbd_menu_navigation flag is set when the menu was + invoked by F10, which probably means they have no + mouse. In that case, we let them switch between + top-level menu-bar menus by using C-f/C-b and + horizontal arrow keys, since they cannot click the + mouse to open a different submenu. This flag is only + supported by tty_menu_show. We set it when POSITION + and last_nonmenu_event are different, which means we + constructed POSITION by hand (in popup-menu, see + menu-bar.el) to look like a mouse click on the menu bar + event. */ + if (!EQ (POSN_POSN (last_nonmenu_event), + POSN_POSN (position)) + && CONSP (tem2) && EQ (Fcar (tem2), Qmenu_bar)) + kbd_menu_navigation = 1; tem = Fcar (Fcdr (Fcdr (tem))); /* POSN_WINDOW_POSN (tem) */ x = Fcar (tem); y = Fcdr (tem); @@ -1434,8 +1451,8 @@ no quit occurs and `x-popup-menu' returns nil. */) else #endif if (FRAME_TERMCAP_P (f)) - selection = tty_menu_show (f, xpos, ypos, for_click, - keymaps, title, &error_name); + selection = tty_menu_show (f, xpos, ypos, for_click, keymaps, title, + kbd_menu_navigation, &error_name); #ifdef HAVE_NS unbind_to (specpdl_count, Qnil); diff --git a/src/menu.h b/src/menu.h index cdc1838ff9..9b3b71d757 100644 --- a/src/menu.h +++ b/src/menu.h @@ -52,5 +52,6 @@ extern Lisp_Object ns_menu_show (struct frame *, int, int, bool, bool, extern Lisp_Object xmenu_show (struct frame *, int, int, bool, bool, Lisp_Object, const char **, Time); extern Lisp_Object tty_menu_show (struct frame *, int, int, int, int, - Lisp_Object, const char **); + Lisp_Object, int, const char **); +extern int menu_item_width (const char *); #endif /* MENU_H */ diff --git a/src/term.c b/src/term.c index d5c5f6ed79..8515edf88a 100644 --- a/src/term.c +++ b/src/term.c @@ -2789,6 +2789,8 @@ DEFUN ("gpm-mouse-stop", Fgpm_mouse_stop, Sgpm_mouse_stop, #define TTYM_SUCCESS 1 #define TTYM_NO_SELECT 2 #define TTYM_IA_SELECT 3 +#define TTYM_NEXT 4 +#define TTYM_PREV 5 /* These hold text of the current and the previous menu help messages. */ static const char *menu_help_message, *prev_menu_help_message; @@ -3174,7 +3176,8 @@ screen_update (struct frame *f, struct glyph_matrix *mtx) puts us. We only consider mouse movement and click events and keyboard movement commands; the rest are ignored. - Value is -1 if C-g was pressed, 1 if an item was selected, zero + Value is -1 if C-g was pressed, 1 if an item was selected, 2 or 3 + if we need to move to the next or previous menu-bar menu, zero otherwise. */ static int read_menu_input (struct frame *sf, int *x, int *y, int min_y, int max_y, @@ -3219,9 +3222,15 @@ read_menu_input (struct frame *sf, int *x, int *y, int min_y, int max_y, *y = my; } else if (EQ (cmd, Qtty_menu_next_menu)) - *x += 1; + { + usable_input = 0; + st = 2; + } else if (EQ (cmd, Qtty_menu_prev_menu)) - *x -= 1; + { + usable_input = 0; + st = 3; + } else if (EQ (cmd, Qtty_menu_next_item)) { if (*y < max_y) @@ -3255,10 +3264,11 @@ read_menu_input (struct frame *sf, int *x, int *y, int min_y, int max_y, } /* Display menu, wait for user's response, and return that response. */ -int +static int tty_menu_activate (tty_menu *menu, int *pane, int *selidx, int x0, int y0, char **txt, - void (*help_callback)(char const *, int, int)) + void (*help_callback)(char const *, int, int), + int kbd_navigation) { struct tty_menu_state *state; int statecount, x, y, i, b, leave, result, onepane; @@ -3353,6 +3363,7 @@ tty_menu_activate (tty_menu *menu, int *pane, int *selidx, input_status = read_menu_input (sf, &x, &y, min_y, max_y, &first_time); if (input_status) { + leave = 1; if (input_status == -1) { /* Remove the last help-echo, so that it doesn't @@ -3360,7 +3371,20 @@ tty_menu_activate (tty_menu *menu, int *pane, int *selidx, show_help_echo (Qnil, Qnil, Qnil, Qnil); result = TTYM_NO_SELECT; } - leave = 1; + else if (input_status == 2) + { + if (kbd_navigation) + result = TTYM_NEXT; + else + leave = 0; + } + else if (input_status == 3) + { + if (kbd_navigation) + result = TTYM_PREV; + else + leave = 0; + } } if (sf->mouse_moved && input_status != -1) { @@ -3509,15 +3533,97 @@ tty_pop_down_menu (Lisp_Object arg) unblock_input (); } +/* Return the zero-based index of the last menu-bar item on frame F. */ +static int +tty_menu_last_menubar_item (struct frame *f) +{ + int i = 0; + + eassert (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f)); + if (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f)) + { + Lisp_Object items = FRAME_MENU_BAR_ITEMS (f); + + while (i < ASIZE (items)) + { + Lisp_Object str; + + str = AREF (items, i + 1); + if (NILP (str)) + break; + i += 4; + } + i -= 4; /* went one too far */ + } + return i; +} + +/* Find in frame F's menu bar the menu item that is next or previous + to the item at X/Y, and return that item's position in X/Y. WHICH + says which one--next or previous--item to look for. X and Y are + measured in character cells. This should only be called on TTY + frames. */ +static void +tty_menu_new_item_coords (struct frame *f, int which, int *x, int *y) +{ + eassert (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f)); + if (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f)) + { + Lisp_Object items = FRAME_MENU_BAR_ITEMS (f); + int last_i = tty_menu_last_menubar_item (f); + int i, prev_x; + + /* This loop assumes a single menu-bar line, and will fail to + find an item if it is not in the first line. Note that + make_lispy_event in keyboard.c makes the same assumption. */ + for (i = 0, prev_x = -1; i < ASIZE (items); i += 4) + { + Lisp_Object pos, str; + int ix; + + str = AREF (items, i + 1); + pos = AREF (items, i + 3); + if (NILP (str)) + return; + ix = XINT (pos); + if (ix <= *x + /* We use <= so the blank between 2 items on a TTY is + considered part of the previous item. */ + && *x <= ix + menu_item_width (SSDATA (str))) + { + /* Found current item. Now compute the X coordinate of + the previous or next item. */ + if (which == TTYM_NEXT) + { + if (i < last_i) + *x = XINT (AREF (items, i + 4 + 3)); + else + *x = 0; /* wrap around to the first item */ + } + else if (prev_x < 0) + { + /* Wrap around to the last item. */ + *x = XINT (AREF (items, last_i + 3)); + } + else + *x = prev_x; + return; + } + prev_x = ix; + } + } +} + Lisp_Object tty_menu_show (struct frame *f, int x, int y, int for_click, int keymaps, - Lisp_Object title, const char **error_name) + Lisp_Object title, int kbd_navigation, const char **error_name) { tty_menu *menu; int pane, selidx, lpane, status; Lisp_Object entry, pane_prefix; char *datap; int ulx, uly, width, height; + int item_x, item_y; int dispwidth, dispheight; int i, j, lines, maxlines; int maxwidth; @@ -3551,8 +3657,8 @@ tty_menu_show (struct frame *f, int x, int y, int for_click, int keymaps, inhibit_garbage_collection (); /* Adjust coordinates to be root-window-relative. */ - x += f->left_pos; - y += f->top_pos; + item_x = x += f->left_pos; + item_y = y += f->top_pos; /* Create all the necessary panes and their items. */ maxwidth = maxlines = lines = i = 0; @@ -3710,7 +3816,7 @@ tty_menu_show (struct frame *f, int x, int y, int for_click, int keymaps, specbind (Qoverriding_terminal_local_map, Fsymbol_value (Qtty_menu_navigation_map)); status = tty_menu_activate (menu, &pane, &selidx, x, y, &datap, - tty_menu_help_callback); + tty_menu_help_callback, kbd_navigation); entry = pane_prefix = Qnil; switch (status) @@ -3751,6 +3857,12 @@ tty_menu_show (struct frame *f, int x, int y, int for_click, int keymaps, } break; + case TTYM_NEXT: + case TTYM_PREV: + tty_menu_new_item_coords (f, status, &item_x, &item_y); + entry = Fcons (make_number (item_x), make_number (item_y)); + break; + case TTYM_FAILURE: *error_name = "Can't activate menu"; case TTYM_IA_SELECT: -- 2.20.1