Linux 上的 Tkinter Ctrl+Shift+Z

Tkinter Ctrl+Shift+Z on Linux

我想在 Linux (Wayland) 上使用 Tkinter 在 GUI 应用程序中实现自定义重做功能。

这是我当前的代码:

"""Plotting canvas."""

from PIL import Image
from PIL.ImageTk import PhotoImage
from tkinter import NW
from tkinter import Canvas
from tkinter import Event
from tkinter import Tk

from diffusion_scribbles.functions import overlay
from diffusion_scribbles.gui.colors import BLUE, RED
from diffusion_scribbles.selections import DiffusionSelection, Selection
from diffusion_scribbles.types import Color, Dataset, Vector2D


__all__ = ['PlotCanvas']


class PlotCanvas(Canvas):
    """Image canvas."""

    def __init__(self, root: Tk, width: int, height: int, **kwargs):
        super().__init__(root, width=width, height=height, **kwargs)
        self.width = width
        self.height = height
        self.selection_method: Selection = DiffusionSelection(width, height)
        self.bind("<Button-1>", self.start_draw)
        self.bind("<B1-Motion>", self.draw)
        self.bind("<ButtonRelease-1>", self.stop_draw)
        root.bind('<Control-z>', self.undo)
        root.bind('<Control-Shift-KeyPress-z>', self.redo)
        self._dataset: Dataset | None = None
        self._data_points: Image.Image | None = None
        self._scribble: Image.Image | None = None
        self._image: Image.Image | None = None
        self._photo_image: PhotoImage | None = None
        self.primary_color: Color = RED
        self.secondary_color: Color = BLUE

    @property
    def dataset(self) -> Dataset | None:
        """Returns the dataset."""
        return self._dataset

    @dataset.setter
    def dataset(self, dataset: Dataset) -> None:
        """Sets the dataset."""
        self._dataset = dataset
        self._scribble = None
        self.image = self._data_points = dataset.draw(
            (self.width, self.height)
        )

    @property
    def data_points(self) -> Image.Image:
        """Returns the data points image."""
        return self._data_points

    @data_points.setter
    def data_points(self, data_points: Image.Image) -> None:
        """Sets the data points image."""
        self._data_points = data_points
        self.image = overlay(data_points, self.scribble)

    @property
    def scribble(self) -> Image.Image:
        """Returns the scribble image."""
        return self._scribble

    @scribble.setter
    def scribble(self, scribble: Image.Image) -> None:
        """Sets the scribble image."""
        self._scribble = scribble
        self.image = overlay(self.data_points, scribble)

    @property
    def image(self) -> Image.Image:
        """Returns the currently displayed image."""
        return self._image

    @image.setter
    def image(self, image: Image.Image) -> None:
        """Sets the currently displayed image."""
        self._image = image
        self.photo_image = PhotoImage(self._image)

    @property
    def photo_image(self) -> PhotoImage:
        """Returns the current photo image."""
        return self._photo_image

    @photo_image.setter
    def photo_image(self, photo_image: PhotoImage) -> None:
        """Sets the current photo image."""
        self._photo_image = photo_image
        self.create_image(0, 0, anchor=NW, image=self._photo_image)

    def start_draw(self, event: Event) -> None:
        """Start drawing a line."""
        if self.dataset:
            self.selection_method.start(
                Vector2D(event.x, event.y),
                self.primary_color,
                self.secondary_color
            )

    def draw(self, event: Event) -> None:
        """Draw lines."""
        if not self.dataset:
            return

        self.scribble = self.selection_method.move(Vector2D(event.x, event.y))

    def stop_draw(self, _: Event) -> None:
        """Stop drawing a line."""
        if not self.dataset:
            return

        self.selection_method.stop()
        self.data_points = self.selection_method.colorize(self.dataset)

    def undo(self, _: Event) -> None:
        """Undo last action."""
        self.scribble = self.selection_method.undo()
        self.data_points = self.selection_method.colorize(self.dataset)

    def redo(self, _: Event) -> None:
        """Undo last action."""
        print('Redo.', flush=True)

虽然撤消工作正常,但当我按下 Ctrl+Shift+Z 的适当组合键时,redo() 永远不会执行。我还尝试了其他变体,例如 '<Control-Shift-z>''<Shift-Control-KeyPress-z>''<Control-Shift-KeyPress-z>' 无济于事。

如何为 Wayland 下的 Linux 上的自定义重做操作绑定组合键 Ctrl+Shift+Z?

最小可重现示例

from functools import partial
from tkinter import Tk


class Window(Tk):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.bind('<Control-z>', partial(print, 'Undo'))
        self.bind('<Control-Shift-KeyPress-z>', partial(print, 'Redo'))


if __name__ == '__main__':
    Window().mainloop()

如果我使用大写 Z 而不是 Shift-z(小写 z

,代码对我有用
import tkinter as tk

def test1(event):
    print('test1:', event)

def test2(event):
    print('test2:', event)
    
root = tk.Tk()

root.bind('<Control-z>', test1)  # Control+Z
root.bind('<Control-Z>', test2)  # Control+Shift+Z

root.mainloop()

如果我使用 '<Control-Shift-Z>' 那么它也可以工作,因为有大写 Z.