python / cpython

The Python programming language
https://www.python.org
Other
63.12k stars 30.22k forks source link

Mouse position shifted, widget not deleted tkinter macOS 14.2.1 #114934

Closed jojo-monk closed 8 months ago

jojo-monk commented 8 months ago

Bug report

Bug description:

Hello,

In this code, if you click on matrix, then create grid, there is a spinbox that remains displayed. and the mouse click is shifted from the blackened boxes.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Feb  1 23:04:57 2024

@author: jojo
"""
import tkinter as tk
from tkinter import ttk

root = tk.Tk()
btn_frame = tk.Frame(root)
btn_frame.grid(row=0, column=0, columnspan=3)
grille_frame = tk.Canvas(root, width=600, height=600)
grille_frame.grid(row=1, column = 0)
yscrollbar_grille = ttk.Scrollbar(root, orient="vertical", command=grille_frame.yview)
yscrollbar_grille.grid(row=0, column=3, rowspan=3, sticky="ns")
xscrollbar_grille = ttk.Scrollbar(root, orient="horizontal", command=grille_frame.xview)
xscrollbar_grille.grid(row=2, column=0, columnspan=3, sticky="ew")
grille_frame.config(yscrollcommand=yscrollbar_grille.set, xscrollcommand=xscrollbar_grille.set)
rows = 12
cols = 12
cell_size = 40

x = 30
y = 30

def zoom():
    grille_frame.scale("all", 30, 30, 0.5, 0.5)

scale_btn = tk.Button(btn_frame, text="scale", command=zoom)
scale_btn.grid(row=0, column=0)

def mouse_position(event):
    grille_frame.event_generate("<Motion>", warp=True, x=30, y=30)
    print("mouse position winfo", root.winfo_pointerxy())
    print("mouse position on canvas", event.x, event.y)
    print("mouse postition on screen", event.x_root, event.y_root)
    print("canvas postition", grille_frame.bbox("all"))

root.bind("<Key>", mouse_position)
def matrice():
    grille_frame.delete("all")
    y = 30
    for r in range(rows):
        x = 30

        spinbox_row = tk.Spinbox(grille_frame, from_=0, to=cols, width=2)
        grille_frame.create_window((x, y+cell_size/2), window=spinbox_row, anchor="e")

        temp = []
        for c in range(cols):
            cell = grille_frame.create_rectangle((x, y), (x + cell_size, y + cell_size), fill="white", outline= "black")
            temp.append(cell)
            x = x + cell_size
        y = y + cell_size

    x = y = 30
    for c in range(cols):
        spinbox_col = tk.Spinbox(grille_frame, from_=0, to=rows, width=2)
        grille_frame.create_window((x+cell_size/2, y), window=spinbox_col, anchor="s")
        x = x + cell_size

    grille_frame.update_idletasks()
    grille_frame.config(scrollregion=grille_frame.bbox("all"))

matrice_btn = tk.Button(btn_frame, text="Matrice", command=matrice)
matrice_btn.grid(row=0, column=2)
def creer_hanjie():
    """ affiche une grille de cases blanches """
    grille_frame.delete("all")
    x = 30
    y = 30

    for r in range(rows):
        x = 30
        for c in range(cols):
            cell = grille_frame.create_rectangle((x, y), (x + cell_size, y + cell_size ), fill="white", outline= "black")
            x = x + cell_size
            #print("coordonnées case:", grille_frame.coords(cell))

        y = y + cell_size
    root.bind("<Button-1>", click_1case)

    grille_frame.update_idletasks()
    print("canvaas taille", grille_frame.bbox("all"))
    grille_frame.config(scrollregion=grille_frame.bbox("all"))

grille_btn = tk.Button(btn_frame, text="create grid", command=creer_hanjie)
grille_btn.grid(row=0, column=1)

def click_1case(event):
    """ change la couleur de la case par click simple, 1 case à la fois """
    print("clic souris", event.x, event.y)

    case = grille_frame.find_closest(event.x, event.y)
    couleur_actuelle = grille_frame.itemcget(case, "fill")
    if couleur_actuelle == "white":
        nouvelle_couleur = "black"
    else:
        nouvelle_couleur = "white"
    grille_frame.itemconfigure(case, fill=nouvelle_couleur)

root.mainloop()

CPython versions tested on:

3.11

Operating systems tested on:

macOS

terryjreedy commented 8 months ago

I edited the description to match the buttons displayed. [Matrice] shows grid with spinboxes, [create grid] shows grid alone, so if already present, all should disappear. They do on Windows, so I added OS-macos tag. I have no idea what "mouse click is shifted from the blackened boxes." means. The only black box I see is a quick black flash of upper left box.

ronaldoussoren commented 8 months ago

I cannot reproduce with Python 3.12 from our installer.

The hit testing for the grid cells is a bit off when first creating a matrix and then the grid, that's because the entire grid is shifted a little when the spin boxes are shown and that position is not reset when the spin boxes are hidden again. That's not an issue we can fix on our side (if this is a bug at all), but is behaviour from Tk.

BTW. I get the following exception when clicking on one of the spin boxes I get the following traceback ("Matrice", "create grid", "Matrice"):

Exception in Tkinter callback
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/tkinter/__init__.py", line 1962, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "/Users/ronald/Projects/py2app/repro.py", line 101, in click_1case
    couleur_actuelle = grille_frame.itemcget(case, "fill")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/tkinter/__init__.py", line 2964, in itemcget
    return self.tk.call(
           ^^^^^^^^^^^^^
_tkinter.TclError: unknown option "-fill"

The exception does only happen if I click on "Matrice" after clicking on "create grid".

jojo-monk commented 8 months ago

Yes I have the same exception, to illustrate my point, here is a screenshot obtained after clicking on [Matrice], then [create grid], I added red dots on the boxes where I clicked to show the offset between the click and the affected box: Capture d’écran 2024-02-03 à 10 34 32

ronaldoussoren commented 8 months ago

I don't see the spinner on my system, but do see similar clicking behaviour although I have to click closer to the edge of a cell to get a wrong hit.

Some more testing:

jojo-monk commented 8 months ago

The mouse offset bug is solved with:

x, y = grille_frame.canvasx(event.x), grille_frame.canvasy(event.y)
print("clic souris", event.x, event.y, x, y)
case = grille_frame.find_closest(x, y) # used the real coordinates

In the click_1case function.

ronaldoussoren commented 8 months ago

The offset issue is also solved by binding the click event to the frame (see my previous message). Might be related to window-relative vs. widget-relative coordinates, but I'm not enough of an Tk expert to know this for sure.

All in all I don't think this is a bug in CPython.