Subject: bug#42887: 28.0.50; Customize Group doesn't show/hide option on repeated clicking

From: Mauro Aranda <[email-masked]>
Date: Fri, 14 Mar 2025 08:55:54 -0300

Mauro Aranda <[email-masked]> writes:

 > On 11/9/23 15:01, Stefan Kangas wrote:
 >> Mauro Aranda <[email-masked]> writes:
 >>
 >>> I don't know why I couldn't reproduce it three years ago, but I managed
 >>> to do that now, I think.
 >>>
 >>> Could you please check if the following change to
 >>> widget-button-release-event-p fixes this for you?
 >>>
 >>> (defun widget-button-release-event-p (event)
 >>>    "Non-nil if EVENT is a mouse-button-release event object."
 >>>    (and (eventp event)
 >>>         (memq (event-basic-type event) '(mouse-1 mouse-2 mouse-3))
 >>>         (seq-some (lambda (el)
 >>>                     (memq el '(click drag triple double)))
 >>>                   (event-modifiers event))))
 >>>
 >>>
 >>> Please do try it with mouse-1 and mouse-2 if you can.
 >>>
 >>> (I know that the mark gets set with repeating clicks with mouse-1,
 >>> and there's a signal with mouse-2, but I'm focusing on whether this
 >>> changes fixes the detection of repeated clicks)
 >>
 >> Thanks.
 >>
 >> I don't currently have convenient access to the machine where I can
 >> reproduce this, and it will be at least another couple of weeks until I
 >> do.  Apologies in advance if it takes a while before I can review this.
 >

I've improved my fix for this bug, so that it doesn't produce weird
highlights when double clicking a button.

The patch touches a very tricky part of wid-edit.el.  I've been testing
it for a while now, mostly with Customize and some packages of my own.
I haven't found many problems, but I figure it might need some testing
in the outside world.

Furthermore, I don't understand touchscreen commands and I don't have a
way of testing them.  Po Lu, could you please check if the handling of
touchscreen commands are still correct after splitting the functionality
between the down event and the up event?
From 94456f5fb8fdd527ac48903b3f6492d3aa64d517 Mon Sep 17 00:00:00 2001
From: Mauro Aranda <[email-masked]>
Date: Tue, 11 Mar 2025 08:26:11 -0300
Subject: [PATCH] Improve click handling in buffers with widgets

This change improves handling of double-click and triple-click
events on widgets.  (Bug#42887)
It also attempts to separate the handling of down-mouse events
from the up mouse events.  This is desired, and has been
attempted before.  (Bug#19185, Bug#20398, Bug#23571)

* lisp/wid-edit.el (widget-down-mouse): New function, to handle
down-mouse-1 and down-mouse-2 events.
(widget-button-release-event-p): Consider double and triple
modifiers as release events too.
(widget-button-click): Adapt it so that it only handles up
events.
(widget-button--check-and-call-button): Do not run the
mouse-down-action function here, since this is a helper for
widget-button-click, which now only handles up events.
(widget-keymap): Update bindings.
---
 lisp/wid-edit.el | 151 ++++++++++++++++++++++++++++++++++-------------
 1 file changed, 109 insertions(+), 42 deletions(-)

diff --git a/lisp/wid-edit.el b/lisp/wid-edit.el
index db241ca914a..fa2ef9e26c9 100644
--- a/lisp/wid-edit.el
+++ b/lisp/wid-edit.el
@@ -72,8 +72,9 @@ widget-button-release-event-p
   "Non-nil if EVENT is a mouse-button-release event object."
   (and (eventp event)
        (memq (event-basic-type event) '(mouse-1 mouse-2 mouse-3))
-       (or (memq 'click (event-modifiers event))
-	   (memq  'drag (event-modifiers event)))))
+       (seq-some (lambda (el)
+                   (memq el '(click drag triple double)))
+                 (event-modifiers event))))
 
 ;;; Customization.
 
@@ -1102,8 +1103,10 @@ widget-keymap
     (define-key map [(shift tab)] 'widget-backward)
     (put 'widget-backward :advertised-binding [(shift tab)])
     (define-key map [backtab] 'widget-backward)
-    (define-key map [down-mouse-2] 'widget-button-click)
-    (define-key map [down-mouse-1] 'widget-button-click)
+    (define-key map [mouse-2] 'widget-button-click)
+    (define-key map [mouse-1] 'widget-button-click)
+    (define-key map [down-mouse-1] 'widget-down-mouse)
+    (define-key map [down-mouse-2] 'widget-down-mouse)
     (define-key map [touchscreen-begin] 'widget-button-click)
     ;; The following definition needs to avoid using escape sequences that
     ;; might get converted to ^M when building loaddefs.el
@@ -1208,33 +1211,31 @@ widget-button--check-and-call-button
                                 ;; Report the current position of point
                                 ;; to the catch block.
                                 (throw 'button-press-cancelled (point))))
-                          (unless (widget-apply button :mouse-down-action event)
-                            (let ((track-mouse t))
-                              (while (not (widget-button-release-event-p event))
-                                (setq event (read--potential-mouse-event))
-                                (when (and mouse-1 (mouse-movement-p event))
-                                  (push event unread-command-events)
-                                  (setq event oevent)
-                                  (throw 'button-press-cancelled nil))
-                                (unless (or (integerp event)
-                                            (memq (car event)
-                                                  '(switch-frame select-window))
-                                            (eq (car event)
-                                                'scroll-bar-movement))
-                                  (setq pos (widget-event-point event))
-                                  (if (and pos
-                                           (eq (get-char-property pos 'button)
-                                               button))
-                                      (when face
-                                        (overlay-put overlay
-                                                     'face pressed-face)
-                                        (overlay-put overlay
-                                                     'mouse-face pressed-face))
-                                    (overlay-put overlay
-                                                 'face face)
-                                    (overlay-put overlay
-                                                 'mouse-face mouse-face)))))))
-
+                          (let ((track-mouse t))
+                            (while (not (widget-button-release-event-p event))
+                              (setq event (read--potential-mouse-event))
+                              (when (and mouse-1 (mouse-movement-p event))
+                                (push event unread-command-events)
+                                (setq event oevent)
+                                (throw 'button-press-cancelled nil))
+                              (unless (or (integerp event)
+                                          (memq (car event)
+                                                '(switch-frame select-window))
+                                          (eq (car event)
+                                              'scroll-bar-movement))
+                                (setq pos (widget-event-point event))
+                                (if (and pos
+                                         (eq (get-char-property pos 'button)
+                                             button))
+                                    (when face
+                                      (overlay-put overlay
+                                                   'face pressed-face)
+                                      (overlay-put overlay
+                                                   'mouse-face pressed-face))
+                                  (overlay-put overlay
+                                               'face face)
+                                  (overlay-put overlay
+                                               'mouse-face mouse-face))))))
 		        ;; When mouse is released over the button, run
 		        ;; its action function.
 		        (when (and pos (eq (get-char-property pos 'button)
@@ -1259,7 +1260,7 @@ widget-button-click
   "Invoke the button that the mouse is pointing at."
   (interactive "e")
   (if (widget-event-point event)
-      (let* ((mouse-1 (memq (event-basic-type event) '(mouse-1 down-mouse-1)))
+      (let* ((mouse-1 (eq (event-basic-type event) 'mouse-1))
 	     (pos (widget-event-point event))
 	     (start (event-start event))
              (button (get-char-property
@@ -1276,16 +1277,8 @@ widget-button-click
             (cond
              ((eq (car event) 'touchscreen-begin)
               (setq command 'touch-screen-handle-touch))
-             (mouse-1 (cond ((setq command	;down event
-                                   (lookup-key widget-global-map [down-mouse-1]))
-                             (setq up nil))
-                            ((setq command	;up event
-                                   (lookup-key widget-global-map [mouse-1])))))
-             (t (cond ((setq command	;down event
-                             (lookup-key widget-global-map [down-mouse-2]))
-                       (setq up nil))
-                      ((setq command	;up event
-                             (lookup-key widget-global-map [mouse-2]))))))
+             (mouse-1 (setq command (lookup-key widget-global-map [mouse-1])))
+             (t (setq command (lookup-key widget-global-map [mouse-2]))))
 	    (when up
 	      ;; Don't execute up events twice.
 	      (while (not (and (widget-button-release-event-p event)))
@@ -1294,6 +1287,80 @@ widget-button-click
 	      (call-interactively command)))))
     (message "You clicked somewhere weird.")))
 
+(defun widget-down-mouse (event)
+  "React to down-mouse event EVENT.
+
+If down-mouse is on a button, change its face and run its :mouse-down-action
+function.
+
+If down-mouse is not on a button, run the command EVENT would run in any other
+case.
+
+If `widget-button-click-moves-point' is non-nil, moves point to where the
+interaction with the mouse ends."
+  (interactive "e")
+  (if (widget-event-point event)
+      (let* ((mouse-1 (memq (event-basic-type event) '(mouse-1 down-mouse-1)))
+             (pos (widget-event-point event))
+             (start (event-start event))
+             (button (get-char-property
+                      pos 'button (and (windowp (posn-window start))
+                                       (window-buffer (posn-window start))))))
+        (save-selected-window
+          (select-window (posn-window (event-start event)))
+          (when button
+            (let* ((ov (widget-get button :button-overlay))
+                   (pressed-face (or (widget-get button :pressed-face)
+                                     widget-button-pressed-face))
+                   (face (overlay-get ov 'face))
+                   (mouse-face (overlay-get ov 'mouse-face))
+                   (oevent event)
+                   canceled)
+              (unwind-protect
+                  (progn
+                    (when face
+                      (overlay-put ov 'face pressed-face)
+                      (overlay-put ov 'mouse-face pressed-face))
+                    (unless (widget-apply button :mouse-down-action event)
+                      (let ((track-mouse t))
+                        (while (not (widget-button-release-event-p event))
+                          (setq event (read--potential-mouse-event))
+                          (when (and mouse-1 (mouse-movement-p event)
+                                     (not canceled))
+                            (push event unread-command-events)
+                            (setq event oevent)
+                            (setq canceled t))
+                          (unless (or canceled
+                                      (integerp event)
+                                      (memq (car event)
+                                            '(switch-frame select-window))
+                                      (eq (car event) 'scroll-bar-movement))
+                            (setq pos (widget-event-point event))
+                            (if (and pos
+                                     (eq (get-char-property pos 'button)
+                                         button))
+                                (when face
+                                  (overlay-put ov 'face pressed-face)
+                                  (overlay-put ov 'mouse-face pressed-face))
+                              (overlay-put ov 'face face)
+                              (overlay-put ov 'mouse-face mouse-face)))))))
+                (overlay-put ov 'face face)
+                (overlay-put ov 'mouse-face mouse-face))
+              (when (and (memq (event-basic-type event) '(mouse-1 mouse-2))
+                         (not (memq 'down (event-modifiers event))))
+                (push event unread-command-events))))
+          (let (command)
+            (cond
+             ((eq (car event) 'touchscreen-begin)
+              (setq command 'touch-screen-handle-touch))
+             (mouse-1 (setq command
+                            (lookup-key widget-global-map [down-mouse-1])))
+             (t (setq command (lookup-key widget-global-map [down-mouse-2]))))
+            (when command
+              (call-interactively command))
+            (setq pos (widget-event-point event)))))
+    (message "You clicked somewhere weird.")))
+
 ;; Make sure `touch-screen-handle-touch' abstains from emulating
 ;; down-mouse-1 events for `widget-button-click'.
 
-- 
2.34.1