Home > Back-end >  Tkinter Ctrl Shift Z on Linux
Tkinter Ctrl Shift Z on Linux

Time:03-13

I want to implement a custom redo fuction in GUI app using Tkinter on Linux (Wayland).

This is my current code:

"""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)

While undo works just fine, redo() is never executed when I press the appropriate key combination of Ctrl Shift Z. I also tried other variants such as '<Control-Shift-z>', '<Shift-Control-KeyPress-z>' and '<Control-Shift-KeyPress-z>' to no avail.

How can I bind the key combination Ctrl Shift Z for a custom redo action on Linux under Wayland?

Minimal reproducible example

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()

CodePudding user response:

Code works for me if I use upper case Z instead of Shift-z (with lower case 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()

If I use '<Control-Shift-Z>' then it also works because there is upper case Z.

  • Related