From e6bea0cbc76bc47f435be9c7f6f3c4b770994924 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Wed, 18 Aug 2021 20:32:32 +0300 Subject: [PATCH] Redesign tab-bar event processing (bug#41342, bug#41343) Instead of emitting menu-item keys like [tab-1], emit normal mouse events like [mouse-1] and [down-mouse-3] for all mouse clicks issued on the tab-bar. * lisp/mouse.el (mouse-posn-property): Handle 'tab-bar' posn-area. * lisp/tab-bar.el (tab--key-to-number): New internal function. (tab-bar-handle-mouse): Use tab key to select/close tab. (tab-bar-mouse-select-tab, tab-bar-mouse-close-tab) (tab-bar-mouse-context-menu): New commands. (tab-bar-map): Bind [down-mouse-1] to tab-bar-mouse-select-tab, [down-mouse-2] to tab-bar-mouse-close-tab, [down-mouse-3] to tab-bar-mouse-context-menu. (tab-bar-keymap-cache): Remove. (tab-bar-make-keymap): Don't use cache. (tab-bar--format-tab): Remove default bindings from menu items. (tab-bar-make-keymap-1): Prepend tab-bar-map. * src/keyboard.c (make_lispy_event): Append event->arg to position for Qtab_bar. * src/term.c (handle_one_term_event): Simplify to set event arg. * src/w32inevt.c (do_mouse_event): Set emacs_ev->arg to the value returned from tty_handle_tab_bar_click. * src/w32term.c (w32_handle_tab_bar_click): Return value from handle_tab_bar_click. (w32_read_socket): Set tab_bar_key to value returned from w32_handle_tab_bar_click, and set event arg from it. * src/xdisp.c (handle_tab_bar_click): Instead of emitting event, return a list with Qtab_bar and tab caption with text properties that contain Qmenu_item with key and binding. (tty_handle_tab_bar_click): Simplify to return a list of Qtab_bar, key and close_p, instead of emitting event. * src/xterm.c (handle_one_xevent): Set tab_bar_key to value returned from handle_tab_bar_click, and set event arg from it. --- etc/NEWS | 4 ++ lisp/mouse.el | 7 ++-- lisp/tab-bar.el | 100 ++++++++++++++++++++++++++++++----------------- src/dispextern.h | 4 +- src/keyboard.c | 6 +++ src/term.c | 17 +------- src/termchar.h | 4 +- src/w32inevt.c | 6 +-- src/w32term.c | 20 ++++++---- src/xdisp.c | 81 ++++++++++++++++---------------------- src/xterm.c | 8 +++- 11 files changed, 138 insertions(+), 119 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 1b4712828c5..cb500dd697e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -748,6 +748,10 @@ of the next command to be displayed in a new frame. ** Tab Bars +*** The tab bar now supports more mouse commands. +Clicking 'mouse-2' closes the tab, 'mouse-3' displays the context menu +with items that operate on the clicked tab. + *** The key prefix 'C-x t t' displays next command buffer in a new tab. It's bound to the command 'other-tab-prefix' that requests the buffer of the next command to be displayed in a new tab. diff --git a/lisp/mouse.el b/lisp/mouse.el index 4c4a7d35a89..0047475a0d5 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el @@ -1374,9 +1374,10 @@ its value is returned." ;; Mouse clicks in the fringe come with a position in ;; (nth 5). This is useful but is not exactly where we clicked, so ;; don't look up that position's properties! - (and pt (not (memq (posn-area pos) '(left-fringe right-fringe - left-margin right-margin))) - (get-char-property pt property w)))) + (and pt (not (memq (posn-area pos) + '(left-fringe right-fringe + left-margin right-margin tab-bar))) + (get-char-property pt property w)))) (get-char-property pos property))) (defun mouse-on-link-p (pos) diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index 4ec1143128b..91d22b4b7b2 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el @@ -27,10 +27,7 @@ ;; bindings for the global tab bar. ;; The normal global binding for [tab-bar] (below) uses the value of -;; `tab-bar-map' as the actual keymap to define the tab bar. Modes -;; may either bind items under the [tab-bar] prefix key of the local -;; map to add to the global bar or may set `tab-bar-map' -;; buffer-locally to override it. +;; `tab-bar-map' as the actual keymap to define the tab bar. ;;; Code: @@ -224,6 +221,11 @@ a list of frames to update." (tab-bar--define-keys) (tab-bar--undefine-keys))) +(defun tab--key-to-number (key) + (unless (or (null key) (eq key 'current-tab)) + (string-to-number + (string-replace "tab-" "" (format "%S" key))))) + (defun tab-bar-handle-mouse (event) "Text-mode emulation of switching tabs on the tab bar. This command is used when you click the mouse in the tab bar @@ -238,18 +240,51 @@ on a console which has no window system but does have a mouse." (lambda (key binding) (when (eq (car-safe binding) 'menu-item) (when (> (+ column (length (nth 1 binding))) x-position) - (if (get-text-property (- x-position column) 'close-tab (nth 1 binding)) - (let* ((close-key (vector (intern (format "C-%s" key)))) - (close-def (lookup-key keymap close-key))) - (when close-def - (call-interactively close-def))) - (call-interactively (nth 2 binding))) + (if (get-text-property + (- x-position column) 'close-tab (nth 1 binding)) + (tab-bar-close-tab (tab--key-to-number key)) + (if (nth 2 binding) + (call-interactively (nth 2 binding)) + (tab-bar-select-tab (tab--key-to-number key)))) (throw 'done t)) (setq column (+ column (length (nth 1 binding)))))) keymap)) ;; Clicking anywhere outside existing tabs will add a new tab (tab-bar-new-tab))))) +(defun tab-bar-mouse-select-tab (event) + (interactive "e") + (if (posn-window (event-start event)) + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption)))) + (if (nth 2 item) + (tab-bar-close-tab (tab--key-to-number (nth 0 item))) + (if (functionp (nth 1 item)) + (call-interactively (nth 1 item)) + (tab-bar-select-tab (tab--key-to-number (nth 0 item)))))) + ;; TTY + (tab-bar-handle-mouse event))) + +(defun tab-bar-mouse-close-tab (event) + (interactive "e") + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption)))) + (tab-bar-close-tab (tab--key-to-number (nth 0 item))))) + +(defun tab-bar-mouse-context-menu (event) + (interactive "e") + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption))) + (tab-number (tab--key-to-number (nth 0 item))) + (menu (make-sparse-keymap "Context Menu"))) + + (define-key-after menu [close] + `(menu-item "Close" (lambda () (interactive) + (tab-bar-close-tab ,tab-number)) + :help "Close the tab")) + + (popup-menu menu event))) + (defun toggle-tab-bar-mode-from-frame (&optional arg) "Toggle tab bar on or off, based on the status of the current frame. Used in the Show/Hide menu, to have the toggle reflect the current frame. @@ -273,24 +308,26 @@ new frame when the global `tab-bar-mode' is enabled, by using (set-frame-parameter frame 'tab-bar-lines-keep-state (not (frame-parameter frame 'tab-bar-lines-keep-state)))) -(defvar tab-bar-map (make-sparse-keymap) - "Keymap for the tab bar. -Define this locally to override the global tab bar.") +(defvar tab-bar-map + (let ((map (make-sparse-keymap))) + (define-key map [down-mouse-1] 'tab-bar-mouse-select-tab) + (define-key map [mouse-1] 'ignore) + (define-key map [down-mouse-2] 'tab-bar-mouse-close-tab) + (define-key map [mouse-2] 'ignore) + (define-key map [down-mouse-3] 'tab-bar-mouse-context-menu) + + map) + "Keymap for the commands used on the tab bar.") (global-set-key [tab-bar] `(menu-item ,(purecopy "tab bar") ignore :filter tab-bar-make-keymap)) -(defconst tab-bar-keymap-cache (make-hash-table :weakness t :test 'equal)) - (defun tab-bar-make-keymap (&optional _ignore) "Generate an actual keymap from `tab-bar-map'. -Its main job is to show tabs in the tab bar." - (if (= 1 (length tab-bar-map)) - (tab-bar-make-keymap-1) - (let ((key (cons (frame-terminal) tab-bar-map))) - (or (gethash key tab-bar-keymap-cache) - (puthash key tab-bar-map tab-bar-keymap-cache))))) +Its main job is to show tabs in the tab bar +and to bind mouse events to the commands." + (tab-bar-make-keymap-1)) (defcustom tab-bar-show t @@ -608,19 +645,12 @@ You can hide these buttons by customizing `tab-bar-format' and removing `((,(intern (format "tab-%i" i)) menu-item ,(funcall tab-bar-tab-name-format-function tab i) - ,(or - (alist-get 'binding tab) - `(lambda () - (interactive) - (tab-bar-select-tab ,i))) + ,(alist-get 'binding tab) :help "Click to visit tab")))) - `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) - menu-item "" - ,(or - (alist-get 'close-binding tab) - `(lambda () - (interactive) - (tab-bar-close-tab ,i))))))) + (when (alist-get 'close-binding tab) + `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) + menu-item "" + ,(alist-get 'close-binding tab)))))) (defun tab-bar-format-tabs () (let ((i 0)) @@ -760,9 +790,7 @@ on the tab bar instead." (defun tab-bar-make-keymap-1 () "Generate an actual keymap from `tab-bar-map', without caching." - (append - '(keymap (mouse-1 . tab-bar-handle-mouse)) - (tab-bar-format-list tab-bar-format))) + (append tab-bar-map (tab-bar-format-list tab-bar-format))) ;; Some window-configuration parameters don't need to be persistent. diff --git a/src/dispextern.h b/src/dispextern.h index 33fcaa4c078..f4c7575b352 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -3415,8 +3415,8 @@ extern void get_glyph_string_clip_rect (struct glyph_string *, NativeRectangle *nr); extern Lisp_Object find_hot_spot (Lisp_Object, int, int); -extern void handle_tab_bar_click (struct frame *, - int, int, bool, int); +extern Lisp_Object handle_tab_bar_click (struct frame *, + int, int, bool, int); extern void handle_tool_bar_click (struct frame *, int, int, bool, int); diff --git a/src/keyboard.c b/src/keyboard.c index 2e4c4e6aabf..e25833276c1 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -5649,6 +5649,12 @@ make_lispy_event (struct input_event *event) position = make_lispy_position (f, event->x, event->y, event->timestamp); + + if (CONSP (event->arg) && EQ (XCAR (event->arg), Qtab_bar)) + { + XSETCAR (XCDR (position), Qtab_bar); + position = nconc2 (position, Fcons (XCDR (event->arg), Qnil)); + } } #ifndef USE_TOOLKIT_SCROLL_BARS else diff --git a/src/term.c b/src/term.c index c995a4499cf..89b35680034 100644 --- a/src/term.c +++ b/src/term.c @@ -2568,21 +2568,8 @@ handle_one_term_event (struct tty_display_info *tty, Gpm_Event *event) { f->mouse_moved = 0; term_mouse_click (&ie, event, f); - /* eassert (ie.kind == MOUSE_CLICK_EVENT); */ - if (tty_handle_tab_bar_click (f, event->x, event->y, - (ie.modifiers & down_modifier) != 0, &ie)) - { - /* eassert (ie.kind == MOUSE_CLICK_EVENT - * || ie.kind == TAB_BAR_EVENT); */ - /* tty_handle_tab_bar_click stores 2 events in the event - queue, so we are done here. */ - /* FIXME: Actually, `tty_handle_tab_bar_click` returns true - without storing any events, when - (ie.modifiers & down_modifier) != 0 */ - count += 2; - return count; - } - /* eassert (ie.kind == MOUSE_CLICK_EVENT); */ + ie.arg = tty_handle_tab_bar_click (f, event->x, event->y, + (ie.modifiers & down_modifier) != 0, &ie); kbd_buffer_store_event (&ie); count++; } diff --git a/src/termchar.h b/src/termchar.h index f50c1bfb6ea..7ab9337fbe7 100644 --- a/src/termchar.h +++ b/src/termchar.h @@ -234,7 +234,7 @@ extern struct tty_display_info *tty_list; #define CURTTY() FRAME_TTY (SELECTED_FRAME()) struct input_event; -extern bool tty_handle_tab_bar_click (struct frame *, int, int, bool, - struct input_event *); +extern Lisp_Object tty_handle_tab_bar_click (struct frame *, int, int, bool, + struct input_event *); #endif /* EMACS_TERMCHAR_H */ diff --git a/src/w32inevt.c b/src/w32inevt.c index 1255072b7f3..9a69b32bcb0 100644 --- a/src/w32inevt.c +++ b/src/w32inevt.c @@ -586,9 +586,8 @@ do_mouse_event (MOUSE_EVENT_RECORD *event, int x = event->dwMousePosition.X; int y = event->dwMousePosition.Y; struct frame *f = get_frame (); - if (tty_handle_tab_bar_click (f, x, y, (button_state & mask) != 0, - emacs_ev)) - return 0; /* tty_handle_tab_bar_click adds the event to queue */ + emacs_ev->arg = tty_handle_tab_bar_click (f, x, y, (button_state & mask) != 0, + emacs_ev); emacs_ev->modifiers |= ((button_state & mask) ? down_modifier : up_modifier); @@ -597,7 +596,6 @@ do_mouse_event (MOUSE_EVENT_RECORD *event, XSETFASTINT (emacs_ev->x, x); XSETFASTINT (emacs_ev->y, y); XSETFRAME (emacs_ev->frame_or_window, f); - emacs_ev->arg = Qnil; return 1; } diff --git a/src/w32term.c b/src/w32term.c index ad4d1a32829..c9570b0c670 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -168,8 +168,8 @@ int w32_keyboard_codepage; int w32_message_fd = -1; #endif /* CYGWIN */ -static void w32_handle_tab_bar_click (struct frame *, - struct input_event *); +static Lisp_Object w32_handle_tab_bar_click (struct frame *, + struct input_event *); static void w32_handle_tool_bar_click (struct frame *, struct input_event *); static void w32_define_cursor (Window, Emacs_Cursor); @@ -3684,17 +3684,17 @@ w32_mouse_position (struct frame **fp, int insist, Lisp_Object *bar_window, frame-relative coordinates X/Y. EVENT_TYPE is either ButtonPress or ButtonRelease. */ -static void +static Lisp_Object w32_handle_tab_bar_click (struct frame *f, struct input_event *button_event) { int x = XFIXNAT (button_event->x); int y = XFIXNAT (button_event->y); if (button_event->modifiers & down_modifier) - handle_tab_bar_click (f, x, y, 1, 0); + return handle_tab_bar_click (f, x, y, 1, 0); else - handle_tab_bar_click (f, x, y, 0, - button_event->modifiers & ~up_modifier); + return handle_tab_bar_click (f, x, y, 0, + button_event->modifiers & ~up_modifier); } @@ -5186,6 +5186,7 @@ w32_read_socket (struct terminal *terminal, { /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ + Lisp_Object tab_bar_key = Qnil; bool tab_bar_p = 0; bool tool_bar_p = 0; int button = 0; @@ -5208,12 +5209,12 @@ w32_read_socket (struct terminal *terminal, if (EQ (window, f->tab_bar_window)) { - w32_handle_tab_bar_click (f, &inev); + tab_bar_key = w32_handle_tab_bar_click (f, &inev); tab_bar_p = 1; } } - if (tab_bar_p + if ((tab_bar_p && NILP (tab_bar_key)) || (dpyinfo->w32_focus_frame && f != dpyinfo->w32_focus_frame /* This does not help when the click happens in @@ -5221,6 +5222,9 @@ w32_read_socket (struct terminal *terminal, && !frame_ancestor_p (f, dpyinfo->w32_focus_frame))) inev.kind = NO_EVENT; + if (!NILP (tab_bar_key)) + inev.arg = tab_bar_key; + /* Is this in the tool-bar? */ if (WINDOWP (f->tool_bar_window) && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window))) diff --git a/src/xdisp.c b/src/xdisp.c index 972b90177c6..1a4efba4b82 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -13745,7 +13745,7 @@ get_tab_bar_item (struct frame *f, int x, int y, struct glyph **glyph, false for button release. MODIFIERS is event modifiers for button release. */ -void +Lisp_Object handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, int modifiers) { @@ -13763,12 +13763,12 @@ handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, /* If the button is released on a tab other than the one where it was pressed, don't generate the tab-bar button click event. */ || (ts != 0 && !down_p)) - return; + return Qnil; /* If item is disabled, do nothing. */ enabled_p = AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_ENABLED_P); if (NILP (enabled_p)) - return; + return Qnil; if (down_p) { @@ -13779,24 +13779,24 @@ handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, } else { - Lisp_Object key, frame; - struct input_event event; - EVENT_INIT (event); - /* Show item in released state. */ if (!NILP (Vmouse_highlight)) show_mouse_face (hlinfo, DRAW_IMAGE_RAISED); - - key = AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_KEY); - - XSETFRAME (frame, f); - event.kind = TAB_BAR_EVENT; - event.frame_or_window = frame; - event.arg = key; - event.modifiers = close_p ? ctrl_modifier | modifiers : modifiers; - kbd_buffer_store_event (&event); f->last_tab_bar_item = -1; } + + Lisp_Object caption = + Fcopy_sequence (AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_CAPTION)); + + AUTO_LIST2 (props, Qmenu_item, + list3 (AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_KEY), + AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_BINDING), + close_p ? Qt : Qnil)); + + Fadd_text_properties (make_fixnum (0), make_fixnum (SCHARS (caption)), + props, caption); + + return Fcons (Qtab_bar, Fcons (caption, make_fixnum (0))); } @@ -13920,14 +13920,14 @@ tty_get_tab_bar_item (struct frame *f, int x, int *idx, ptrdiff_t *end) structure, store it in keyboard queue, and return true; otherwise return false. MODIFIERS are event modifiers for generating the tab release event. */ -bool +Lisp_Object tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, struct input_event *event) { /* Did they click on the tab bar? */ if (y < FRAME_MENU_BAR_LINES (f) || y >= FRAME_MENU_BAR_LINES (f) + FRAME_TAB_BAR_LINES (f)) - return false; + return Qnil; /* Find the tab-bar item where the X,Y coordinates belong. */ int prop_idx; @@ -13935,46 +13935,33 @@ tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, Lisp_Object caption = tty_get_tab_bar_item (f, x, &prop_idx, &clen); if (NILP (caption)) - return false; + return Qnil; if (NILP (AREF (f->tab_bar_items, prop_idx * TAB_BAR_ITEM_NSLOTS + TAB_BAR_ITEM_ENABLED_P))) - return false; + return Qnil; if (down_p) f->last_tab_bar_item = prop_idx; else { - /* Force reset of up_modifier bit from the event modifiers. */ - if (event->modifiers & up_modifier) - event->modifiers &= ~up_modifier; - - /* Generate a TAB_BAR_EVENT event. */ - Lisp_Object frame; - Lisp_Object key = AREF (f->tab_bar_items, - prop_idx * TAB_BAR_ITEM_NSLOTS - + TAB_BAR_ITEM_KEY); - /* Kludge alert: we assume the last two characters of a tab - label are " x", and treat clicks on those 2 characters as a - Close Tab command. */ - eassert (STRINGP (caption)); - int lastc = SSDATA (caption)[SCHARS (caption) - 1]; - bool close_p = false; - if ((x == clen - 1 || (clen > 1 && x == clen - 2)) && lastc == 'x') - close_p = true; - - event->code = 0; - XSETFRAME (frame, f); - event->kind = TAB_BAR_EVENT; - event->frame_or_window = frame; - event->arg = key; - if (close_p) - event->modifiers |= ctrl_modifier; - kbd_buffer_store_event (event); f->last_tab_bar_item = -1; } - return true; + /* Generate a TAB_BAR_EVENT event. */ + Lisp_Object key = AREF (f->tab_bar_items, + prop_idx * TAB_BAR_ITEM_NSLOTS + + TAB_BAR_ITEM_KEY); + /* Kludge alert: we assume the last two characters of a tab + label are " x", and treat clicks on those 2 characters as a + Close Tab command. */ + eassert (STRINGP (caption)); + int lastc = SSDATA (caption)[SCHARS (caption) - 1]; + bool close_p = false; + if ((x == clen - 1 || (clen > 1 && x == clen - 2)) && lastc == 'x') + close_p = true; + + return list3 (Qtab_bar, key, close_p ? Qt : Qnil); } diff --git a/src/xterm.c b/src/xterm.c index 1887c3255d4..80fa747b97d 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -9166,6 +9166,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, { /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ + Lisp_Object tab_bar_key = Qnil; bool tab_bar_p = false; bool tool_bar_p = false; @@ -9215,7 +9216,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, tab_bar_p = EQ (window, f->tab_bar_window); if (tab_bar_p && event->xbutton.button < 4) - handle_tab_bar_click + tab_bar_key = handle_tab_bar_click (f, x, y, event->xbutton.type == ButtonPress, x_x_to_emacs_modifiers (dpyinfo, event->xbutton.state)); } @@ -9239,7 +9240,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif /* !USE_GTK */ - if (!tab_bar_p && !tool_bar_p) + if (!(tab_bar_p && NILP (tab_bar_key)) && !tool_bar_p) #if defined (USE_X_TOOLKIT) || defined (USE_GTK) if (! popup_activated ()) #endif @@ -9257,6 +9258,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, } else x_construct_mouse_click (&inev.ie, &event->xbutton, f); + + if (!NILP (tab_bar_key)) + inev.ie.arg = tab_bar_key; } if (FRAME_X_EMBEDDED_P (f)) xembed_send_message (f, event->xbutton.time, -- 2.39.2