From be06653b46fed96c9d3982b1ab13a81caaeff2dc Mon Sep 17 00:00:00 2001 From: Joltras <72395471+Joltras@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:04:26 +0000 Subject: [PATCH] fix: ignore floor shortcuts while typing in the seed field --- src/generators/tkinter_generator.py | 43 +++++++++++++++++++++----- test/tkinter_generator_test.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/generators/tkinter_generator.py b/src/generators/tkinter_generator.py index f794a5e..b32f2a0 100644 --- a/src/generators/tkinter_generator.py +++ b/src/generators/tkinter_generator.py @@ -5,7 +5,7 @@ import os import secrets import tkinter as tk -from typing import Optional +from typing import Callable, Optional from tkinter import filedialog, messagebox from tkinter import ttk @@ -263,19 +263,21 @@ def add_buttons(self) -> None: ) button_pre.pack(side=tk.LEFT) # Left arrow to decrease - self._tk.bind("", lambda event: self._decrease_floor()) + self._tk.bind("", self._shortcut(lambda: self._decrease_floor())) button_gen = ttk.Button( button_frame, text="Generate", command=self._generate_and_draw_floor ) button_gen.pack(side=tk.LEFT, padx=6) # Space to generate - self._tk.bind("", lambda event: self._generate_and_draw_floor()) + self._tk.bind( + "", self._shortcut(lambda: self._generate_and_draw_floor()) + ) button_next = ttk.Button( button_frame, text="▶", width=3, command=self._increase_floor ) button_next.pack(side=tk.LEFT) # Right arrow to increase - self._tk.bind("", lambda event: self._increase_floor()) + self._tk.bind("", self._shortcut(lambda: self._increase_floor())) # Next seed entry next_seed_label = ttk.Label( button_frame, text="Next seed", style="Muted.TLabel" @@ -284,12 +286,37 @@ def add_buttons(self) -> None: self._next_seed_entry = ttk.Entry( button_frame, textvariable=self._floor_manager.next_seed, width=34 ) - # Enter generates a floor with the entered seed - self._next_seed_entry.bind( - "", lambda event: self._generate_and_draw_floor() - ) + # Enter generates a floor with the entered seed, Escape leaves the field. + # Both give the focus back so that the shortcuts work again. + self._next_seed_entry.bind("", lambda event: self._submit_seed()) + self._next_seed_entry.bind("", lambda event: self._tk.focus_set()) + self._canvas.bind("", lambda event: self._tk.focus_set()) self._next_seed_entry.pack(side=tk.LEFT) + def _submit_seed(self) -> None: + """ + Generates a floor with the entered seed and leaves the seed field. + """ + self._tk.focus_set() + self._generate_and_draw_floor() + + @staticmethod + def _shortcut(action: Callable[[], None]) -> Callable[[tk.Event], None]: + """ + Creates a handler for a single key shortcut that is ignored while an + entry or a button has the focus, so typing a seed or pressing a button + with the keyboard does not trigger it. + @param action: action of the shortcut + @return: event handler + """ + + def handler(event: tk.Event) -> None: + if isinstance(event.widget, (tk.Entry, ttk.Entry, tk.Button, ttk.Button)): + return + action() + + return handler + def create_menu_bar(self) -> None: """ Creates the menu bar with the file, view and floor menus. diff --git a/test/tkinter_generator_test.py b/test/tkinter_generator_test.py index bec5054..a584e0a 100644 --- a/test/tkinter_generator_test.py +++ b/test/tkinter_generator_test.py @@ -231,6 +231,53 @@ def test_dark_theme_check_mark_follows_the_shortcut(self): self._press("") self.assertFalse(generator._theme_handler.dark.get()) + def _type_in_seed_field(self, key: str) -> None: + tk = self._generator._tk + entry = self._generator._next_seed_entry + tk.update() + entry.focus_force() + tk.update() + entry.event_generate(key, when="now") + tk.update() + + def test_space_in_the_seed_field_does_not_generate_a_floor(self): + generator = self._generator + generator.add_buttons() + generator._floor_manager.next_seed.set("my") + generator._next_seed_entry.icursor("end") + + with mock.patch.object(generator, "_generate_and_draw_floor") as generate: + self._type_in_seed_field("") + + generate.assert_not_called() + + def test_arrow_keys_in_the_seed_field_do_not_switch_the_floor(self): + generator = self._generator + generator.add_buttons() + generator.generate("2") + current = generator._floor + + self._type_in_seed_field("") + self._type_in_seed_field("") + + self.assertIs(current, generator._floor) + + def test_shortcuts_work_again_after_leaving_the_seed_field(self): + generator = self._generator + generator.add_buttons() + for key in ("", ""): + with self.subTest(key=key), mock.patch( + "floors.tkinter_floor.TkinterFloor.draw_step_by_step" + ): + self._type_in_seed_field(key) + self.assertIs(generator._tk, generator._tk.focus_get()) + + with mock.patch.object( + generator, "_generate_and_draw_floor" + ) as generate: + self._press("") + generate.assert_called_once() + if __name__ == "__main__": unittest.main()