Try to fix layout switching

This commit is contained in:
Thomas Farstrike
2025-11-16 19:32:07 +01:00
parent 189d4a8d62
commit 19c15ba89b
3 changed files with 368 additions and 9 deletions
+15 -9
View File
@@ -62,8 +62,13 @@ class MposKeyboard:
# Configure layouts
self._setup_layouts()
# Set default mode to lowercase
# Initialize ALL keyboard mode maps (prevents LVGL from using default maps)
self._keyboard.set_map(self.MODE_LOWERCASE, self._lowercase_map, self._lowercase_ctrl)
self._keyboard.set_map(self.MODE_UPPERCASE, self._uppercase_map, self._uppercase_ctrl)
self._keyboard.set_map(self.MODE_NUMBERS, self._numbers_map, self._numbers_ctrl)
self._keyboard.set_map(self.MODE_SPECIALS, self._specials_map, self._specials_ctrl)
# Set default mode to lowercase
self._keyboard.set_mode(self.MODE_LOWERCASE)
# Add event handler for custom behavior
@@ -134,6 +139,11 @@ class MposKeyboard:
if text is None:
return
# Stop event propagation to prevent LVGL's default mode-switching behavior
# This is critical to prevent LVGL from switching to its default TEXT_LOWER,
# TEXT_UPPER, NUMBER modes when it sees mode-switching buttons
event.stop_processing()
# Get current textarea content (from our own reference, not LVGL's)
ta = self._textarea
if not ta:
@@ -149,26 +159,22 @@ class MposKeyboard:
elif text == lv.SYMBOL.UP:
# Switch to uppercase
self._keyboard.set_map(self.MODE_UPPERCASE, self._uppercase_map, self._uppercase_ctrl)
self._keyboard.set_mode(self.MODE_UPPERCASE)
self.set_mode(self.MODE_UPPERCASE)
return # Don't modify text
elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
# Switch to lowercase
self._keyboard.set_map(self.MODE_LOWERCASE, self._lowercase_map, self._lowercase_ctrl)
self._keyboard.set_mode(self.MODE_LOWERCASE)
self.set_mode(self.MODE_LOWERCASE)
return # Don't modify text
elif text == self.LABEL_NUMBERS_SPECIALS:
# Switch to numbers/specials
self._keyboard.set_map(self.MODE_NUMBERS, self._numbers_map, self._numbers_ctrl)
self._keyboard.set_mode(self.MODE_NUMBERS)
self.set_mode(self.MODE_NUMBERS)
return # Don't modify text
elif text == self.LABEL_SPECIALS:
# Switch to additional specials
self._keyboard.set_map(self.MODE_SPECIALS, self._specials_map, self._specials_ctrl)
self._keyboard.set_mode(self.MODE_SPECIALS)
self.set_mode(self.MODE_SPECIALS)
return # Don't modify text
elif text == self.LABEL_SPACE:
+65
View File
@@ -0,0 +1,65 @@
"""
Manual test for the "abc" button bug.
This test creates a keyboard and lets you manually switch modes to observe the bug.
Run with: ./scripts/run_desktop.sh tests/manual_test_abc_button.py
Steps to reproduce the bug:
1. Keyboard starts in lowercase mode
2. Click "?123" button to switch to numbers mode
3. Click "abc" button to switch back to lowercase
4. OBSERVE: Does it show "?123" (correct) or "1#" (wrong/default LVGL)?
"""
import lvgl as lv
from mpos.ui.keyboard import MposKeyboard
# Get active screen
screen = lv.screen_active()
screen.clean()
# Create title
title = lv.label(screen)
title.set_text("ABC Button Test")
title.align(lv.ALIGN.TOP_MID, 0, 5)
# Create instructions
instructions = lv.label(screen)
instructions.set_text(
"1. Start in lowercase (has ?123 button)\n"
"2. Click '?123' to switch to numbers\n"
"3. Click 'abc' to switch back\n"
"4. CHECK: Do you see '?123' or '1#'?\n"
" - '?123' = CORRECT (custom keyboard)\n"
" - '1#' = BUG (default LVGL keyboard)"
)
instructions.set_style_text_align(lv.TEXT_ALIGN.LEFT, 0)
instructions.align(lv.ALIGN.TOP_LEFT, 10, 30)
# Create textarea
textarea = lv.textarea(screen)
textarea.set_size(280, 30)
textarea.set_one_line(True)
textarea.align(lv.ALIGN.TOP_MID, 0, 120)
textarea.set_placeholder_text("Type here...")
# Create keyboard
keyboard = MposKeyboard(screen)
keyboard.set_textarea(textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
print("\n" + "="*60)
print("ABC Button Bug Test")
print("="*60)
print("Instructions:")
print("1. Keyboard starts in LOWERCASE mode")
print(" - Look for '?123' button (bottom left area)")
print("2. Click '?123' to switch to NUMBERS mode")
print(" - Should show numbers 1,2,3, etc.")
print(" - Should have 'abc' button (bottom left)")
print("3. Click 'abc' to return to lowercase")
print("4. CRITICAL CHECK:")
print(" - If you see '?123' button → CORRECT (custom keyboard)")
print(" - If you see '1#' button → BUG (default LVGL keyboard)")
print("="*60 + "\n")
@@ -0,0 +1,288 @@
"""
Test for keyboard layout switching bug.
This test reproduces the issue where clicking the "abc" button in numbers mode
goes to the wrong (default LVGL) keyboard layout instead of our custom lowercase layout.
Usage:
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_layout_switching.py
Device: ./tests/unittest.sh tests/test_graphical_keyboard_layout_switching.py --ondevice
"""
import unittest
import lvgl as lv
from mpos.ui.keyboard import MposKeyboard
from graphical_test_helper import wait_for_render
class TestKeyboardLayoutSwitching(unittest.TestCase):
"""Test keyboard layout switching between different modes."""
def setUp(self):
"""Set up test fixtures."""
self.screen = lv.obj()
self.screen.set_size(320, 240)
# Create textarea
self.textarea = lv.textarea(self.screen)
self.textarea.set_size(280, 40)
self.textarea.align(lv.ALIGN.TOP_MID, 0, 10)
self.textarea.set_one_line(True)
# Load screen
lv.screen_load(self.screen)
wait_for_render(5)
def tearDown(self):
"""Clean up."""
lv.screen_load(lv.obj())
wait_for_render(5)
def test_abc_button_from_numbers_mode(self):
"""
Test that clicking "abc" button in numbers mode goes to lowercase mode.
BUG: Currently goes to the wrong (default LVGL) keyboard layout
instead of our custom lowercase layout.
Expected behavior:
1. Start in lowercase mode (has "q", "w", "e", etc.)
2. Switch to numbers mode (has "1", "2", "3", etc. and "abc" button)
3. Click "abc" button
4. Should return to lowercase mode (has "q", "w", "e", etc.)
"""
print("\n=== Testing 'abc' button from numbers mode ===")
# Create keyboard
keyboard = MposKeyboard(self.screen)
keyboard.set_textarea(self.textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
# Verify we start in lowercase mode
print("Step 1: Verify initial lowercase mode")
# Find 'q' button (should be in lowercase layout)
q_button_index = None
for i in range(100):
try:
text = keyboard.get_button_text(i)
if text == "q":
q_button_index = i
print(f" Found 'q' at index {i} - GOOD (lowercase mode)")
break
except:
pass
self.assertIsNotNone(q_button_index, "Should find 'q' in lowercase mode")
# Switch to numbers mode
print("\nStep 2: Switch to numbers mode")
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
wait_for_render(5)
# Verify we're in numbers mode by finding '1' button
one_button_index = None
for i in range(100):
try:
text = keyboard.get_button_text(i)
if text == "1":
one_button_index = i
print(f" Found '1' at index {i} - GOOD (numbers mode)")
break
except:
pass
self.assertIsNotNone(one_button_index, "Should find '1' in numbers mode")
# Find the 'abc' button in numbers mode
print("\nStep 3: Find 'abc' button in numbers mode")
abc_button_index = None
for i in range(100):
try:
text = keyboard.get_button_text(i)
if text == "abc":
abc_button_index = i
print(f" Found 'abc' at index {i}")
break
except:
pass
self.assertIsNotNone(abc_button_index, "Should find 'abc' button in numbers mode")
# Switch back to lowercase by calling set_mode (simulating clicking 'abc')
print("\nStep 4: Click 'abc' to switch back to lowercase")
keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
wait_for_render(5)
# Verify we're back in lowercase mode using DISTINGUISHING LABELS
# When in LOWERCASE mode:
# - Our custom keyboard has "?123" (to switch to numbers)
# - Default LVGL keyboard has "1#" (to switch to numbers) and "ABC" (to switch to uppercase)
#
# Note: "abc" only appears in NUMBERS/SPECIALS modes to switch back to lowercase
print("\nStep 5: Verify we're in OUR custom lowercase mode (not default LVGL)")
found_labels = {}
for i in range(100):
try:
text = keyboard.get_button_text(i)
# Check for all possible distinguishing labels
if text in ["abc", "ABC", "?123", "1#", lv.SYMBOL.UP, lv.SYMBOL.DOWN]:
found_labels[text] = i
print(f" Found label '{text}' at index {i}")
except:
pass
# Check for WRONG labels (default LVGL keyboard in lowercase mode)
if "ABC" in found_labels:
print(f" ERROR: Found 'ABC' - this is the DEFAULT LVGL keyboard!")
self.fail("BUG DETECTED: Got default LVGL lowercase keyboard with 'ABC' label instead of custom keyboard")
if "1#" in found_labels:
print(f" ERROR: Found '1#' - this is the DEFAULT LVGL keyboard!")
self.fail("BUG DETECTED: Got default LVGL lowercase keyboard with '1#' label instead of custom keyboard with '?123'")
# Check for CORRECT labels (our custom lowercase keyboard)
if "?123" not in found_labels:
print(f" ERROR: Did not find '?123' - should be in custom lowercase layout!")
print(f" Found labels: {list(found_labels.keys())}")
self.fail("BUG: Should find '?123' label in custom lowercase mode, but got: " + str(list(found_labels.keys())))
# Also verify we have the UP symbol (our custom keyboard) not ABC (default)
if lv.SYMBOL.UP not in found_labels:
print(f" ERROR: Did not find UP symbol - should be in custom lowercase layout!")
print(f" Found labels: {list(found_labels.keys())}")
self.fail("BUG: Should find UP symbol in custom lowercase mode")
print(f" Found '?123' at index {found_labels['?123']} - GOOD (custom keyboard)")
print(f" Found UP symbol at index {found_labels[lv.SYMBOL.UP]} - GOOD (custom keyboard)")
print("\nSUCCESS: 'abc' button correctly returns to custom lowercase layout!")
def test_layout_switching_cycle(self):
"""
Test full cycle of layout switching: lowercase -> numbers -> specials -> lowercase.
This ensures all mode switches preserve our custom layouts.
"""
print("\n=== Testing full layout switching cycle ===")
keyboard = MposKeyboard(self.screen)
keyboard.set_textarea(self.textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
# Define what we expect to find in each mode
mode_tests = [
(MposKeyboard.MODE_LOWERCASE, "q", "lowercase"),
(MposKeyboard.MODE_NUMBERS, "1", "numbers"),
(MposKeyboard.MODE_SPECIALS, "~", "specials"),
(MposKeyboard.MODE_LOWERCASE, "q", "lowercase (again)"),
]
for mode, expected_key, mode_name in mode_tests:
print(f"\nSwitching to {mode_name}...")
keyboard.set_mode(mode)
wait_for_render(5)
# Find the expected key
found = False
for i in range(100):
try:
text = keyboard.get_button_text(i)
if text == expected_key:
print(f" Found '{expected_key}' at index {i} - GOOD")
found = True
break
except:
pass
self.assertTrue(found,
f"Should find '{expected_key}' in {mode_name} mode")
print("\nSUCCESS: All layout switches preserve custom layouts!")
def test_event_handler_switches_layout(self):
"""
Test that the event handler properly switches layouts.
This simulates what happens when the user actually CLICKS the "abc" button,
going through the _handle_events method instead of calling set_mode() directly.
"""
print("\n=== Testing event handler layout switching ===")
keyboard = MposKeyboard(self.screen)
keyboard.set_textarea(self.textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
# Switch to numbers mode first
print("Step 1: Switch to numbers mode")
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
wait_for_render(5)
# Verify we're in numbers mode
one_found = False
for i in range(100):
try:
if keyboard.get_button_text(i) == "1":
one_found = True
print(f" Found '1' - in numbers mode")
break
except:
pass
self.assertTrue(one_found, "Should be in numbers mode")
# Now simulate what the event handler does when "abc" is clicked
# The event handler checks: elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
# Then it calls: self._keyboard.set_map() and self._keyboard.set_mode()
print("\nStep 2: Simulate clicking 'abc' (via event handler logic)")
# This is what the event handler does:
keyboard._keyboard.set_map(
MposKeyboard.MODE_LOWERCASE,
keyboard._lowercase_map,
keyboard._lowercase_ctrl
)
keyboard._keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
wait_for_render(5)
# Verify we're back in lowercase mode with OUR custom layout
# When in LOWERCASE mode:
# - Our custom keyboard has "?123" (to switch to numbers)
# - Default LVGL keyboard has "1#" (to switch to numbers) and "ABC" (to switch to uppercase)
print("\nStep 3: Verify we have custom lowercase layout (not default LVGL)")
found_labels = {}
for i in range(100):
try:
text = keyboard.get_button_text(i)
if text in ["abc", "ABC", "?123", "1#", lv.SYMBOL.UP]:
found_labels[text] = i
print(f" Found label '{text}' at index {i}")
except:
pass
# Check for WRONG labels (default LVGL keyboard)
if "ABC" in found_labels:
print(f" ERROR: Found 'ABC' - this is the DEFAULT LVGL keyboard!")
print(" Found these labels:", list(found_labels.keys()))
self.fail("BUG DETECTED: Event handler caused switch to default LVGL keyboard with 'ABC' label")
if "1#" in found_labels:
print(f" ERROR: Found '1#' - this is the DEFAULT LVGL keyboard!")
print(" Found these labels:", list(found_labels.keys()))
self.fail("BUG DETECTED: Event handler caused switch to default LVGL keyboard with '1#' label")
# Check for CORRECT labels (our custom keyboard in lowercase mode)
self.assertIn("?123", found_labels,
"Should find '?123' label in custom lowercase mode (not '1#' from default)")
self.assertIn(lv.SYMBOL.UP, found_labels,
"Should find UP symbol in custom lowercase mode")
print(f" Found '?123' at index {found_labels['?123']} - GOOD")
print(f" Found UP symbol at index {found_labels[lv.SYMBOL.UP]} - GOOD")
print("\nSUCCESS: Event handler preserves custom layout!")
if __name__ == "__main__":
unittest.main()