Tk Source Code

View Ticket
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.
Ticket UUID: 609e0045f502127ce2fe33cc54c78ca054099bf1
Title: MouseWheel binding for canvas on MacOS provides wrong values for %x %y
Type: Bug Version: 8.6.8
Submitter: rattleCAD Created on: 2019-02-03 20:42:33
Subsystem: 66. Aqua Window Operations Assigned To: marc_culler
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2019-04-22 21:11:15
Resolution: Fixed Closed By: fvogel
    Closed on: 2019-04-22 21:11:15
I want to implement a scaling function on a canvas using the mousewheel. The current position of the pointer (Mouse) should be the center for the scaling format. This works fine for windows.
On my Mac the binding provides wrong values for %x %y coordinates. It looks like as the binding to the canvas does not use the origin of the canvas. 

To make it clear or to test I appended my testscript.
The script creates 2 canvases:
   1st: with white background on the left, 
   2nd: with gray background on the right.

To test the behaviour:
on a position of the pointer on the canvas you can click <B1> that creates a red cross with the pointer position as center. Using the mousewheel on the same position should create a red square with the same center.

On MacOS this works for the white left canvas
but does not work on the gray right canvas.

It looks like as if the right gray canvas does not consider the existance of the left canvas ... and should of course!

-- testcode --
puts "$tcl_version"    
package require Tk
    # -- canvases
set plCanvas_01 [canvas .cv_plain_01 -bg white   -width 150]
set plCanvas_02 [canvas .cv_plain_02 -bg gray90  -width 450]
    # -- pack 
pack $plCanvas_01 $plCanvas_02  -side left  -fill both  -expand 1 
wm geometry . 600x400
    # -- binding 
bind $plCanvas_01 <MouseWheel>   [list eventMouseWheel  %W %x %y %D]
bind $plCanvas_01 <1>            [list eventClick_B1    %W %x %y]
bind $plCanvas_02 <MouseWheel>   [list eventMouseWheel  %W %x %y %D]
bind $plCanvas_02 <1>            [list eventClick_B1    %W %x %y]
    # -- procedure
proc eventMouseWheel {w x y scale} {
    puts "    -> eventMouseWheel    $x $y $scale <- $w    [info patchlevel]"
    set x1 [expr {$x -10}]
    set x2 [expr {$x +10}]
    set y1 [expr {$y -10}]
    set y2 [expr {$y +10}]
    foreach item {$w find withtag __square__} {
        $w delete $item
    $w create rectangle $x1 $y1 $x2 $y2  -outline blue  -tags __square__
proc eventClick_B1 {w x y} {
    puts "    -> eventClick_B1      $x $y <- $w    [info patchlevel]"
    set x1 [expr {$x - 20}]
    set x2 [expr {$x + 20}]
    set y1 [expr {$y - 20}]
    set y2 [expr {$y + 20}]
    foreach item {$w find withtag __cross__} {
        $w delete $item
    $w create line $x1 $y $x2 $y  -fill red  -tags __cross__
    $w create line $x $y1 $x $y2  -fill red  -tags __cross__
User Comments: fvogel added on 2019-04-22 21:11:15:

This problem was actually known since at least 2013, see [3609839fff]

fvogel added on 2019-03-14 21:03:54:
Merged into core-8-6-branch and trunk.

fvogel added on 2019-03-05 16:42:52:
Understood, thanks for the thorough explanation. Let's forget about testing then.

marc_culler (claiming to be Marc Culler) added on 2019-03-05 16:21:07:
On macOS the way the Tk event loop works is that the event queue of the
NSApplication is used an event source for Tk.  Specifically, a Tcl Event
source is created with its checkProc set to TkMacOSXEventsCheckProc.
Tcl_DoOneEvent calls the checkProc for each of its event sources to add
events to the Tcl event queue. What TkMacOSXEventsCheckProc does is to 
ask the NSApplication whether it has any NSEvents on its event queue.  If
so it processes each of those NSEvents, using the data in the NSEvent to
create an XEvent and adding that XEvent to the Tcl queue.

The task of creating an XEvent from an NSEvent is handled by a method of
the TKApplication subclass of NSApplication.  The method is named processEvent.
When processEvent encounters a mouse NSEvent, it calls a different method
of TKApplication named tkProcessMouseEvent.  The code that was changed in
the bugfix branch was inside of the tkProcessMouseEvent method.

So the bottom line is that tkProcessMouseEvent is only called when a
mouse-related NSEvent appears on the Apple event queue belonging to the
TKApplication.  If you call the "event generate" command it causes an XEvent
to be directly added to the Tcl event queue.  The event source is not involved.
A test which used "event generate" would have to compute the x and y fields
to use for the XEvent it is generating. So a test using "event generate"
would only be testing whether the test itself had computed the x and y
coordinates correctly.  It would not be testing whether the tkProcessMouseEvent
method of TKApplication computed the x and y coordinates correctly.

fvogel added on 2019-03-05 15:14:59:
Couldn't we exercise this code by something like event generate <B2> and test %x and %y, in a test case like the one provided by the OP?

marc_culler (claiming to be Marc Culler) added on 2019-03-05 14:09:43:
To exercise this code you would have to inject an artificial scroll wheel event
into Apple's event loop, which serves as the event source for Tk running on
macOS.  Apple provides a way to do that in Objective C, but I don't know of
any way to do it from Tk.  I think it would require adding a new command to the
tktest extension.

fvogel added on 2019-03-05 07:04:14:
Thanks for your review and additional work on the code, it indeed is easier to follow now.

Do you know if there is perhaps already a test exercising this part of the code? Or should we add a new test?

marc_culler (claiming to be Marc Culler) added on 2019-03-04 23:37:47:
Your fix is correct, Fran├žois.  It is amazing that this bug could have been
there for so long.  I suspect that the reason bugs like this appear
in the first place is that someone is trying to make a small change to some
code that is hard to follow.  So I rearranged the code a bit and added some
comments to try to explain what is happening in tkMacOSXMouseEvent.c.  These
changes have been committed in a bugfix branch called bug-609e0045f5.

fvogel added on 2019-02-03 21:21:20:

Right, I see the problem. Thanks for the good quality of your report.

The following patch seems to fix it, but I didn't test very much:

Index: macosx/tkMacOSXMouseEvent.c
--- macosx/tkMacOSXMouseEvent.c
+++ macosx/tkMacOSXMouseEvent.c
@@ -189,12 +189,12 @@
        CGFloat delta;
        int coarseDelta;
        XEvent xEvent;

        xEvent.type = MouseWheelEvent;
-       xEvent.xbutton.x = local.x;
-       xEvent.xbutton.y = local.y;
+       xEvent.xbutton.x = win_x;
+       xEvent.xbutton.y = win_y;
        xEvent.xbutton.x_root = global.x;
        xEvent.xbutton.y_root = global.y;
        xEvent.xany.send_event = false;
        xEvent.xany.display = Tk_Display(tkwin);
        xEvent.xany.window = Tk_WindowId(tkwin);